Update docker #159

Merged
mpeltriaux merged 20 commits from master into Docker 2022-05-09 14:03:12 +02:00
53 changed files with 827 additions and 333 deletions

View File

@ -132,6 +132,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
id__in=payments id__in=payments
) )
obj.payments.set(payments) obj.payments.set(payments)
obj.send_data_to_egon()
return obj return obj
def create_model_from_json(self, json_model, user): def create_model_from_json(self, json_model, user):
@ -197,7 +198,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
obj.legal.save() obj.legal.save()
obj.save() obj.save()
obj.mark_as_edited(user) obj.mark_as_edited(user, edit_comment="API update")
celery_update_parcels.delay(obj.geometry.id) celery_update_parcels.delay(obj.geometry.id)

View File

@ -75,7 +75,10 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
Returns: Returns:
""" """
if json_str is None or len(json_str) == 0: if json_str is None:
return None
json_str = str(json_str)
if len(json_str) == 0:
return None return None
code = KonovaCode.objects.get( code = KonovaCode.objects.get(
atom_id=json_str, atom_id=json_str,

View File

@ -33,6 +33,7 @@ class KonovaCodeAdmin(admin.ModelAdmin):
"is_selectable", "is_selectable",
"is_leaf", "is_leaf",
"parent", "parent",
"found_in_codelists",
] ]
search_fields = [ search_fields = [
@ -42,6 +43,12 @@ class KonovaCodeAdmin(admin.ModelAdmin):
"short_name", "short_name",
] ]
def found_in_codelists(self, obj):
codelists = KonovaCodeList.objects.filter(
codes__in=[obj]
).values_list("id", flat=True)
codelists = "\n".join(str(x) for x in codelists)
return codelists
#admin.site.register(KonovaCodeList, KonovaCodeListAdmin) #admin.site.register(KonovaCodeList, KonovaCodeListAdmin)
admin.site.register(KonovaCode, KonovaCodeAdmin) admin.site.register(KonovaCode, KonovaCodeAdmin)

View File

@ -21,16 +21,30 @@ class AbstractCompensationAdmin(BaseObjectAdmin):
"identifier", "identifier",
"title", "title",
"comment", "comment",
"after_states", "list_after_states",
"before_states", "list_before_states",
"geometry",
] ]
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
return super().get_readonly_fields(request, obj) + [ return super().get_readonly_fields(request, obj) + [
"after_states", "list_after_states",
"before_states", "list_before_states",
"geometry",
] ]
def list_after_states(self, obj):
states = obj.after_states.all()
states = [str(state) for state in states]
states = "\n".join(states)
return states
def list_before_states(self, obj):
states = obj.before_states.all()
states = [str(state) for state in states]
states = "\n".join(states)
return states
class CompensationAdmin(AbstractCompensationAdmin): class CompensationAdmin(AbstractCompensationAdmin):
autocomplete_fields = [ autocomplete_fields = [

View File

@ -128,6 +128,7 @@ class EditPaymentModalForm(NewPaymentForm):
payment.comment = self.cleaned_data.get("comment", None) payment.comment = self.cleaned_data.get("comment", None)
payment.save() payment.save()
self.instance.mark_as_edited(self.user, self.request, edit_comment=PAYMENT_EDITED) self.instance.mark_as_edited(self.user, self.request, edit_comment=PAYMENT_EDITED)
self.instance.send_data_to_egon()
return payment return payment

View File

@ -418,6 +418,18 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
super().set_status_messages(request) super().set_status_messages(request)
return request return request
@property
def is_recorded(self):
""" Getter for record status as property
Since compensations inherit their record status from their intervention, the intervention's status is being
returned
Returns:
"""
return self.intervention.is_recorded
class CompensationDocument(AbstractDocument): class CompensationDocument(AbstractDocument):
""" """

View File

@ -181,9 +181,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
""" """
if value is None: if value is None:
value = User.objects.none() value = User.objects.none()
has_access = value.filter( has_access = record.is_shared_with(self.user)
id=self.user.id
).exists()
html = self.render_icn( html = self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"), tooltip=_("Full access granted") if has_access else _("Access not granted"),
@ -343,7 +341,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
html = "" html = ""
# Do not use value in here, since value does use unprefetched 'users' manager, where record has already # Do not use value in here, since value does use unprefetched 'users' manager, where record has already
# prefetched users data # prefetched users data
has_access = self.user in record.users.all() has_access = record.is_shared_with(self.user)
html += self.render_icn( html += self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"), tooltip=_("Full access granted") if has_access else _("Access not granted"),
icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit", icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",

View File

@ -122,7 +122,7 @@
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/comment_card.html' %} {% include 'konova/includes/comment_card.html' %}

View File

@ -104,7 +104,7 @@
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/comment_card.html' %} {% include 'konova/includes/comment_card.html' %}

View File

@ -38,17 +38,10 @@
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> {% include 'konova/includes/report/qrcodes.html' %}
<h4>{% trans 'Open in browser' %}</h4>
{{ qrcode|safe }}
</div>
<div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'View in LANIS' %}</h4>
{{ qrcode_lanis|safe }}
</div>
</div> </div>
</div> </div>

View File

@ -51,17 +51,10 @@
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> {% include 'konova/includes/report/qrcodes.html' %}
<h4>{% trans 'Open in browser' %}</h4>
{{ qrcode|safe }}
</div>
<div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'View in LANIS' %}</h4>
{{ qrcode_lanis|safe }}
</div>
</div> </div>
</div> </div>

View File

@ -60,8 +60,9 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
# Preserve the current number of intervention's compensations # Preserve the current number of intervention's compensations
num_compensations = self.intervention.compensations.count() num_compensations = self.intervention.compensations.count()
self.client_user.post(new_url, post_data) response = self.client_user.post(new_url, post_data)
self.assertEqual(302, response.status_code)
self.intervention.refresh_from_db() self.intervention.refresh_from_db()
self.assertEqual(num_compensations + 1, self.intervention.compensations.count()) self.assertEqual(num_compensations + 1, self.intervention.compensations.count())
new_compensation = self.intervention.compensations.get(identifier=test_id) new_compensation = self.intervention.compensations.get(identifier=test_id)
@ -261,3 +262,26 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
self.assertIn(recorded, self.compensation.log.all()) self.assertIn(recorded, self.compensation.log.all())
self.assertEqual(pre_record_log_count + 1, self.compensation.log.count()) self.assertEqual(pre_record_log_count + 1, self.compensation.log.count())
def test_non_editable_after_recording(self):
""" Tests that the compensation can not be edited after being recorded
User must be redirected to another page
Returns:
"""
self.assertIsNotNone(self.compensation)
self.assertFalse(self.compensation.is_recorded)
edit_url = reverse("compensation:edit", args=(self.compensation.id,))
response = self.client_user.get(edit_url)
has_redirect = response.status_code == 302
self.assertFalse(has_redirect)
self.compensation.intervention.set_recorded(self.user)
self.assertTrue(self.compensation.is_recorded)
edit_url = reverse("compensation:edit", args=(self.compensation.id,))
response = self.client_user.get(edit_url)
has_redirect = response.status_code == 302
self.assertTrue(has_redirect)
self.compensation.intervention.set_unrecorded(self.user)

View File

@ -302,3 +302,27 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
self.assertEqual(pre_edit_account_log_count + 1, account.log.count()) self.assertEqual(pre_edit_account_log_count + 1, account.log.count())
self.assertEqual(intervention.log.first().action, UserAction.EDITED) self.assertEqual(intervention.log.first().action, UserAction.EDITED)
self.assertEqual(account.log.first().action, UserAction.EDITED) self.assertEqual(account.log.first().action, UserAction.EDITED)
def test_non_editable_after_recording(self):
""" Tests that the eco_account can not be edited after being recorded
User must be redirected to another page
Returns:
"""
self.assertIsNotNone(self.eco_account)
self.assertFalse(self.eco_account.is_recorded)
edit_url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
response = self.client_user.get(edit_url)
has_redirect = response.status_code == 302
self.assertFalse(has_redirect)
self.eco_account.set_recorded(self.user)
self.assertTrue(self.eco_account.is_recorded)
edit_url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
response = self.client_user.get(edit_url)
has_redirect = response.status_code == 302
self.assertTrue(has_redirect)
self.eco_account.set_unrecorded(self.user)

View File

@ -1,4 +1,5 @@
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Sum from django.db.models import Sum
from django.http import HttpRequest, JsonResponse from django.http import HttpRequest, JsonResponse
from django.shortcuts import render from django.shortcuts import render
@ -22,7 +23,7 @@ from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DA
CHECKED_RECORDED_RESET, COMPENSATION_ADDED_TEMPLATE, COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, \ CHECKED_RECORDED_RESET, COMPENSATION_ADDED_TEMPLATE, COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, \
COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, \ COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, \
DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, \ DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, \
DEADLINE_EDITED DEADLINE_EDITED, RECORDED_BLOCKS_EDIT, PARAMS_INVALID
from konova.utils.user_checks import in_group from konova.utils.user_checks import in_group
@ -69,6 +70,19 @@ def new_view(request: HttpRequest, intervention_id: str = None):
""" """
template = "compensation/form/view.html" template = "compensation/form/view.html"
if intervention_id is not None:
try:
intervention = Intervention.objects.get(id=intervention_id)
except ObjectDoesNotExist:
messages.error(request, PARAMS_INVALID)
return redirect("home")
if intervention.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("intervention:detail", id=intervention_id)
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id) data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
geom_form = SimpleGeomForm(request.POST or None, read_only=False) geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST": if request.method == "POST":
@ -134,6 +148,13 @@ def edit_view(request: HttpRequest, id: str):
template = "compensation/form/view.html" template = "compensation/form/view.html"
# Get object from db # Get object from db
comp = get_object_or_404(Compensation, id=id) comp = get_object_or_404(Compensation, id=id)
if comp.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("compensation:detail", id=id)
# Create forms, initialize with values from db/from POST request # Create forms, initialize with values from db/from POST request
data_form = EditCompensationForm(request.POST or None, instance=comp) data_form = EditCompensationForm(request.POST or None, instance=comp)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp) geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
@ -596,14 +617,12 @@ def report_view(request: HttpRequest, id: str):
instance=comp instance=comp
) )
parcels = comp.get_underlying_parcels() parcels = comp.get_underlying_parcels()
qrcode_img = generate_qr_code(
request.build_absolute_uri(reverse("compensation:report", args=(id,))), qrcode_url = request.build_absolute_uri(reverse("compensation:report", args=(id,)))
10 qrcode_img = generate_qr_code(qrcode_url, 10)
) qrcode_lanis_url = comp.get_LANIS_link()
qrcode_img_lanis = generate_qr_code( qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
comp.get_LANIS_link(),
7
)
# Order states by surface # Order states by surface
before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type") before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type") after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type")
@ -611,8 +630,14 @@ def report_view(request: HttpRequest, id: str):
context = { context = {
"obj": comp, "obj": comp,
"qrcode": qrcode_img, "qrcode": {
"qrcode_lanis": qrcode_img_lanis, "img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"has_access": False, # disables action buttons during rendering "has_access": False, # disables action buttons during rendering
"before_states": before_states, "before_states": before_states,
"after_states": after_states, "after_states": after_states,

View File

@ -35,7 +35,8 @@ from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \ from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, \ CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, \
COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, \ COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, \
DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, \
RECORDED_BLOCKS_EDIT
from konova.utils.user_checks import in_group from konova.utils.user_checks import in_group
@ -145,6 +146,13 @@ def edit_view(request: HttpRequest, id: str):
template = "compensation/form/view.html" template = "compensation/form/view.html"
# Get object from db # Get object from db
acc = get_object_or_404(EcoAccount, id=id) acc = get_object_or_404(EcoAccount, id=id)
if acc.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("compensation:acc:detail", id=id)
# Create forms, initialize with values from db/from POST request # Create forms, initialize with values from db/from POST request
data_form = EditEcoAccountForm(request.POST or None, instance=acc) data_form = EditEcoAccountForm(request.POST or None, instance=acc)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc) geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
@ -731,18 +739,16 @@ def report_view(request:HttpRequest, id: str):
instance=acc instance=acc
) )
parcels = acc.get_underlying_parcels() parcels = acc.get_underlying_parcels()
qrcode_img = generate_qr_code(
request.build_absolute_uri(reverse("ema:report", args=(id,))), qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,)))
10 qrcode_img = generate_qr_code(qrcode_url, 10)
) qrcode_lanis_url = acc.get_LANIS_link()
qrcode_img_lanis = generate_qr_code( qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
acc.get_LANIS_link(),
7
)
# Order states by surface # Order states by surface
before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent") before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent")
after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent") after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent")
actions = acc.actions.all().select_related("action_type__parent") actions = acc.actions.all().prefetch_related("action_type__parent")
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier) # Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier)
deductions = acc.deductions.all()\ deductions = acc.deductions.all()\
@ -752,8 +758,14 @@ def report_view(request:HttpRequest, id: str):
context = { context = {
"obj": acc, "obj": acc,
"qrcode": qrcode_img, "qrcode": {
"qrcode_lanis": qrcode_img_lanis, "img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"has_access": False, # disables action buttons during rendering "has_access": False, # disables action buttons during rendering
"before_states": before_states, "before_states": before_states,
"after_states": after_states, "after_states": after_states,

View File

@ -151,9 +151,7 @@ class EmaTable(BaseTable, TableRenderMixin):
""" """
html = "" html = ""
has_access = value.filter( has_access = record.is_shared_with(self.user)
id=self.user.id
).exists()
html += self.render_icn( html += self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"), tooltip=_("Full access granted") if has_access else _("Access not granted"),

View File

@ -90,7 +90,7 @@
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/comment_card.html' %} {% include 'konova/includes/comment_card.html' %}

View File

@ -38,17 +38,10 @@
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> {% include 'konova/includes/report/qrcodes.html' %}
<h4>{% trans 'Open in browser' %}</h4>
{{ qrcode|safe }}
</div>
<div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'View in LANIS' %}</h4>
{{ qrcode_lanis|safe }}
</div>
</div> </div>
</div> </div>

View File

@ -117,6 +117,32 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
self.assertEqual(pre_edit_log_count + 1, self.ema.log.count()) self.assertEqual(pre_edit_log_count + 1, self.ema.log.count())
self.assertEqual(self.ema.log.first().action, UserAction.EDITED) self.assertEqual(self.ema.log.first().action, UserAction.EDITED)
def test_non_editable_after_recording(self):
""" Tests that the EMA can not be edited after being recorded
User must be redirected to another page
Returns:
"""
self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
self.assertIsNotNone(self.ema)
self.ema.share_with_user(self.superuser)
self.assertFalse(self.ema.is_recorded)
edit_url = reverse("ema:edit", args=(self.ema.id,))
response = self.client_user.get(edit_url)
has_redirect = response.status_code == 302
self.assertFalse(has_redirect)
self.ema.set_recorded(self.superuser)
self.assertTrue(self.ema.is_recorded)
edit_url = reverse("ema:edit", args=(self.ema.id,))
response = self.client_user.get(edit_url)
has_redirect = response.status_code == 302
self.assertTrue(has_redirect)
self.ema.set_unrecorded(self.superuser)
def test_recordability(self): def test_recordability(self):
""" """
This tests if the recordability of the Ema is triggered by the quality of it's data (e.g. not all fields filled) This tests if the recordability of the Ema is triggered by the quality of it's data (e.g. not all fields filled)

View File

@ -26,7 +26,7 @@ from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \ from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, \ DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, \
COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, \ COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, \
COMPENSATION_ACTION_EDITED, DEADLINE_EDITED COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, RECORDED_BLOCKS_EDIT
from konova.utils.user_checks import in_group from konova.utils.user_checks import in_group
@ -213,6 +213,13 @@ def edit_view(request: HttpRequest, id: str):
template = "compensation/form/view.html" template = "compensation/form/view.html"
# Get object from db # Get object from db
ema = get_object_or_404(Ema, id=id) ema = get_object_or_404(Ema, id=id)
if ema.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("ema:detail", id=id)
# Create forms, initialize with values from db/from POST request # Create forms, initialize with values from db/from POST request
data_form = EditEmaForm(request.POST or None, instance=ema) data_form = EditEmaForm(request.POST or None, instance=ema)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema) geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema)
@ -563,14 +570,12 @@ def report_view(request:HttpRequest, id: str):
instance=ema, instance=ema,
) )
parcels = ema.get_underlying_parcels() parcels = ema.get_underlying_parcels()
qrcode_img = generate_qr_code(
request.build_absolute_uri(reverse("ema:report", args=(id,))), qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,)))
10 qrcode_img = generate_qr_code(qrcode_url, 10)
) qrcode_lanis_url = ema.get_LANIS_link()
qrcode_img_lanis = generate_qr_code( qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
ema.get_LANIS_link(),
7
)
# Order states by surface # Order states by surface
before_states = ema.before_states.all().order_by("-surface").prefetch_related("biotope_type") before_states = ema.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = ema.after_states.all().order_by("-surface").prefetch_related("biotope_type") after_states = ema.after_states.all().order_by("-surface").prefetch_related("biotope_type")
@ -578,8 +583,14 @@ def report_view(request:HttpRequest, id: str):
context = { context = {
"obj": ema, "obj": ema,
"qrcode": qrcode_img, "qrcode": {
"qrcode_lanis": qrcode_img_lanis, "img": qrcode_img,
"url": qrcode_url
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url
},
"has_access": False, # disables action buttons during rendering "has_access": False, # disables action buttons during rendering
"before_states": before_states, "before_states": before_states,
"after_states": after_states, "after_states": after_states,

View File

@ -25,12 +25,14 @@ class InterventionAdmin(BaseObjectAdmin):
"checked", "checked",
"recorded", "recorded",
"users", "users",
"geometry",
] ]
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
return super().get_readonly_fields(request, obj) + [ return super().get_readonly_fields(request, obj) + [
"checked", "checked",
"recorded", "recorded",
"geometry",
] ]

