diff --git a/api/utils/serializer/v1/intervention.py b/api/utils/serializer/v1/intervention.py index 1ce2d684..af13c4a2 100644 --- a/api/utils/serializer/v1/intervention.py +++ b/api/utils/serializer/v1/intervention.py @@ -132,6 +132,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1, id__in=payments ) obj.payments.set(payments) + obj.send_data_to_egon() return obj def create_model_from_json(self, json_model, user): @@ -197,7 +198,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1, obj.legal.save() obj.save() - obj.mark_as_edited(user) + obj.mark_as_edited(user, edit_comment="API update") celery_update_parcels.delay(obj.geometry.id) diff --git a/api/utils/serializer/v1/serializer.py b/api/utils/serializer/v1/serializer.py index 23d1f692..9d3b9dfb 100644 --- a/api/utils/serializer/v1/serializer.py +++ b/api/utils/serializer/v1/serializer.py @@ -75,7 +75,10 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer): 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 code = KonovaCode.objects.get( atom_id=json_str, diff --git a/codelist/admin.py b/codelist/admin.py index 55ce827d..ccdcb057 100644 --- a/codelist/admin.py +++ b/codelist/admin.py @@ -33,6 +33,7 @@ class KonovaCodeAdmin(admin.ModelAdmin): "is_selectable", "is_leaf", "parent", + "found_in_codelists", ] search_fields = [ @@ -42,6 +43,12 @@ class KonovaCodeAdmin(admin.ModelAdmin): "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(KonovaCode, KonovaCodeAdmin) diff --git a/compensation/admin.py b/compensation/admin.py index 5f792f76..a5cd1a88 100644 --- a/compensation/admin.py +++ b/compensation/admin.py @@ -21,16 +21,30 @@ class AbstractCompensationAdmin(BaseObjectAdmin): "identifier", "title", "comment", - "after_states", - "before_states", + "list_after_states", + "list_before_states", + "geometry", ] def get_readonly_fields(self, request, obj=None): return super().get_readonly_fields(request, obj) + [ - "after_states", - "before_states", + "list_after_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): autocomplete_fields = [ diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py index ebfb1ebd..8404c7ad 100644 --- a/compensation/forms/modalForms.py +++ b/compensation/forms/modalForms.py @@ -128,6 +128,7 @@ class EditPaymentModalForm(NewPaymentForm): payment.comment = self.cleaned_data.get("comment", None) payment.save() self.instance.mark_as_edited(self.user, self.request, edit_comment=PAYMENT_EDITED) + self.instance.send_data_to_egon() return payment diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py index ea69cefc..3deff50a 100644 --- a/compensation/models/compensation.py +++ b/compensation/models/compensation.py @@ -418,6 +418,18 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin): super().set_status_messages(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): """ diff --git a/compensation/tables.py b/compensation/tables.py index ed89b636..401a7416 100644 --- a/compensation/tables.py +++ b/compensation/tables.py @@ -181,9 +181,7 @@ class CompensationTable(BaseTable, TableRenderMixin): """ if value is None: value = User.objects.none() - has_access = value.filter( - id=self.user.id - ).exists() + has_access = record.is_shared_with(self.user) html = self.render_icn( tooltip=_("Full access granted") if has_access else _("Access not granted"), @@ -343,7 +341,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin): html = "" # Do not use value in here, since value does use unprefetched 'users' manager, where record has already # prefetched users data - has_access = self.user in record.users.all() + has_access = record.is_shared_with(self.user) html += self.render_icn( 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", diff --git a/compensation/templates/compensation/detail/compensation/view.html b/compensation/templates/compensation/detail/compensation/view.html index 80b5c0f0..8e1b1267 100644 --- a/compensation/templates/compensation/detail/compensation/view.html +++ b/compensation/templates/compensation/detail/compensation/view.html @@ -122,7 +122,7 @@ {% include 'map/geom_form.html' %}
- {% include 'konova/includes/parcels.html' %} + {% include 'konova/includes/parcels/parcels.html' %}
{% include 'konova/includes/comment_card.html' %} diff --git a/compensation/templates/compensation/detail/eco_account/view.html b/compensation/templates/compensation/detail/eco_account/view.html index 116b6670..0eb354fe 100644 --- a/compensation/templates/compensation/detail/eco_account/view.html +++ b/compensation/templates/compensation/detail/eco_account/view.html @@ -104,7 +104,7 @@ {% include 'map/geom_form.html' %}
- {% include 'konova/includes/parcels.html' %} + {% include 'konova/includes/parcels/parcels.html' %}
{% include 'konova/includes/comment_card.html' %} diff --git a/compensation/templates/compensation/report/compensation/report.html b/compensation/templates/compensation/report/compensation/report.html index 25130f72..7088ff07 100644 --- a/compensation/templates/compensation/report/compensation/report.html +++ b/compensation/templates/compensation/report/compensation/report.html @@ -38,17 +38,10 @@ {% include 'map/geom_form.html' %}
- {% include 'konova/includes/parcels.html' %} + {% include 'konova/includes/parcels/parcels.html' %}
-
-

{% trans 'Open in browser' %}

- {{ qrcode|safe }} -
-
-

{% trans 'View in LANIS' %}

- {{ qrcode_lanis|safe }} -
+ {% include 'konova/includes/report/qrcodes.html' %}
diff --git a/compensation/templates/compensation/report/eco_account/report.html b/compensation/templates/compensation/report/eco_account/report.html index 44496ea8..a3632ee2 100644 --- a/compensation/templates/compensation/report/eco_account/report.html +++ b/compensation/templates/compensation/report/eco_account/report.html @@ -51,17 +51,10 @@ {% include 'map/geom_form.html' %}
- {% include 'konova/includes/parcels.html' %} + {% include 'konova/includes/parcels/parcels.html' %}
-
-

{% trans 'Open in browser' %}

- {{ qrcode|safe }} -
-
-

{% trans 'View in LANIS' %}

