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/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 96888cc1..401a7416 100644 --- a/compensation/tables.py +++ b/compensation/tables.py @@ -134,7 +134,7 @@ class CompensationTable(BaseTable, TableRenderMixin): """ parcels = value.get_underlying_parcels().values_list( - "gmrkng", + "parcel_group__name", flat=True ).distinct() html = render_to_string( @@ -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"), @@ -295,7 +293,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin): """ parcels = value.get_underlying_parcels().values_list( - "gmrkng", + "parcel_group__name", flat=True ).distinct() html = render_to_string( @@ -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 cc9c4ebf..cd5332aa 100644 --- a/compensation/templates/compensation/detail/compensation/view.html +++ b/compensation/templates/compensation/detail/compensation/view.html @@ -90,9 +90,15 @@ {% trans 'Last modified' %} - {{obj.modified.timestamp|default_if_none:""|naturalday}} -
- {{obj.modified.user.username}} + {% if obj.modified %} + {{obj.modified.timestamp|default_if_none:""}} +
+ {{obj.modified.user.username}} + {% else %} + {{obj.created.timestamp|default_if_none:""}} +
+ {{obj.created.user.username}} + {% endif %} diff --git a/compensation/templates/compensation/detail/eco_account/view.html b/compensation/templates/compensation/detail/eco_account/view.html index ba1ce96a..f5fc7cf3 100644 --- a/compensation/templates/compensation/detail/eco_account/view.html +++ b/compensation/templates/compensation/detail/eco_account/view.html @@ -73,9 +73,15 @@ {% trans 'Last modified' %} - {{obj.modified.timestamp|default_if_none:""|naturalday}} -
- {{obj.modified.user.username}} + {% if obj.modified %} + {{obj.modified.timestamp|default_if_none:""}} +
+ {{obj.modified.user.username}} + {% else %} + {{obj.created.timestamp|default_if_none:""}} +
+ {{obj.created.user.username}} + {% endif %} diff --git a/compensation/templates/compensation/report/compensation/report.html b/compensation/templates/compensation/report/compensation/report.html index 4a773d6b..9caa7100 100644 --- a/compensation/templates/compensation/report/compensation/report.html +++ b/compensation/templates/compensation/report/compensation/report.html @@ -43,14 +43,7 @@ {% include 'konova/includes/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 c0959244..01a631a8 100644 --- a/compensation/templates/compensation/report/eco_account/report.html +++ b/compensation/templates/compensation/report/eco_account/report.html @@ -56,14 +56,7 @@ {% include 'konova/includes/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 f9689517..38d8a8c0 100644 --- a/ema/tables.py +++ b/ema/tables.py @@ -104,7 +104,7 @@ class EmaTable(BaseTable, TableRenderMixin): """ parcels = value.get_underlying_parcels().values_list( - "gmrkng", + "parcel_group__name", flat=True ).distinct() html = render_to_string( @@ -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 38f2c1ed..14a6a11a 100644 --- a/ema/templates/ema/detail/view.html +++ b/ema/templates/ema/detail/view.html @@ -60,14 +60,13 @@ {% trans 'Last modified' %} {% if obj.modified %} - {{obj.modified.timestamp|default_if_none:""|naturalday}} + {{obj.modified.timestamp|default_if_none:""}}
{{obj.modified.user.username}} {% else %} - {{obj.created.timestamp|default_if_none:""|naturalday}} + {{obj.created.timestamp|default_if_none:""}}
{{obj.created.user.username}} - {% endif %} diff --git a/ema/templates/ema/report/report.html b/ema/templates/ema/report/report.html index 4cc80fee..3f62e599 100644 --- a/ema/templates/ema/report/report.html +++ b/ema/templates/ema/report/report.html @@ -43,14 +43,7 @@ {% include 'konova/includes/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/tables.py b/intervention/tables.py index f535039d..8f312099 100644 --- a/intervention/tables.py +++ b/intervention/tables.py @@ -131,7 +131,7 @@ class InterventionTable(BaseTable, TableRenderMixin): """ parcels = value.get_underlying_parcels().values_list( - "gmrkng", + "parcel_group__name", flat=True ).distinct() html = render_to_string( @@ -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 5247a84c..41c59621 100644 --- a/intervention/templates/intervention/detail/view.html +++ b/intervention/templates/intervention/detail/view.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% load i18n l10n static fontawesome_5 humanize %} +{% load i18n l10n static fontawesome_5 %} {% block head %} {% comment %} @@ -106,9 +106,15 @@ {% trans 'Last modified' %} - {{obj.created.timestamp|default_if_none:""|naturalday}} -
- {{obj.created.user.username}} + {% if obj.modified %} + {{obj.modified.timestamp|default_if_none:""}} +
+ {{obj.modified.user.username}} + {% else %} + {{obj.created.timestamp|default_if_none:""}} +
+ {{obj.created.user.username}} + {% endif %} diff --git a/intervention/templates/intervention/report/report.html b/intervention/templates/intervention/report/report.html index 97981c3c..b435bfcb 100644 --- a/intervention/templates/intervention/report/report.html +++ b/intervention/templates/intervention/report/report.html @@ -102,14 +102,7 @@ {% include 'konova/includes/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 39a4c318..1ef9f727 100644 --- a/intervention/utils/egon_export.py +++ b/intervention/utils/egon_export.py @@ -80,7 +80,7 @@ class EgonGmlBuilder: Returns: str """ - gmrkng_code = "000000" + gmrkng_code = "{0:06d}".format(int(parcel.parcel_group.key) or 0) flr_code = "{0:03d}".format(int(parcel.flr or 0)) flrstckzhlr_code = "{0:05d}".format(int(parcel.flrstck_zhlr or 0)) flrstcknnr_code = "{0:06d}".format(int(parcel.flrstck_nnr or 0)) @@ -124,13 +124,13 @@ class EgonGmlBuilder: "oneo:ortsangabe": { "oneo:Ortsangaben": { "oneo:kreisSchluessel": { - "xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/588/{parcel.district.krs}", + "xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/588/{parcel.district.key}", }, "oneo:gemeindeSchluessel": { - "xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/910/{parcel.district.gmnd}", + "xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/910/{parcel.municipal.key}", }, "oneo:verbandsgemeindeSchluessel": { - "xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/589/{parcel.gmrkng}", + "xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/589/{None}", }, "oneo:flurstuecksKennzeichen": self._gen_flurstuecksKennzeichen(parcel), } @@ -156,6 +156,10 @@ 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_date = payment_date.strftime(DEFAULT_DATE_FORMAT) + xml_dict = { "wfs:FeatureCollection": { "@xmlns:wfs": "http://www.opengis.net/wfs", @@ -207,7 +211,7 @@ class EgonGmlBuilder: }, "oneo:zulassung": { "oneo:Zulassungstermin": { - "oneo:bauBeginn": self.intervention.payments.first().due_on.strftime(DEFAULT_DATE_FORMAT), + "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), } 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 81db8fe2..213120ea 100644 --- a/konova/admin.py +++ b/konova/admin.py @@ -7,7 +7,8 @@ Created on: 22.07.21 """ from django.contrib import admin -from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District +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,13 +17,28 @@ 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): list_display = [ "id", - "gmrkng", + "parcel_group", "flr", "flrstck_nnr", "flrstck_zhlr", @@ -32,9 +48,27 @@ class ParcelAdmin(admin.ModelAdmin): class DistrictAdmin(admin.ModelAdmin): list_display = [ + "name", + "key", + "id", + ] + + +class MunicipalAdmin(admin.ModelAdmin): + list_display = [ + "name", + "key", + "district", + "id", + ] + + +class ParcelGroupAdmin(admin.ModelAdmin): + list_display = [ + "name", + "key", + "municipal", "id", - "gmnd", - "krs", ] @@ -105,5 +139,7 @@ class BaseObjectAdmin(BaseResourceAdmin): #admin.site.register(Geometry, GeometryAdmin) #admin.site.register(Parcel, ParcelAdmin) #admin.site.register(District, DistrictAdmin) +#admin.site.register(Municipal, MunicipalAdmin) +#admin.site.register(ParcelGroup, ParcelGroupAdmin) #admin.site.register(GeometryConflict, GeometryConflictAdmin) #admin.site.register(Deadline, DeadlineAdmin) 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/filters/mixins.py b/konova/filters/mixins.py index e6a841ea..beb44dac 100644 --- a/konova/filters/mixins.py +++ b/konova/filters/mixins.py @@ -145,26 +145,20 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet): class Meta: abstract = True - def _filter_parcel_reference(self, queryset, name, value, filter_value) -> QuerySet: - """ Filters the parcel entries by a given filter_value. - - filter_value may already include further filter annotations like 'xy__icontains' + def _filter_parcel_reference(self, queryset, filter_q) -> QuerySet: + """ Filters the parcel entries by a given filter_q Args: - queryset (): - name (): - value (): - filter_value (): + queryset (QuerySet): The queryset + filter_q (Q): The Q-style filter expression Returns: """ - _filter = { - filter_value: value - } matching_parcels = Parcel.objects.filter( - **_filter + filter_q ) + related_geoms = matching_parcels.values( "geometries" ).distinct() @@ -185,8 +179,9 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet): """ matching_districts = District.objects.filter( - krs__icontains=value - ) + Q(name__icontains=value) | + Q(key__icontains=value) + ).distinct() matching_parcels = Parcel.objects.filter( district__in=matching_districts ) @@ -209,7 +204,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet): Returns: """ - queryset = self._filter_parcel_reference(queryset, name, value, "gmrkng__icontains") + queryset = self._filter_parcel_reference( + queryset, + Q(parcel_group__name__icontains=value) | Q(parcel_group__key__icontains=value), + ) return queryset def filter_parcel(self, queryset, name, value) -> QuerySet: @@ -224,7 +222,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet): """ value = value.replace("-", "") - queryset = self._filter_parcel_reference(queryset, name, value, "flr") + queryset = self._filter_parcel_reference( + queryset, + Q(flr=value), + ) return queryset def filter_parcel_counter(self, queryset, name, value) -> QuerySet: @@ -239,7 +240,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet): """ value = value.replace("-", "") - queryset = self._filter_parcel_reference(queryset, name, value, "flrstck_zhlr") + queryset = self._filter_parcel_reference( + queryset, + Q(flrstck_zhlr=value) + ) return queryset def filter_parcel_number(self, queryset, name, value) -> QuerySet: @@ -254,7 +258,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet): """ value = value.replace("-", "") - queryset = self._filter_parcel_reference(queryset, name, value, "flrstck_nnr") + queryset = self._filter_parcel_reference( + queryset, + Q(flrstck_nnr=value), + ) return queryset diff --git a/konova/forms.py b/konova/forms.py index b85501ba..d64ab364 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( @@ -400,7 +434,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 } @@ -587,4 +620,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/sanitize_db.py b/konova/management/commands/sanitize_db.py index b5be8349..b296b2a7 100644 --- a/konova/management/commands/sanitize_db.py +++ b/konova/management/commands/sanitize_db.py @@ -9,7 +9,7 @@ from compensation.models import CompensationState, Compensation, EcoAccount, Com from ema.models import Ema from intervention.models import Intervention from konova.management.commands.setup import BaseKonovaCommand -from konova.models import Deadline, Geometry, Parcel, District +from konova.models import Deadline, Geometry, Parcel, District, Municipal, ParcelGroup from user.models import UserActionLogEntry, UserAction @@ -271,13 +271,26 @@ class Command(BaseKonovaCommand): self._write_success("No unused states found.") self._break_line() + def __sanitize_parcel_sub_type(self, cls): + unrelated_entries = cls.objects.filter( + parcels=None, + ) + num_unrelated_entries = unrelated_entries.count() + cls_name = cls.__name__ + if num_unrelated_entries > 0: + self._write_error(f"Found {num_unrelated_entries} unrelated {cls_name} entries. Delete now...") + unrelated_entries.delete() + self._write_success(f"Unrelated {cls_name} deleted.") + else: + self._write_success(f"No unrelated {cls_name} found.") + def sanitize_parcels_and_districts(self): """ Removes unattached parcels and districts Returns: """ - self._write_warning("=== Sanitize parcels and districts ===") + self._write_warning("=== Sanitize administrative spatial references ===") unrelated_parcels = Parcel.objects.filter( geometries=None, ) @@ -289,16 +302,12 @@ class Command(BaseKonovaCommand): else: self._write_success("No unrelated parcels found.") - unrelated_districts = District.objects.filter( - parcels=None, - ) - num_unrelated_districts = unrelated_districts.count() - if num_unrelated_districts > 0: - self._write_error(f"Found {num_unrelated_districts} unrelated district entries. Delete now...") - unrelated_districts.delete() - self._write_success("Unrelated districts deleted.") - else: - self._write_success("No unrelated districts found.") - - self._break_line() + sub_types = [ + District, + Municipal, + ParcelGroup + ] + for sub_type in sub_types: + self.__sanitize_parcel_sub_type(sub_type) + self._break_line() \ No newline at end of file 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/migrations/0006_auto_20220411_0835.py b/konova/migrations/0006_auto_20220411_0835.py new file mode 100644 index 00000000..bf74643c --- /dev/null +++ b/konova/migrations/0006_auto_20220411_0835.py @@ -0,0 +1,71 @@ +# Generated by Django 3.1.3 on 2022-04-11 06:35 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('konova', '0005_auto_20220216_0856'), + ] + + operations = [ + migrations.CreateModel( + name='Municipal', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('key', models.IntegerField(blank=True, help_text='Represents Gemeindeschlüssel', null=True)), + ('name', models.CharField(blank=True, help_text='Gemeinde', max_length=1000, null=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.RenameField( + model_name='district', + old_name='krs', + new_name='name', + ), + migrations.RemoveField( + model_name='district', + name='gmnd', + ), + migrations.RemoveField( + model_name='parcel', + name='gmrkng', + ), + migrations.AddField( + model_name='district', + name='key', + field=models.IntegerField(blank=True, help_text='Represents Kreisschlüssel', null=True), + ), + migrations.CreateModel( + name='ParcelGroup', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('key', models.IntegerField(blank=True, help_text='Represents Gemarkungsschlüssel', null=True)), + ('name', models.CharField(blank=True, help_text='Gemarkung', max_length=1000, null=True)), + ('municipal', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='konova.municipal')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='municipal', + name='district', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='konova.district'), + ), + migrations.AddField( + model_name='parcel', + name='municipal', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parcels', to='konova.municipal'), + ), + migrations.AddField( + model_name='parcel', + name='parcel_group', + field=models.ForeignKey(blank=True, help_text='Gemarkung', null=True, on_delete=django.db.models.deletion.SET_NULL, to='konova.parcelgroup'), + ), + ] diff --git a/konova/migrations/0007_auto_20220411_0848.py b/konova/migrations/0007_auto_20220411_0848.py new file mode 100644 index 00000000..fcc2b45b --- /dev/null +++ b/konova/migrations/0007_auto_20220411_0848.py @@ -0,0 +1,28 @@ +# Generated by Django 3.1.3 on 2022-04-11 06:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('konova', '0006_auto_20220411_0835'), + ] + + operations = [ + migrations.AlterField( + model_name='district', + name='key', + field=models.CharField(blank=True, help_text='Represents Kreisschlüssel', max_length=255, null=True), + ), + migrations.AlterField( + model_name='municipal', + name='key', + field=models.CharField(blank=True, help_text='Represents Gemeindeschlüssel', max_length=255, null=True), + ), + migrations.AlterField( + model_name='parcelgroup', + name='key', + field=models.CharField(blank=True, help_text='Represents Gemarkungsschlüssel', max_length=255, null=True), + ), + ] diff --git a/konova/migrations/0008_auto_20220411_0914.py b/konova/migrations/0008_auto_20220411_0914.py new file mode 100644 index 00000000..c56b6215 --- /dev/null +++ b/konova/migrations/0008_auto_20220411_0914.py @@ -0,0 +1,48 @@ +# Generated by Django 3.1.3 on 2022-04-11 07:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('konova', '0007_auto_20220411_0848'), + ] + + operations = [ + migrations.AlterField( + model_name='municipal', + name='key', + field=models.CharField(blank=True, help_text='Represents Kreisschlüssel', max_length=255, null=True), + ), + migrations.AlterField( + model_name='municipal', + name='name', + field=models.CharField(blank=True, help_text='Kreis', max_length=1000, null=True), + ), + migrations.AlterField( + model_name='parcel', + name='flr', + field=models.IntegerField(blank=True, help_text='Flur', null=True), + ), + migrations.AlterField( + model_name='parcel', + name='flrstck_nnr', + field=models.IntegerField(blank=True, help_text='Flurstücksnenner', null=True), + ), + migrations.AlterField( + model_name='parcel', + name='flrstck_zhlr', + field=models.IntegerField(blank=True, help_text='Flurstückszähler', null=True), + ), + migrations.AlterField( + model_name='parcelgroup', + name='key', + field=models.CharField(blank=True, help_text='Represents Kreisschlüssel', max_length=255, null=True), + ), + migrations.AlterField( + model_name='parcelgroup', + name='name', + field=models.CharField(blank=True, help_text='Kreis', max_length=1000, null=True), + ), + ] diff --git a/konova/migrations/0009_auto_20220411_1004.py b/konova/migrations/0009_auto_20220411_1004.py new file mode 100644 index 00000000..d0679bf3 --- /dev/null +++ b/konova/migrations/0009_auto_20220411_1004.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.3 on 2022-04-11 08:04 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('konova', '0008_auto_20220411_0914'), + ] + + operations = [ + migrations.AlterField( + model_name='parcel', + name='parcel_group', + field=models.ForeignKey(blank=True, help_text='Gemarkung', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parcels', to='konova.parcelgroup'), + ), + ] diff --git a/konova/models/geometry.py b/konova/models/geometry.py index bec89c39..a71a2afa 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() @@ -99,7 +102,7 @@ class Geometry(BaseResource): Returns: """ - from konova.models import Parcel, District, ParcelIntersection + from konova.models import Parcel, District, ParcelIntersection, Municipal, ParcelGroup parcel_fetcher = ParcelWFSFetcher( geometry_id=self.id, ) @@ -115,16 +118,28 @@ class Geometry(BaseResource): # 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 ", "") + district = District.objects.get_or_create( + key=fetched_parcel["ave:kreisschl"], + name=fetched_parcel["ave:kreis"], + )[0] + municipal = Municipal.objects.get_or_create( + key=fetched_parcel["ave:gmdschl"], + name=fetched_parcel["ave:gemeinde"], + district=district, + )[0] + parcel_group = ParcelGroup.objects.get_or_create( + key=fetched_parcel["ave:gemaschl"], + name=fetched_parcel["ave:gemarkung"], + municipal=municipal, + )[0] parcel_obj = Parcel.objects.get_or_create( - gmrkng=fetched_parcel["ave:gemarkung"], + district=district, + municipal=municipal, + parcel_group=parcel_group, flr=flr_val, flrstck_nnr=fetched_parcel['ave:flstnrnen'], flrstck_zhlr=fetched_parcel['ave:flstnrzae'], )[0] - district = District.objects.get_or_create( - gmnd=fetched_parcel["ave:gemeinde"], - krs=fetched_parcel["ave:kreis"], - )[0] parcel_obj.district = district parcel_obj.updated_on = _now parcel_obj.save() @@ -155,9 +170,10 @@ class Geometry(BaseResource): parcels = self.parcels.filter( parcelintersection__calculated_on__isnull=False, ).prefetch_related( - "district" + "district", + "municipal", ).order_by( - "gmrkng", + "municipal__name", ) return parcels 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/models/parcel.py b/konova/models/parcel.py index 9c887f1a..f74b7af9 100644 --- a/konova/models/parcel.py +++ b/konova/models/parcel.py @@ -10,8 +10,64 @@ from django.db import models from konova.models import UuidModel +class AdministrativeSpatialReference(models.Model): + key = models.CharField( + max_length=255, + help_text="Represents Kreisschlüssel", + null=True, + blank=True + ) + name = models.CharField( + max_length=1000, + help_text="Kreis", + null=True, + blank=True, + ) + + class Meta: + abstract = True + + def __str__(self): + return f"{self.name} ({self.key})" + + @property + def table_str(self): + return f"{self.name} ({self.key})" + + +class District(UuidModel, AdministrativeSpatialReference): + """ The model District refers to "Kreis" + + """ + pass + + +class Municipal(UuidModel, AdministrativeSpatialReference): + """ The model Municipal refers to "Gemeinde" + + """ + district = models.ForeignKey( + District, + on_delete=models.SET_NULL, + null=True, + blank=True, + ) + + +class ParcelGroup(UuidModel, AdministrativeSpatialReference): + """ The model ParcelGroup refers to "Gemarkung", which is defined as a loose group of parcels + + """ + municipal = models.ForeignKey( + Municipal, + on_delete=models.SET_NULL, + null=True, + blank=True, + ) + + class Parcel(UuidModel): - """ The Parcel model holds administrative data on the covered properties. + """ The Parcel model holds administrative data on covered properties. Due to the unique but relevant naming of the administrative data, we have to use these namings as field names in german. Any try to translate them to English result in strange or insufficient translations. @@ -24,59 +80,34 @@ class Parcel(UuidModel): """ geometries = models.ManyToManyField("konova.Geometry", blank=True, related_name="parcels", through='ParcelIntersection') district = models.ForeignKey("konova.District", on_delete=models.SET_NULL, null=True, blank=True, related_name="parcels") - gmrkng = models.CharField( - max_length=1000, + municipal = models.ForeignKey("konova.Municipal", on_delete=models.SET_NULL, null=True, blank=True, related_name="parcels") + parcel_group = models.ForeignKey( + "konova.ParcelGroup", + on_delete=models.SET_NULL, help_text="Gemarkung", null=True, blank=True, + related_name="parcels" ) - flrstck_nnr = models.CharField( - max_length=1000, + flr = models.IntegerField( + help_text="Flur", + null=True, + blank=True, + ) + flrstck_nnr = models.IntegerField( help_text="Flurstücksnenner", null=True, blank=True, ) - flrstck_zhlr = models.CharField( - max_length=1000, + flrstck_zhlr = models.IntegerField( help_text="Flurstückszähler", null=True, blank=True, ) - flr = models.CharField( - max_length=1000, - help_text="Flur", - null=True, - blank=True, - ) updated_on = models.DateTimeField(auto_now_add=True) def __str__(self): - return f"{self.gmrkng} | {self.flr} | {self.flrstck_zhlr} | {self.flrstck_nnr}" - - -class District(UuidModel): - """ The model District holds more coarse information, such as Kreis, Verbandsgemeinde and Gemeinde. - - There might be the case that a geometry lies on a hundred Parcel entries but only on one District entry. - Therefore a geometry can have a lot of relations to Parcel entries but only a few or only a single one to one - District. - - """ - gmnd = models.CharField( - max_length=1000, - help_text="Gemeinde", - null=True, - blank=True, - ) - krs = models.CharField( - max_length=1000, - help_text="Kreis", - null=True, - blank=True, - ) - - def __str__(self): - return f"{self.gmnd} | {self.krs}" + return f"{self.parcel_group} | {self.flr} | {self.flrstck_zhlr} | {self.flrstck_nnr}" class ParcelIntersection(UuidModel): diff --git a/konova/sub_settings/django_settings.py b/konova/sub_settings/django_settings.py index 7a8f9067..e5de97a0 100644 --- a/konova/sub_settings/django_settings.py +++ b/konova/sub_settings/django_settings.py @@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/3.1/ref/settings/ """ import os from django.utils.translation import gettext_lazy as _ +from django.conf.locale.de import formats as de_formats # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = os.path.dirname( @@ -162,9 +163,15 @@ LANGUAGES = [ USE_THOUSAND_SEPARATOR = True +# Regular python relevant date/datetime formatting DEFAULT_DATE_TIME_FORMAT = '%d.%m.%Y %H:%M:%S' DEFAULT_DATE_FORMAT = '%d.%m.%Y' +# Template relevant date/datetime formatting +# See the Note on here: https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#date +de_formats.DATETIME_FORMAT = "d.m.Y, H:i" +de_formats.DATE_FORMAT = "d.m.Y" + TIME_ZONE = 'Europe/Berlin' USE_I18N = True 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/parcel_table.html b/konova/templates/konova/includes/parcel_table.html index 68904894..76503572 100644 --- a/konova/templates/konova/includes/parcel_table.html +++ b/konova/templates/konova/includes/parcel_table.html @@ -1,15 +1,36 @@ -{% load i18n %} +{% load i18n l10n %}
{% if parcels|length == 0 %}
{% trans 'Parcels can not be calculated, since no geometry is given.' %}
{% else %} - +
- - + + + + + + + + {% for municipal in municipals %} + + + + + + + {% endfor %} + + +
{% trans 'Kreis' %}{% trans 'Gemarkung' %}{% trans 'Municipal' %}{% trans 'Municipal key' %}{% trans 'District' %}{% trans 'District key' %}
{{municipal.name}}{{municipal.key|unlocalize}}{{municipal.district.name}}{{municipal.district.key|unlocalize}}
+ + + + + @@ -18,11 +39,11 @@ {% for parcel in parcels %} - - - - - + + + + + {% endfor %} diff --git a/konova/templates/konova/includes/parcels.html b/konova/templates/konova/includes/parcels.html index 952fde11..a8a882ed 100644 --- a/konova/templates/konova/includes/parcels.html +++ b/konova/templates/konova/includes/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/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/views.py b/konova/views.py index db967e85..2fd5ff91 100644 --- a/konova/views.py +++ b/konova/views.py @@ -17,7 +17,7 @@ from compensation.models import Compensation, EcoAccount from intervention.models import Intervention from konova.contexts import BaseContext from konova.decorators import any_group_check -from konova.models import Deadline, Geometry +from konova.models import Deadline, Geometry, Municipal from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER from news.models import ServerMessage from konova.settings import SSO_SERVER_BASE @@ -130,8 +130,12 @@ def get_geom_parcels(request: HttpRequest, id: str): status_code = 200 if parcels_available or no_geometry_given: + 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) context = { "parcels": parcels, + "municipals": municipals, } html = render_to_string(template, context, request) return HttpResponse(html, status=status_code) diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index b3bf61fd..9626e763 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 e8786ab9..2a78deb7 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -15,18 +15,18 @@ #: konova/filters/mixins.py:107 konova/filters/mixins.py:108 #: konova/filters/mixins.py:120 konova/filters/mixins.py:121 #: konova/filters/mixins.py:134 konova/filters/mixins.py:135 -#: konova/filters/mixins.py:270 konova/filters/mixins.py:316 -#: konova/filters/mixins.py:354 konova/filters/mixins.py:355 -#: konova/filters/mixins.py:386 konova/filters/mixins.py:387 -#: 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/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: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-03-03 12:08+0100\n" +"POT-Creation-Date: 2022-04-19 13:28+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -52,7 +52,7 @@ msgstr "Bis" #: intervention/forms/forms.py:102 #: intervention/templates/intervention/detail/view.html:56 #: intervention/templates/intervention/report/report.html:37 -#: intervention/utils/quality.py:49 konova/filters/mixins.py:396 +#: intervention/utils/quality.py:49 konova/filters/mixins.py:403 msgid "Conservation office" msgstr "Eintragungsstelle" @@ -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" @@ -1016,10 +1016,10 @@ msgstr "Verzeichnet am" msgid "Last modified" msgstr "Zuletzt bearbeitet" -#: compensation/templates/compensation/detail/compensation/view.html:100 -#: compensation/templates/compensation/detail/eco_account/view.html:83 -#: ema/templates/ema/detail/view.html:76 -#: intervention/templates/intervention/detail/view.html:116 +#: compensation/templates/compensation/detail/compensation/view.html:106 +#: compensation/templates/compensation/detail/eco_account/view.html:89 +#: ema/templates/ema/detail/view.html:75 +#: intervention/templates/intervention/detail/view.html:122 msgid "Shared with" msgstr "Freigegeben für" @@ -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" @@ -1286,7 +1272,7 @@ msgstr "Mehrfachauswahl möglich" #: intervention/forms/forms.py:86 #: intervention/templates/intervention/detail/view.html:48 #: intervention/templates/intervention/report/report.html:29 -#: intervention/utils/quality.py:46 konova/filters/mixins.py:364 +#: intervention/utils/quality.py:46 konova/filters/mixins.py:371 msgid "Registration office" msgstr "Zulassungsbehörde" @@ -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,6 +1582,7 @@ msgid "Search for file number" msgstr "Nach Aktenzeichen suchen" #: konova/filters/mixins.py:85 +#: konova/templates/konova/includes/parcel_table.html:13 msgid "District" msgstr "Kreis" @@ -1600,7 +1595,7 @@ msgid "Search for parcel gmrkng" msgstr "Nach Gemarkung suchen" #: konova/filters/mixins.py:111 -#: konova/templates/konova/includes/parcel_table.html:13 +#: konova/templates/konova/includes/parcel_table.html:34 msgid "Parcel" msgstr "Flur" @@ -1609,7 +1604,7 @@ msgid "Search for parcel" msgstr "Nach Flur suchen" #: konova/filters/mixins.py:124 -#: konova/templates/konova/includes/parcel_table.html:14 +#: konova/templates/konova/includes/parcel_table.html:35 msgid "Parcel counter" msgstr "Flurstückzähler" @@ -1618,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:15 +#: konova/templates/konova/includes/parcel_table.html:36 msgid "Parcel number" msgstr "Flurstücknenner" @@ -1626,19 +1621,19 @@ msgstr "Flurstücknenner" msgid "Search for parcel number" msgstr "Nach Flurstücknenner suchen" -#: konova/filters/mixins.py:269 +#: konova/filters/mixins.py:276 msgid "Show unshared" msgstr "Nicht freigegebene anzeigen" -#: konova/filters/mixins.py:315 +#: konova/filters/mixins.py:322 msgid "Show recorded" msgstr "Verzeichnete anzeigen" -#: konova/filters/mixins.py:365 +#: konova/filters/mixins.py:372 msgid "Search for registration office" msgstr "Nach Zulassungsbehörde suchen" -#: konova/filters/mixins.py:397 +#: konova/filters/mixins.py:404 msgid "Search for conservation office" msgstr "Nch Eintragungsstelle suchen" @@ -1646,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." @@ -1745,11 +1740,11 @@ msgstr "Kontrolle am" msgid "Other" msgstr "Sonstige" -#: konova/sub_settings/django_settings.py:159 +#: konova/sub_settings/django_settings.py:160 msgid "German" msgstr "" -#: konova/sub_settings/django_settings.py:160 +#: konova/sub_settings/django_settings.py:161 msgid "English" msgstr "" @@ -1760,21 +1755,29 @@ msgstr "" "wurde." #: konova/templates/konova/includes/parcel_table.html:11 -msgid "Kreis" -msgstr "Kreis" +msgid "Municipal" +msgstr "Gemeinde" #: konova/templates/konova/includes/parcel_table.html:12 -msgid "Gemarkung" +msgid "Municipal key" +msgstr "Gemeindeschlüssel" + +#: konova/templates/konova/includes/parcel_table.html:14 +msgid "District key" +msgstr "Kreisschlüssel" + +#: konova/templates/konova/includes/parcel_table.html:32 +msgid "Parcel group" msgstr "Gemarkung" +#: konova/templates/konova/includes/parcel_table.html:33 +msgid "Parcel group key" +msgstr "Gemarkungsschlüssel" + #: konova/templates/konova/includes/parcels.html:7 msgid "Spatial reference" msgstr "Raumreferenz" -#: konova/templates/konova/includes/parcels.html:11 -msgid "Loading..." -msgstr "Lade..." - #: konova/templates/konova/includes/quickstart/compensations.html:20 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:20 #: konova/templates/konova/includes/quickstart/interventions.html:20 @@ -1793,6 +1796,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" @@ -1886,11 +1897,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 " @@ -1900,15 +1918,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." @@ -1916,119 +1934,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" @@ -2355,6 +2373,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." @@ -2435,13 +2472,16 @@ msgstr "Daten nicht veröffentlicht" msgid "" "\n" " The data you want to see is not recorded and might still be work " -"in progress. Please come back another time.\n" +"in progress or the legal binding date has\n" +" not been reached yet. We can not publish this report as long as " +"revocations could occur.\n" +" Please come back later.\n" " " msgstr "" "\n" -" Diese Daten sind noch nicht veröffentlicht und 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 @@ -2492,11 +2532,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" @@ -2540,11 +2580,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" @@ -2606,22 +2650,22 @@ msgid "Notification settings" msgstr "Benachrichtigungen" #: user/templates/user/index.html:58 -msgid "See or edit your API token" -msgstr "API token einsehen oder neu generieren" - -#: user/templates/user/index.html:61 -msgid "API" -msgstr "" - -#: user/templates/user/index.html:66 msgid "Manage teams" msgstr "" -#: user/templates/user/index.html:69 user/templates/user/team/index.html:18 +#: user/templates/user/index.html:61 user/templates/user/team/index.html:18 #: user/views.py:167 msgid "Teams" msgstr "" +#: user/templates/user/index.html:66 +msgid "See or edit your API token" +msgstr "API token einsehen oder neu generieren" + +#: user/templates/user/index.html:69 +msgid "API" +msgstr "" + #: user/templates/user/team/index.html:20 msgid "Add new team" msgstr "Neues Team hinzufügen" @@ -2630,7 +2674,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" @@ -2690,6 +2734,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 @@ -4189,5 +4241,14 @@ msgstr "" msgid "Unable to connect to qpid with SASL mechanism %s" msgstr "" +#~ msgid "Kreis" +#~ msgstr "Kreis" + +#~ msgid "Gemarkung" +#~ msgstr "Gemarkung" + +#~ msgid "Loading..." +#~ msgstr "Lade..." + #~ msgid "Who handles the eco-account" #~ msgstr "Wer für die Herrichtung des Ökokontos verantwortlich ist" 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/templates/report/unavailable.html b/templates/report/unavailable.html index 87b1ecb9..b3b22948 100644 --- a/templates/report/unavailable.html +++ b/templates/report/unavailable.html @@ -7,7 +7,9 @@