View File

@ -427,13 +427,22 @@ class NewDeductionModalForm(BaseModalForm):
""" """
super_result = super().is_valid() super_result = super().is_valid()
acc = self.cleaned_data["account"] acc = self.cleaned_data["account"]
intervention = self.cleaned_data["intervention"]
objects_valid = True
if not acc.recorded: if not acc.recorded:
self.add_error( self.add_error(
"account", "account",
_("Eco-account {} is not recorded yet. You can only deduct from recorded accounts.").format(acc.identifier) _("Eco-account {} is not recorded yet. You can only deduct from recorded accounts.").format(acc.identifier)
) )
return False objects_valid = False
if intervention.is_recorded:
self.add_error(
"intervention",
_("Intervention {} is currently recorded. To change any data on it, the entry must be unrecorded.").format(intervention.identifier)
)
objects_valid = False
rest_surface = self._get_available_surface(acc) rest_surface = self._get_available_surface(acc)
form_surface = float(self.cleaned_data["surface"]) form_surface = float(self.cleaned_data["surface"])
@ -447,7 +456,7 @@ class NewDeductionModalForm(BaseModalForm):
format_german_float(rest_surface), format_german_float(rest_surface),
), ),
) )
return is_valid_surface and super_result return is_valid_surface and objects_valid and super_result
def __create_deduction(self): def __create_deduction(self):
""" Creates the deduction """ Creates the deduction

View File

@ -145,7 +145,6 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
def set_recorded(self, user: User) -> UserActionLogEntry: def set_recorded(self, user: User) -> UserActionLogEntry:
log_entry = super().set_recorded(user) log_entry = super().set_recorded(user)
self.add_log_entry_to_compensations(log_entry) self.add_log_entry_to_compensations(log_entry)
self.send_data_to_egon()
return log_entry return log_entry
def add_log_entry_to_compensations(self, log_entry: UserActionLogEntry): def add_log_entry_to_compensations(self, log_entry: UserActionLogEntry):
@ -183,6 +182,8 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
intervention=self, intervention=self,
) )
self.mark_as_edited(user, form.request, edit_comment=PAYMENT_ADDED) self.mark_as_edited(user, form.request, edit_comment=PAYMENT_ADDED)
self.send_data_to_egon()
return pay return pay
def add_revocation(self, form): def add_revocation(self, form):
@ -347,6 +348,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
with transaction.atomic(): with transaction.atomic():
payment.delete() payment.delete()
self.mark_as_edited(user, request=form.request, edit_comment=PAYMENT_REMOVED) self.mark_as_edited(user, request=form.request, edit_comment=PAYMENT_REMOVED)
self.send_data_to_egon()
class InterventionDocument(AbstractDocument): class InterventionDocument(AbstractDocument):

View File

@ -177,9 +177,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
""" """
html = "" html = ""
has_access = value.filter( has_access = record.is_shared_with(self.user)
id=self.user.id
).exists()
html += self.render_icn( html += self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"), tooltip=_("Full access granted") if has_access else _("Access not granted"),

View File

@ -137,7 +137,7 @@
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/comment_card.html' %} {% include 'konova/includes/comment_card.html' %}

View File

@ -97,17 +97,10 @@
{% include 'map/geom_form.html' %} {% include 'map/geom_form.html' %}
</div> </div>
<div class="row"> <div class="row">
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> {% include 'konova/includes/report/qrcodes.html' %}
<h4>{% trans 'Open in browser' %}</h4>
{{ qrcode|safe }}
</div>
<div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'View in LANIS' %}</h4>
{{ qrcode_lanis|safe }}
</div>
</div> </div>
</div> </div>

View File

@ -89,6 +89,30 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
self.assertIn(self.superuser, obj.users.all()) self.assertIn(self.superuser, obj.users.all())
self.assertEqual(1, obj.users.count()) self.assertEqual(1, obj.users.count())
def test_non_editable_after_recording(self):
""" Tests that the intervention can not be edited after being recorded
User must be redirected to another page
Returns:
"""
self.assertIsNotNone(self.intervention)
self.assertFalse(self.intervention.is_recorded)
edit_url = reverse("intervention:edit", args=(self.intervention.id,))
response = self.client_user.get(edit_url)
has_redirect = response.status_code == 302
self.assertFalse(has_redirect)
self.intervention.set_recorded(self.user)
self.assertTrue(self.intervention.is_recorded)
edit_url = reverse("intervention:edit", args=(self.intervention.id,))
response = self.client_user.get(edit_url)
has_redirect = response.status_code == 302
self.assertTrue(has_redirect)
self.intervention.set_unrecorded(self.user)
def test_checkability(self): def test_checkability(self):
""" Tests that the intervention can only be checked if all required data has been added """ Tests that the intervention can only be checked if all required data has been added

View File