- {{ qrcode_lanis|safe }} -
+ {% include 'konova/includes/report/qrcodes.html' %}
diff --git a/compensation/tests/compensation/test_workflow.py b/compensation/tests/compensation/test_workflow.py index 5b7decff..570045f2 100644 --- a/compensation/tests/compensation/test_workflow.py +++ b/compensation/tests/compensation/test_workflow.py @@ -60,8 +60,9 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase): # Preserve the current number of intervention's compensations 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.assertEqual(num_compensations + 1, self.intervention.compensations.count()) new_compensation = self.intervention.compensations.get(identifier=test_id) @@ -261,3 +262,26 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase): self.assertIn(recorded, self.compensation.log.all()) 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) diff --git a/compensation/tests/ecoaccount/test_workflow.py b/compensation/tests/ecoaccount/test_workflow.py index 03b4996a..bd6894ae 100644 --- a/compensation/tests/ecoaccount/test_workflow.py +++ b/compensation/tests/ecoaccount/test_workflow.py @@ -302,3 +302,27 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): self.assertEqual(pre_edit_account_log_count + 1, account.log.count()) self.assertEqual(intervention.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) diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py index 6dcf442b..c3d3e2b5 100644 --- a/compensation/views/compensation.py +++ b/compensation/views/compensation.py @@ -1,4 +1,5 @@ from django.contrib.auth.decorators import login_required +from django.core.exceptions import ObjectDoesNotExist from django.db.models import Sum from django.http import HttpRequest, JsonResponse 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, \ 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_EDITED + DEADLINE_EDITED, RECORDED_BLOCKS_EDIT, PARAMS_INVALID 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" + 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) geom_form = SimpleGeomForm(request.POST or None, read_only=False) if request.method == "POST": @@ -134,6 +148,13 @@ def edit_view(request: HttpRequest, id: str): template = "compensation/form/view.html" # Get object from db 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 data_form = EditCompensationForm(request.POST or None, 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 ) parcels = comp.get_underlying_parcels() - qrcode_img = generate_qr_code( - request.build_absolute_uri(reverse("compensation:report", args=(id,))), - 10 - ) - qrcode_img_lanis = generate_qr_code( - comp.get_LANIS_link(), - 7 - ) + + qrcode_url = request.build_absolute_uri(reverse("compensation:report", args=(id,))) + qrcode_img = generate_qr_code(qrcode_url, 10) + qrcode_lanis_url = comp.get_LANIS_link() + qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7) + # Order states by surface 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") @@ -611,8 +630,14 @@ def report_view(request: HttpRequest, id: str): context = { "obj": comp, - "qrcode": qrcode_img, - "qrcode_lanis": qrcode_img_lanis, + "qrcode": { + "img": qrcode_img, + "url": qrcode_url, + }, + "qrcode_lanis": { + "img": qrcode_img_lanis, + "url": qrcode_lanis_url, + }, "has_access": False, # disables action buttons during rendering "before_states": before_states, "after_states": after_states, diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py index 85b13714..ecaccbe6 100644 --- a/compensation/views/eco_account.py +++ b/compensation/views/eco_account.py @@ -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, \ 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, \ - 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 @@ -145,6 +146,13 @@ def edit_view(request: HttpRequest, id: str): template = "compensation/form/view.html" # Get object from db 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 data_form = EditEcoAccountForm(request.POST or None, 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 ) parcels = acc.get_underlying_parcels() - qrcode_img = generate_qr_code( - request.build_absolute_uri(reverse("ema:report", args=(id,))), - 10 - ) - qrcode_img_lanis = generate_qr_code( - acc.get_LANIS_link(), - 7 - ) + + qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,))) + qrcode_img = generate_qr_code(qrcode_url, 10) + qrcode_lanis_url = acc.get_LANIS_link() + qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7) + # Order states by surface 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") - 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) deductions = acc.deductions.all()\ @@ -752,8 +758,14 @@ def report_view(request:HttpRequest, id: str): context = { "obj": acc, - "qrcode": qrcode_img, - "qrcode_lanis": qrcode_img_lanis, + "qrcode": { + "img": qrcode_img, + "url": qrcode_url, + }, + "qrcode_lanis": { + "img": qrcode_img_lanis, + "url": qrcode_lanis_url, + }, "has_access": False, # disables action buttons during rendering "before_states": before_states, "after_states": after_states, diff --git a/ema/tables.py b/ema/tables.py index 30968f96..38d8a8c0 100644 --- a/ema/tables.py +++ b/ema/tables.py @@ -151,9 +151,7 @@ class EmaTable(BaseTable, TableRenderMixin): """ html = "" - has_access = value.filter( - id=self.user.id - ).exists() + has_access = record.is_shared_with(self.user) html += self.render_icn( tooltip=_("Full access granted") if has_access else _("Access not granted"), diff --git a/ema/templates/ema/detail/view.html b/ema/templates/ema/detail/view.html index 020b7d4b..31d26e0b 100644 --- a/ema/templates/ema/detail/view.html +++ b/ema/templates/ema/detail/view.html @@ -90,7 +90,7 @@ {% include 'map/geom_form.html' %}
- {% include 'konova/includes/parcels.html' %} + {% include 'konova/includes/parcels/parcels.html' %}
{% include 'konova/includes/comment_card.html' %} diff --git a/ema/templates/ema/report/report.html b/ema/templates/ema/report/report.html index 86252c0a..43b64865 100644 --- a/ema/templates/ema/report/report.html +++ b/ema/templates/ema/report/report.html @@ -38,17 +38,10 @@ {% include 'map/geom_form.html' %}
- {% include 'konova/includes/parcels.html' %} + {% include 'konova/includes/parcels/parcels.html' %}
-
-

{% trans 'Open in browser' %}

- {{ qrcode|safe }} -
-
-

{% trans 'View in LANIS' %}

