diff --git a/compensation/admin.py b/compensation/admin.py index 4d148582..ced9bfe7 100644 --- a/compensation/admin.py +++ b/compensation/admin.py @@ -2,6 +2,7 @@ from django.contrib import admin from compensation.models import Compensation, CompensationAction, CompensationState, Payment, \ EcoAccountDeduction, EcoAccount +from konova.admin import BaseObjectAdmin class CompensationStateAdmin(admin.ModelAdmin): @@ -22,7 +23,7 @@ class CompensationActionAdmin(admin.ModelAdmin): ] -class CompensationAdmin(admin.ModelAdmin): +class CompensationAdmin(BaseObjectAdmin): list_display = [ "id", "identifier", diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py index ccf511a1..703f7cd4 100644 --- a/compensation/models/compensation.py +++ b/compensation/models/compensation.py @@ -7,6 +7,7 @@ Created on: 16.11.21 """ import shutil +from django.contrib import messages from django.contrib.auth.models import User from django.db import models, transaction from django.db.models import QuerySet, Sum @@ -16,12 +17,14 @@ from django.utils.translation import gettext_lazy as _ from compensation.managers import CompensationManager from compensation.models import CompensationState, CompensationAction from compensation.utils.quality import CompensationQualityChecker -from konova.models import BaseObject, AbstractDocument, Geometry, Deadline, generate_document_file_upload_path +from konova.models import BaseObject, AbstractDocument, Deadline, generate_document_file_upload_path, \ + GeoReferencedMixin from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE +from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION from user.models import UserActionLogEntry -class AbstractCompensation(BaseObject): +class AbstractCompensation(BaseObject, GeoReferencedMixin): """ Abstract compensation model which holds basic attributes, shared by subclasses like the regular Compensation, EMA or EcoAccount. @@ -41,8 +44,6 @@ class AbstractCompensation(BaseObject): deadlines = models.ManyToManyField("konova.Deadline", blank=True, related_name="+") - geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL) - class Meta: abstract = True @@ -156,6 +157,22 @@ class AbstractCompensation(BaseObject): checker.run_check() return checker + def set_status_messages(self, request: HttpRequest): + """ Setter for different information that need to be rendered + + Adds messages to the given HttpRequest + + Args: + request (HttpRequest): The incoming request + + Returns: + request (HttpRequest): The modified request + """ + if not self.is_shared_with(request.user): + messages.info(request, DATA_UNSHARED_EXPLANATION) + request = self._set_geometry_conflict_message(request) + return request + class CEFMixin(models.Model): """ Provides CEF flag as Mixin diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py index 678504e3..b5efc984 100644 --- a/compensation/views/compensation.py +++ b/compensation/views/compensation.py @@ -184,8 +184,7 @@ def detail_view(request: HttpRequest, id: str): sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0 diff_states = abs(sum_before_states - sum_after_states) - if not is_data_shared: - messages.info(request, DATA_UNSHARED_EXPLANATION) + request = comp.set_status_messages(request) context = { "obj": comp, diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py index 7b7d1eae..7b4c27e2 100644 --- a/compensation/views/eco_account.py +++ b/compensation/views/eco_account.py @@ -202,8 +202,7 @@ def detail_view(request: HttpRequest, id: str): ) actions = acc.actions.all() - if not is_data_shared: - messages.info(request, DATA_UNSHARED_EXPLANATION) + request = acc.set_status_messages(request) context = { "obj": acc, diff --git a/ema/models/ema.py b/ema/models/ema.py index 8c55bd4b..8bc4ac65 100644 --- a/ema/models/ema.py +++ b/ema/models/ema.py @@ -7,15 +7,17 @@ Created on: 15.11.21 """ import shutil -from django.contrib.auth.models import User +from django.contrib import messages from django.db import models from django.db.models import QuerySet +from django.http import HttpRequest from compensation.models import AbstractCompensation from ema.managers import EmaManager from ema.utils.quality import EmaQualityChecker from konova.models import AbstractDocument, generate_document_file_upload_path, RecordableObjectMixin, ShareableObjectMixin from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE +from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin): @@ -91,6 +93,22 @@ class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin): ) return docs + def set_status_messages(self, request: HttpRequest): + """ Setter for different information that need to be rendered + + Adds messages to the given HttpRequest + + Args: + request (HttpRequest): The incoming request + + Returns: + request (HttpRequest): The modified request + """ + if not self.is_shared_with(request.user): + messages.info(request, DATA_UNSHARED_EXPLANATION) + self._set_geometry_conflict_message(request) + return request + class EmaDocument(AbstractDocument): """ diff --git a/ema/views.py b/ema/views.py index 02bf53e1..dc2fa49a 100644 --- a/ema/views.py +++ b/ema/views.py @@ -138,8 +138,7 @@ def detail_view(request: HttpRequest, id: str): sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0 diff_states = abs(sum_before_states - sum_after_states) - if not is_data_shared: - messages.info(request, DATA_UNSHARED_EXPLANATION) + ema.set_status_messages(request) context = { "obj": ema, diff --git a/intervention/admin.py b/intervention/admin.py index f65cb332..bae89830 100644 --- a/intervention/admin.py +++ b/intervention/admin.py @@ -1,10 +1,10 @@ from django.contrib import admin from intervention.models import Intervention, Responsibility, Legal, Revocation, InterventionDocument -from konova.admin import AbstractDocumentAdmin +from konova.admin import AbstractDocumentAdmin, BaseObjectAdmin -class InterventionAdmin(admin.ModelAdmin): +class InterventionAdmin(BaseObjectAdmin): list_display = [ "id", "identifier", @@ -13,9 +13,11 @@ class InterventionAdmin(admin.ModelAdmin): "deleted", ] + class InterventionDocumentAdmin(AbstractDocumentAdmin): pass + class ResponsibilityAdmin(admin.ModelAdmin): list_display = [ "id", diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py index fd1a1c2a..a31eb9a3 100644 --- a/intervention/models/intervention.py +++ b/intervention/models/intervention.py @@ -7,6 +7,7 @@ Created on: 15.11.21 """ import shutil +from django.contrib import messages from django.contrib.auth.models import User from django.db import models, transaction from django.db.models import QuerySet @@ -18,13 +19,15 @@ from intervention.models.legal import Legal from intervention.models.responsibility import Responsibility from intervention.models.revocation import RevocationDocument, Revocation from intervention.utils.quality import InterventionQualityChecker -from konova.models import generate_document_file_upload_path, AbstractDocument, Geometry, BaseObject, ShareableObjectMixin, \ - RecordableObjectMixin, CheckableObjectMixin +from konova.models import generate_document_file_upload_path, AbstractDocument, BaseObject, \ + ShareableObjectMixin, \ + RecordableObjectMixin, CheckableObjectMixin, GeoReferencedMixin from konova.settings import LANIS_LINK_TEMPLATE, LANIS_ZOOM_LUT, DEFAULT_SRID_RLP +from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION from user.models import UserActionLogEntry -class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, CheckableObjectMixin): +class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, CheckableObjectMixin, GeoReferencedMixin): """ Interventions are e.g. construction sites where nature used to be. """ @@ -42,7 +45,6 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec blank=True, help_text="Holds data on legal dates or law" ) - geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL) objects = InterventionManager() @@ -263,6 +265,22 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec if self.checked: self.set_unchecked() + def set_status_messages(self, request: HttpRequest): + """ Setter for different information that need to be rendered + + Adds messages to the given HttpRequest + + Args: + request (HttpRequest): The incoming request + + Returns: + request (HttpRequest): The modified request + """ + if not self.is_shared_with(request.user): + messages.info(request, DATA_UNSHARED_EXPLANATION) + request = self._set_geometry_conflict_message(request) + return request + class InterventionDocument(AbstractDocument): """ diff --git a/intervention/views.py b/intervention/views.py index a3f05f7d..60d75249 100644 --- a/intervention/views.py +++ b/intervention/views.py @@ -255,8 +255,7 @@ def detail_view(request: HttpRequest, id: str): "LANIS_LINK": intervention.get_LANIS_link() } - if not is_data_shared: - messages.info(request, DATA_UNSHARED_EXPLANATION) + request = intervention.set_status_messages(request) context = BaseContext(request, context).context return render(request, template, context) diff --git a/konova/admin.py b/konova/admin.py index c57117af..213e0412 100644 --- a/konova/admin.py +++ b/konova/admin.py @@ -7,7 +7,7 @@ Created on: 22.07.21 """ from django.contrib import admin -from konova.models import Geometry, Deadline +from konova.models import Geometry, Deadline, GeometryConflict class GeometryAdmin(admin.ModelAdmin): @@ -17,6 +17,14 @@ class GeometryAdmin(admin.ModelAdmin): ] +class GeometryConflictAdmin(admin.ModelAdmin): + list_display = [ + "conflicting_geometry", + "affected_geometry", + "detected_on", + ] + + class AbstractDocumentAdmin(admin.ModelAdmin): list_display = [ "id", @@ -35,5 +43,14 @@ class DeadlineAdmin(admin.ModelAdmin): ] +class BaseObjectAdmin(admin.ModelAdmin): + readonly_fields = [ + "modified", + "deleted", + "created", + ] + + admin.site.register(Geometry, GeometryAdmin) +admin.site.register(GeometryConflict, GeometryConflictAdmin) admin.site.register(Deadline, DeadlineAdmin) diff --git a/konova/forms.py b/konova/forms.py index 359b78bc..43c83498 100644 --- a/konova/forms.py +++ b/konova/forms.py @@ -282,11 +282,13 @@ class SimpleGeomForm(BaseForm): """ try: + if self.instance is None or self.instance.geometry is None: + raise LookupError geometry = self.instance.geometry geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID)) geometry.modified = action geometry.save() - except AttributeError: + except LookupError: # No geometry or linked instance holding a geometry exist --> create a new one! geometry = Geometry.objects.create( geom=self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID)), diff --git a/konova/management/commands/sanitize_db.py b/konova/management/commands/sanitize_db.py index de3c249d..c5526517 100644 --- a/konova/management/commands/sanitize_db.py +++ b/konova/management/commands/sanitize_db.py @@ -208,7 +208,7 @@ class Command(BaseKonovaCommand): if num_entries > 0: self._write_error(f"Found {num_entries} geometries not attached to anything. Delete now...") unattached_geometries.delete() - self._write_success("Deadlines deleted.") + self._write_success("Geometries deleted.") else: self._write_success("No unattached geometries found.") self._break_line() diff --git a/konova/models/geometry.py b/konova/models/geometry.py index 736e1b3b..8c155732 100644 --- a/konova/models/geometry.py +++ b/konova/models/geometry.py @@ -6,13 +6,110 @@ Created on: 15.11.21 """ from django.contrib.gis.db.models import MultiPolygonField +from django.db import models +from django.db.models import Q -from konova.models import BaseResource +from konova.models import BaseResource, UuidModel class Geometry(BaseResource): """ - Outsourced geometry model so multiple versions of the same object can refer to the same geometry if it is not changed + Geometry model """ from konova.settings import DEFAULT_SRID - geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID) \ No newline at end of file + geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID) + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + self.check_for_conflicts() + + def check_for_conflicts(self): + """ Checks for new geometry overlaps + + Creates a new GeometryConflict entry for each overlap to another geometry, which has already been there before + + Returns: + + """ + # If no geometry is given or important data is missing, we can not perform any checks + if self.geom is None or (self.created is None and self.modified is None): + return None + + self.recheck_existing_conflicts() + + overlapping_geoms = Geometry.objects.filter( + geom__intersects=self.geom, + ).exclude( + id=self.id + ).distinct() + + for match in overlapping_geoms: + GeometryConflict.objects.get_or_create(conflicting_geometry=self, affected_geometry=match) + + def recheck_existing_conflicts(self): + """ Rechecks GeometryConflict entries + + If a conflict seems to be resolved due to no longer intersection between the two geometries, the entry + will be deleted. + + Returns: + + """ + all_conflicts_as_conflicting = self.conflicts_geometries.all() + still_conflicting_conflicts = all_conflicts_as_conflicting.filter( + affected_geometry__geom__intersects=self.geom + ) + resolved_conflicts = all_conflicts_as_conflicting.exclude(id__in=still_conflicting_conflicts) + resolved_conflicts.delete() + + all_conflicted_by_conflicts = self.conflicted_by_geometries.all() + still_conflicting_conflicts = all_conflicted_by_conflicts.filter( + conflicting_geometry__geom__intersects=self.geom + ) + resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts) + resolved_conflicts.delete() + + + def get_data_objects(self): + """ Getter for all objects which are related to this geometry + + Returns: + objs (list): The list of objects + """ + objs = [] + sets = [ + self.intervention_set, + self.compensation_set, + self.ema_set, + self.ecoaccount_set, + ] + for _set in sets: + set_objs = _set.filter( + deleted=None + ) + objs += set_objs + return objs + + +class GeometryConflict(UuidModel): + """ + Geometry conflicts model + + If a new/edited geometry overlays an existing geometry, there will be a new GeometryConflict on the db + """ + conflicting_geometry = models.ForeignKey( + Geometry, + on_delete=models.CASCADE, + help_text="The geometry which came second", + related_name="conflicts_geometries" + ) + affected_geometry = models.ForeignKey( + Geometry, + on_delete=models.CASCADE, + help_text="The geometry which came first", + related_name="conflicted_by_geometries" + ) + detected_on = models.DateTimeField(auto_now_add=True, null=True) + + def __str__(self): + return f"{self.conflicting_geometry.id} conflicts with {self.affected_geometry.id}" diff --git a/konova/models/object.py b/konova/models/object.py index 5b8f9b72..ded39ad1 100644 --- a/konova/models/object.py +++ b/konova/models/object.py @@ -7,6 +7,7 @@ Created on: 15.11.21 """ import uuid +from abc import abstractmethod from django.contrib import messages from django.contrib.auth.models import User @@ -20,7 +21,7 @@ from ema.settings import EMA_ACCOUNT_IDENTIFIER_LENGTH, EMA_ACCOUNT_IDENTIFIER_T from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_IDENTIFIER_TEMPLATE from konova.utils import generators from konova.utils.generators import generate_random_string -from konova.utils.message_templates import CHECKED_RECORDED_RESET +from konova.utils.message_templates import CHECKED_RECORDED_RESET, GEOMETRY_CONFLICT_WITH_TEMPLATE from user.models import UserActionLogEntry, UserAction @@ -94,6 +95,10 @@ class BaseObject(BaseResource): class Meta: abstract = True + @abstractmethod + def set_status_messages(self, request: HttpRequest): + raise NotImplementedError + def mark_as_deleted(self, user: User): """ Mark an entry as deleted @@ -407,3 +412,29 @@ class ShareableObjectMixin(models.Model): id__in=accessing_users ) self.share_with_list(users) + + +class GeoReferencedMixin(models.Model): + geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL) + + class Meta: + abstract = True + + def _set_geometry_conflict_message(self, request: HttpRequest): + instance_objs = [] + add_message = False + conflicts = self.geometry.conflicts_geometries.all() + for conflict in conflicts: + instance_objs += conflict.affected_geometry.get_data_objects() + add_message = True + conflicts = self.geometry.conflicted_by_geometries.all() + for conflict in conflicts: + instance_objs += conflict.conflicting_geometry.get_data_objects() + add_message = True + + if add_message: + instance_identifiers = [x.identifier for x in instance_objs] + instance_identifiers = ", ".join(instance_identifiers) + message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers) + messages.info(request, message_str) + return request diff --git a/konova/tests/test_geometries.py b/konova/tests/test_geometries.py new file mode 100644 index 00000000..a9a840f5 --- /dev/null +++ b/konova/tests/test_geometries.py @@ -0,0 +1,49 @@ +""" +Author: Michel Peltriaux +Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany +Contact: michel.peltriaux@sgdnord.rlp.de +Created on: 15.12.21 + +""" +from django.contrib.gis.db.models.functions import Translate + +from konova.models import Geometry, GeometryConflict +from konova.tests.test_views import BaseTestCase +from user.models import UserActionLogEntry + + +class GeometryTestCase(BaseTestCase): + @classmethod + def setUpTestData(cls): + super().setUpTestData() + geom = cls.create_dummy_geometry() + action = UserActionLogEntry.get_created_action(cls.superuser) + cls.geom_1 = Geometry.objects.create( + geom=geom, + created=action, + ) + cls.geom_2 = Geometry.objects.create( + geom=geom, + created=action, + ) + + def test_geometry_conflict(self): + """ Tests whether a geometry conflict will be present in case of identical/overlaying geometries and + if the conflict will be resolved if one geometry is edited. + + Returns: + + """ + self.geom_1.check_for_conflicts() + conflict = GeometryConflict.objects.all().first() + self.assertEqual(conflict.conflicting_geometry, self.geom_2) + self.assertEqual(conflict.affected_geometry, self.geom_1) + + # Move geom_2 somewhere else, expect the conflict to be resolved + Geometry.objects.filter(id=self.geom_2.id).update( + geom=Translate('geom', 100000, 100000) + ) + self.geom_2.refresh_from_db() + self.geom_1.check_for_conflicts() + num_conflict = GeometryConflict.objects.all().count() + self.assertEqual(0, num_conflict) diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py index 5b26c460..29f8197c 100644 --- a/konova/utils/message_templates.py +++ b/konova/utils/message_templates.py @@ -25,4 +25,7 @@ CANCEL_ACC_RECORDED_OR_DEDUCTED = _("Action canceled. Eco account is recorded or EDITED_GENERAL_DATA = _("Edited general data") ADDED_COMPENSATION_STATE = _("Added compensation state") ADDED_DEADLINE = _("Added deadline") -ADDED_COMPENSATION_ACTION = _("Added compensation action") \ No newline at end of file +ADDED_COMPENSATION_ACTION = _("Added compensation action") + +# Geometry conflicts +GEOMETRY_CONFLICT_WITH_TEMPLATE = _("Geometry conflict detected with {}") diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index 94f1f426..bd3e82d8 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 fdf4fc98..04aefdf0 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -11,15 +11,15 @@ #: intervention/forms/forms.py:52 intervention/forms/forms.py:154 #: intervention/forms/forms.py:166 intervention/forms/modalForms.py:125 #: intervention/forms/modalForms.py:138 intervention/forms/modalForms.py:151 -#: konova/forms.py:139 konova/forms.py:240 konova/forms.py:306 -#: konova/forms.py:333 konova/forms.py:343 konova/forms.py:356 -#: konova/forms.py:368 konova/forms.py:386 user/forms.py:38 +#: konova/forms.py:139 konova/forms.py:240 konova/forms.py:308 +#: konova/forms.py:335 konova/forms.py:345 konova/forms.py:358 +#: konova/forms.py:370 konova/forms.py:388 user/forms.py:38 #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-12-09 12:36+0100\n" +"POT-Creation-Date: 2021-12-16 09:17+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -329,7 +329,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:332 +#: konova/forms.py:334 msgid "Title" msgstr "Bezeichnung" @@ -356,7 +356,7 @@ msgstr "Kompensation XY; Flur ABC" #: intervention/templates/intervention/detail/includes/documents.html:31 #: intervention/templates/intervention/detail/includes/payments.html:34 #: intervention/templates/intervention/detail/includes/revocation.html:38 -#: konova/forms.py:367 konova/templates/konova/comment_card.html:16 +#: konova/forms.py:369 konova/templates/konova/comment_card.html:16 msgid "Comment" msgstr "Kommentar" @@ -472,7 +472,7 @@ msgstr "Zahlung wird an diesem Datum erwartet" #: compensation/forms/modalForms.py:62 compensation/forms/modalForms.py:239 #: compensation/forms/modalForms.py:317 intervention/forms/modalForms.py:152 -#: konova/forms.py:369 +#: konova/forms.py:371 msgid "Additional comment, maximum {} letters" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" @@ -614,7 +614,7 @@ msgstr "" msgid "Pieces" msgstr "Stück" -#: compensation/models/compensation.py:62 konova/utils/message_templates.py:27 +#: compensation/models/compensation.py:63 konova/utils/message_templates.py:27 msgid "Added deadline" msgstr "Frist/Termin hinzugefügt" @@ -793,7 +793,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:385 +#: konova/forms.py:387 msgid "Add new document" msgstr "Neues Dokument hinzufügen" @@ -1056,42 +1056,42 @@ msgstr "Kompensation {} hinzugefügt" msgid "Compensation {} edited" msgstr "Kompensation {} bearbeitet" -#: compensation/views/compensation.py:229 compensation/views/eco_account.py:308 -#: ema/views.py:182 intervention/views.py:475 +#: compensation/views/compensation.py:228 compensation/views/eco_account.py:307 +#: ema/views.py:181 intervention/views.py:474 msgid "Log" msgstr "Log" -#: compensation/views/compensation.py:252 +#: compensation/views/compensation.py:251 msgid "Compensation removed" msgstr "Kompensation entfernt" -#: compensation/views/compensation.py:273 compensation/views/eco_account.py:460 -#: ema/views.py:349 intervention/views.py:129 +#: compensation/views/compensation.py:272 compensation/views/eco_account.py:459 +#: ema/views.py:348 intervention/views.py:129 msgid "Document added" msgstr "Dokument hinzugefügt" -#: compensation/views/compensation.py:342 compensation/views/eco_account.py:354 -#: ema/views.py:287 +#: compensation/views/compensation.py:341 compensation/views/eco_account.py:353 +#: ema/views.py:286 msgid "State added" msgstr "Zustand hinzugefügt" -#: compensation/views/compensation.py:363 compensation/views/eco_account.py:375 -#: ema/views.py:308 +#: compensation/views/compensation.py:362 compensation/views/eco_account.py:374 +#: ema/views.py:307 msgid "Action added" msgstr "Maßnahme hinzugefügt" -#: compensation/views/compensation.py:384 compensation/views/eco_account.py:440 -#: ema/views.py:329 +#: compensation/views/compensation.py:383 compensation/views/eco_account.py:439 +#: ema/views.py:328 msgid "Deadline added" msgstr "Frist/Termin hinzugefügt" -#: compensation/views/compensation.py:406 compensation/views/eco_account.py:397 -#: ema/views.py:419 +#: compensation/views/compensation.py:405 compensation/views/eco_account.py:396 +#: ema/views.py:418 msgid "State removed" msgstr "Zustand gelöscht" -#: compensation/views/compensation.py:428 compensation/views/eco_account.py:419 -#: ema/views.py:441 +#: compensation/views/compensation.py:427 compensation/views/eco_account.py:418 +#: ema/views.py:440 msgid "Action removed" msgstr "Maßnahme entfernt" @@ -1103,45 +1103,45 @@ msgstr "Ökokonto {} hinzugefügt" msgid "Eco-Account {} edited" msgstr "Ökokonto {} bearbeitet" -#: compensation/views/eco_account.py:256 +#: compensation/views/eco_account.py:255 msgid "Eco-account removed" msgstr "Ökokonto entfernt" -#: compensation/views/eco_account.py:284 +#: compensation/views/eco_account.py:283 msgid "Deduction removed" msgstr "Abbuchung entfernt" -#: compensation/views/eco_account.py:329 ema/views.py:262 -#: intervention/views.py:517 +#: compensation/views/eco_account.py:328 ema/views.py:261 +#: intervention/views.py:516 msgid "{} unrecorded" msgstr "{} entzeichnet" -#: compensation/views/eco_account.py:329 ema/views.py:262 -#: intervention/views.py:517 +#: compensation/views/eco_account.py:328 ema/views.py:261 +#: intervention/views.py:516 msgid "{} recorded" msgstr "{} verzeichnet" -#: compensation/views/eco_account.py:530 intervention/views.py:498 +#: compensation/views/eco_account.py:529 intervention/views.py:497 msgid "Deduction added" msgstr "Abbuchung hinzugefügt" -#: compensation/views/eco_account.py:613 ema/views.py:517 -#: intervention/views.py:373 +#: compensation/views/eco_account.py:612 ema/views.py:516 +#: intervention/views.py:372 msgid "{} has already been shared with you" msgstr "{} wurde bereits für Sie freigegeben" -#: compensation/views/eco_account.py:618 ema/views.py:522 -#: intervention/views.py:378 +#: compensation/views/eco_account.py:617 ema/views.py:521 +#: intervention/views.py:377 msgid "{} has been shared with you" msgstr "{} ist nun für Sie freigegeben" -#: compensation/views/eco_account.py:625 ema/views.py:529 -#: intervention/views.py:385 +#: compensation/views/eco_account.py:624 ema/views.py:528 +#: intervention/views.py:384 msgid "Share link invalid" msgstr "Freigabelink ungültig" -#: compensation/views/eco_account.py:648 ema/views.py:552 -#: intervention/views.py:408 +#: compensation/views/eco_account.py:647 ema/views.py:551 +#: intervention/views.py:407 msgid "Share settings updated" msgstr "Freigabe Einstellungen aktualisiert" @@ -1185,11 +1185,11 @@ msgstr "Ersatzzahlungsmaßnahme" msgid "EMA {} added" msgstr "EMA {} hinzugefügt" -#: ema/views.py:211 +#: ema/views.py:210 msgid "EMA {} edited" msgstr "EMA {} bearbeitet" -#: ema/views.py:243 +#: ema/views.py:242 msgid "EMA removed" msgstr "EMA entfernt" @@ -1333,7 +1333,7 @@ msgstr "Kompensationen und Zahlungen geprüft" msgid "Run check" msgstr "Prüfung vornehmen" -#: intervention/forms/modalForms.py:196 konova/forms.py:451 +#: intervention/forms/modalForms.py:196 konova/forms.py:453 msgid "" "I, {} {}, confirm that all necessary control steps have been performed by " "myself." @@ -1476,27 +1476,27 @@ msgstr "Eingriff {} hinzugefügt" msgid "This intervention has {} revocations" msgstr "Dem Eingriff liegen {} Widersprüche vor" -#: intervention/views.py:291 +#: intervention/views.py:290 msgid "Intervention {} edited" msgstr "Eingriff {} bearbeitet" -#: intervention/views.py:326 +#: intervention/views.py:325 msgid "{} removed" msgstr "{} entfernt" -#: intervention/views.py:347 +#: intervention/views.py:346 msgid "Revocation removed" msgstr "Widerspruch entfernt" -#: intervention/views.py:429 +#: intervention/views.py:428 msgid "Check performed" msgstr "Prüfung durchgeführt" -#: intervention/views.py:451 +#: intervention/views.py:450 msgid "Revocation added" msgstr "Widerspruch hinzugefügt" -#: intervention/views.py:522 +#: intervention/views.py:521 msgid "There are errors on this intervention:" msgstr "Es liegen Fehler in diesem Eingriff vor:" @@ -1525,11 +1525,11 @@ msgstr "Speichern" msgid "Not editable" msgstr "Nicht editierbar" -#: konova/forms.py:138 konova/forms.py:305 +#: konova/forms.py:138 konova/forms.py:307 msgid "Confirm" msgstr "Bestätige" -#: konova/forms.py:150 konova/forms.py:314 +#: konova/forms.py:150 konova/forms.py:316 msgid "Remove" msgstr "Löschen" @@ -1542,56 +1542,56 @@ msgstr "Sie sind dabei {} {} zu löschen" msgid "Geometry" msgstr "Geometrie" -#: konova/forms.py:315 +#: konova/forms.py:317 msgid "Are you sure?" msgstr "Sind Sie sicher?" -#: konova/forms.py:342 +#: konova/forms.py:344 msgid "Created on" msgstr "Erstellt" -#: konova/forms.py:344 +#: konova/forms.py:346 msgid "When has this file been created? Important for photos." msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?" -#: konova/forms.py:355 +#: konova/forms.py:357 #: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231 msgid "File" msgstr "Datei" -#: konova/forms.py:357 +#: konova/forms.py:359 msgid "Allowed formats: pdf, jpg, png. Max size 15 MB." msgstr "Formate: pdf, jpg, png. Maximal 15 MB." -#: konova/forms.py:403 +#: konova/forms.py:405 msgid "Unsupported file type" msgstr "Dateiformat nicht unterstützt" -#: konova/forms.py:410 +#: konova/forms.py:412 msgid "File too large" msgstr "Datei zu groß" -#: konova/forms.py:419 +#: konova/forms.py:421 msgid "Added document" msgstr "Dokument hinzugefügt" -#: konova/forms.py:442 +#: konova/forms.py:444 msgid "Confirm record" msgstr "Verzeichnen bestätigen" -#: konova/forms.py:450 +#: konova/forms.py:452 msgid "Record data" msgstr "Daten verzeichnen" -#: konova/forms.py:457 +#: konova/forms.py:459 msgid "Confirm unrecord" msgstr "Entzeichnen bestätigen" -#: konova/forms.py:458 +#: konova/forms.py:460 msgid "Unrecord data" msgstr "Daten entzeichnen" -#: konova/forms.py:459 +#: konova/forms.py:461 msgid "I, {} {}, confirm that this data must be unrecorded." msgstr "" "Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen." @@ -1726,6 +1726,8 @@ msgid "" "Action canceled. Eco account is recorded or deductions exist. Only " "conservation office member can perform this action." 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:25 msgid "Edited general data" @@ -1739,6 +1741,10 @@ msgstr "Zustand hinzugefügt" msgid "Added compensation action" msgstr "Maßnahme hinzufügen" +#: konova/utils/message_templates.py:31 +msgid "Geometry conflict detected with {}" +msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}" + #: konova/utils/messenger.py:69 msgid "{} checked" msgstr "{} geprüft"