@ -156,10 +156,20 @@ class EgonGmlBuilder:
def build_gml(self): def build_gml(self):
comp_type, comp_type_code = self._gen_kompensationsArt() comp_type, comp_type_code = self._gen_kompensationsArt()
payment_date = self.intervention.payments.first().due_on payment = self.intervention.payments.first()
if payment_date is not None: payment_date = None
if payment is not None:
payment_date = payment.due_on
payment_date = payment_date.strftime(DEFAULT_DATE_FORMAT) payment_date = payment_date.strftime(DEFAULT_DATE_FORMAT)
cons_office = self.intervention.responsible.conservation_office
reg_office = self.intervention.responsible.registration_office
law = self.intervention.legal.laws.first()
process_type = self.intervention.legal.process_type
handler = self.intervention.responsible.handler
reg_date = self.intervention.legal.registration_date
bind_date = self.intervention.legal.binding_date
xml_dict = { xml_dict = {
"wfs:FeatureCollection": { "wfs:FeatureCollection": {
"@xmlns:wfs": "http://www.opengis.net/wfs", "@xmlns:wfs": "http://www.opengis.net/wfs",
@ -174,12 +184,12 @@ class EgonGmlBuilder:
"oneo:azZulassungsstelle": self.intervention.responsible.registration_file_number, "oneo:azZulassungsstelle": self.intervention.responsible.registration_file_number,
"oneo:bemerkungZulassungsstelle": None, "oneo:bemerkungZulassungsstelle": None,
"oneo:eintragungsstelle": { "oneo:eintragungsstelle": {
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/907/{self.intervention.responsible.conservation_office.atom_id}", "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/907/{cons_office.atom_id if cons_office else None}",
"#text": self.intervention.responsible.conservation_office.long_name "#text": cons_office.long_name if cons_office else None
}, },
"oneo:zulassungsstelle": { "oneo:zulassungsstelle": {
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{self.intervention.responsible.registration_office.atom_id}", "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{reg_office.atom_id if reg_office else None}",
"#text": self.intervention.responsible.registration_office.long_name "#text": reg_office.long_name if reg_office else None
}, },
"oneo:ersatzzahlung": self._sum_all_payments(), "oneo:ersatzzahlung": self._sum_all_payments(),
"oneo:kompensationsart": { "oneo:kompensationsart": {
@ -187,20 +197,20 @@ class EgonGmlBuilder:
"#text": comp_type "#text": comp_type
}, },
"oneo:verfahrensrecht": { "oneo:verfahrensrecht": {
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1048/{self.intervention.legal.laws.first().atom_id}", "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1048/{law.atom_id if law else None}",
"#text": self.intervention.legal.laws.first().short_name "#text": law.short_name if law else None
}, },
"oneo:verfahrenstyp": { "oneo:verfahrenstyp": {
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/44382/{self.intervention.legal.process_type.atom_id}", "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/44382/{process_type.atom_id if process_type else None}",
"#text": self.intervention.legal.process_type.long_name, "#text": process_type.long_name if process_type else None,
}, },
"oneo:eingreifer": { "oneo:eingreifer": {
"oneo:Eingreifer": { "oneo:Eingreifer": {
"oneo:art": { "oneo:art": {
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{self.intervention.responsible.handler.type.atom_id}", "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{handler.type.atom_id if handler.type else None}",
"#text": self.intervention.responsible.handler.type.long_name, "#text": handler.type.long_name if handler.type else None,
}, },
"oneo:bemerkung": self.intervention.responsible.handler.detail, "oneo:bemerkung": handler.detail if handler else None,
} }
}, },
"oneo:erfasser": { "oneo:erfasser": {
@ -212,8 +222,8 @@ class EgonGmlBuilder:
"oneo:zulassung": { "oneo:zulassung": {
"oneo:Zulassungstermin": { "oneo:Zulassungstermin": {
"oneo:bauBeginn": payment_date, "oneo:bauBeginn": payment_date,
"oneo:erlass": self.intervention.legal.registration_date.strftime(DEFAULT_DATE_FORMAT), "oneo:erlass": reg_date.strftime(DEFAULT_DATE_FORMAT) if reg_date else None,
"oneo:rechtsKraft": self.intervention.legal.binding_date.strftime(DEFAULT_DATE_FORMAT), "oneo:rechtsKraft": bind_date.strftime(DEFAULT_DATE_FORMAT) if bind_date else None,
} }
}, },
"oneo:geometrie": { "oneo:geometrie": {

View File

@ -18,7 +18,8 @@ from konova.utils.documents import remove_document, get_document
from konova.utils.generators import generate_qr_code from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED, \ from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED, \
CHECKED_RECORDED_RESET, DEDUCTION_REMOVED, DEDUCTION_ADDED, REVOCATION_ADDED, REVOCATION_REMOVED, \ CHECKED_RECORDED_RESET, DEDUCTION_REMOVED, DEDUCTION_ADDED, REVOCATION_ADDED, REVOCATION_REMOVED, \
COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED, DOCUMENT_EDITED COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED, DOCUMENT_EDITED, \
RECORDED_BLOCKS_EDIT
from konova.utils.user_checks import in_group from konova.utils.user_checks import in_group
@ -302,6 +303,13 @@ def edit_view(request: HttpRequest, id: str):
template = "intervention/form/view.html" template = "intervention/form/view.html"
# Get object from db # Get object from db
intervention = get_object_or_404(Intervention, id=id) intervention = get_object_or_404(Intervention, id=id)
if intervention.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("intervention:detail", id=id)
# Create forms, initialize with values from db/from POST request # Create forms, initialize with values from db/from POST request
data_form = EditInterventionForm(request.POST or None, instance=intervention) data_form = EditInterventionForm(request.POST or None, instance=intervention)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention) geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention)
@ -693,19 +701,22 @@ def report_view(request:HttpRequest, id: str):
distinct_deductions = intervention.deductions.all().distinct( distinct_deductions = intervention.deductions.all().distinct(
"account" "account"
) )
qrcode_img = generate_qr_code( qrcode_url = request.build_absolute_uri(reverse("intervention:report", args=(id,)))
request.build_absolute_uri(reverse("intervention:report", args=(id,))), qrcode_img = generate_qr_code(qrcode_url, 10)
10 qrcode_lanis_url = intervention.get_LANIS_link()
) qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
qrcode_img_lanis = generate_qr_code(
intervention.get_LANIS_link(),
7
)
context = { context = {
"obj": intervention, "obj": intervention,
"deductions": distinct_deductions, "deductions": distinct_deductions,
"qrcode": qrcode_img, "qrcode": {
"qrcode_lanis": qrcode_img_lanis, "img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"geom_form": geom_form, "geom_form": geom_form,
"parcels": parcels, "parcels": parcels,
TAB_TITLE_IDENTIFIER: tab_title, TAB_TITLE_IDENTIFIER: tab_title,

View File

@ -8,6 +8,7 @@ Created on: 22.07.21
from django.contrib import admin from django.contrib import admin
from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District, Municipal, ParcelGroup from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District, Municipal, ParcelGroup
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
from user.models import UserAction from user.models import UserAction
@ -16,7 +17,22 @@ class GeometryAdmin(admin.ModelAdmin):
list_display = [ list_display = [
"id", "id",
"created", "created",
"st_area",
] ]
readonly_fields = [
"st_area",
"created",
"modified",
]
def st_area(self, obj):
val = None
geom = obj.geom
if geom is not None:
geom.transform(ct=DEFAULT_SRID_RLP)
val = geom.area
return val
st_area.short_description = f"Area (srid={DEFAULT_SRID_RLP})"
class ParcelAdmin(admin.ModelAdmin): class ParcelAdmin(admin.ModelAdmin):

View File

@ -52,14 +52,16 @@ class InterventionAutocomplete(Select2QuerySetView):
""" """
def get_queryset(self): def get_queryset(self):
if self.request.user.is_anonymous: user = self.request.user
if user.is_anonymous:
return Intervention.objects.none() return Intervention.objects.none()
qs = Intervention.objects.filter( qs = Intervention.objects.filter(
deleted=None, Q(deleted=None) &
users__in=[self.request.user], Q(users__in=[user]) |
Q(teams__in=user.teams.all())
).order_by( ).order_by(
"identifier" "identifier"
) ).distinct()
if self.q: if self.q:
qs = qs.filter( qs = qs.filter(
Q(identifier__icontains=self.q) | Q(identifier__icontains=self.q) |

View File

@ -57,6 +57,8 @@ class BaseForm(forms.Form):
self.has_required_fields = True self.has_required_fields = True
break break
self.check_for_recorded_instance()
@abstractmethod @abstractmethod
def save(self): def save(self):
# To be implemented in subclasses! # To be implemented in subclasses!
@ -136,6 +138,38 @@ class BaseForm(forms.Form):
set_class = set_class.replace(cls, "") set_class = set_class.replace(cls, "")
self.fields[field].widget.attrs["class"] = set_class self.fields[field].widget.attrs["class"] = set_class
def check_for_recorded_instance(self):
""" Checks if the instance is recorded and runs some special logic if yes
If the instance is recorded, the form shall not display any possibility to
edit any data. Instead, the users should get some information about why they can not edit anything.
There are situations where the form should be rendered regularly,
e.g deduction forms for (recorded) eco accounts.
Returns:
"""
from intervention.forms.modalForms import NewDeductionModalForm, EditEcoAccountDeductionModalForm, \
RemoveEcoAccountDeductionModalForm
is_none = self.instance is None
is_other_data_type = not isinstance(self.instance, BaseObject)
is_deduction_form = isinstance(
self,
(
NewDeductionModalForm,
EditEcoAccountDeductionModalForm,
RemoveEcoAccountDeductionModalForm,
)
)
if is_none or is_other_data_type or is_deduction_form:
# Do nothing
return
if self.instance.is_recorded:
self.template = "form/recorded_no_edit.html"
class RemoveForm(BaseForm): class RemoveForm(BaseForm):
check = forms.BooleanField( check = forms.BooleanField(
@ -410,7 +444,6 @@ class NewDocumentModalForm(BaseModalForm):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Add new document") self.form_title = _("Add new document")
self.form_caption = _("") self.form_caption = _("")
self.template = "modal/modal_form.html"
self.form_attrs = { self.form_attrs = {
"enctype": "multipart/form-data", # important for file upload "enctype": "multipart/form-data", # important for file upload
} }
@ -598,3 +631,11 @@ class RecordModalForm(BaseModalForm):
else: else:
self.instance.set_recorded(self.user) self.instance.set_recorded(self.user)
return self.instance return self.instance
def check_for_recorded_instance(self):
""" Overwrite the check method for doing nothing on the RecordModalForm
Returns:
"""
pass

View File

@ -5,6 +5,10 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 04.01.22 Created on: 04.01.22
""" """
import datetime
from django.contrib.gis.db.models.functions import Area
from konova.management.commands.setup import BaseKonovaCommand from konova.management.commands.setup import BaseKonovaCommand
from konova.models import Geometry, Parcel, District from konova.models import Geometry, Parcel, District
@ -23,12 +27,21 @@ class Command(BaseKonovaCommand):
num_parcels_before = Parcel.objects.count() num_parcels_before = Parcel.objects.count()
num_districts_before = District.objects.count() num_districts_before = District.objects.count()
self._write_warning("=== Update parcels and districts ===") self._write_warning("=== Update parcels and districts ===")
# Order geometries by size to process smaller once at first
geometries = Geometry.objects.all().exclude( geometries = Geometry.objects.all().exclude(
geom=None geom=None
).annotate(area=Area("geom")).order_by(
'area'
) )
self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...") self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...")
i = 0
num_geoms = geometries.count()
for geometry in geometries: for geometry in geometries:
self._write_warning(f"--- {datetime.datetime.now()} Process {geometry.id} now ...")
geometry.update_parcels() geometry.update_parcels()
self._write_warning(f"--- Processed {geometry.get_underlying_parcels().count()} underlying parcels")
i += 1
self._write_warning(f"--- {i}/{num_geoms} processed")
num_parcels_after = Parcel.objects.count() num_parcels_after = Parcel.objects.count()
num_districts_after = District.objects.count() num_districts_after = District.objects.count()

View File

@ -20,6 +20,9 @@ class Geometry(BaseResource):
from konova.settings import DEFAULT_SRID from konova.settings import DEFAULT_SRID
geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID) geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID)
def __str__(self):
return str(self.id)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super().save(*args, **kwargs) super().save(*args, **kwargs)
self.check_for_conflicts() self.check_for_conflicts()
@ -110,32 +113,38 @@ class Geometry(BaseResource):
_now = timezone.now() _now = timezone.now()
underlying_parcels = [] underlying_parcels = []
for result in fetched_parcels: for result in fetched_parcels:
fetched_parcel = result[typename] parcel_properties = result["properties"]
# There could be parcels which include the word 'Flur', # There could be parcels which include the word 'Flur',
# which needs to be deleted and just keep the numerical values # which needs to be deleted and just keep the numerical values
## THIS CAN BE REMOVED IN THE FUTURE, WHEN 'Flur' WON'T OCCUR ANYMORE! ## THIS CAN BE REMOVED IN THE FUTURE, WHEN 'Flur' WON'T OCCUR ANYMORE!
flr_val = fetched_parcel["ave:flur"].replace("Flur ", "") flr_val = parcel_properties["flur"].replace("Flur ", "")
district = District.objects.get_or_create( district = District.objects.get_or_create(
key=fetched_parcel["ave:kreisschl"], key=parcel_properties["kreisschl"],
name=fetched_parcel["ave:kreis"], name=parcel_properties["kreis"],
)[0] )[0]
municipal = Municipal.objects.get_or_create( municipal = Municipal.objects.get_or_create(
key=fetched_parcel["ave:gmdschl"], key=parcel_properties["gmdschl"],
name=fetched_parcel["ave:gemeinde"], name=parcel_properties["gemeinde"],
district=district, district=district,
)[0] )[0]
parcel_group = ParcelGroup.objects.get_or_create( parcel_group = ParcelGroup.objects.get_or_create(
key=fetched_parcel["ave:gemaschl"], key=parcel_properties["gemaschl"],
name=fetched_parcel["ave:gemarkung"], name=parcel_properties["gemarkung"],
municipal=municipal, municipal=municipal,
)[0] )[0]
flrstck_nnr = parcel_properties['flstnrnen']
if not flrstck_nnr:
flrstck_nnr = None
flrstck_zhlr = parcel_properties['flstnrzae']
if not flrstck_zhlr:
flrstck_zhlr = None
parcel_obj = Parcel.objects.get_or_create( parcel_obj = Parcel.objects.get_or_create(
district=district, district=district,
municipal=municipal, municipal=municipal,
parcel_group=parcel_group, parcel_group=parcel_group,
flr=flr_val, flr=flr_val,
flrstck_nnr=fetched_parcel['ave:flstnrnen'], flrstck_nnr=flrstck_nnr,
flrstck_zhlr=fetched_parcel['ave:flstnrzae'], flrstck_zhlr=flrstck_zhlr,
)[0] )[0]
parcel_obj.district = district parcel_obj.district = district
parcel_obj.updated_on = _now parcel_obj.updated_on = _now

View File

@ -289,6 +289,8 @@ class RecordableObjectMixin(models.Model):
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
if self.recorded: if self.recorded:
return None return None
self.unshare_with_default_users()
action = UserActionLogEntry.get_recorded_action(user) action = UserActionLogEntry.get_recorded_action(user)
self.recorded = action self.recorded = action
self.save() self.save()
@ -335,6 +337,15 @@ class RecordableObjectMixin(models.Model):
""" """
raise NotImplementedError("Implement this in the subclass!") raise NotImplementedError("Implement this in the subclass!")
@property
def is_recorded(self):
""" Getter for record status as property
Returns:
"""
return self.recorded is not None
class CheckableObjectMixin(models.Model): class CheckableObjectMixin(models.Model):
# Checks - Refers to "Genehmigen" but optional # Checks - Refers to "Genehmigen" but optional
@ -608,6 +619,26 @@ class ShareableObjectMixin(models.Model):
""" """
raise NotImplementedError("Must be implemented in subclasses!") raise NotImplementedError("Must be implemented in subclasses!")
def unshare_with_default_users(self):
""" Removes all shared users from direct shared access which are only default group users
Returns:
"""
from konova.utils.user_checks import is_default_group_only
users = self.shared_users
cleaned_users = []
default_users = []
for user in users:
if not is_default_group_only(user):
cleaned_users.append(user)
else:
default_users.append(user)
self.share_with_user_list(cleaned_users)
for user in default_users:
celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user.id)
class GeoReferencedMixin(models.Model): class GeoReferencedMixin(models.Model):
geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL) geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL)

View File

@ -19,6 +19,6 @@ PAGE_SIZE_OPTIONS_TUPLES = [
(50, 50), (50, 50),
(100, 100), (100, 100),
] ]
PAGE_SIZE_DEFAULT = 5 PAGE_SIZE_DEFAULT = 10
PAGE_SIZE_MAX = 100 PAGE_SIZE_MAX = 100
PAGE_DEFAULT = 1 PAGE_DEFAULT = 1

View File

@ -0,0 +1,22 @@
{% load l10n i18n %}
{% for parcel in parcels %}
{% if forloop.last and next_page %}
<tr hx-get="{% url 'geometry-parcels-content' geom_id next_page %}"
hx-trigger="intersect once"
hx-swap="afterend">
<td>{{parcel.parcel_group.name|default_if_none:"-"}}</td>
<td>{{parcel.parcel_group.key|default_if_none:"-"}}</td>
<td>{{parcel.flr|default_if_none:"-"|unlocalize}}</td>
<td>{{parcel.flrstck_zhlr|default_if_none:"-"|unlocalize}}</td>
<td>{{parcel.flrstck_nnr|default_if_none:"-"|unlocalize}}</td>
</tr>
{% else %}
<tr>
<td>{{parcel.parcel_group.name|default_if_none:"-"}}</td>
<td>{{parcel.parcel_group.key|default_if_none:"-"}}</td>
<td>{{parcel.flr|default_if_none:"-"|unlocalize}}</td>
<td>{{parcel.flrstck_zhlr|default_if_none:"-"|unlocalize}}</td>
<td>{{parcel.flrstck_nnr|default_if_none:"-"|unlocalize}}</td>
</tr>
{% endif %}
{% endfor %}

View File

@ -37,16 +37,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for parcel in parcels %} {% include 'konova/includes/parcels/parcel_table_content.html' %}
<tr>
<td>{{parcel.parcel_group.name|default_if_none:"-"}}</td>
<td>{{parcel.parcel_group.key|default_if_none:"-"}}</td>
<td>{{parcel.flr|default_if_none:"-"|unlocalize}}</td>
<td>{{parcel.flrstck_zhlr|default_if_none:"-"|unlocalize}}</td>
<td>{{parcel.flrstck_nnr|default_if_none:"-"|unlocalize}}</td>
</tr>
{% endfor %}
</tbody> </tbody>
</table> </table>
{% endif %} {% endif %}

View File

@ -8,7 +8,7 @@
</h5> </h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div hx-trigger="every 2s" hx-get="{% url 'geometry-parcels' geom_form.instance.geometry.id %}"> <div hx-trigger="load, every 5s" hx-get="{% url 'geometry-parcels' geom_form.instance.geometry.id %}">
<div class="row justify-content-center"> <div class="row justify-content-center">
<span class="spinner-border rlp-r-inv" role="status"></span> <span class="spinner-border rlp-r-inv" role="status"></span>
</div> </div>

View File

@ -0,0 +1,19 @@
{% load i18n %}
<div class="col-sm-6 col-md-6 col-lg-6">
<button class="btn btn-outline-default col-sm-12">
<a href="{{qrcode.url}}" target="_blank">
<h4>{% trans 'Open in browser' %}</h4>
{{ qrcode.img|safe }}
</a>
</button>
</div>
<div class="col-sm-6 col-md-6 col-lg-6">
<button class="btn btn-outline-default col-sm-12">
<a href="{{qrcode_lanis.url}}" target="_blank">
<h4>{% trans 'View in LANIS' %}</h4>
{{ qrcode_lanis.img|safe }}
</a>
</button>
</div>

View File

@ -24,7 +24,7 @@ from konova.autocompletes import EcoAccountAutocomplete, \
ShareTeamAutocomplete, HandlerCodeAutocomplete ShareTeamAutocomplete, HandlerCodeAutocomplete
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
from konova.sso.sso import KonovaSSOClient from konova.sso.sso import KonovaSSOClient
from konova.views import logout_view, home_view, get_geom_parcels from konova.views import logout_view, home_view, get_geom_parcels, get_geom_parcels_content
sso_client = KonovaSSOClient(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY) sso_client = KonovaSSOClient(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY)
urlpatterns = [ urlpatterns = [
@ -40,7 +40,8 @@ urlpatterns = [
path('cl/', include("codelist.urls")), path('cl/', include("codelist.urls")),
path('analysis/', include("analysis.urls")), path('analysis/', include("analysis.urls")),
path('api/', include("api.urls")), path('api/', include("api.urls")),
path('geom/<id>/parcels', get_geom_parcels, name="geometry-parcels"), path('geom/<id>/parcels/', get_geom_parcels, name="geometry-parcels"),
path('geom/<id>/parcels/<int:page>', get_geom_parcels_content, name="geometry-parcels-content"),
# Autocomplete paths for all apps # Autocomplete paths for all apps
path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"), path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"),

View File

@ -17,6 +17,7 @@ IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since ano
ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office users are allowed to remove entries.") ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office users are allowed to remove entries.")
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.") MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted") CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted")
RECORDED_BLOCKS_EDIT = _("Entry is recorded. To edit data, the entry first needs to be unrecorded.")
# SHARE # SHARE
DATA_UNSHARED = _("This data is not shared with you") DATA_UNSHARED = _("This data is not shared with you")

View File

@ -5,11 +5,12 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 17.12.21 Created on: 17.12.21
""" """
import json
from abc import abstractmethod from abc import abstractmethod
from json import JSONDecodeError
from time import sleep from time import sleep
import requests import requests
import xmltodict
from django.contrib.gis.db.models.functions import AsGML, Transform from django.contrib.gis.db.models.functions import AsGML, Transform
from requests.auth import HTTPDigestAuth from requests.auth import HTTPDigestAuth
@ -115,7 +116,7 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
geometry_operation, geometry_operation,
filter_srid filter_srid
) )
_filter = f'<wfs:GetFeature service="WFS" version="{self.version}" xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:fes="http://www.opengis.net/fes/2.0" xmlns:myns="http://www.someserver.com/myns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0.0/wfs.xsd" count="{self.count}" startindex="{start_index}"><wfs:Query typeNames="{typenames}">{spatial_filter}</wfs:Query></wfs:GetFeature>' _filter = f'<wfs:GetFeature service="WFS" version="{self.version}" xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:fes="http://www.opengis.net/fes/2.0" xmlns:myns="http://www.someserver.com/myns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0.0/wfs.xsd" count="{self.count}" startindex="{start_index}" outputFormat="application/json; subtype=geojson"><wfs:Query typeNames="{typenames}">{spatial_filter}</wfs:Query></wfs:GetFeature>'
return _filter return _filter
def get_features(self, def get_features(self,
@ -139,7 +140,7 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
Returns: Returns:
features (list): A list of returned features features (list): A list of returned features
""" """
features = [] found_features = []
while start_index is not None: while start_index is not None:
post_body = self._create_post_data( post_body = self._create_post_data(
spatial_operator, spatial_operator,
@ -155,19 +156,11 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
) )
content = response.content.decode("utf-8") content = response.content.decode("utf-8")
content = xmltodict.parse(content) try:
collection = content.get( # Check if collection is an exception and does not contain the requested data
"wfs:FeatureCollection", content = json.loads(content)
{}, except JSONDecodeError as e:
) if rerun_on_exception:
# Check if collection is an exception and does not contain the requested data
if len(collection) == 0:
exception = content.get(
"ows:ExceptionReport",
{}
)
if len(exception) > 0 and rerun_on_exception:
# Wait a second before another try # Wait a second before another try
sleep(1) sleep(1)
self.get_features( self.get_features(
@ -177,22 +170,21 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
start_index, start_index,
rerun_on_exception=False rerun_on_exception=False
) )
members = collection.get(
"wfs:member",
None,
)
if members is not None:
if len(members) > 1:
# extend feature list with found list of new feature members
features += members
else: else:
# convert single found feature member into list and extent feature list e.msg += content
features += [members] raise e
fetched_features = content.get(
"features",
{},
)
if collection.get("@next", None) is not None: found_features += fetched_features
start_index += self.count
else: if len(fetched_features) < self.count:
# The response was not 'full', so we got everything to fetch
start_index = None start_index = None
else:
# If a 'full' response returned, there might be more to fetch. Increase the start_index!
start_index += self.count
return features return found_features

View File

@ -110,12 +110,12 @@ def get_geom_parcels(request: HttpRequest, id: str):
id (str): The geometry's id id (str): The geometry's id
Returns: Returns:
A rendered piece of HTML
""" """
# HTTP code 286 states that the HTMX should stop polling for updates # HTTP code 286 states that the HTMX should stop polling for updates
# https://htmx.org/docs/#polling # https://htmx.org/docs/#polling
status_code = 286 status_code = 286
template = "konova/includes/parcel_table.html" template = "konova/includes/parcels/parcel_table_frame.html"
geom = get_object_or_404(Geometry, id=id) geom = get_object_or_404(Geometry, id=id)
parcels = geom.get_underlying_parcels() parcels = geom.get_underlying_parcels()
geos_geom = geom.geom geos_geom = geom.geom
@ -133,9 +133,18 @@ def get_geom_parcels(request: HttpRequest, id: str):
parcels = parcels.order_by("-municipal", "flr", "flrstck_zhlr", "flrstck_nnr") parcels = parcels.order_by("-municipal", "flr", "flrstck_zhlr", "flrstck_nnr")
municipals = parcels.order_by("municipal").distinct("municipal").values("municipal__id") municipals = parcels.order_by("municipal").distinct("municipal").values("municipal__id")
municipals = Municipal.objects.filter(id__in=municipals) municipals = Municipal.objects.filter(id__in=municipals)
rpp = 100
parcels = parcels[:rpp]
next_page = 1
if len(parcels) < rpp:
next_page = None
context = { context = {
"parcels": parcels, "parcels": parcels,
"municipals": municipals, "municipals": municipals,
"geom_id": str(id),
"next_page": next_page,
} }
html = render_to_string(template, context, request) html = render_to_string(template, context, request)
return HttpResponse(html, status=status_code) return HttpResponse(html, status=status_code)
@ -143,6 +152,49 @@ def get_geom_parcels(request: HttpRequest, id: str):
return HttpResponse(None, status=404) return HttpResponse(None, status=404)
@login_required
def get_geom_parcels_content(request: HttpRequest, id: str, page: int):
""" Getter for infinite scroll of HTMX
Returns parcels of a specific page/slice of the found parcel set.
Implementation of infinite scroll htmx example: https://htmx.org/examples/infinite-scroll/
Args:
request (HttpRequest): The incoming request
id (str): The geometry's id
page (int): The requested page number
Returns:
A rendered piece of HTML
"""
if page < 0:
raise AssertionError("Parcel page can not be negative")
# HTTP code 286 states that the HTMX should stop polling for updates
# https://htmx.org/docs/#polling
status_code = 286
template = "konova/includes/parcels/parcel_table_content.html"
geom = get_object_or_404(Geometry, id=id)
parcels = geom.get_underlying_parcels()
parcels = parcels.order_by("-municipal", "flr", "flrstck_zhlr", "flrstck_nnr")
rpp = 100
from_p = rpp * (page-1)
to_p = rpp * (page)
next_page = page + 1
parcels = parcels[from_p:to_p]
if len(parcels) < rpp:
next_page = None
context = {
"parcels": parcels,
"geom_id": str(id),
"next_page": next_page,
}
html = render_to_string(template, context, request)
return HttpResponse(html, status=status_code)
def get_404_view(request: HttpRequest, exception=None): def get_404_view(request: HttpRequest, exception=None):
""" Returns a 404 handling view """ Returns a 404 handling view

Binary file not shown.

View File

@ -18,15 +18,15 @@
#: konova/filters/mixins.py:277 konova/filters/mixins.py:323 #: konova/filters/mixins.py:277 konova/filters/mixins.py:323
#: konova/filters/mixins.py:361 konova/filters/mixins.py:362 #: konova/filters/mixins.py:361 konova/filters/mixins.py:362
#: konova/filters/mixins.py:393 konova/filters/mixins.py:394 #: konova/filters/mixins.py:393 konova/filters/mixins.py:394
#: konova/forms.py:143 konova/forms.py:244 konova/forms.py:315 #: konova/forms.py:177 konova/forms.py:278 konova/forms.py:349
#: konova/forms.py:359 konova/forms.py:369 konova/forms.py:382 #: konova/forms.py:393 konova/forms.py:403 konova/forms.py:416
#: konova/forms.py:394 konova/forms.py:412 user/forms.py:42 #: konova/forms.py:428 konova/forms.py:446 user/forms.py:42
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-12 10:28+0200\n" "POT-Creation-Date: 2022-04-21 14:16+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -77,7 +77,7 @@ msgstr "Bericht generieren"
msgid "Select a timespan and the desired conservation office" msgid "Select a timespan and the desired conservation office"
msgstr "Wählen Sie die Zeitspanne und die gewünschte Eintragungsstelle" msgstr "Wählen Sie die Zeitspanne und die gewünschte Eintragungsstelle"
#: analysis/forms.py:69 konova/forms.py:191 #: analysis/forms.py:69 konova/forms.py:225
msgid "Continue" msgid "Continue"
msgstr "Weiter" msgstr "Weiter"
@ -154,7 +154,7 @@ msgstr "Geprüft"
#: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:9 #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:9
#: analysis/templates/analysis/reports/includes/intervention/laws.html:20 #: analysis/templates/analysis/reports/includes/intervention/laws.html:20
#: analysis/templates/analysis/reports/includes/old_data/amount.html:18 #: analysis/templates/analysis/reports/includes/old_data/amount.html:18
#: compensation/tables.py:46 compensation/tables.py:222 #: compensation/tables.py:46 compensation/tables.py:220
#: compensation/templates/compensation/detail/compensation/view.html:78 #: compensation/templates/compensation/detail/compensation/view.html:78
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31
#: compensation/templates/compensation/detail/eco_account/view.html:45 #: compensation/templates/compensation/detail/eco_account/view.html:45
@ -294,7 +294,7 @@ msgid "Intervention"
msgstr "Eingriff" msgstr "Eingriff"
#: analysis/templates/analysis/reports/includes/old_data/amount.html:34 #: analysis/templates/analysis/reports/includes/old_data/amount.html:34
#: compensation/tables.py:266 #: compensation/tables.py:264
#: compensation/templates/compensation/detail/eco_account/view.html:20 #: compensation/templates/compensation/detail/eco_account/view.html:20
#: intervention/forms/modalForms.py:348 intervention/forms/modalForms.py:355 #: intervention/forms/modalForms.py:348 intervention/forms/modalForms.py:355
#: konova/templates/konova/includes/quickstart/ecoaccounts.html:4 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4
@ -315,7 +315,7 @@ msgid "Show only unrecorded"
msgstr "Nur unverzeichnete anzeigen" msgstr "Nur unverzeichnete anzeigen"
#: compensation/forms/forms.py:32 compensation/tables.py:25 #: compensation/forms/forms.py:32 compensation/tables.py:25
#: compensation/tables.py:197 ema/tables.py:29 intervention/forms/forms.py:28 #: compensation/tables.py:195 ema/tables.py:29 intervention/forms/forms.py:28
#: intervention/tables.py:24 #: intervention/tables.py:24
#: intervention/templates/intervention/detail/includes/compensations.html:30 #: intervention/templates/intervention/detail/includes/compensations.html:30
msgid "Identifier" msgid "Identifier"
@ -327,7 +327,7 @@ msgid "Generated automatically"
msgstr "Automatisch generiert" msgstr "Automatisch generiert"
#: compensation/forms/forms.py:44 compensation/tables.py:30 #: compensation/forms/forms.py:44 compensation/tables.py:30
#: compensation/tables.py:202 #: compensation/tables.py:200
#: compensation/templates/compensation/detail/compensation/includes/documents.html:28 #: compensation/templates/compensation/detail/compensation/includes/documents.html:28
#: compensation/templates/compensation/detail/compensation/view.html:32 #: compensation/templates/compensation/detail/compensation/view.html:32
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:28 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:28
@ -342,7 +342,7 @@ msgstr "Automatisch generiert"
#: intervention/templates/intervention/detail/includes/documents.html:28 #: intervention/templates/intervention/detail/includes/documents.html:28
#: intervention/templates/intervention/detail/view.html:31 #: intervention/templates/intervention/detail/view.html:31
#: intervention/templates/intervention/report/report.html:12 #: intervention/templates/intervention/report/report.html:12
#: konova/forms.py:358 #: konova/forms.py:392
msgid "Title" msgid "Title"
msgstr "Bezeichnung" msgstr "Bezeichnung"
@ -369,7 +369,7 @@ msgstr "Kompensation XY; Flur ABC"
#: intervention/templates/intervention/detail/includes/documents.html:34 #: intervention/templates/intervention/detail/includes/documents.html:34
#: intervention/templates/intervention/detail/includes/payments.html:34 #: intervention/templates/intervention/detail/includes/payments.html:34
#: intervention/templates/intervention/detail/includes/revocation.html:38 #: intervention/templates/intervention/detail/includes/revocation.html:38
#: konova/forms.py:393 konova/templates/konova/includes/comment_card.html:16 #: konova/forms.py:427 konova/templates/konova/includes/comment_card.html:16
msgid "Comment" msgid "Comment"
msgstr "Kommentar" msgstr "Kommentar"
@ -441,7 +441,7 @@ msgstr "kompensiert Eingriff"
msgid "Select the intervention for which this compensation compensates" msgid "Select the intervention for which this compensation compensates"
msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist" msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist"
#: compensation/forms/forms.py:202 compensation/views/compensation.py:96 #: compensation/forms/forms.py:202 compensation/views/compensation.py:110
msgid "New compensation" msgid "New compensation"
msgstr "Neue Kompensation" msgstr "Neue Kompensation"
@ -467,7 +467,7 @@ msgstr "Vereinbarungsdatum"
msgid "When did the parties agree on this?" msgid "When did the parties agree on this?"
msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?" msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?"
#: compensation/forms/forms.py:373 compensation/views/eco_account.py:107 #: compensation/forms/forms.py:373 compensation/views/eco_account.py:108
msgid "New Eco-Account" msgid "New Eco-Account"
msgstr "Neues Ökokonto" msgstr "Neues Ökokonto"
@ -493,7 +493,7 @@ msgid "Due on which date"
msgstr "Zahlung wird an diesem Datum erwartet" msgstr "Zahlung wird an diesem Datum erwartet"
#: compensation/forms/modalForms.py:64 compensation/forms/modalForms.py:359 #: compensation/forms/modalForms.py:64 compensation/forms/modalForms.py:359
#: intervention/forms/modalForms.py:177 konova/forms.py:395 #: intervention/forms/modalForms.py:177 konova/forms.py:429
msgid "Additional comment, maximum {} letters" msgid "Additional comment, maximum {} letters"
msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
@ -538,7 +538,7 @@ msgstr "Neuer Zustand"
msgid "Insert data for the new state" msgid "Insert data for the new state"
msgstr "Geben Sie die Daten des neuen Zustandes ein" msgstr "Geben Sie die Daten des neuen Zustandes ein"
#: compensation/forms/modalForms.py:217 konova/forms.py:193 #: compensation/forms/modalForms.py:217 konova/forms.py:227
msgid "Object removed" msgid "Object removed"
msgstr "Objekt entfernt" msgstr "Objekt entfernt"
@ -675,22 +675,22 @@ msgstr ""
"Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar einstellen " "Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar einstellen "
"wollen. Kontaktieren Sie die für die Abbuchungen verantwortlichen Nutzer!" "wollen. Kontaktieren Sie die für die Abbuchungen verantwortlichen Nutzer!"
#: compensation/tables.py:35 compensation/tables.py:207 ema/tables.py:39 #: compensation/tables.py:35 compensation/tables.py:205 ema/tables.py:39
#: intervention/tables.py:34 konova/filters/mixins.py:98 #: intervention/tables.py:34 konova/filters/mixins.py:98
msgid "Parcel gmrkng" msgid "Parcel gmrkng"
msgstr "Gemarkung" msgstr "Gemarkung"
#: compensation/tables.py:52 compensation/tables.py:228 ema/tables.py:50 #: compensation/tables.py:52 compensation/tables.py:226 ema/tables.py:50
#: intervention/tables.py:51 #: intervention/tables.py:51
msgid "Editable" msgid "Editable"
msgstr "Freigegeben" msgstr "Freigegeben"
#: compensation/tables.py:58 compensation/tables.py:234 ema/tables.py:56 #: compensation/tables.py:58 compensation/tables.py:232 ema/tables.py:56
#: intervention/tables.py:57 #: intervention/tables.py:57
msgid "Last edit" msgid "Last edit"
msgstr "Zuletzt bearbeitet" msgstr "Zuletzt bearbeitet"
#: compensation/tables.py:89 compensation/tables.py:266 ema/tables.py:89 #: compensation/tables.py:89 compensation/tables.py:264 ema/tables.py:89
#: intervention/tables.py:88 #: intervention/tables.py:88
msgid "Open {}" msgid "Open {}"
msgstr "Öffne {}" msgstr "Öffne {}"
@ -713,32 +713,32 @@ msgstr "Am {} von {} geprüft worden"
msgid "Not recorded yet" msgid "Not recorded yet"
msgstr "Noch nicht verzeichnet" msgstr "Noch nicht verzeichnet"
#: compensation/tables.py:165 compensation/tables.py:326 ema/tables.py:136 #: compensation/tables.py:165 compensation/tables.py:324 ema/tables.py:136
#: intervention/tables.py:162 #: intervention/tables.py:162
msgid "Recorded on {} by {}" msgid "Recorded on {} by {}"
msgstr "Am {} von {} verzeichnet worden" msgstr "Am {} von {} verzeichnet worden"
#: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159 #: compensation/tables.py:187 compensation/tables.py:346 ema/tables.py:157
#: intervention/tables.py:185 #: intervention/tables.py:183
msgid "Full access granted" msgid "Full access granted"
msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden" msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden"
#: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159 #: compensation/tables.py:187 compensation/tables.py:346 ema/tables.py:157
#: intervention/tables.py:185 #: intervention/tables.py:183
msgid "Access not granted" msgid "Access not granted"
msgstr "Nicht freigegeben - Datensatz nur lesbar" msgstr "Nicht freigegeben - Datensatz nur lesbar"
#: compensation/tables.py:212 #: compensation/tables.py:210
#: compensation/templates/compensation/detail/eco_account/view.html:36 #: compensation/templates/compensation/detail/eco_account/view.html:36
#: konova/templates/konova/widgets/progressbar.html:3 #: konova/templates/konova/widgets/progressbar.html:3
msgid "Available" msgid "Available"
msgstr "Verfügbar" msgstr "Verfügbar"
#: compensation/tables.py:243 #: compensation/tables.py:241
msgid "Eco Accounts" msgid "Eco Accounts"
msgstr "Ökokonten" msgstr "Ökokonten"
#: compensation/tables.py:321 #: compensation/tables.py:319
msgid "Not recorded yet. Can not be used for deductions, yet." msgid "Not recorded yet. Can not be used for deductions, yet."
msgstr "" msgstr ""
"Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden." "Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden."
@ -871,7 +871,7 @@ msgstr "Dokumente"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:14 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:14
#: ema/templates/ema/detail/includes/documents.html:14 #: ema/templates/ema/detail/includes/documents.html:14
#: intervention/templates/intervention/detail/includes/documents.html:14 #: intervention/templates/intervention/detail/includes/documents.html:14
#: konova/forms.py:411 #: konova/forms.py:445
msgid "Add new document" msgid "Add new document"
msgstr "Neues Dokument hinzufügen" msgstr "Neues Dokument hinzufügen"
@ -879,7 +879,7 @@ msgstr "Neues Dokument hinzufügen"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:31 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:31
#: ema/templates/ema/detail/includes/documents.html:31 #: ema/templates/ema/detail/includes/documents.html:31
#: intervention/templates/intervention/detail/includes/documents.html:31 #: intervention/templates/intervention/detail/includes/documents.html:31
#: konova/forms.py:368 #: konova/forms.py:402
msgid "Created on" msgid "Created on"
msgstr "Erstellt" msgstr "Erstellt"
@ -887,7 +887,7 @@ msgstr "Erstellt"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:61 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:61
#: ema/templates/ema/detail/includes/documents.html:61 #: ema/templates/ema/detail/includes/documents.html:61
#: intervention/templates/intervention/detail/includes/documents.html:65 #: intervention/templates/intervention/detail/includes/documents.html:65
#: konova/forms.py:474 #: konova/forms.py:507
msgid "Edit document" msgid "Edit document"
msgstr "Dokument bearbeiten" msgstr "Dokument bearbeiten"
@ -1067,7 +1067,7 @@ msgid "Recorded on"
msgstr "Verzeichnet am" msgstr "Verzeichnet am"
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:65 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:65
#: intervention/forms/modalForms.py:481 #: intervention/forms/modalForms.py:490
#: intervention/templates/intervention/detail/includes/deductions.html:60 #: intervention/templates/intervention/detail/includes/deductions.html:60
msgid "Edit Deduction" msgid "Edit Deduction"
msgstr "Abbuchung bearbeiten" msgstr "Abbuchung bearbeiten"
@ -1112,20 +1112,6 @@ msgstr "Maßnahmenträger"
msgid "Report" msgid "Report"
msgstr "Bericht" msgstr "Bericht"
#: compensation/templates/compensation/report/compensation/report.html:45
#: compensation/templates/compensation/report/eco_account/report.html:58
#: ema/templates/ema/report/report.html:45
#: intervention/templates/intervention/report/report.html:104
msgid "Open in browser"
msgstr "Im Browser öffnen"
#: compensation/templates/compensation/report/compensation/report.html:49
#: compensation/templates/compensation/report/eco_account/report.html:62
#: ema/templates/ema/report/report.html:49
#: intervention/templates/intervention/report/report.html:108
msgid "View in LANIS"
msgstr "In LANIS öffnen"
#: compensation/templates/compensation/report/eco_account/report.html:24 #: compensation/templates/compensation/report/eco_account/report.html:24
msgid "Deductions for" msgid "Deductions for"
msgstr "Abbuchungen für" msgstr "Abbuchungen für"
@ -1155,72 +1141,72 @@ msgstr ""
msgid "Responsible data" msgid "Responsible data"
msgstr "Daten zu den verantwortlichen Stellen" msgstr "Daten zu den verantwortlichen Stellen"
#: compensation/views/compensation.py:52 #: compensation/views/compensation.py:53
msgid "Compensations - Overview" msgid "Compensations - Overview"
msgstr "Kompensationen - Übersicht" msgstr "Kompensationen - Übersicht"
#: compensation/views/compensation.py:151 konova/utils/message_templates.py:35 #: compensation/views/compensation.py:172 konova/utils/message_templates.py:36
msgid "Compensation {} edited" msgid "Compensation {} edited"
msgstr "Kompensation {} bearbeitet" msgstr "Kompensation {} bearbeitet"
#: compensation/views/compensation.py:161 compensation/views/eco_account.py:165 #: compensation/views/compensation.py:182 compensation/views/eco_account.py:173
#: ema/views.py:233 intervention/views.py:327 #: ema/views.py:240 intervention/views.py:335
msgid "Edit {}" msgid "Edit {}"
msgstr "Bearbeite {}" msgstr "Bearbeite {}"
#: compensation/views/compensation.py:240 compensation/views/eco_account.py:351 #: compensation/views/compensation.py:261 compensation/views/eco_account.py:359
#: ema/views.py:194 intervention/views.py:531 #: ema/views.py:194 intervention/views.py:539
msgid "Log" msgid "Log"
msgstr "Log" msgstr "Log"
#: compensation/views/compensation.py:584 compensation/views/eco_account.py:719 #: compensation/views/compensation.py:605 compensation/views/eco_account.py:727
#: ema/views.py:551 intervention/views.py:677 #: ema/views.py:558 intervention/views.py:685
msgid "Report {}" msgid "Report {}"
msgstr "Bericht {}" msgstr "Bericht {}"
#: compensation/views/eco_account.py:64 #: compensation/views/eco_account.py:65
msgid "Eco-account - Overview" msgid "Eco-account - Overview"
msgstr "Ökokonten - Übersicht" msgstr "Ökokonten - Übersicht"
#: compensation/views/eco_account.py:97 #: compensation/views/eco_account.py:98
msgid "Eco-Account {} added" msgid "Eco-Account {} added"
msgstr "Ökokonto {} hinzugefügt" msgstr "Ökokonto {} hinzugefügt"
#: compensation/views/eco_account.py:155 #: compensation/views/eco_account.py:163
msgid "Eco-Account {} edited" msgid "Eco-Account {} edited"
msgstr "Ökokonto {} bearbeitet" msgstr "Ökokonto {} bearbeitet"
#: compensation/views/eco_account.py:268 #: compensation/views/eco_account.py:276
msgid "Eco-account removed" msgid "Eco-account removed"
msgstr "Ökokonto entfernt" msgstr "Ökokonto entfernt"
#: compensation/views/eco_account.py:372 ema/views.py:275 #: compensation/views/eco_account.py:380 ema/views.py:282
#: intervention/views.py:630 #: intervention/views.py:638
msgid "{} unrecorded" msgid "{} unrecorded"
msgstr "{} entzeichnet" msgstr "{} entzeichnet"
#: compensation/views/eco_account.py:372 ema/views.py:275 #: compensation/views/eco_account.py:380 ema/views.py:282
#: intervention/views.py:630 #: intervention/views.py:638
msgid "{} recorded" msgid "{} recorded"
msgstr "{} verzeichnet" msgstr "{} verzeichnet"
#: compensation/views/eco_account.py:792 ema/views.py:617 #: compensation/views/eco_account.py:804 ema/views.py:628
#: intervention/views.py:428 #: intervention/views.py:436
msgid "{} has already been shared with you" msgid "{} has already been shared with you"
msgstr "{} wurde bereits für Sie freigegeben" msgstr "{} wurde bereits für Sie freigegeben"
#: compensation/views/eco_account.py:797 ema/views.py:622 #: compensation/views/eco_account.py:809 ema/views.py:633
#: intervention/views.py:433 #: intervention/views.py:441
msgid "{} has been shared with you" msgid "{} has been shared with you"
msgstr "{} ist nun für Sie freigegeben" msgstr "{} ist nun für Sie freigegeben"
#: compensation/views/eco_account.py:804 ema/views.py:629 #: compensation/views/eco_account.py:816 ema/views.py:640
#: intervention/views.py:440 #: intervention/views.py:448
msgid "Share link invalid" msgid "Share link invalid"
msgstr "Freigabelink ungültig" msgstr "Freigabelink ungültig"
#: compensation/views/eco_account.py:827 ema/views.py:652 #: compensation/views/eco_account.py:839 ema/views.py:663
#: intervention/views.py:463 #: intervention/views.py:471
msgid "Share settings updated" msgid "Share settings updated"
msgstr "Freigabe Einstellungen aktualisiert" msgstr "Freigabe Einstellungen aktualisiert"
@ -1260,11 +1246,11 @@ msgstr "EMAs - Übersicht"
msgid "EMA {} added" msgid "EMA {} added"
msgstr "EMA {} hinzugefügt" msgstr "EMA {} hinzugefügt"
#: ema/views.py:223 #: ema/views.py:230
msgid "EMA {} edited" msgid "EMA {} edited"
msgstr "EMA {} bearbeitet" msgstr "EMA {} bearbeitet"
#: ema/views.py:256 #: ema/views.py:263
msgid "EMA removed" msgid "EMA removed"
msgstr "EMA entfernt" msgstr "EMA entfernt"
@ -1326,7 +1312,7 @@ msgstr "Datum Zulassung bzw. Satzungsbeschluss"
msgid "Binding on" msgid "Binding on"
msgstr "Datum Bestandskraft" msgstr "Datum Bestandskraft"
#: intervention/forms/forms.py:211 intervention/views.py:94 #: intervention/forms/forms.py:211 intervention/views.py:95
msgid "New intervention" msgid "New intervention"
msgstr "Neuer Eingriff" msgstr "Neuer Eingriff"
@ -1406,7 +1392,7 @@ msgstr "Kompensationen und Zahlungen geprüft"
msgid "Run check" msgid "Run check"
msgstr "Prüfung vornehmen" msgstr "Prüfung vornehmen"
#: intervention/forms/modalForms.py:264 konova/forms.py:515 #: intervention/forms/modalForms.py:264 konova/forms.py:548
msgid "" msgid ""
"I, {} {}, confirm that all necessary control steps have been performed by " "I, {} {}, confirm that all necessary control steps have been performed by "
"myself." "myself."
@ -1430,7 +1416,7 @@ msgstr "Neue Abbuchung"
msgid "Enter the information for a new deduction from a chosen eco-account" msgid "Enter the information for a new deduction from a chosen eco-account"
msgstr "Geben Sie die Informationen für eine neue Abbuchung ein." msgstr "Geben Sie die Informationen für eine neue Abbuchung ein."
#: intervention/forms/modalForms.py:434 #: intervention/forms/modalForms.py:436
msgid "" msgid ""
"Eco-account {} is not recorded yet. You can only deduct from recorded " "Eco-account {} is not recorded yet. You can only deduct from recorded "
"accounts." "accounts."
@ -1438,7 +1424,15 @@ msgstr ""
"Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von " "Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von "
"verzeichneten Ökokonten erfolgen." "verzeichneten Ökokonten erfolgen."
#: intervention/forms/modalForms.py:444 #: intervention/forms/modalForms.py:443
msgid ""
"Intervention {} is currently recorded. To change any data on it, the entry "
"must be unrecorded."
msgstr ""
"Eingriff {} ist verzeichnet. Der Eintrag muss erst entzeichnet werden um "
"fortfahren zu können."
#: intervention/forms/modalForms.py:453
msgid "" msgid ""
"The account {} has not enough surface for a deduction of {} m². There are " "The account {} has not enough surface for a deduction of {} m². There are "
"only {} m² left" "only {} m² left"
@ -1538,27 +1532,27 @@ msgstr ""
"Kein Ausgleich jeglicher Art gefunden (Kompensation, Ersatzzahlung, " "Kein Ausgleich jeglicher Art gefunden (Kompensation, Ersatzzahlung, "
"Abbuchung)" "Abbuchung)"
#: intervention/views.py:51 #: intervention/views.py:52
msgid "Interventions - Overview" msgid "Interventions - Overview"
msgstr "Eingriffe - Übersicht" msgstr "Eingriffe - Übersicht"
#: intervention/views.py:84 #: intervention/views.py:85
msgid "Intervention {} added" msgid "Intervention {} added"
msgstr "Eingriff {} hinzugefügt" msgstr "Eingriff {} hinzugefügt"
#: intervention/views.py:315 #: intervention/views.py:323
msgid "Intervention {} edited" msgid "Intervention {} edited"
msgstr "Eingriff {} bearbeitet" msgstr "Eingriff {} bearbeitet"
#: intervention/views.py:351 #: intervention/views.py:359
msgid "{} removed" msgid "{} removed"
msgstr "{} entfernt" msgstr "{} entfernt"
#: intervention/views.py:484 #: intervention/views.py:492
msgid "Check performed" msgid "Check performed"
msgstr "Prüfung durchgeführt" msgstr "Prüfung durchgeführt"
#: intervention/views.py:635 #: intervention/views.py:643
msgid "There are errors on this intervention:" msgid "There are errors on this intervention:"
msgstr "Es liegen Fehler in diesem Eingriff vor:" msgstr "Es liegen Fehler in diesem Eingriff vor:"
@ -1588,7 +1582,7 @@ msgid "Search for file number"
msgstr "Nach Aktenzeichen suchen" msgstr "Nach Aktenzeichen suchen"
#: konova/filters/mixins.py:85 #: konova/filters/mixins.py:85
#: konova/templates/konova/includes/parcel_table.html:13 #: konova/templates/konova/includes/parcels/parcel_table_frame.html:13
msgid "District" msgid "District"
msgstr "Kreis" msgstr "Kreis"
@ -1601,7 +1595,7 @@ msgid "Search for parcel gmrkng"
msgstr "Nach Gemarkung suchen" msgstr "Nach Gemarkung suchen"
#: konova/filters/mixins.py:111 #: konova/filters/mixins.py:111
#: konova/templates/konova/includes/parcel_table.html:34 #: konova/templates/konova/includes/parcels/parcel_table_frame.html:34
msgid "Parcel" msgid "Parcel"
msgstr "Flur" msgstr "Flur"
@ -1610,7 +1604,7 @@ msgid "Search for parcel"
msgstr "Nach Flur suchen" msgstr "Nach Flur suchen"
#: konova/filters/mixins.py:124 #: konova/filters/mixins.py:124
#: konova/templates/konova/includes/parcel_table.html:35 #: konova/templates/konova/includes/parcels/parcel_table_frame.html:35
msgid "Parcel counter" msgid "Parcel counter"
msgstr "Flurstückzähler" msgstr "Flurstückzähler"
@ -1619,7 +1613,7 @@ msgid "Search for parcel counter"
msgstr "Nach Flurstückzähler suchen" msgstr "Nach Flurstückzähler suchen"
#: konova/filters/mixins.py:138 #: konova/filters/mixins.py:138
#: konova/templates/konova/includes/parcel_table.html:36 #: konova/templates/konova/includes/parcels/parcel_table_frame.html:36
msgid "Parcel number" msgid "Parcel number"
msgstr "Flurstücknenner" msgstr "Flurstücknenner"
@ -1647,65 +1641,65 @@ msgstr "Nch Eintragungsstelle suchen"
msgid "Save" msgid "Save"
msgstr "Speichern" msgstr "Speichern"
#: konova/forms.py:71 #: konova/forms.py:73
msgid "Not editable" msgid "Not editable"
msgstr "Nicht editierbar" msgstr "Nicht editierbar"
#: konova/forms.py:142 konova/forms.py:314 #: konova/forms.py:176 konova/forms.py:348
msgid "Confirm" msgid "Confirm"
msgstr "Bestätige" msgstr "Bestätige"
#: konova/forms.py:154 konova/forms.py:323 #: konova/forms.py:188 konova/forms.py:357
msgid "Remove" msgid "Remove"
msgstr "Löschen" msgstr "Löschen"
#: konova/forms.py:156 #: konova/forms.py:190
msgid "You are about to remove {} {}" msgid "You are about to remove {} {}"
msgstr "Sie sind dabei {} {} zu löschen" msgstr "Sie sind dabei {} {} zu löschen"
#: konova/forms.py:243 konova/utils/quality.py:44 konova/utils/quality.py:46 #: konova/forms.py:277 konova/utils/quality.py:44 konova/utils/quality.py:46
#: templates/form/collapsable/form.html:45 #: templates/form/collapsable/form.html:45
msgid "Geometry" msgid "Geometry"
msgstr "Geometrie" msgstr "Geometrie"
#: konova/forms.py:324 #: konova/forms.py:358
msgid "Are you sure?" msgid "Are you sure?"
msgstr "Sind Sie sicher?" msgstr "Sind Sie sicher?"
#: konova/forms.py:370 #: konova/forms.py:404
msgid "When has this file been created? Important for photos." msgid "When has this file been created? Important for photos."
msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?" msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?"
#: konova/forms.py:381 #: konova/forms.py:415
#: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231 #: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231
msgid "File" msgid "File"
msgstr "Datei" msgstr "Datei"
#: konova/forms.py:383 #: konova/forms.py:417
msgid "Allowed formats: pdf, jpg, png. Max size 15 MB." msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
msgstr "Formate: pdf, jpg, png. Maximal 15 MB." msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
#: konova/forms.py:449 #: konova/forms.py:482
msgid "Added document" msgid "Added document"
msgstr "Dokument hinzugefügt" msgstr "Dokument hinzugefügt"
#: konova/forms.py:506 #: konova/forms.py:539
msgid "Confirm record" msgid "Confirm record"
msgstr "Verzeichnen bestätigen" msgstr "Verzeichnen bestätigen"
#: konova/forms.py:514 #: konova/forms.py:547
msgid "Record data" msgid "Record data"
msgstr "Daten verzeichnen" msgstr "Daten verzeichnen"
#: konova/forms.py:521 #: konova/forms.py:554
msgid "Confirm unrecord" msgid "Confirm unrecord"
msgstr "Entzeichnen bestätigen" msgstr "Entzeichnen bestätigen"
#: konova/forms.py:522 #: konova/forms.py:555
msgid "Unrecord data" msgid "Unrecord data"
msgstr "Daten entzeichnen" msgstr "Daten entzeichnen"
#: konova/forms.py:523 #: konova/forms.py:556
msgid "I, {} {}, confirm that this data must be unrecorded." msgid "I, {} {}, confirm that this data must be unrecorded."
msgstr "" msgstr ""
"Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen." "Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
@ -1754,33 +1748,37 @@ msgstr ""
msgid "English" msgid "English"
msgstr "" msgstr ""
#: konova/templates/konova/includes/parcel_table.html:5 #: konova/templates/konova/includes/parcels/parcel_table_content.html:18
msgid "Show more..."
msgstr "Mehr anzeigen..."
#: konova/templates/konova/includes/parcels/parcel_table_frame.html:5
msgid "Parcels can not be calculated, since no geometry is given." msgid "Parcels can not be calculated, since no geometry is given."
msgstr "" msgstr ""
"Flurstücke können nicht berechnet werden, da keine Geometrie eingegeben " "Flurstücke können nicht berechnet werden, da keine Geometrie eingegeben "
"wurde." "wurde."
#: konova/templates/konova/includes/parcel_table.html:11 #: konova/templates/konova/includes/parcels/parcel_table_frame.html:11
msgid "Municipal" msgid "Municipal"
msgstr "Gemeinde" msgstr "Gemeinde"
#: konova/templates/konova/includes/parcel_table.html:12 #: konova/templates/konova/includes/parcels/parcel_table_frame.html:12
msgid "Municipal key" msgid "Municipal key"
msgstr "Gemeindeschlüssel" msgstr "Gemeindeschlüssel"
#: konova/templates/konova/includes/parcel_table.html:14 #: konova/templates/konova/includes/parcels/parcel_table_frame.html:14
msgid "District key" msgid "District key"
msgstr "Kreisschlüssel" msgstr "Kreisschlüssel"
#: konova/templates/konova/includes/parcel_table.html:32 #: konova/templates/konova/includes/parcels/parcel_table_frame.html:32
msgid "Parcel group" msgid "Parcel group"
msgstr "Gemarkung" msgstr "Gemarkung"
#: konova/templates/konova/includes/parcel_table.html:33 #: konova/templates/konova/includes/parcels/parcel_table_frame.html:33
msgid "Parcel group key" msgid "Parcel group key"
msgstr "Gemarkungsschlüssel" msgstr "Gemarkungsschlüssel"
#: konova/templates/konova/includes/parcels.html:7 #: konova/templates/konova/includes/parcels/parcels.html:7
msgid "Spatial reference" msgid "Spatial reference"
msgstr "Raumreferenz" msgstr "Raumreferenz"
@ -1802,6 +1800,14 @@ msgstr "Neu"
msgid "Show" msgid "Show"
msgstr "Anzeigen" msgstr "Anzeigen"
#: konova/templates/konova/includes/report/qrcodes.html:7
msgid "Open in browser"
msgstr "Im Browser öffnen"
#: konova/templates/konova/includes/report/qrcodes.html:15
msgid "View in LANIS"
msgstr "In LANIS öffnen"
#: konova/templates/konova/widgets/checkbox-tree-select.html:4 #: konova/templates/konova/widgets/checkbox-tree-select.html:4
#: templates/generic_index.html:56 #: templates/generic_index.html:56
msgid "Search" msgid "Search"
@ -1895,11 +1901,18 @@ msgstr "Hierfür müssen Sie einer anderen Nutzergruppe angehören!"
msgid "Status of Checked and Recorded reseted" msgid "Status of Checked and Recorded reseted"
msgstr "'Geprüft'/'Verzeichnet' wurde zurückgesetzt" msgstr "'Geprüft'/'Verzeichnet' wurde zurückgesetzt"
#: konova/utils/message_templates.py:22 #: konova/utils/message_templates.py:20
msgid ""
"Entry is recorded. To edit data, the entry first needs to be unrecorded."
msgstr ""
"Eintrag ist verzeichnet. Um Daten zu bearbeiten, muss der Eintrag erst "
"entzeichnet werden."
#: konova/utils/message_templates.py:23
msgid "This data is not shared with you" msgid "This data is not shared with you"
msgstr "Diese Daten sind für Sie nicht freigegeben" msgstr "Diese Daten sind für Sie nicht freigegeben"
#: konova/utils/message_templates.py:23 #: konova/utils/message_templates.py:24
msgid "" msgid ""
"Remember: This data has not been shared with you, yet. This means you can " "Remember: This data has not been shared with you, yet. This means you can "
"only read but can not edit or perform any actions like running a check or " "only read but can not edit or perform any actions like running a check or "
@ -1909,15 +1922,15 @@ msgstr ""
"bedeutet, dass Sie nur lesenden Zugriff hierauf haben und weder bearbeiten, " "bedeutet, dass Sie nur lesenden Zugriff hierauf haben und weder bearbeiten, "
"noch Prüfungen durchführen oder verzeichnen können." "noch Prüfungen durchführen oder verzeichnen können."
#: konova/utils/message_templates.py:26 #: konova/utils/message_templates.py:27
msgid "Unsupported file type" msgid "Unsupported file type"
msgstr "Dateiformat nicht unterstützt" msgstr "Dateiformat nicht unterstützt"
#: konova/utils/message_templates.py:27 #: konova/utils/message_templates.py:28
msgid "File too large" msgid "File too large"
msgstr "Datei zu groß" msgstr "Datei zu groß"
#: konova/utils/message_templates.py:30 #: konova/utils/message_templates.py:31
msgid "" msgid ""
"Action canceled. Eco account is recorded or deductions exist. Only " "Action canceled. Eco account is recorded or deductions exist. Only "
"conservation office member can perform this action." "conservation office member can perform this action."
@ -1925,119 +1938,119 @@ msgstr ""
"Aktion abgebrochen. Ökokonto ist bereits verzeichnet oder Abbuchungen liegen " "Aktion abgebrochen. Ökokonto ist bereits verzeichnet oder Abbuchungen liegen "
"vor. Nur Eintragungsstellennutzer können diese Aktion jetzt durchführen." "vor. Nur Eintragungsstellennutzer können diese Aktion jetzt durchführen."
#: konova/utils/message_templates.py:33 #: konova/utils/message_templates.py:34
msgid "Compensation {} added" msgid "Compensation {} added"
msgstr "Kompensation {} hinzugefügt" msgstr "Kompensation {} hinzugefügt"
#: konova/utils/message_templates.py:34 #: konova/utils/message_templates.py:35
msgid "Compensation {} removed" msgid "Compensation {} removed"
msgstr "Kompensation {} entfernt" msgstr "Kompensation {} entfernt"
#: konova/utils/message_templates.py:36 #: konova/utils/message_templates.py:37
msgid "Added compensation action" msgid "Added compensation action"
msgstr "Maßnahme hinzugefügt" msgstr "Maßnahme hinzugefügt"
#: konova/utils/message_templates.py:37 #: konova/utils/message_templates.py:38
msgid "Added compensation state" msgid "Added compensation state"
msgstr "Zustand hinzugefügt" msgstr "Zustand hinzugefügt"
#: konova/utils/message_templates.py:40 #: konova/utils/message_templates.py:41
msgid "State removed" msgid "State removed"
msgstr "Zustand gelöscht" msgstr "Zustand gelöscht"
#: konova/utils/message_templates.py:41 #: konova/utils/message_templates.py:42
msgid "State edited" msgid "State edited"
msgstr "Zustand bearbeitet" msgstr "Zustand bearbeitet"
#: konova/utils/message_templates.py:42 #: konova/utils/message_templates.py:43
msgid "State added" msgid "State added"
msgstr "Zustand hinzugefügt" msgstr "Zustand hinzugefügt"
#: konova/utils/message_templates.py:45 #: konova/utils/message_templates.py:46
msgid "Action added" msgid "Action added"
msgstr "Maßnahme hinzugefügt" msgstr "Maßnahme hinzugefügt"
#: konova/utils/message_templates.py:46 #: konova/utils/message_templates.py:47
msgid "Action edited" msgid "Action edited"
msgstr "Maßnahme bearbeitet" msgstr "Maßnahme bearbeitet"
#: konova/utils/message_templates.py:47 #: konova/utils/message_templates.py:48
msgid "Action removed" msgid "Action removed"
msgstr "Maßnahme entfernt" msgstr "Maßnahme entfernt"
#: konova/utils/message_templates.py:50 #: konova/utils/message_templates.py:51
msgid "Deduction added" msgid "Deduction added"
msgstr "Abbuchung hinzugefügt" msgstr "Abbuchung hinzugefügt"
#: konova/utils/message_templates.py:51 #: konova/utils/message_templates.py:52
msgid "Deduction edited" msgid "Deduction edited"
msgstr "Abbuchung bearbeitet" msgstr "Abbuchung bearbeitet"
#: konova/utils/message_templates.py:52 #: konova/utils/message_templates.py:53
msgid "Deduction removed" msgid "Deduction removed"
msgstr "Abbuchung entfernt" msgstr "Abbuchung entfernt"
#: konova/utils/message_templates.py:55 #: konova/utils/message_templates.py:56
msgid "Deadline added" msgid "Deadline added"
msgstr "Frist/Termin hinzugefügt" msgstr "Frist/Termin hinzugefügt"
#: konova/utils/message_templates.py:56 #: konova/utils/message_templates.py:57
msgid "Deadline edited" msgid "Deadline edited"
msgstr "Frist/Termin bearbeitet" msgstr "Frist/Termin bearbeitet"
#: konova/utils/message_templates.py:57 #: konova/utils/message_templates.py:58
msgid "Deadline removed" msgid "Deadline removed"
msgstr "Frist/Termin gelöscht" msgstr "Frist/Termin gelöscht"
#: konova/utils/message_templates.py:60 #: konova/utils/message_templates.py:61
msgid "Payment added" msgid "Payment added"
msgstr "Zahlung hinzugefügt" msgstr "Zahlung hinzugefügt"
#: konova/utils/message_templates.py:61 #: konova/utils/message_templates.py:62
msgid "Payment edited" msgid "Payment edited"
msgstr "Zahlung bearbeitet" msgstr "Zahlung bearbeitet"
#: konova/utils/message_templates.py:62 #: konova/utils/message_templates.py:63
msgid "Payment removed" msgid "Payment removed"
msgstr "Zahlung gelöscht" msgstr "Zahlung gelöscht"
#: konova/utils/message_templates.py:65 #: konova/utils/message_templates.py:66
msgid "Revocation added" msgid "Revocation added"
msgstr "Widerspruch hinzugefügt" msgstr "Widerspruch hinzugefügt"
#: konova/utils/message_templates.py:66 #: konova/utils/message_templates.py:67
msgid "Revocation edited" msgid "Revocation edited"
msgstr "Widerspruch bearbeitet" msgstr "Widerspruch bearbeitet"
#: konova/utils/message_templates.py:67 #: konova/utils/message_templates.py:68
msgid "Revocation removed" msgid "Revocation removed"
msgstr "Widerspruch entfernt" msgstr "Widerspruch entfernt"
#: konova/utils/message_templates.py:70 #: konova/utils/message_templates.py:71
msgid "Document '{}' deleted" msgid "Document '{}' deleted"
msgstr "Dokument '{}' gelöscht" msgstr "Dokument '{}' gelöscht"
#: konova/utils/message_templates.py:71 #: konova/utils/message_templates.py:72
msgid "Document added" msgid "Document added"
msgstr "Dokument hinzugefügt" msgstr "Dokument hinzugefügt"
#: konova/utils/message_templates.py:72 #: konova/utils/message_templates.py:73
msgid "Document edited" msgid "Document edited"
msgstr "Dokument bearbeitet" msgstr "Dokument bearbeitet"
#: konova/utils/message_templates.py:75 #: konova/utils/message_templates.py:76
msgid "Edited general data" msgid "Edited general data"
msgstr "Allgemeine Daten bearbeitet" msgstr "Allgemeine Daten bearbeitet"
#: konova/utils/message_templates.py:76 #: konova/utils/message_templates.py:77
msgid "Added deadline" msgid "Added deadline"
msgstr "Frist/Termin hinzugefügt" msgstr "Frist/Termin hinzugefügt"
#: konova/utils/message_templates.py:79 #: konova/utils/message_templates.py:80
msgid "Geometry conflict detected with {}" msgid "Geometry conflict detected with {}"
msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}" msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}"
#: konova/utils/message_templates.py:82 #: konova/utils/message_templates.py:83
msgid "This intervention has {} revocations" msgid "This intervention has {} revocations"
msgstr "Dem Eingriff liegen {} Widersprüche vor" msgstr "Dem Eingriff liegen {} Widersprüche vor"
@ -2364,6 +2377,25 @@ msgstr "Allgemeine Daten"
msgid "Cancel" msgid "Cancel"
msgstr "Abbrechen" msgstr "Abbrechen"
#: templates/form/recorded_no_edit.html:9
msgid "This data is recorded"
msgstr "Daten sind verzeichnet"
#: templates/form/recorded_no_edit.html:14
msgid ""
"\n"
" Whilst recorded the data is published publicly. If you wish to edit "
"any information on this data, the data needs\n"
" to be unrecorded first. Do not forget to record it afterwards, "
"again.\n"
" "
msgstr ""
"\n"
"Verzeichnete Daten sind öffentlich einsehbar. Wenn Sie Informationen "
"überarbeiten möchten muss dieser Datensatz zunächst entzeichnet werden. "
"Vergessen Sie nicht ihn anschließend wieder zu verzeichnen.\n"
" "
#: templates/form/table/generic_table_form_body.html:24 #: templates/form/table/generic_table_form_body.html:24
msgid "Fields with * are required." msgid "Fields with * are required."
msgstr "* sind Pflichtfelder." msgstr "* sind Pflichtfelder."
@ -2451,9 +2483,9 @@ msgid ""
" " " "
msgstr "" msgstr ""
"\n" "\n"
" Diese Daten sind noch nicht veröffentlicht und/oder haben das Bestandskraftdatum noch nicht erreicht. " " Diese Daten sind noch nicht veröffentlicht und/oder haben das "
"Sie können daher aktuell nicht eingesehen werden. Schauen Sie zu einem späteren Zeitpunkt " "Bestandskraftdatum noch nicht erreicht. Sie können daher aktuell nicht "
"wieder vorbei. \n" "eingesehen werden. Schauen Sie zu einem späteren Zeitpunkt wieder vorbei. \n"
" " " "
#: templates/table/gmrkng_col.html:6 #: templates/table/gmrkng_col.html:6
@ -2504,11 +2536,11 @@ msgstr "Neuen Token generieren"
msgid "A new token needs to be validated by an administrator!" msgid "A new token needs to be validated by an administrator!"
msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!" msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!"
#: user/forms.py:168 user/forms.py:172 user/forms.py:323 user/forms.py:328 #: user/forms.py:168 user/forms.py:172 user/forms.py:332 user/forms.py:337
msgid "Team name" msgid "Team name"
msgstr "Team Name" msgstr "Team Name"
#: user/forms.py:179 user/forms.py:336 user/templates/user/team/index.html:30 #: user/forms.py:179 user/forms.py:345 user/templates/user/team/index.html:30
msgid "Description" msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
@ -2552,11 +2584,15 @@ msgstr "Administratoren verwalten die Teamdaten und Mitglieder"
msgid "Selected admin ({}) needs to be a member of this team." msgid "Selected admin ({}) needs to be a member of this team."
msgstr "Gewählter Administrator ({}) muss ein Mitglied des Teams sein." msgstr "Gewählter Administrator ({}) muss ein Mitglied des Teams sein."
#: user/forms.py:291 user/templates/user/team/index.html:51 #: user/forms.py:291 user/templates/user/team/index.html:54
msgid "Edit team" msgid "Edit team"
msgstr "Team bearbeiten" msgstr "Team bearbeiten"
#: user/forms.py:347 #: user/forms.py:323 user/templates/user/team/index.html:50
msgid "Leave team"
msgstr "Team verlassen"
#: user/forms.py:356
msgid "Team" msgid "Team"
msgstr "Team" msgstr "Team"
@ -2642,7 +2678,7 @@ msgstr "Neues Team hinzufügen"
msgid "Members" msgid "Members"
msgstr "Mitglieder" msgstr "Mitglieder"
#: user/templates/user/team/index.html:54 #: user/templates/user/team/index.html:57
msgid "Remove team" msgid "Remove team"
msgstr "Team entfernen" msgstr "Team entfernen"
@ -2702,6 +2738,14 @@ msgstr "Team bearbeitet"
msgid "Team removed" msgid "Team removed"
msgstr "Team gelöscht" msgstr "Team gelöscht"
#: user/views.py:218
msgid "You are not a member of this team"
msgstr "Sie sind kein Mitglied dieses Teams"
#: user/views.py:225
msgid "Left Team"
msgstr "Team verlassen"
#: venv/lib/python3.7/site-packages/bootstrap4/components.py:17 #: venv/lib/python3.7/site-packages/bootstrap4/components.py:17
#: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/form_errors.html:3 #: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/form_errors.html:3
#: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/messages.html:4 #: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/messages.html:4

View File

@ -0,0 +1,19 @@
{% load i18n fontawesome_5 %}
<div class="p-5 col-sm-12">
<h4>
<span class="registered-bookmark">
{% fa5_icon 'bookmark' %}
</span>
<span>
{% trans 'This data is recorded' %}
</span>
</h4>
<hr>
<article>
{% blocktrans %}
Whilst recorded the data is published publicly. If you wish to edit any information on this data, the data needs
to be unrecorded first. Do not forget to record it afterwards, again.
{% endblocktrans %}
</article>
</div>

View File

@ -74,6 +74,15 @@ class TeamAdmin(admin.ModelAdmin):
"name", "name",
"description", "description",
] ]
filter_horizontal = [
"users"
]
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "admin":
team_id = request.resolver_match.kwargs.get("object_id", None)
kwargs["queryset"] = User.objects.filter(teams__id__in=[team_id])
return super().formfield_for_foreignkey(db_field, request, **kwargs)
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)

View File

@ -317,6 +317,15 @@ class RemoveTeamModalForm(RemoveModalForm):
pass pass
class LeaveTeamModalForm(RemoveModalForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Leave team")
def save(self):
self.instance.remove_user(self.user)
class TeamDataForm(BaseModalForm): class TeamDataForm(BaseModalForm):
name = forms.CharField( name = forms.CharField(
label_suffix="", label_suffix="",

View File

@ -93,3 +93,17 @@ class Team(UuidModel):
""" """
mailer = Mailer() mailer = Mailer()
mailer.send_mail_shared_data_deleted_team(obj_identifier, obj_title, self) mailer.send_mail_shared_data_deleted_team(obj_identifier, obj_title, self)
def remove_user(self, user):
""" Removes a user from the team
Args:
user (User): The user to be removed
Returns:
"""
self.users.remove(user)
if self.admin == user:
self.admin = self.users.first()
self.save()

View File

@ -46,6 +46,9 @@
{% endfor %} {% endfor %}
</td> </td>
<td> <td>
<button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-leave' team.id %}" title="{% trans 'Leave team' %}">
{% fa5_icon 'sign-out-alt' %}
</button>
{% if team.admin == user %} {% if team.admin == user %}
<button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-edit' team.id %}" title="{% trans 'Edit team' %}"> <button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-edit' team.id %}" title="{% trans 'Edit team' %}">
{% fa5_icon 'edit' %} {% fa5_icon 'edit' %}

View File

@ -20,5 +20,6 @@ urlpatterns = [
path("team/<id>", data_team_view, name="team-data"), path("team/<id>", data_team_view, name="team-data"),
path("team/<id>/edit", edit_team_view, name="team-edit"), path("team/<id>/edit", edit_team_view, name="team-edit"),
path("team/<id>/remove", remove_team_view, name="team-remove"), path("team/<id>/remove", remove_team_view, name="team-remove"),
path("team/<id>/leave", leave_team_view, name="team-leave"),
] ]

View File

@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import any_group_check, default_group_required from konova.decorators import any_group_check, default_group_required
from user.forms import UserNotificationForm, UserContactForm, UserAPITokenForm, NewTeamModalForm, EditTeamModalForm, \ from user.forms import UserNotificationForm, UserContactForm, UserAPITokenForm, NewTeamModalForm, EditTeamModalForm, \
RemoveTeamModalForm, TeamDataForm RemoveTeamModalForm, TeamDataForm, LeaveTeamModalForm
@login_required @login_required
@ -204,3 +204,24 @@ def remove_team_view(request: HttpRequest, id: str):
_("Team removed"), _("Team removed"),
redirect_url=reverse("user:team-index") redirect_url=reverse("user:team-index")
) )
@login_required
def leave_team_view(request: HttpRequest, id: str):
team = get_object_or_404(Team, id=id)
user = request.user
is_user_team_member = team.users.filter(id=user.id).exists()
if not is_user_team_member:
messages.info(
request,
_("You are not a member of this team")
)
return redirect("user:team-index")
form = LeaveTeamModalForm(request.POST or None, instance=team, request=request)
return form.process_request(
request,
_("Left Team"),
redirect_url=reverse("user:team-index")
)