- {{ qrcode_lanis|safe }} -
+ {% include 'konova/includes/report/qrcodes.html' %}
diff --git a/ema/tests/test_workflow.py b/ema/tests/test_workflow.py index 3306a21f..ecc3f195 100644 --- a/ema/tests/test_workflow.py +++ b/ema/tests/test_workflow.py @@ -117,6 +117,32 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase): self.assertEqual(pre_edit_log_count + 1, self.ema.log.count()) 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): """ This tests if the recordability of the Ema is triggered by the quality of it's data (e.g. not all fields filled) diff --git a/ema/views.py b/ema/views.py index c145511f..ce0d68f5 100644 --- a/ema/views.py +++ b/ema/views.py @@ -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, \ 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_EDITED, DEADLINE_EDITED + COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, RECORDED_BLOCKS_EDIT from konova.utils.user_checks import in_group @@ -213,6 +213,13 @@ def edit_view(request: HttpRequest, id: str): template = "compensation/form/view.html" # Get object from db 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 data_form = EditEmaForm(request.POST or None, 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, ) parcels = ema.get_underlying_parcels() - qrcode_img = generate_qr_code( - request.build_absolute_uri(reverse("ema:report", args=(id,))), - 10 - ) - qrcode_img_lanis = generate_qr_code( - ema.get_LANIS_link(), - 7 - ) + + qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,))) + qrcode_img = generate_qr_code(qrcode_url, 10) + qrcode_lanis_url = ema.get_LANIS_link() + qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7) + # Order states by surface 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") @@ -578,8 +583,14 @@ def report_view(request:HttpRequest, id: str): context = { "obj": ema, - "qrcode": qrcode_img, - "qrcode_lanis": qrcode_img_lanis, + "qrcode": { + "img": qrcode_img, + "url": qrcode_url + }, + "qrcode_lanis": { + "img": qrcode_img_lanis, + "url": qrcode_lanis_url + }, "has_access": False, # disables action buttons during rendering "before_states": before_states, "after_states": after_states, diff --git a/intervention/admin.py b/intervention/admin.py index 3d874df6..932ddb93 100644 --- a/intervention/admin.py +++ b/intervention/admin.py @@ -25,12 +25,14 @@ class InterventionAdmin(BaseObjectAdmin): "checked", "recorded", "users", + "geometry", ] def get_readonly_fields(self, request, obj=None): return super().get_readonly_fields(request, obj) + [ "checked", "recorded", + "geometry", ] diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py index 6a02962e..07911be9 100644 --- a/intervention/forms/modalForms.py +++ b/intervention/forms/modalForms.py @@ -427,13 +427,22 @@ class NewDeductionModalForm(BaseModalForm): """ super_result = super().is_valid() acc = self.cleaned_data["account"] + intervention = self.cleaned_data["intervention"] + objects_valid = True if not acc.recorded: self.add_error( "account", _("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) form_surface = float(self.cleaned_data["surface"]) @@ -447,7 +456,7 @@ class NewDeductionModalForm(BaseModalForm): 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): """ Creates the deduction diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py index c215a139..dd15beb1 100644 --- a/intervention/models/intervention.py +++ b/intervention/models/intervention.py @@ -145,7 +145,6 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec def set_recorded(self, user: User) -> UserActionLogEntry: log_entry = super().set_recorded(user) self.add_log_entry_to_compensations(log_entry) - self.send_data_to_egon() return log_entry def add_log_entry_to_compensations(self, log_entry: UserActionLogEntry): @@ -183,6 +182,8 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec intervention=self, ) self.mark_as_edited(user, form.request, edit_comment=PAYMENT_ADDED) + + self.send_data_to_egon() return pay def add_revocation(self, form): @@ -347,6 +348,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec with transaction.atomic(): payment.delete() self.mark_as_edited(user, request=form.request, edit_comment=PAYMENT_REMOVED) + self.send_data_to_egon() class InterventionDocument(AbstractDocument): diff --git a/intervention/tables.py b/intervention/tables.py index c8ee504e..8f312099 100644 --- a/intervention/tables.py +++ b/intervention/tables.py @@ -177,9 +177,7 @@ class InterventionTable(BaseTable, TableRenderMixin): """ html = "" - has_access = value.filter( - id=self.user.id - ).exists() + has_access = record.is_shared_with(self.user) html += self.render_icn( tooltip=_("Full access granted") if has_access else _("Access not granted"), diff --git a/intervention/templates/intervention/detail/view.html b/intervention/templates/intervention/detail/view.html index c5f9b9c1..2d9628cd 100644 --- a/intervention/templates/intervention/detail/view.html +++ b/intervention/templates/intervention/detail/view.html @@ -137,7 +137,7 @@ {% include 'map/geom_form.html' %}
- {% include 'konova/includes/parcels.html' %} + {% include 'konova/includes/parcels/parcels.html' %}
{% include 'konova/includes/comment_card.html' %} diff --git a/intervention/templates/intervention/report/report.html b/intervention/templates/intervention/report/report.html index ccc2f226..6e238fa2 100644 --- a/intervention/templates/intervention/report/report.html +++ b/intervention/templates/intervention/report/report.html @@ -97,17 +97,10 @@ {% include 'map/geom_form.html' %}
- {% include 'konova/includes/parcels.html' %} + {% include 'konova/includes/parcels/parcels.html' %}
-
-

{% trans 'Open in browser' %}

- {{ qrcode|safe }} -
-
-

{% trans 'View in LANIS' %}

- {{ qrcode_lanis|safe }} -
+ {% include 'konova/includes/report/qrcodes.html' %}
diff --git a/intervention/tests/test_workflow.py b/intervention/tests/test_workflow.py index c5290503..66770977 100644 --- a/intervention/tests/test_workflow.py +++ b/intervention/tests/test_workflow.py @@ -89,6 +89,30 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase): self.assertIn(self.superuser, obj.users.all()) 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): """ Tests that the intervention can only be checked if all required data has been added diff --git a/intervention/utils/egon_export.py b/intervention/utils/egon_export.py index 1ef9f727..14ea6b61 100644 --- a/intervention/utils/egon_export.py +++ b/intervention/utils/egon_export.py @@ -156,10 +156,20 @@ class EgonGmlBuilder: def build_gml(self): comp_type, comp_type_code = self._gen_kompensationsArt() - payment_date = self.intervention.payments.first().due_on - if payment_date is not None: + payment = self.intervention.payments.first() + payment_date = None + if payment is not None: + payment_date = payment.due_on 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 = { "wfs:FeatureCollection": { "@xmlns:wfs": "http://www.opengis.net/wfs", @@ -174,12 +184,12 @@ class EgonGmlBuilder: "oneo:azZulassungsstelle": self.intervention.responsible.registration_file_number, "oneo:bemerkungZulassungsstelle": None, "oneo:eintragungsstelle": { - "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/907/{self.intervention.responsible.conservation_office.atom_id}", - "#text": self.intervention.responsible.conservation_office.long_name + "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/907/{cons_office.atom_id if cons_office else None}", + "#text": cons_office.long_name if cons_office else None }, "oneo:zulassungsstelle": { - "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{self.intervention.responsible.registration_office.atom_id}", - "#text": self.intervention.responsible.registration_office.long_name + "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{reg_office.atom_id if reg_office else None}", + "#text": reg_office.long_name if reg_office else None }, "oneo:ersatzzahlung": self._sum_all_payments(), "oneo:kompensationsart": { @@ -187,20 +197,20 @@ class EgonGmlBuilder: "#text": comp_type }, "oneo:verfahrensrecht": { - "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1048/{self.intervention.legal.laws.first().atom_id}", - "#text": self.intervention.legal.laws.first().short_name + "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1048/{law.atom_id if law else None}", + "#text": law.short_name if law else None }, "oneo:verfahrenstyp": { - "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/44382/{self.intervention.legal.process_type.atom_id}", - "#text": self.intervention.legal.process_type.long_name, + "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/44382/{process_type.atom_id if process_type else None}", + "#text": process_type.long_name if process_type else None, }, "oneo:eingreifer": { "oneo:Eingreifer": { "oneo:art": { - "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{self.intervention.responsible.handler.type.atom_id}", - "#text": self.intervention.responsible.handler.type.long_name, + "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{handler.type.atom_id if handler.type else None}", + "#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": { @@ -212,8 +222,8 @@ class EgonGmlBuilder: "oneo:zulassung": { "oneo:Zulassungstermin": { "oneo:bauBeginn": payment_date, - "oneo:erlass": self.intervention.legal.registration_date.strftime(DEFAULT_DATE_FORMAT), - "oneo:rechtsKraft": self.intervention.legal.binding_date.strftime(DEFAULT_DATE_FORMAT), + "oneo:erlass": reg_date.strftime(DEFAULT_DATE_FORMAT) if reg_date else None, + "oneo:rechtsKraft": bind_date.strftime(DEFAULT_DATE_FORMAT) if bind_date else None, } }, "oneo:geometrie": { diff --git a/intervention/views.py b/intervention/views.py index 3004a79f..f882f214 100644 --- a/intervention/views.py +++ b/intervention/views.py @@ -18,7 +18,8 @@ from konova.utils.documents import remove_document, get_document from konova.utils.generators import generate_qr_code from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED, \ 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 @@ -302,6 +303,13 @@ def edit_view(request: HttpRequest, id: str): template = "intervention/form/view.html" # Get object from db 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 data_form = EditInterventionForm(request.POST or None, 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( "account" ) - qrcode_img = generate_qr_code( - request.build_absolute_uri(reverse("intervention:report", args=(id,))), - 10 - ) - qrcode_img_lanis = generate_qr_code( - intervention.get_LANIS_link(), - 7 - ) + qrcode_url = request.build_absolute_uri(reverse("intervention:report", args=(id,))) + qrcode_img = generate_qr_code(qrcode_url, 10) + qrcode_lanis_url = intervention.get_LANIS_link() + qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7) + context = { "obj": intervention, "deductions": distinct_deductions, - "qrcode": qrcode_img, - "qrcode_lanis": qrcode_img_lanis, + "qrcode": { + "img": qrcode_img, + "url": qrcode_url, + }, + "qrcode_lanis": { + "img": qrcode_img_lanis, + "url": qrcode_lanis_url, + }, "geom_form": geom_form, "parcels": parcels, TAB_TITLE_IDENTIFIER: tab_title, diff --git a/konova/admin.py b/konova/admin.py index 07be7213..213120ea 100644 --- a/konova/admin.py +++ b/konova/admin.py @@ -8,6 +8,7 @@ Created on: 22.07.21 from django.contrib import admin 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 user.models import UserAction @@ -16,7 +17,22 @@ class GeometryAdmin(admin.ModelAdmin): list_display = [ "id", "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): diff --git a/konova/autocompletes.py b/konova/autocompletes.py index 9f60d54f..e6036f02 100644 --- a/konova/autocompletes.py +++ b/konova/autocompletes.py @@ -52,14 +52,16 @@ class InterventionAutocomplete(Select2QuerySetView): """ def get_queryset(self): - if self.request.user.is_anonymous: + user = self.request.user + if user.is_anonymous: return Intervention.objects.none() qs = Intervention.objects.filter( - deleted=None, - users__in=[self.request.user], + Q(deleted=None) & + Q(users__in=[user]) | + Q(teams__in=user.teams.all()) ).order_by( "identifier" - ) + ).distinct() if self.q: qs = qs.filter( Q(identifier__icontains=self.q) | diff --git a/konova/forms.py b/konova/forms.py index fd359cb8..a1e14478 100644 --- a/konova/forms.py +++ b/konova/forms.py @@ -57,6 +57,8 @@ class BaseForm(forms.Form): self.has_required_fields = True break + self.check_for_recorded_instance() + @abstractmethod def save(self): # To be implemented in subclasses! @@ -136,6 +138,38 @@ class BaseForm(forms.Form): set_class = set_class.replace(cls, "") 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): check = forms.BooleanField( @@ -410,7 +444,6 @@ class NewDocumentModalForm(BaseModalForm): super().__init__(*args, **kwargs) self.form_title = _("Add new document") self.form_caption = _("") - self.template = "modal/modal_form.html" self.form_attrs = { "enctype": "multipart/form-data", # important for file upload } @@ -597,4 +630,12 @@ class RecordModalForm(BaseModalForm): self.instance.set_unrecorded(self.user) else: self.instance.set_recorded(self.user) - return self.instance \ No newline at end of file + return self.instance + + def check_for_recorded_instance(self): + """ Overwrite the check method for doing nothing on the RecordModalForm + + Returns: + + """ + pass diff --git a/konova/management/commands/update_all_parcels.py b/konova/management/commands/update_all_parcels.py index 9d96ebae..c9dd5158 100644 --- a/konova/management/commands/update_all_parcels.py +++ b/konova/management/commands/update_all_parcels.py @@ -5,6 +5,10 @@ Contact: michel.peltriaux@sgdnord.rlp.de 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.models import Geometry, Parcel, District @@ -23,12 +27,21 @@ class Command(BaseKonovaCommand): num_parcels_before = Parcel.objects.count() num_districts_before = District.objects.count() self._write_warning("=== Update parcels and districts ===") + # Order geometries by size to process smaller once at first geometries = Geometry.objects.all().exclude( geom=None + ).annotate(area=Area("geom")).order_by( + 'area' ) self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...") + i = 0 + num_geoms = geometries.count() for geometry in geometries: + self._write_warning(f"--- {datetime.datetime.now()} Process {geometry.id} now ...") 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_districts_after = District.objects.count() diff --git a/konova/models/geometry.py b/konova/models/geometry.py index fc484a79..283f8508 100644 --- a/konova/models/geometry.py +++ b/konova/models/geometry.py @@ -20,6 +20,9 @@ class Geometry(BaseResource): from konova.settings import DEFAULT_SRID geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID) + def __str__(self): + return str(self.id) + def save(self, *args, **kwargs): super().save(*args, **kwargs) self.check_for_conflicts() @@ -110,32 +113,38 @@ class Geometry(BaseResource): _now = timezone.now() underlying_parcels = [] for result in fetched_parcels: - fetched_parcel = result[typename] + parcel_properties = result["properties"] # There could be parcels which include the word 'Flur', # which needs to be deleted and just keep the numerical values ## 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( - key=fetched_parcel["ave:kreisschl"], - name=fetched_parcel["ave:kreis"], + key=parcel_properties["kreisschl"], + name=parcel_properties["kreis"], )[0] municipal = Municipal.objects.get_or_create( - key=fetched_parcel["ave:gmdschl"], - name=fetched_parcel["ave:gemeinde"], + key=parcel_properties["gmdschl"], + name=parcel_properties["gemeinde"], district=district, )[0] parcel_group = ParcelGroup.objects.get_or_create( - key=fetched_parcel["ave:gemaschl"], - name=fetched_parcel["ave:gemarkung"], + key=parcel_properties["gemaschl"], + name=parcel_properties["gemarkung"], municipal=municipal, )[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( district=district, municipal=municipal, parcel_group=parcel_group, flr=flr_val, - flrstck_nnr=fetched_parcel['ave:flstnrnen'], - flrstck_zhlr=fetched_parcel['ave:flstnrzae'], + flrstck_nnr=flrstck_nnr, + flrstck_zhlr=flrstck_zhlr, )[0] parcel_obj.district = district parcel_obj.updated_on = _now diff --git a/konova/models/object.py b/konova/models/object.py index f224b68c..a1cff71b 100644 --- a/konova/models/object.py +++ b/konova/models/object.py @@ -289,6 +289,8 @@ class RecordableObjectMixin(models.Model): from user.models import UserActionLogEntry if self.recorded: return None + + self.unshare_with_default_users() action = UserActionLogEntry.get_recorded_action(user) self.recorded = action self.save() @@ -335,6 +337,15 @@ class RecordableObjectMixin(models.Model): """ 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): # Checks - Refers to "Genehmigen" but optional @@ -608,6 +619,26 @@ class ShareableObjectMixin(models.Model): """ 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): geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL) diff --git a/konova/sub_settings/table_settings.py b/konova/sub_settings/table_settings.py index f32a0835..1699ad1b 100644 --- a/konova/sub_settings/table_settings.py +++ b/konova/sub_settings/table_settings.py @@ -19,6 +19,6 @@ PAGE_SIZE_OPTIONS_TUPLES = [ (50, 50), (100, 100), ] -PAGE_SIZE_DEFAULT = 5 +PAGE_SIZE_DEFAULT = 10 PAGE_SIZE_MAX = 100 PAGE_DEFAULT = 1 diff --git a/konova/templates/konova/includes/parcels/parcel_table_content.html b/konova/templates/konova/includes/parcels/parcel_table_content.html new file mode 100644 index 00000000..549a8092 --- /dev/null +++ b/konova/templates/konova/includes/parcels/parcel_table_content.html @@ -0,0 +1,22 @@ +{% load l10n i18n %} +{% for parcel in parcels %} + {% if forloop.last and next_page %} + + {{parcel.parcel_group.name|default_if_none:"-"}} + {{parcel.parcel_group.key|default_if_none:"-"}} + {{parcel.flr|default_if_none:"-"|unlocalize}} + {{parcel.flrstck_zhlr|default_if_none:"-"|unlocalize}} + {{parcel.flrstck_nnr|default_if_none:"-"|unlocalize}} + + {% else %} + + {{parcel.parcel_group.name|default_if_none:"-"}} + {{parcel.parcel_group.key|default_if_none:"-"}} + {{parcel.flr|default_if_none:"-"|unlocalize}} + {{parcel.flrstck_zhlr|default_if_none:"-"|unlocalize}} + {{parcel.flrstck_nnr|default_if_none:"-"|unlocalize}} + + {% endif %} +{% endfor %} \ No newline at end of file diff --git a/konova/templates/konova/includes/parcel_table.html b/konova/templates/konova/includes/parcels/parcel_table_frame.html similarity index 76% rename from konova/templates/konova/includes/parcel_table.html rename to konova/templates/konova/includes/parcels/parcel_table_frame.html index 76503572..e3292004 100644 --- a/konova/templates/konova/includes/parcel_table.html +++ b/konova/templates/konova/includes/parcels/parcel_table_frame.html @@ -37,16 +37,7 @@ - {% for parcel in parcels %} - - {{parcel.parcel_group.name|default_if_none:"-"}} - {{parcel.parcel_group.key|default_if_none:"-"}} - {{parcel.flr|default_if_none:"-"|unlocalize}} - {{parcel.flrstck_zhlr|default_if_none:"-"|unlocalize}} - {{parcel.flrstck_nnr|default_if_none:"-"|unlocalize}} - - {% endfor %} - + {% include 'konova/includes/parcels/parcel_table_content.html' %} {% endif %} diff --git a/konova/templates/konova/includes/parcels.html b/konova/templates/konova/includes/parcels/parcels.html similarity index 81% rename from konova/templates/konova/includes/parcels.html rename to konova/templates/konova/includes/parcels/parcels.html index 30feda43..a8a882ed 100644 --- a/konova/templates/konova/includes/parcels.html +++ b/konova/templates/konova/includes/parcels/parcels.html @@ -8,7 +8,7 @@
-
+
diff --git a/konova/templates/konova/includes/report/qrcodes.html b/konova/templates/konova/includes/report/qrcodes.html new file mode 100644 index 00000000..5b52d0c1 --- /dev/null +++ b/konova/templates/konova/includes/report/qrcodes.html @@ -0,0 +1,19 @@ +{% load i18n %} + + + + \ No newline at end of file diff --git a/konova/urls.py b/konova/urls.py index d2458f50..00386a11 100644 --- a/konova/urls.py +++ b/konova/urls.py @@ -24,7 +24,7 @@ from konova.autocompletes import EcoAccountAutocomplete, \ ShareTeamAutocomplete, HandlerCodeAutocomplete from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG 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) urlpatterns = [ @@ -40,7 +40,8 @@ urlpatterns = [ path('cl/', include("codelist.urls")), path('analysis/', include("analysis.urls")), path('api/', include("api.urls")), - path('geom//parcels', get_geom_parcels, name="geometry-parcels"), + path('geom//parcels/', get_geom_parcels, name="geometry-parcels"), + path('geom//parcels/', get_geom_parcels_content, name="geometry-parcels-content"), # Autocomplete paths for all apps path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"), diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py index 64b7eab1..e062005a 100644 --- a/konova/utils/message_templates.py +++ b/konova/utils/message_templates.py @@ -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.") MISSING_GROUP_PERMISSION = _("You need to be part of another user group.") 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 DATA_UNSHARED = _("This data is not shared with you") diff --git a/konova/utils/wfs/spatial.py b/konova/utils/wfs/spatial.py index 8a7bf360..2f1452d6 100644 --- a/konova/utils/wfs/spatial.py +++ b/konova/utils/wfs/spatial.py @@ -5,11 +5,12 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 17.12.21 """ +import json from abc import abstractmethod +from json import JSONDecodeError from time import sleep import requests -import xmltodict from django.contrib.gis.db.models.functions import AsGML, Transform from requests.auth import HTTPDigestAuth @@ -115,7 +116,7 @@ class ParcelWFSFetcher(AbstractWFSFetcher): geometry_operation, filter_srid ) - _filter = f'{spatial_filter}' + _filter = f'{spatial_filter}' return _filter def get_features(self, @@ -139,7 +140,7 @@ class ParcelWFSFetcher(AbstractWFSFetcher): Returns: features (list): A list of returned features """ - features = [] + found_features = [] while start_index is not None: post_body = self._create_post_data( spatial_operator, @@ -155,19 +156,11 @@ class ParcelWFSFetcher(AbstractWFSFetcher): ) content = response.content.decode("utf-8") - content = xmltodict.parse(content) - collection = content.get( - "wfs:FeatureCollection", - {}, - ) - - # 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: + try: + # Check if collection is an exception and does not contain the requested data + content = json.loads(content) + except JSONDecodeError as e: + if rerun_on_exception: # Wait a second before another try sleep(1) self.get_features( @@ -177,22 +170,21 @@ class ParcelWFSFetcher(AbstractWFSFetcher): start_index, 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: - # convert single found feature member into list and extent feature list - features += [members] + e.msg += content + raise e + fetched_features = content.get( + "features", + {}, + ) - if collection.get("@next", None) is not None: - start_index += self.count - else: + found_features += fetched_features + + if len(fetched_features) < self.count: + # The response was not 'full', so we got everything to fetch 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 diff --git a/konova/views.py b/konova/views.py index 2fd5ff91..97cb7d82 100644 --- a/konova/views.py +++ b/konova/views.py @@ -110,12 +110,12 @@ def get_geom_parcels(request: HttpRequest, id: str): id (str): The geometry's id Returns: - + A rendered piece of HTML """ # HTTP code 286 states that the HTMX should stop polling for updates # https://htmx.org/docs/#polling 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) parcels = geom.get_underlying_parcels() 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") municipals = parcels.order_by("municipal").distinct("municipal").values("municipal__id") municipals = Municipal.objects.filter(id__in=municipals) + + rpp = 100 + parcels = parcels[:rpp] + next_page = 1 + if len(parcels) < rpp: + next_page = None + context = { "parcels": parcels, "municipals": municipals, + "geom_id": str(id), + "next_page": next_page, } html = render_to_string(template, context, request) return HttpResponse(html, status=status_code) @@ -143,6 +152,49 @@ def get_geom_parcels(request: HttpRequest, id: str): 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): """ Returns a 404 handling view diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index 6459128e..45575064 100644 Binary files a/locale/de/LC_MESSAGES/django.mo and b/locale/de/LC_MESSAGES/django.mo differ diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 1cb4871a..35ee2c5c 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -18,15 +18,15 @@ #: konova/filters/mixins.py:277 konova/filters/mixins.py:323 #: konova/filters/mixins.py:361 konova/filters/mixins.py:362 #: 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:359 konova/forms.py:369 konova/forms.py:382 -#: konova/forms.py:394 konova/forms.py:412 user/forms.py:42 +#: konova/forms.py:177 konova/forms.py:278 konova/forms.py:349 +#: konova/forms.py:393 konova/forms.py:403 konova/forms.py:416 +#: konova/forms.py:428 konova/forms.py:446 user/forms.py:42 #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\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" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -77,7 +77,7 @@ msgstr "Bericht generieren" msgid "Select a timespan and the desired conservation office" 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" 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/laws.html:20 #: 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/eco_account/includes/deductions.html:31 #: compensation/templates/compensation/detail/eco_account/view.html:45 @@ -294,7 +294,7 @@ msgid "Intervention" msgstr "Eingriff" #: 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 #: intervention/forms/modalForms.py:348 intervention/forms/modalForms.py:355 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4 @@ -315,7 +315,7 @@ msgid "Show only unrecorded" msgstr "Nur unverzeichnete anzeigen" #: 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/templates/intervention/detail/includes/compensations.html:30 msgid "Identifier" @@ -327,7 +327,7 @@ msgid "Generated automatically" msgstr "Automatisch generiert" #: 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/view.html:32 #: 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/view.html:31 #: intervention/templates/intervention/report/report.html:12 -#: konova/forms.py:358 +#: konova/forms.py:392 msgid "Title" msgstr "Bezeichnung" @@ -369,7 +369,7 @@ msgstr "Kompensation XY; Flur ABC" #: intervention/templates/intervention/detail/includes/documents.html:34 #: intervention/templates/intervention/detail/includes/payments.html:34 #: 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" msgstr "Kommentar" @@ -441,7 +441,7 @@ msgstr "kompensiert Eingriff" msgid "Select the intervention for which this compensation compensates" 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" msgstr "Neue Kompensation" @@ -467,7 +467,7 @@ msgstr "Vereinbarungsdatum" msgid "When did the parties agree on this?" 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" msgstr "Neues Ökokonto" @@ -493,7 +493,7 @@ msgid "Due on which date" msgstr "Zahlung wird an diesem Datum erwartet" #: 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" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" @@ -538,7 +538,7 @@ msgstr "Neuer Zustand" msgid "Insert data for the new state" 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" msgstr "Objekt entfernt" @@ -675,22 +675,22 @@ msgstr "" "Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar einstellen " "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 msgid "Parcel gmrkng" 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 msgid "Editable" 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 msgid "Last edit" 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 msgid "Open {}" msgstr "Öffne {}" @@ -713,32 +713,32 @@ msgstr "Am {} von {} geprüft worden" msgid "Not recorded yet" 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 msgid "Recorded on {} by {}" msgstr "Am {} von {} verzeichnet worden" -#: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159 -#: intervention/tables.py:185 +#: compensation/tables.py:187 compensation/tables.py:346 ema/tables.py:157 +#: intervention/tables.py:183 msgid "Full access granted" msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden" -#: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159 -#: intervention/tables.py:185 +#: compensation/tables.py:187 compensation/tables.py:346 ema/tables.py:157 +#: intervention/tables.py:183 msgid "Access not granted" msgstr "Nicht freigegeben - Datensatz nur lesbar" -#: compensation/tables.py:212 +#: compensation/tables.py:210 #: compensation/templates/compensation/detail/eco_account/view.html:36 #: konova/templates/konova/widgets/progressbar.html:3 msgid "Available" msgstr "Verfügbar" -#: compensation/tables.py:243 +#: compensation/tables.py:241 msgid "Eco Accounts" msgstr "Ökokonten" -#: compensation/tables.py:321 +#: compensation/tables.py:319 msgid "Not recorded yet. Can not be used for deductions, yet." msgstr "" "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 #: ema/templates/ema/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" msgstr "Neues Dokument hinzufügen" @@ -879,7 +879,7 @@ msgstr "Neues Dokument hinzufügen" #: compensation/templates/compensation/detail/eco_account/includes/documents.html:31 #: ema/templates/ema/detail/includes/documents.html:31 #: intervention/templates/intervention/detail/includes/documents.html:31 -#: konova/forms.py:368 +#: konova/forms.py:402 msgid "Created on" msgstr "Erstellt" @@ -887,7 +887,7 @@ msgstr "Erstellt" #: compensation/templates/compensation/detail/eco_account/includes/documents.html:61 #: ema/templates/ema/detail/includes/documents.html:61 #: intervention/templates/intervention/detail/includes/documents.html:65 -#: konova/forms.py:474 +#: konova/forms.py:507 msgid "Edit document" msgstr "Dokument bearbeiten" @@ -1067,7 +1067,7 @@ msgid "Recorded on" msgstr "Verzeichnet am" #: 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 msgid "Edit Deduction" msgstr "Abbuchung bearbeiten" @@ -1112,20 +1112,6 @@ msgstr "Maßnahmenträger" msgid "Report" 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 msgid "Deductions for" msgstr "Abbuchungen für" @@ -1155,72 +1141,72 @@ msgstr "" msgid "Responsible data" msgstr "Daten zu den verantwortlichen Stellen" -#: compensation/views/compensation.py:52 +#: compensation/views/compensation.py:53 msgid "Compensations - Overview" 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" msgstr "Kompensation {} bearbeitet" -#: compensation/views/compensation.py:161 compensation/views/eco_account.py:165 -#: ema/views.py:233 intervention/views.py:327 +#: compensation/views/compensation.py:182 compensation/views/eco_account.py:173 +#: ema/views.py:240 intervention/views.py:335 msgid "Edit {}" msgstr "Bearbeite {}" -#: compensation/views/compensation.py:240 compensation/views/eco_account.py:351 -#: ema/views.py:194 intervention/views.py:531 +#: compensation/views/compensation.py:261 compensation/views/eco_account.py:359 +#: ema/views.py:194 intervention/views.py:539 msgid "Log" msgstr "Log" -#: compensation/views/compensation.py:584 compensation/views/eco_account.py:719 -#: ema/views.py:551 intervention/views.py:677 +#: compensation/views/compensation.py:605 compensation/views/eco_account.py:727 +#: ema/views.py:558 intervention/views.py:685 msgid "Report {}" msgstr "Bericht {}" -#: compensation/views/eco_account.py:64 +#: compensation/views/eco_account.py:65 msgid "Eco-account - Overview" msgstr "Ökokonten - Übersicht" -#: compensation/views/eco_account.py:97 +#: compensation/views/eco_account.py:98 msgid "Eco-Account {} added" msgstr "Ökokonto {} hinzugefügt" -#: compensation/views/eco_account.py:155 +#: compensation/views/eco_account.py:163 msgid "Eco-Account {} edited" msgstr "Ökokonto {} bearbeitet" -#: compensation/views/eco_account.py:268 +#: compensation/views/eco_account.py:276 msgid "Eco-account removed" msgstr "Ökokonto entfernt" -#: compensation/views/eco_account.py:372 ema/views.py:275 -#: intervention/views.py:630 +#: compensation/views/eco_account.py:380 ema/views.py:282 +#: intervention/views.py:638 msgid "{} unrecorded" msgstr "{} entzeichnet" -#: compensation/views/eco_account.py:372 ema/views.py:275 -#: intervention/views.py:630 +#: compensation/views/eco_account.py:380 ema/views.py:282 +#: intervention/views.py:638 msgid "{} recorded" msgstr "{} verzeichnet" -#: compensation/views/eco_account.py:792 ema/views.py:617 -#: intervention/views.py:428 +#: compensation/views/eco_account.py:804 ema/views.py:628 +#: intervention/views.py:436 msgid "{} has already been shared with you" msgstr "{} wurde bereits für Sie freigegeben" -#: compensation/views/eco_account.py:797 ema/views.py:622 -#: intervention/views.py:433 +#: compensation/views/eco_account.py:809 ema/views.py:633 +#: intervention/views.py:441 msgid "{} has been shared with you" msgstr "{} ist nun für Sie freigegeben" -#: compensation/views/eco_account.py:804 ema/views.py:629 -#: intervention/views.py:440 +#: compensation/views/eco_account.py:816 ema/views.py:640 +#: intervention/views.py:448 msgid "Share link invalid" msgstr "Freigabelink ungültig" -#: compensation/views/eco_account.py:827 ema/views.py:652 -#: intervention/views.py:463 +#: compensation/views/eco_account.py:839 ema/views.py:663 +#: intervention/views.py:471 msgid "Share settings updated" msgstr "Freigabe Einstellungen aktualisiert" @@ -1260,11 +1246,11 @@ msgstr "EMAs - Übersicht" msgid "EMA {} added" msgstr "EMA {} hinzugefügt" -#: ema/views.py:223 +#: ema/views.py:230 msgid "EMA {} edited" msgstr "EMA {} bearbeitet" -#: ema/views.py:256 +#: ema/views.py:263 msgid "EMA removed" msgstr "EMA entfernt" @@ -1326,7 +1312,7 @@ msgstr "Datum Zulassung bzw. Satzungsbeschluss" msgid "Binding on" msgstr "Datum Bestandskraft" -#: intervention/forms/forms.py:211 intervention/views.py:94 +#: intervention/forms/forms.py:211 intervention/views.py:95 msgid "New intervention" msgstr "Neuer Eingriff" @@ -1406,7 +1392,7 @@ msgstr "Kompensationen und Zahlungen geprüft" msgid "Run check" msgstr "Prüfung vornehmen" -#: intervention/forms/modalForms.py:264 konova/forms.py:515 +#: intervention/forms/modalForms.py:264 konova/forms.py:548 msgid "" "I, {} {}, confirm that all necessary control steps have been performed by " "myself." @@ -1430,7 +1416,7 @@ msgstr "Neue Abbuchung" msgid "Enter the information for a new deduction from a chosen eco-account" msgstr "Geben Sie die Informationen für eine neue Abbuchung ein." -#: intervention/forms/modalForms.py:434 +#: intervention/forms/modalForms.py:436 msgid "" "Eco-account {} is not recorded yet. You can only deduct from recorded " "accounts." @@ -1438,7 +1424,15 @@ msgstr "" "Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von " "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 "" "The account {} has not enough surface for a deduction of {} m². There are " "only {} m² left" @@ -1538,27 +1532,27 @@ msgstr "" "Kein Ausgleich jeglicher Art gefunden (Kompensation, Ersatzzahlung, " "Abbuchung)" -#: intervention/views.py:51 +#: intervention/views.py:52 msgid "Interventions - Overview" msgstr "Eingriffe - Übersicht" -#: intervention/views.py:84 +#: intervention/views.py:85 msgid "Intervention {} added" msgstr "Eingriff {} hinzugefügt" -#: intervention/views.py:315 +#: intervention/views.py:323 msgid "Intervention {} edited" msgstr "Eingriff {} bearbeitet" -#: intervention/views.py:351 +#: intervention/views.py:359 msgid "{} removed" msgstr "{} entfernt" -#: intervention/views.py:484 +#: intervention/views.py:492 msgid "Check performed" msgstr "Prüfung durchgeführt" -#: intervention/views.py:635 +#: intervention/views.py:643 msgid "There are errors on this intervention:" msgstr "Es liegen Fehler in diesem Eingriff vor:" @@ -1588,7 +1582,7 @@ msgid "Search for file number" msgstr "Nach Aktenzeichen suchen" #: 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" msgstr "Kreis" @@ -1601,7 +1595,7 @@ msgid "Search for parcel gmrkng" msgstr "Nach Gemarkung suchen" #: 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" msgstr "Flur" @@ -1610,7 +1604,7 @@ msgid "Search for parcel" msgstr "Nach Flur suchen" #: 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" msgstr "Flurstückzähler" @@ -1619,7 +1613,7 @@ msgid "Search for parcel counter" msgstr "Nach Flurstückzähler suchen" #: 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" msgstr "Flurstücknenner" @@ -1647,65 +1641,65 @@ msgstr "Nch Eintragungsstelle suchen" msgid "Save" msgstr "Speichern" -#: konova/forms.py:71 +#: konova/forms.py:73 msgid "Not editable" msgstr "Nicht editierbar" -#: konova/forms.py:142 konova/forms.py:314 +#: konova/forms.py:176 konova/forms.py:348 msgid "Confirm" msgstr "Bestätige" -#: konova/forms.py:154 konova/forms.py:323 +#: konova/forms.py:188 konova/forms.py:357 msgid "Remove" msgstr "Löschen" -#: konova/forms.py:156 +#: konova/forms.py:190 msgid "You are about to remove {} {}" 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 msgid "Geometry" msgstr "Geometrie" -#: konova/forms.py:324 +#: konova/forms.py:358 msgid "Are you sure?" msgstr "Sind Sie sicher?" -#: konova/forms.py:370 +#: konova/forms.py:404 msgid "When has this file been created? Important for photos." 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 msgid "File" msgstr "Datei" -#: konova/forms.py:383 +#: konova/forms.py:417 msgid "Allowed formats: pdf, jpg, png. Max size 15 MB." msgstr "Formate: pdf, jpg, png. Maximal 15 MB." -#: konova/forms.py:449 +#: konova/forms.py:482 msgid "Added document" msgstr "Dokument hinzugefügt" -#: konova/forms.py:506 +#: konova/forms.py:539 msgid "Confirm record" msgstr "Verzeichnen bestätigen" -#: konova/forms.py:514 +#: konova/forms.py:547 msgid "Record data" msgstr "Daten verzeichnen" -#: konova/forms.py:521 +#: konova/forms.py:554 msgid "Confirm unrecord" msgstr "Entzeichnen bestätigen" -#: konova/forms.py:522 +#: konova/forms.py:555 msgid "Unrecord data" msgstr "Daten entzeichnen" -#: konova/forms.py:523 +#: konova/forms.py:556 msgid "I, {} {}, confirm that this data must be unrecorded." msgstr "" "Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen." @@ -1754,33 +1748,37 @@ msgstr "" msgid "English" 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." msgstr "" "Flurstücke können nicht berechnet werden, da keine Geometrie eingegeben " "wurde." -#: konova/templates/konova/includes/parcel_table.html:11 +#: konova/templates/konova/includes/parcels/parcel_table_frame.html:11 msgid "Municipal" msgstr "Gemeinde" -#: konova/templates/konova/includes/parcel_table.html:12 +#: konova/templates/konova/includes/parcels/parcel_table_frame.html:12 msgid "Municipal key" msgstr "Gemeindeschlüssel" -#: konova/templates/konova/includes/parcel_table.html:14 +#: konova/templates/konova/includes/parcels/parcel_table_frame.html:14 msgid "District key" msgstr "Kreisschlüssel" -#: konova/templates/konova/includes/parcel_table.html:32 +#: konova/templates/konova/includes/parcels/parcel_table_frame.html:32 msgid "Parcel group" msgstr "Gemarkung" -#: konova/templates/konova/includes/parcel_table.html:33 +#: konova/templates/konova/includes/parcels/parcel_table_frame.html:33 msgid "Parcel group key" msgstr "Gemarkungsschlüssel" -#: konova/templates/konova/includes/parcels.html:7 +#: konova/templates/konova/includes/parcels/parcels.html:7 msgid "Spatial reference" msgstr "Raumreferenz" @@ -1802,6 +1800,14 @@ msgstr "Neu" msgid "Show" 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 #: templates/generic_index.html:56 msgid "Search" @@ -1895,11 +1901,18 @@ msgstr "Hierfür müssen Sie einer anderen Nutzergruppe angehören!" msgid "Status of Checked and Recorded reseted" 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" msgstr "Diese Daten sind für Sie nicht freigegeben" -#: konova/utils/message_templates.py:23 +#: konova/utils/message_templates.py:24 msgid "" "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 " @@ -1909,15 +1922,15 @@ msgstr "" "bedeutet, dass Sie nur lesenden Zugriff hierauf haben und weder bearbeiten, " "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" msgstr "Dateiformat nicht unterstützt" -#: konova/utils/message_templates.py:27 +#: konova/utils/message_templates.py:28 msgid "File too large" msgstr "Datei zu groß" -#: konova/utils/message_templates.py:30 +#: konova/utils/message_templates.py:31 msgid "" "Action canceled. Eco account is recorded or deductions exist. Only " "conservation office member can perform this action." @@ -1925,119 +1938,119 @@ msgstr "" "Aktion abgebrochen. Ökokonto ist bereits verzeichnet oder Abbuchungen liegen " "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" msgstr "Kompensation {} hinzugefügt" -#: konova/utils/message_templates.py:34 +#: konova/utils/message_templates.py:35 msgid "Compensation {} removed" msgstr "Kompensation {} entfernt" -#: konova/utils/message_templates.py:36 +#: konova/utils/message_templates.py:37 msgid "Added compensation action" msgstr "Maßnahme hinzugefügt" -#: konova/utils/message_templates.py:37 +#: konova/utils/message_templates.py:38 msgid "Added compensation state" msgstr "Zustand hinzugefügt" -#: konova/utils/message_templates.py:40 +#: konova/utils/message_templates.py:41 msgid "State removed" msgstr "Zustand gelöscht" -#: konova/utils/message_templates.py:41 +#: konova/utils/message_templates.py:42 msgid "State edited" msgstr "Zustand bearbeitet" -#: konova/utils/message_templates.py:42 +#: konova/utils/message_templates.py:43 msgid "State added" msgstr "Zustand hinzugefügt" -#: konova/utils/message_templates.py:45 +#: konova/utils/message_templates.py:46 msgid "Action added" msgstr "Maßnahme hinzugefügt" -#: konova/utils/message_templates.py:46 +#: konova/utils/message_templates.py:47 msgid "Action edited" msgstr "Maßnahme bearbeitet" -#: konova/utils/message_templates.py:47 +#: konova/utils/message_templates.py:48 msgid "Action removed" msgstr "Maßnahme entfernt" -#: konova/utils/message_templates.py:50 +#: konova/utils/message_templates.py:51 msgid "Deduction added" msgstr "Abbuchung hinzugefügt" -#: konova/utils/message_templates.py:51 +#: konova/utils/message_templates.py:52 msgid "Deduction edited" msgstr "Abbuchung bearbeitet" -#: konova/utils/message_templates.py:52 +#: konova/utils/message_templates.py:53 msgid "Deduction removed" msgstr "Abbuchung entfernt" -#: konova/utils/message_templates.py:55 +#: konova/utils/message_templates.py:56 msgid "Deadline added" msgstr "Frist/Termin hinzugefügt" -#: konova/utils/message_templates.py:56 +#: konova/utils/message_templates.py:57 msgid "Deadline edited" msgstr "Frist/Termin bearbeitet" -#: konova/utils/message_templates.py:57 +#: konova/utils/message_templates.py:58 msgid "Deadline removed" msgstr "Frist/Termin gelöscht" -#: konova/utils/message_templates.py:60 +#: konova/utils/message_templates.py:61 msgid "Payment added" msgstr "Zahlung hinzugefügt" -#: konova/utils/message_templates.py:61 +#: konova/utils/message_templates.py:62 msgid "Payment edited" msgstr "Zahlung bearbeitet" -#: konova/utils/message_templates.py:62 +#: konova/utils/message_templates.py:63 msgid "Payment removed" msgstr "Zahlung gelöscht" -#: konova/utils/message_templates.py:65 +#: konova/utils/message_templates.py:66 msgid "Revocation added" msgstr "Widerspruch hinzugefügt" -#: konova/utils/message_templates.py:66 +#: konova/utils/message_templates.py:67 msgid "Revocation edited" msgstr "Widerspruch bearbeitet" -#: konova/utils/message_templates.py:67 +#: konova/utils/message_templates.py:68 msgid "Revocation removed" msgstr "Widerspruch entfernt" -#: konova/utils/message_templates.py:70 +#: konova/utils/message_templates.py:71 msgid "Document '{}' deleted" msgstr "Dokument '{}' gelöscht" -#: konova/utils/message_templates.py:71 +#: konova/utils/message_templates.py:72 msgid "Document added" msgstr "Dokument hinzugefügt" -#: konova/utils/message_templates.py:72 +#: konova/utils/message_templates.py:73 msgid "Document edited" msgstr "Dokument bearbeitet" -#: konova/utils/message_templates.py:75 +#: konova/utils/message_templates.py:76 msgid "Edited general data" msgstr "Allgemeine Daten bearbeitet" -#: konova/utils/message_templates.py:76 +#: konova/utils/message_templates.py:77 msgid "Added deadline" msgstr "Frist/Termin hinzugefügt" -#: konova/utils/message_templates.py:79 +#: konova/utils/message_templates.py:80 msgid "Geometry conflict detected with {}" msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}" -#: konova/utils/message_templates.py:82 +#: konova/utils/message_templates.py:83 msgid "This intervention has {} revocations" msgstr "Dem Eingriff liegen {} Widersprüche vor" @@ -2364,6 +2377,25 @@ msgstr "Allgemeine Daten" msgid "Cancel" 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 msgid "Fields with * are required." msgstr "* sind Pflichtfelder." @@ -2451,9 +2483,9 @@ msgid "" " " msgstr "" "\n" -" Diese Daten sind noch nicht veröffentlicht und/oder haben das Bestandskraftdatum noch nicht erreicht. " -"Sie können daher aktuell nicht eingesehen werden. Schauen Sie zu einem späteren Zeitpunkt " -"wieder vorbei. \n" +" Diese Daten sind noch nicht veröffentlicht und/oder haben das " +"Bestandskraftdatum noch nicht erreicht. Sie können daher aktuell nicht " +"eingesehen werden. Schauen Sie zu einem späteren Zeitpunkt wieder vorbei. \n" " " #: 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!" 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" 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" msgstr "Beschreibung" @@ -2552,11 +2584,15 @@ msgstr "Administratoren verwalten die Teamdaten und Mitglieder" msgid "Selected admin ({}) needs to be a member of this team." 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" 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" msgstr "Team" @@ -2642,7 +2678,7 @@ msgstr "Neues Team hinzufügen" msgid "Members" msgstr "Mitglieder" -#: user/templates/user/team/index.html:54 +#: user/templates/user/team/index.html:57 msgid "Remove team" msgstr "Team entfernen" @@ -2702,6 +2738,14 @@ msgstr "Team bearbeitet" msgid "Team removed" 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/templates/bootstrap4/form_errors.html:3 #: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/messages.html:4 diff --git a/templates/form/recorded_no_edit.html b/templates/form/recorded_no_edit.html new file mode 100644 index 00000000..45fd08df --- /dev/null +++ b/templates/form/recorded_no_edit.html @@ -0,0 +1,19 @@ +{% load i18n fontawesome_5 %} + +
+

+ + {% fa5_icon 'bookmark' %} + + + {% trans 'This data is recorded' %} + +

+
+
+ {% 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 %} +
+
\ No newline at end of file diff --git a/user/admin.py b/user/admin.py index 1aeacee3..f4ef9fce 100644 --- a/user/admin.py +++ b/user/admin.py @@ -74,6 +74,15 @@ class TeamAdmin(admin.ModelAdmin): "name", "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) diff --git a/user/forms.py b/user/forms.py index 34c8fab9..4a657afb 100644 --- a/user/forms.py +++ b/user/forms.py @@ -317,6 +317,15 @@ class RemoveTeamModalForm(RemoveModalForm): 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): name = forms.CharField( label_suffix="", diff --git a/user/models/team.py b/user/models/team.py index e36c95b4..f14c7e0f 100644 --- a/user/models/team.py +++ b/user/models/team.py @@ -93,3 +93,17 @@ class Team(UuidModel): """ mailer = Mailer() 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() diff --git a/user/templates/user/team/index.html b/user/templates/user/team/index.html index d2040a35..3cd08e74 100644 --- a/user/templates/user/team/index.html +++ b/user/templates/user/team/index.html @@ -46,6 +46,9 @@ {% endfor %} + {% if team.admin == user %}