{% blocktrans %} - The data you want to see is not recorded and might still be work in progress. Please come back another time. + The data you want to see is not recorded and might still be work in progress or the legal binding date has + not been reached yet. We can not publish this report as long as revocations could occur. + Please come back later. {% endblocktrans %}

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/models/user_action.py b/user/models/user_action.py index 14d8f41c..d797bb2c 100644 --- a/user/models/user_action.py +++ b/user/models/user_action.py @@ -10,7 +10,7 @@ import uuid from django.db import models from django.utils.translation import gettext_lazy as _ -from konova.sub_settings.django_settings import DEFAULT_DATE_FORMAT, DEFAULT_DATE_TIME_FORMAT +from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT class UserAction(models.TextChoices): diff --git a/user/templates/user/index.html b/user/templates/user/index.html index c31de94f..f8fd616d 100644 --- a/user/templates/user/index.html +++ b/user/templates/user/index.html @@ -54,14 +54,6 @@
- +
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 %}
{% trans 'Parcel group' %}{% trans 'Parcel group key' %} {% trans 'Parcel' %} {% trans 'Parcel counter' %} {% trans 'Parcel number' %}
{{parcel.district.krs|default_if_none:"-"}}{{parcel.gmrkng|default_if_none:"-"}}{{parcel.flr|default_if_none:"-"}}{{parcel.flrstck_zhlr|default_if_none:"-"}}{{parcel.flrstck_nnr|default_if_none:"-"}}{{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}}
+ {% if team.admin == user %}