From 7535f008b79ca7a2f9e618c293d0fbeb4e360a35 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Fri, 4 Feb 2022 16:56:08 +0100
Subject: [PATCH 01/31] #86 Logs

* adds log detail support for compensation state and action
---
 compensation/models/action.py       | 11 ++++++++++
 compensation/models/compensation.py |  9 ++++----
 compensation/models/eco_account.py  |  2 +-
 compensation/models/state.py        | 13 +++++++++++
 compensation/views/compensation.py  | 11 +++++-----
 compensation/views/eco_account.py   | 11 +++++-----
 ema/views.py                        | 11 +++++-----
 intervention/forms/modalForms.py    |  2 +-
 intervention/models/intervention.py |  4 +++-
 konova/models/object.py             | 34 +++++++++++++++++++----------
 konova/utils/message_templates.py   | 13 +++++++++--
 11 files changed, 84 insertions(+), 37 deletions(-)

diff --git a/compensation/models/action.py b/compensation/models/action.py
index 087f48be..bd2400e7 100644
--- a/compensation/models/action.py
+++ b/compensation/models/action.py
@@ -12,6 +12,7 @@ from codelist.models import KonovaCode
 from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID
 from compensation.managers import CompensationActionManager
 from konova.models import BaseResource
+from konova.utils.message_templates import COMPENSATION_ACTION_REMOVED
 
 
 class UnitChoices(models.TextChoices):
@@ -75,3 +76,13 @@ class CompensationAction(BaseResource):
             if choice[0] == self.unit:
                 return choice[1]
         return None
+
+    def delete(self, user=None, *args, **kwargs):
+        from compensation.models import Compensation
+        if user:
+            comps = Compensation.objects.filter(
+                actions__id__in=[self.id]
+            ).distinct()
+            for comp in comps:
+                comp.mark_as_edited(user, edit_comment=COMPENSATION_ACTION_REMOVED)
+        super().delete(*args, **kwargs)
\ No newline at end of file
diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py
index f50553f0..ef8e20fe 100644
--- a/compensation/models/compensation.py
+++ b/compensation/models/compensation.py
@@ -21,7 +21,7 @@ from konova.models import BaseObject, AbstractDocument, Deadline, generate_docum
     GeoReferencedMixin
 from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
 from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, COMPENSATION_REMOVED_TEMPLATE, \
-    DOCUMENT_REMOVED_TEMPLATE
+    DOCUMENT_REMOVED_TEMPLATE, COMPENSATION_EDITED_TEMPLATE
 from user.models import UserActionLogEntry
 
 
@@ -61,7 +61,6 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
         user = form.user
         with transaction.atomic():
             created_action = UserActionLogEntry.get_created_action(user)
-            edited_action = UserActionLogEntry.get_edited_action(user, _("Added deadline"))
 
             deadline = Deadline.objects.create(
                 type=form_data["type"],
@@ -70,9 +69,7 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
                 created=created_action,
             )
 
-            self.modified = edited_action
             self.save()
-            self.log.add(edited_action)
             self.deadlines.add(deadline)
             return deadline
 
@@ -332,7 +329,9 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
         Returns:
 
         """
-        return self.intervention.mark_as_edited(user, request, edit_comment, reset_recorded)
+        self.intervention.unrecord(user, request)
+        action = super().mark_as_edited(user, edit_comment)
+        return action
 
     def is_ready_for_publish(self) -> bool:
         """ Not inherited by RecordableObjectMixin
diff --git a/compensation/models/eco_account.py b/compensation/models/eco_account.py
index af660615..895d537f 100644
--- a/compensation/models/eco_account.py
+++ b/compensation/models/eco_account.py
@@ -273,5 +273,5 @@ class EcoAccountDeduction(BaseResource):
     def delete(self, user=None, *args, **kwargs):
         if user is not None:
             self.intervention.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
-            self.account.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED, reset_recorded=False)
+            self.account.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
         super().delete(*args, **kwargs)
\ No newline at end of file
diff --git a/compensation/models/state.py b/compensation/models/state.py
index ce0fc699..02249145 100644
--- a/compensation/models/state.py
+++ b/compensation/models/state.py
@@ -6,11 +6,13 @@ Created on: 16.11.21
 
 """
 from django.db import models
+from django.db.models import Q
 
 from codelist.models import KonovaCode
 from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID
 from compensation.managers import CompensationStateManager
 from konova.models import UuidModel
+from konova.utils.message_templates import COMPENSATION_STATE_REMOVED
 
 
 class CompensationState(UuidModel):
@@ -45,3 +47,14 @@ class CompensationState(UuidModel):
 
     def __str__(self):
         return f"{self.biotope_type} | {self.surface} m²"
+
+    def delete(self, user=None, *args, **kwargs):
+        from compensation.models import Compensation
+        if user:
+            comps = Compensation.objects.filter(
+                Q(before_states__id__in=[self.id]) |
+                Q(after_states__id__in=[self.id])
+            ).distinct()
+            for comp in comps:
+                comp.mark_as_edited(user, edit_comment=COMPENSATION_STATE_REMOVED)
+        super().delete(*args, **kwargs)
diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py
index f6176e03..26e53fd2 100644
--- a/compensation/views/compensation.py
+++ b/compensation/views/compensation.py
@@ -18,7 +18,8 @@ from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 from konova.utils.documents import get_document, remove_document
 from konova.utils.generators import generate_qr_code
 from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DATA_UNSHARED_EXPLANATION, \
-    CHECKED_RECORDED_RESET, COMPENSATION_ADDED_TEMPLATE, COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED
+    CHECKED_RECORDED_RESET, COMPENSATION_ADDED_TEMPLATE, COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, \
+    COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED
 from konova.utils.user_checks import in_group
 
 
@@ -347,7 +348,7 @@ def state_new_view(request: HttpRequest, id: str):
     form = NewStateModalForm(request.POST or None, instance=comp, request=request)
     return form.process_request(
         request,
-        msg_success=_("State added"),
+        msg_success=COMPENSATION_STATE_ADDED,
         redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
     )
 
@@ -369,7 +370,7 @@ def action_new_view(request: HttpRequest, id: str):
     form = NewActionModalForm(request.POST or None, instance=comp, request=request)
     return form.process_request(
         request,
-        msg_success=_("Action added"),
+        msg_success=COMPENSATION_ACTION_ADDED,
         redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
     )
 
@@ -437,7 +438,7 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     form = RemoveModalForm(request.POST or None, instance=state, request=request)
     return form.process_request(
         request,
-        msg_success=_("State removed"),
+        msg_success=COMPENSATION_STATE_REMOVED,
         redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
     )
 
@@ -460,7 +461,7 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
     form = RemoveModalForm(request.POST or None, instance=action, request=request)
     return form.process_request(
         request,
-        msg_success=_("Action removed"),
+        msg_success=COMPENSATION_ACTION_REMOVED,
         redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
     )
 
diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py
index d04359ef..27a113cb 100644
--- a/compensation/views/eco_account.py
+++ b/compensation/views/eco_account.py
@@ -30,7 +30,8 @@ from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 from konova.utils.documents import get_document, remove_document
 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
+    CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, \
+    COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED
 from konova.utils.user_checks import in_group
 
 
@@ -359,7 +360,7 @@ def state_new_view(request: HttpRequest, id: str):
     form = NewStateModalForm(request.POST or None, instance=acc, request=request)
     return form.process_request(
         request,
-        msg_success=_("State added"),
+        msg_success=COMPENSATION_STATE_ADDED,
         redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
     )
 
@@ -381,7 +382,7 @@ def action_new_view(request: HttpRequest, id: str):
     form = NewActionModalForm(request.POST or None, instance=acc, request=request)
     return form.process_request(
         request,
-        msg_success=_("Action added"),
+        msg_success=COMPENSATION_ACTION_ADDED,
         redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
     )
 
@@ -404,7 +405,7 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     form = RemoveModalForm(request.POST or None, instance=state, request=request)
     return form.process_request(
         request,
-        msg_success=_("State removed"),
+        msg_success=COMPENSATION_STATE_REMOVED,
         redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
     )
 
@@ -427,7 +428,7 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
     form = RemoveModalForm(request.POST or None, instance=action, request=request)
     return form.process_request(
         request,
-        msg_success=_("Action removed"),
+        msg_success=COMPENSATION_ACTION_REMOVED,
         redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
     )
 
diff --git a/ema/views.py b/ema/views.py
index ff372170..44a92c93 100644
--- a/ema/views.py
+++ b/ema/views.py
@@ -21,7 +21,8 @@ from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 from konova.utils.documents import get_document, remove_document
 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
+    DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, \
+    COMPENSATION_ACTION_ADDED
 from konova.utils.user_checks import in_group
 
 
@@ -292,7 +293,7 @@ def state_new_view(request: HttpRequest, id: str):
     form = NewStateModalForm(request.POST or None, instance=ema, request=request)
     return form.process_request(
         request,
-        msg_success=_("State added"),
+        msg_success=COMPENSATION_STATE_ADDED,
         redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
     )
 
@@ -314,7 +315,7 @@ def action_new_view(request: HttpRequest, id: str):
     form = NewActionModalForm(request.POST or None, instance=ema, request=request)
     return form.process_request(
         request,
-        msg_success=_("Action added"),
+        msg_success=COMPENSATION_ACTION_ADDED,
         redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
     )
 
@@ -428,7 +429,7 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     form = RemoveModalForm(request.POST or None, instance=state, request=request)
     return form.process_request(
         request,
-        msg_success=_("State removed"),
+        msg_success=COMPENSATION_STATE_REMOVED,
         redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
     )
 
@@ -451,7 +452,7 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
     form = RemoveModalForm(request.POST or None, instance=action, request=request)
     return form.process_request(
         request,
-        msg_success=_("Action removed"),
+        msg_success=COMPENSATION_ACTION_REMOVED,
         redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
     )
 
diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py
index f8a419d2..e44649da 100644
--- a/intervention/forms/modalForms.py
+++ b/intervention/forms/modalForms.py
@@ -386,7 +386,7 @@ class NewDeductionModalForm(BaseModalForm):
     def save(self):
         deduction = self.__create_deduction()
         self.cleaned_data["intervention"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED)
-        self.cleaned_data["account"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED, reset_recorded=False)
+        self.cleaned_data["account"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED)
         return deduction
 
 
diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py
index 06b9174d..068594cb 100644
--- a/intervention/models/intervention.py
+++ b/intervention/models/intervention.py
@@ -242,7 +242,9 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
         Returns:
 
         """
-        action = super().mark_as_edited(performing_user, request, edit_comment, reset_recorded)
+        action = super().mark_as_edited(performing_user, edit_comment)
+        if reset_recorded:
+            self.unrecord(performing_user, request)
         if self.checked:
             self.set_unchecked()
         return action
diff --git a/konova/models/object.py b/konova/models/object.py
index 9eba8c1d..dfd5fbcc 100644
--- a/konova/models/object.py
+++ b/konova/models/object.py
@@ -132,6 +132,23 @@ class BaseObject(BaseResource):
 
             self.save()
 
+    def mark_as_edited(self, performing_user: User, edit_comment: str = None):
+        """ In case the object or a related object changed the log history needs to be updated
+
+        Args:
+            performing_user (User): The user which performed the editing action
+            request (HttpRequest): The used request for this action
+            edit_comment (str): Additional comment for the log entry
+
+        Returns:
+
+        """
+        edit_action = UserActionLogEntry.get_edited_action(performing_user, edit_comment)
+        self.modified = edit_action
+        self.log.add(edit_action)
+        self.save()
+        return edit_action
+
     def add_log_entry(self, action: UserAction, user: User, comment: str):
         """ Wraps adding of UserActionLogEntry to log
 
@@ -262,25 +279,18 @@ class RecordableObjectMixin(models.Model):
 
         return action
 
-    def mark_as_edited(self, performing_user: User, request: HttpRequest = None, edit_comment: str = None, reset_recorded: bool = True):
-        """ In case the object or a related object changed, internal processes need to be started, such as
-        unrecord and uncheck
+    def unrecord(self, performing_user: User, request: HttpRequest = None):
+        """ Unrecords a dataset
 
         Args:
             performing_user (User): The user which performed the editing action
             request (HttpRequest): The used request for this action
-            edit_comment (str): Additional comment for the log entry
-            reset_recorded (bool): Whether the record-state of the object should be reset
 
         Returns:
 
         """
-        edit_action = UserActionLogEntry.get_edited_action(performing_user, edit_comment)
-        self.modified = edit_action
-        self.log.add(edit_action)
-        self.save()
-
-        if self.recorded and reset_recorded:
+        action = None
+        if self.recorded:
             action = self.set_unrecorded(performing_user)
             self.log.add(action)
             if request:
@@ -288,7 +298,7 @@ class RecordableObjectMixin(models.Model):
                     request,
                     CHECKED_RECORDED_RESET
                 )
-        return edit_action
+        return action
 
     @abstractmethod
     def is_ready_for_publish(self) -> bool:
diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py
index 79a190ca..c38b2e86 100644
--- a/konova/utils/message_templates.py
+++ b/konova/utils/message_templates.py
@@ -24,6 +24,17 @@ CANCEL_ACC_RECORDED_OR_DEDUCTED = _("Action canceled. Eco account is recorded or
 # COMPENSATION
 COMPENSATION_ADDED_TEMPLATE = _("Compensation {} added")
 COMPENSATION_REMOVED_TEMPLATE = _("Compensation {} removed")
+COMPENSATION_EDITED_TEMPLATE = _("Compensation {} edited")
+ADDED_COMPENSATION_ACTION = _("Added compensation action")
+ADDED_COMPENSATION_STATE = _("Added compensation state")
+
+# COMPENSATION STATE
+COMPENSATION_STATE_REMOVED = _("State removed")
+COMPENSATION_STATE_ADDED = _("State added")
+
+# COMPENSATION ACTION
+COMPENSATION_ACTION_ADDED = _("Action added")
+COMPENSATION_ACTION_REMOVED = _("Action removed")
 
 # DEDUCTIONS
 DEDUCTION_ADDED = _("Deduction added")
@@ -43,9 +54,7 @@ DOCUMENT_ADDED = _("Document added")
 
 # Edited
 EDITED_GENERAL_DATA = _("Edited general data")
-ADDED_COMPENSATION_STATE = _("Added compensation state")
 ADDED_DEADLINE = _("Added deadline")
-ADDED_COMPENSATION_ACTION = _("Added compensation action")
 
 # Geometry conflicts
 GEOMETRY_CONFLICT_WITH_TEMPLATE = _("Geometry conflict detected with {}")
-- 
2.45.2


From 7d3c3f030b6c5b7d6791bba40630558bf5a95d2f Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Mon, 7 Feb 2022 09:56:37 +0100
Subject: [PATCH 02/31] # 86 Deadline removal log entry

* adds log entries if deadline is removed
---
 compensation/forms/modalForms.py    |   1 -
 compensation/models/compensation.py |  18 +++++++++++++++++-
 compensation/views/compensation.py  |  12 +++++++-----
 compensation/views/eco_account.py   |  11 ++++++-----
 ema/views.py                        |  11 ++++++-----
 konova/forms.py                     |  17 +++++++++++++++++
 konova/utils/message_templates.py   |   4 ++++
 locale/de/LC_MESSAGES/django.mo     | Bin 36530 -> 36537 bytes
 locale/de/LC_MESSAGES/django.po     |   2 +-
 9 files changed, 58 insertions(+), 18 deletions(-)

diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py
index 9331f60a..c1761736 100644
--- a/compensation/forms/modalForms.py
+++ b/compensation/forms/modalForms.py
@@ -271,7 +271,6 @@ class NewDeadlineModalForm(BaseModalForm):
 
     def save(self):
         deadline = self.instance.add_deadline(self)
-        self.instance.mark_as_edited(self.user, self.request, ADDED_DEADLINE)
         return deadline
 
 
diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py
index ef8e20fe..e2dc9c89 100644
--- a/compensation/models/compensation.py
+++ b/compensation/models/compensation.py
@@ -21,7 +21,7 @@ from konova.models import BaseObject, AbstractDocument, Deadline, generate_docum
     GeoReferencedMixin
 from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
 from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, COMPENSATION_REMOVED_TEMPLATE, \
-    DOCUMENT_REMOVED_TEMPLATE, COMPENSATION_EDITED_TEMPLATE
+    DOCUMENT_REMOVED_TEMPLATE, COMPENSATION_EDITED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE
 from user.models import UserActionLogEntry
 
 
@@ -71,8 +71,24 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
 
             self.save()
             self.deadlines.add(deadline)
+            self.mark_as_edited(user, edit_comment=ADDED_DEADLINE)
             return deadline
 
+    def remove_deadline(self, form):
+        """ Removes a deadline from the abstract compensation
+
+        Args:
+            form (DeadlineRemoveModalForm): The form holding all relevant data
+
+        Returns:
+
+        """
+        deadline = form.deadline
+        user = form.user
+        with transaction.atomic():
+            deadline.delete()
+            self.mark_as_edited(user, edit_comment=DEADLINE_REMOVED)
+
     def add_action(self, form) -> CompensationAction:
         """ Adds a new action to the compensation
 
diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py
index 26e53fd2..e2456ecd 100644
--- a/compensation/views/compensation.py
+++ b/compensation/views/compensation.py
@@ -12,14 +12,15 @@ from compensation.tables import CompensationTable
 from intervention.models import Intervention
 from konova.contexts import BaseContext
 from konova.decorators import *
-from konova.forms import RemoveModalForm, SimpleGeomForm
+from konova.forms import RemoveModalForm, SimpleGeomForm, DeadlineRemoveModalForm
 from konova.models import Deadline
 from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 from konova.utils.documents import get_document, remove_document
 from konova.utils.generators import generate_qr_code
 from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DATA_UNSHARED_EXPLANATION, \
     CHECKED_RECORDED_RESET, COMPENSATION_ADDED_TEMPLATE, COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, \
-    COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED
+    COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, \
+    DEADLINE_ADDED, DEADLINE_REMOVED
 from konova.utils.user_checks import in_group
 
 
@@ -392,7 +393,7 @@ def deadline_new_view(request: HttpRequest, id: str):
     form = NewDeadlineModalForm(request.POST or None, instance=comp, request=request)
     return form.process_request(
         request,
-        msg_success=_("Deadline added"),
+        msg_success=DEADLINE_ADDED,
         redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
     )
 
@@ -411,11 +412,12 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
     Returns:
 
     """
+    comp = get_object_or_404(Compensation, id=id)
     deadline = get_object_or_404(Deadline, id=deadline_id)
-    form = RemoveModalForm(request.POST or None, instance=deadline, request=request)
+    form = DeadlineRemoveModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
     return form.process_request(
         request,
-        msg_success=_("Deadline removed"),
+        msg_success=DEADLINE_REMOVED,
         redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
     )
 
diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py
index 27a113cb..34f979f0 100644
--- a/compensation/views/eco_account.py
+++ b/compensation/views/eco_account.py
@@ -23,7 +23,7 @@ from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm
 from konova.contexts import BaseContext
 from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
     shared_access_required
-from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm
+from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm, DeadlineRemoveModalForm
 from konova.models import Deadline
 from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
@@ -31,7 +31,7 @@ from konova.utils.documents import get_document, remove_document
 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
+    COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED
 from konova.utils.user_checks import in_group
 
 
@@ -447,11 +447,12 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
     Returns:
 
     """
+    comp = get_object_or_404(EcoAccount, id=id)
     deadline = get_object_or_404(Deadline, id=deadline_id)
-    form = RemoveModalForm(request.POST or None, instance=deadline, request=request)
+    form = DeadlineRemoveModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
     return form.process_request(
         request,
-        msg_success=_("Deadline removed"),
+        msg_success=DEADLINE_REMOVED,
         redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
     )
 
@@ -473,7 +474,7 @@ def deadline_new_view(request: HttpRequest, id: str):
     form = NewDeadlineModalForm(request.POST or None, instance=acc, request=request)
     return form.process_request(
         request,
-        msg_success=_("Deadline added"),
+        msg_success=DEADLINE_ADDED,
         redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
     )
 
diff --git a/ema/views.py b/ema/views.py
index 44a92c93..82bfcdc0 100644
--- a/ema/views.py
+++ b/ema/views.py
@@ -14,7 +14,7 @@ from intervention.forms.modalForms import ShareModalForm
 from konova.contexts import BaseContext
 from konova.decorators import conservation_office_group_required, shared_access_required
 from ema.models import Ema, EmaDocument
-from konova.forms import RemoveModalForm, SimpleGeomForm, RecordModalForm
+from konova.forms import RemoveModalForm, SimpleGeomForm, RecordModalForm, DeadlineRemoveModalForm
 from konova.models import Deadline
 from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
@@ -22,7 +22,7 @@ from konova.utils.documents import get_document, remove_document
 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
+    COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED
 from konova.utils.user_checks import in_group
 
 
@@ -337,7 +337,7 @@ def deadline_new_view(request: HttpRequest, id: str):
     form = NewDeadlineModalForm(request.POST or None, instance=ema, request=request)
     return form.process_request(
         request,
-        msg_success=_("Deadline added"),
+        msg_success=DEADLINE_ADDED,
         redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
     )
 
@@ -590,10 +590,11 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
     Returns:
 
     """
+    ema = get_object_or_404(Ema, id=id)
     deadline = get_object_or_404(Deadline, id=deadline_id)
-    form = RemoveModalForm(request.POST or None, instance=deadline, request=request)
+    form = DeadlineRemoveModalForm(request.POST or None, instance=ema, deadline=deadline, request=request)
     return form.process_request(
         request,
-        msg_success=_("Deadline removed"),
+        msg_success=DEADLINE_REMOVED,
         redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
     )
\ No newline at end of file
diff --git a/konova/forms.py b/konova/forms.py
index ab55cb75..8b7539b3 100644
--- a/konova/forms.py
+++ b/konova/forms.py
@@ -330,6 +330,23 @@ class RemoveModalForm(BaseModalForm):
             self.instance.delete(self.user)
 
 
+class DeadlineRemoveModalForm(RemoveModalForm):
+    """ Removing modal form for deadlines
+
+    Can be used for anything, where removing shall be confirmed by the user a second time.
+
+    """
+    deadline = None
+
+    def __init__(self, *args, **kwargs):
+        deadline = kwargs.pop("deadline", None)
+        self.deadline = deadline
+        super().__init__(*args, **kwargs)
+
+    def save(self):
+        self.instance.remove_deadline(self)
+
+
 class NewDocumentForm(BaseModalForm):
     """ Modal form for new documents
 
diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py
index c38b2e86..e13a9cae 100644
--- a/konova/utils/message_templates.py
+++ b/konova/utils/message_templates.py
@@ -40,6 +40,10 @@ COMPENSATION_ACTION_REMOVED = _("Action removed")
 DEDUCTION_ADDED = _("Deduction added")
 DEDUCTION_REMOVED = _("Deduction removed")
 
+# DEADLINE
+DEADLINE_ADDED = _("Deadline added")
+DEADLINE_REMOVED = _("Deadline removed")
+
 # PAYMENTS
 PAYMENT_ADDED = _("Payment added")
 PAYMENT_REMOVED = _("Payment removed")
diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo
index 23892cf84d1776aa8f508d127e5ea2d93a2b6cac..c25c2f113b0cf7f234e21d82feaf2dcc8f869568 100644
GIT binary patch
delta 3137
zcmXZedra3=7{~D=D*ogq-athKZwPV|$}*8qDs|p6BsZFiIR}UeqCy5rUjrmH)LC=k
z1#OW_agnCrr3up1*-W?6a)mk9#mbzy(v_yJ_lI--(ARmsmvf%;oO8ZEKCSmVUhj8s
zS069T7?YN3Okd1GA1=ZyT<$!Jb;J>Q#_0JY*ay#JAYQ{zyorPGKU6<q`PNA6NgRXS
zF~waUpKm-9&V|Wb=#Sa%!DSdgT!y2t8oPHfrV)dP4=%C^pTyq8Upaq5^>YvPeqe!3
zI1C38C!ofgin^au;MvR;yBj4K%Z*x8$1RwQ=TI|#faG9;7uya>F_?HQ_QTDn3N@fA
zdIE>wMO5OqP!+q60odJJLRo2q;xibJ8YmBYVwtlFdk}9z&2Ss4lm}29AHoo9MJ03r
z{jdY|-ZkusH}Gk^iR#}QzSOQ=JgS2<R01<m1LdF|EXAkLLv`4K{@98d@C+)UOV|y+
z!wGl|=i<OZV`g9>>iy$5hVD!|jSMc_!gZKbWXv&a#yBimX3S{Zfr>vzC3+iu_z1Q6
z{EO`pgyR6>c+^t7fSSO37w4gtrUH92zS-K@V9hX!8_mvdQ8WApHKWJQuFLIAeW(FK
zP!${H9FKZ$25O*8)Qs~m6pK(x;h~T5O_jS*iz;EgvjO$sehkEq-Sy+B3bbJmUUYU~
zFXG>j(wcuU5l5`Bdm$5**vqI1%|}nAT0r9+EJtm|;FWgZSkyoXsNFpgHNZ>m`U31t
zT#VYyl^Beh-2I&{ZbWUmW2lMTK_&2TCH0?8qf3cBpL0+pT8!EgtC3@GTCp#la`!Kz
z68i<U1b1;72A0~T$a3bRj$b+I`8w1DK0+<Y(NcH*+qj_2zQB0=1{2WlHCw{5m_(d|
z$+!ilU^}LuZ<S3v75fm+#7UTg+I;WhSZu^}>_Bba2=8^<7>W9HCZI|)4OQA%s19;a
zl_@}FUXJ~+62tL59EzWy-n)pJ>2*|pcTj784`cBmu0b!l%$Qv?8n6l{m9xHh8nu>T
ztL=ASBx+_8P?eg68YtVvOHd_V<*dO-;%yj>&8YXUy6b=91)cw1q@hy$fLh}l&IhPc
zg;dxzo{btH7d3E+vj+PU*SWX}RiRU;O?eL0|0NuZS8){H$IkN~`G%cQGU~Xc;Q*YB
z+5@kkO1&Ock$QLk0BT03QJ>l?sDZynE!mH#lHWoN{5LA`Q1+bqAC80gXHxl3Yda5z
zU_NR_>rgN5#G!Z$Rmv+k2_K_=)za76#IjNE<vS}-1HOyue=q7(wO|<DM=zO1=$m%4
zWZ<*JnW&j=MU`$3>O->+^<pzBp)YX+{^8<YmG=9Qh)R4q>bZPp4R$7is%T3k>#qUM
zb3vuNj2h@yjKI678HKVl6-T3%Bm?zav9kiz;YQSq_n<cEA=D{2iBWg~_5N-2!@<>*
zP-_-hZ5@rA43mT@xDV^F19fal_=PIO-57&GHP&$$Mf@tpp@&M~fV+PbrxIVsnHcky
zG0$VEM`J0C6R3<vueZB)3~CdNLv@snU2z6Ji7#R#R->N#5cPf&`tT6y`4iX!TT%U-
zMOEl)7kfX`P-*X=)+%U&-L-M3wMs#i`Z?4R%|Y#rg{b4T+}&S;dVT|Hz#XWBK0qJt
zMeVgF)ZYcyt)7Y4Xm|ZQ)C(mTg{v?I>o67DF&ZCXHyru4HP)Hvd;&G|@h+Z-N?<Z7
zvFYfKnVt7p|Lo2NYlr&zU5M(S0QEwli%T(pxD2(~s@?VNsORcYiS2dQ8&Ul=p$0tS
zu78Glt_>3y-<+nAh=1W&99C=p7R*G&wWx$nqK;=fW}?|-KO}Qer(`KcVI>a31{{HB
zoWElraqwpQ1so{#tCmhfn`s$_;1<+_yItIPc>mDtQ+gz2<*q0!C_0>xTO9j8`gyy}

delta 3129
zcmXZedra0<9LMn^a(VEEA}Wc%qXvQ~iJ>W$St@FHS(uACm!v6|gb0K%#9v-OGjn;F
z;zF0<BHaX)CP+hPGucYZ73NqMD|5z5SDLymtKJ{Z`9ojl`@5X;J>PTA@8Q(`fD`)z
zj@5>HVaAxpRv0r7=b;au$7e9#c?N5UBUTzS7>{B&p2OaF1^eSQ9EyLS`U%UiMq()O
za16pEcReM?cxDI}(zq}PGu?x^*o(Lr<FFEgdKgoOeTWa|+JsNy0OC)a-=O-rg?c|Q
z&n6s(4-m(r#+!z^pOxp?%$B<w1(?W<Dpbdfn1W|fGroi5V1n~)2Zh*|cnwD2W>kf0
zQ59{%XlzF%ejQb@+t>^L^=L%V2z}9*$ry(kXeowbv2!hk5LcsSxE)o>L#U3AU_Wd|
zC3GGGumknp73_&u@e#a+>fei6W!Eka)xktm0y9wqWuYD{#EDpr>aY=mu^BbsX;eZN
zFc81MG`xZfF}%Q-3|xtN{{)U^eA7zfNiJN+DoiLerUe@?33FaDW)$v1#UG*)y@5Ww
zhuVDqp_ZV3kui}NhgyoMs0l1`@lw>%l;HrzH(R?KtQq>b(ct_XHN)RfGrI5m2Q||k
zFWUh^P!)@Drl8)Nff^_SHRGk&A9GMkQI0;wH*4LEDpbdNoVBP2-^Jeep1XbmRe=`l
zgYC`^3?u#lDXsY(lW|zF-3uA0#O9+Wv<N+wYB7zSSc2M&!6kO!5vYOUQKg-L8sI5+
zeK8Io&O`0y3hay3?*48U*P%AuanwY*Pzl^Eq5kt}{KW;G&pE4YiI$`G#A@W&n`Rt{
zr`-LEsKmZQt?^Bqje(_hDdst|QOB<Y_52Rh1l~a{$+1#*{#&@9%s#>~_!*AHzcCpT
z%ZzylvoHm>U^=$qc<k|tO*|FDiD%+8%tCFxeK-#5@Nw)wZQcm4+%}?7pU!wxX`Vop
zHXYSL7Hab>LuFoq5m<pk@GXqN_fhY)qh{KP>aPp6_O~z*@8VkYV%M<qY1HCIocyXW
zPh%TuEyF78cOV)yvxiZYN=FTp>EahqB`$J$7)!hz<FNtt{$+RlXS}HM?;{PB;w#h|
zUv=I=l`5ptuJJ6?0MDTYE^vA{h<JyK>roXth1!&7QT<=QVR#wi@HTdz|HyTAMx#;3
zZ6Zcu8fp(LM3s6ysv>*b{X?i3wV^(>mrw(LiCVI+Q6;~Q8u(XK;-Tz0^&f>p`Dar3
zPiwmXqcIybqjjhkcVi45N0sstPQ&}CQ#0u`n^-35y=-S0YQSx%{tuu|RU;0@+vugx
z2wiVC%M_eSoPnC@R#fR~P#>Cus23Yh34M$s@h2DiHrVgSC{*IpQO{*NJ?u^dRnf)`
zT7MenxS&#AL=E&kM&V7=j6&I&iephrG6nTqp0f<q;YQSqYfzi?2<jA^L_eNKy?+A(
zaL^`7s5Oh+Wc4E_!zAE%Jcu>efjTw?{6ZDrUQEECYHKq3iJ!$JEJr19$lX7Nvxqx!
zE)L(!?*kTkG*;1QLS^LNVt4IG)Fw(sbu<Zk;tWj1*%*tJsOR>h-mga=9zi|dgdy0B
z>gNoqLZ7<W`<8}E+l5-Ipx5oLeGs))NvKjkidv#MsJ*cSb-eQ3{ZiEP8&Cu8LM8Mz
z`tSg1uhnB3c3M3XvDNPS1*jJa(2qr!fIDyswqiWq!$6FF!#ct_$~gu#^As0PKqZid
zN^Cj?V@CIV)<3hm!P=pIewUy+ScZCGrHc!(7jZFavsJq5J5kT=K_zy;U9Us+SC1O-
zsJs3F>bVvi%lM{^hJIvz!ErcroBdlb0~J@H5;}=Go~@XPf1*AlbKkV5WCi+(D{wg0
u;z&I0{1JN-2XD7uz;LNwwMjIznR2loZb3b`*Tr>Bhhz4oH_cg5l=wf&skEB_

diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po
index 65808d43..eb1b60a4 100644
--- a/locale/de/LC_MESSAGES/django.po
+++ b/locale/de/LC_MESSAGES/django.po
@@ -1117,7 +1117,7 @@ msgstr "Frist/Termin hinzugefügt"
 #: compensation/views/compensation.py:417 compensation/views/eco_account.py:453
 #: ema/views.py:595
 msgid "Deadline removed"
-msgstr "Frist gelöscht"
+msgstr "Frist/Termin gelöscht"
 
 #: compensation/views/compensation.py:440 compensation/views/eco_account.py:407
 #: ema/views.py:430
-- 
2.45.2


From 98df0f93c3c117aec351cf62ec75a7f7adeaae07 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 09:27:28 +0100
Subject: [PATCH 03/31] Further tests

* restructures compensation/tests into subtests for ecoaccount and compensation
* adds tests for ema workflow
* improved test data setup
---
 compensation/tests/compensation/__init__.py   |   7 +
 .../tests/{ => compensation}/test_views.py    | 185 +----------------
 .../tests/{ => compensation}/test_workflow.py |  87 +++-----
 compensation/tests/ecoaccount/__init__.py     |   7 +
 compensation/tests/ecoaccount/test_views.py   | 194 ++++++++++++++++++
 .../tests/ecoaccount/test_workflow.py         |  84 ++++++++
 ema/tests/test_views.py                       |   3 +-
 ema/tests/test_workflow.py                    | 165 +++++++++++++++
 konova/models/object.py                       |   2 +-
 konova/tests/test_views.py                    |  42 +++-
 10 files changed, 526 insertions(+), 250 deletions(-)
 create mode 100644 compensation/tests/compensation/__init__.py
 rename compensation/tests/{ => compensation}/test_views.py (53%)
 rename compensation/tests/{ => compensation}/test_workflow.py (79%)
 create mode 100644 compensation/tests/ecoaccount/__init__.py
 create mode 100644 compensation/tests/ecoaccount/test_views.py
 create mode 100644 compensation/tests/ecoaccount/test_workflow.py
 create mode 100644 ema/tests/test_workflow.py

diff --git a/compensation/tests/compensation/__init__.py b/compensation/tests/compensation/__init__.py
new file mode 100644
index 00000000..7cf7973a
--- /dev/null
+++ b/compensation/tests/compensation/__init__.py
@@ -0,0 +1,7 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: michel.peltriaux@sgdnord.rlp.de
+Created on: 07.02.22
+
+"""
diff --git a/compensation/tests/test_views.py b/compensation/tests/compensation/test_views.py
similarity index 53%
rename from compensation/tests/test_views.py
rename to compensation/tests/compensation/test_views.py
index 8d668220..4039496a 100644
--- a/compensation/tests/test_views.py
+++ b/compensation/tests/compensation/test_views.py
@@ -2,11 +2,11 @@
 Author: Michel Peltriaux
 Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 Contact: michel.peltriaux@sgdnord.rlp.de
-Created on: 27.10.21
+Created on: 07.02.22
 
 """
+from django.test.client import Client
 from django.urls import reverse
-from django.test import Client
 
 from konova.settings import DEFAULT_GROUP
 from konova.tests.test_views import BaseViewTestCase
@@ -223,184 +223,3 @@ class CompensationViewTestCase(BaseViewTestCase):
         self.assert_url_fail(client, fail_urls)
         self.assert_url_success(client, success_urls)
 
-
-class EcoAccountViewTestCase(CompensationViewTestCase):
-    """
-    These tests focus on proper returned views depending on the user's groups privileges and login status
-
-    EcoAccounts can inherit the same tests used for compensations.
-
-    """
-    comp_state = None
-    comp_action = None
-
-    @classmethod
-    def setUpTestData(cls) -> None:
-        super().setUpTestData()
-        state = cls.create_dummy_states()
-        cls.eco_account.before_states.set([state])
-        cls.eco_account.after_states.set([state])
-
-        action = cls.create_dummy_action()
-        cls.eco_account.actions.set([action])
-
-        # Prepare urls
-        cls.index_url = reverse("compensation:acc:index", args=())
-        cls.new_url = reverse("compensation:acc:new", args=())
-        cls.new_id_url = reverse("compensation:acc:new-id", args=())
-        cls.detail_url = reverse("compensation:acc:detail", args=(cls.eco_account.id,))
-        cls.log_url = reverse("compensation:acc:log", args=(cls.eco_account.id,))
-        cls.edit_url = reverse("compensation:acc:edit", args=(cls.eco_account.id,))
-        cls.remove_url = reverse("compensation:acc:remove", args=(cls.eco_account.id,))
-        cls.report_url = reverse("compensation:acc:report", args=(cls.eco_account.id,))
-        cls.state_new_url = reverse("compensation:acc:new-state", args=(cls.eco_account.id,))
-        cls.action_new_url = reverse("compensation:acc:new-action", args=(cls.eco_account.id,))
-        cls.deadline_new_url = reverse("compensation:acc:new-deadline", args=(cls.eco_account.id,))
-        cls.new_doc_url = reverse("compensation:acc:new-doc", args=(cls.eco_account.id,))
-        cls.state_remove_url = reverse("compensation:acc:state-remove", args=(cls.eco_account.id, cls.comp_state.id,))
-        cls.action_remove_url = reverse("compensation:acc:action-remove", args=(cls.eco_account.id, cls.comp_action.id,))
-
-    def test_logged_in_no_groups_shared(self):
-        """ Check correct status code for all requests
-
-        Assumption: User logged in and has no groups and data is shared
-
-        Returns:
-
-        """
-        client = Client()
-        client.login(username=self.superuser.username, password=self.superuser_pw)
-        self.superuser.groups.set([])
-        self.eco_account.share_with_list([self.superuser])
-
-        # Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
-        # to a user without access, since the important permissions are missing
-        success_urls = [
-            self.index_url,
-            self.detail_url,
-            self.report_url,
-        ]
-        fail_urls = [
-            self.new_url,
-            self.new_id_url,
-            self.log_url,
-            self.edit_url,
-            self.remove_url,
-            self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
-            self.state_remove_url,
-            self.action_remove_url,
-            self.new_doc_url,
-        ]
-
-        self.assert_url_success(client, success_urls)
-        self.assert_url_fail(client, fail_urls)
-
-    def test_logged_in_no_groups_unshared(self):
-        """ Check correct status code for all requests
-
-        Assumption: User logged in and has no groups and data is shared
-
-        Returns:
-
-        """
-        client = Client()
-        client.login(username=self.superuser.username, password=self.superuser_pw)
-        self.superuser.groups.set([])
-        self.eco_account.share_with_list([])
-
-        # Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
-        # to a user having shared access, since all important permissions are missing
-        success_urls = [
-            self.index_url,
-            self.detail_url,
-            self.report_url,
-        ]
-        fail_urls = [
-            self.new_url,
-            self.new_id_url,
-            self.log_url,
-            self.edit_url,
-            self.remove_url,
-            self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
-            self.state_remove_url,
-            self.action_remove_url,
-            self.new_doc_url,
-        ]
-
-        self.assert_url_success(client, success_urls)
-        self.assert_url_fail(client, fail_urls)
-
-    def test_logged_in_default_group_shared(self):
-        """ Check correct status code for all requests
-
-        Assumption: User logged in, is default group member and data is shared
-        --> Default group necessary since all base functionalities depend on this group membership
-
-        Returns:
-
-        """
-        client = Client()
-        client.login(username=self.superuser.username, password=self.superuser_pw)
-        group = self.groups.get(name=DEFAULT_GROUP)
-        self.superuser.groups.set([group])
-        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
-        self.eco_account.share_with_list([self.superuser])
-
-        success_urls = [
-            self.index_url,
-            self.detail_url,
-            self.report_url,
-            self.new_url,
-            self.new_id_url,
-            self.edit_url,
-            self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
-            self.state_remove_url,
-            self.action_remove_url,
-            self.new_doc_url,
-            self.log_url,
-            self.remove_url,
-        ]
-        self.assert_url_success(client, success_urls)
-
-    def test_logged_in_default_group_unshared(self):
-        """ Check correct status code for all requests
-
-        Assumption: User logged in, is default group member and data is NOT shared
-        --> Default group necessary since all base functionalities depend on this group membership
-
-        Returns:
-
-        """
-        client = Client()
-        client.login(username=self.superuser.username, password=self.superuser_pw)
-        group = self.groups.get(name=DEFAULT_GROUP)
-        self.superuser.groups.set([group])
-        self.eco_account.share_with_list([])
-
-        success_urls = [
-            self.index_url,
-            self.detail_url,
-            self.report_url,
-            self.new_id_url,
-            self.new_url,
-        ]
-        fail_urls = [
-            self.edit_url,
-            self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
-            self.state_remove_url,
-            self.action_remove_url,
-            self.new_doc_url,
-            self.log_url,
-            self.remove_url,
-        ]
-        self.assert_url_fail(client, fail_urls)
-        self.assert_url_success(client, success_urls)
-
diff --git a/compensation/tests/test_workflow.py b/compensation/tests/compensation/test_workflow.py
similarity index 79%
rename from compensation/tests/test_workflow.py
rename to compensation/tests/compensation/test_workflow.py
index 7f4864d6..d1f13782 100644
--- a/compensation/tests/test_workflow.py
+++ b/compensation/tests/compensation/test_workflow.py
@@ -2,7 +2,7 @@
 Author: Michel Peltriaux
 Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 Contact: michel.peltriaux@sgdnord.rlp.de
-Created on: 11.11.21
+Created on: 07.02.22
 
 """
 import datetime
@@ -11,7 +11,7 @@ from django.contrib.gis.geos import MultiPolygon
 from django.urls import reverse
 
 from compensation.models import Compensation
-from konova.settings import ETS_GROUP, ZB_GROUP
+from konova.settings import ZB_GROUP, ETS_GROUP
 from konova.tests.test_views import BaseWorkflowTestCase
 from user.models import UserAction
 
@@ -55,6 +55,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
             "geom": test_geom.geojson,
             "intervention": self.intervention.id,
         }
+        pre_creation_intervention_log_count = self.intervention.log.count()
 
         # Preserve the current number of intervention's compensations
         num_compensations = self.intervention.compensations.count()
@@ -66,6 +67,13 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
         self.assertEqual(new_compensation.identifier, test_id)
         self.assertEqual(new_compensation.title, test_title)
         self.assert_equal_geometries(new_compensation.geometry.geom, test_geom)
+        self.assertEqual(new_compensation.log.count(), 1)
+
+        # Expect logs to be set
+        self.assertEqual(pre_creation_intervention_log_count + 1, self.intervention.log.count())
+        self.assertEqual(new_compensation.log.count(), 1)
+        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
+        self.assertEqual(new_compensation.log.first().action, UserAction.CREATED)
 
     def test_new_from_intervention(self):
         """ Test the creation of a compensation from a given intervention
@@ -83,6 +91,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
             "title": test_title,
             "geom": test_geom.geojson,
         }
+        pre_creation_intervention_log_count = self.intervention.log.count()
 
         # Preserve the current number of intervention's compensations
         num_compensations = self.intervention.compensations.count()
@@ -95,6 +104,12 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
         self.assertEqual(new_compensation.title, test_title)
         self.assert_equal_geometries(new_compensation.geometry.geom, test_geom)
 
+        # Expect logs to be set
+        self.assertEqual(new_compensation.log.count(), 1)
+        self.assertEqual(new_compensation.log.first().action, UserAction.CREATED)
+        self.assertEqual(pre_creation_intervention_log_count + 1, self.intervention.log.count())
+        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
+
     def test_edit(self):
         """ Checks that the editing of a compensation works
 
@@ -103,6 +118,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
         """
         url = reverse("compensation:edit", args=(self.compensation.id,))
         self.compensation = self.fill_out_compensation(self.compensation)
+        pre_edit_log_count = self.compensation.log.count()
 
         new_title = self.create_dummy_string()
         new_identifier = self.create_dummy_string()
@@ -138,6 +154,10 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 
         self.assert_equal_geometries(self.compensation.geometry.geom, new_geometry)
 
+        # Expect logs to be set
+        self.assertEqual(pre_edit_log_count + 1, self.compensation.log.count())
+        self.assertEqual(self.compensation.log.first().action, UserAction.EDITED)
+
     def test_checkability(self):
         """
         This tests if the checkability of the compensation (which is defined by the linked intervention's checked
@@ -152,6 +172,8 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
         # Add proper privilege for the user
         self.superuser.groups.add(self.groups.get(name=ZB_GROUP))
 
+        pre_check_log_count = self.compensation.log.count()
+
         # Prepare url and form data
         url = reverse("intervention:check", args=(self.intervention.id,))
         post_data = {
@@ -186,6 +208,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 
         # Expect the user action to be in the log
         self.assertIn(checked, self.compensation.log.all())
+        self.assertEqual(pre_check_log_count + 1, self.compensation.log.count())
 
     def test_recordability(self):
         """
@@ -200,6 +223,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
         """
         # Add proper privilege for the user
         self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
+        pre_record_log_count = self.compensation.log.count()
 
         # Prepare url and form data
         record_url = reverse("intervention:record", args=(self.intervention.id,))
@@ -234,62 +258,5 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 
         # Expect the user action to be in the log
         self.assertIn(recorded, self.compensation.log.all())
-
-
-class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
-    @classmethod
-    def setUpTestData(cls):
-        super().setUpTestData()
-
-        # Add user to conservation office group and give shared access to the account
-        cls.superuser.groups.add(cls.groups.get(name=ETS_GROUP))
-        cls.eco_account.share_with_list([cls.superuser])
-
-    def test_deductability(self):
-        """
-        This tests the deductability of an eco account.
-
-        An eco account should only be deductible if it is recorded.
-
-        Returns:
-
-        """
-        # Give user shared access to the dummy intervention, which will be needed here
-        self.intervention.share_with(self.superuser)
-
-        # Prepare data for deduction creation
-        deduct_url = reverse("compensation:acc:new-deduction", args=(self.eco_account.id,))
-        test_surface = 10.00
-        post_data = {
-            "surface": test_surface,
-            "account": self.id,
-            "intervention": self.intervention.id,
-        }
-        # Perform request --> expect to fail
-        self.client_user.post(deduct_url, post_data)
-
-        # Expect that no deduction has been created
-        self.assertEqual(0, self.eco_account.deductions.count())
-        self.assertEqual(0, self.intervention.deductions.count())
-
-        # Now mock the eco account as it would be recorded (with invalid data)
-        # Make sure the deductible surface is high enough for the request
-        self.eco_account.set_recorded(self.superuser)
-        self.eco_account.refresh_from_db()
-        self.eco_account.deductable_surface = test_surface + 1.00
-        self.eco_account.save()
-        self.assertIsNotNone(self.eco_account.recorded)
-        self.assertGreater(self.eco_account.deductable_surface, test_surface)
-
-        # Rerun the request
-        self.client_user.post(deduct_url, post_data)
-
-        # Expect that the deduction has been created
-        self.assertEqual(1, self.eco_account.deductions.count())
-        self.assertEqual(1, self.intervention.deductions.count())
-        deduction = self.eco_account.deductions.first()
-        self.assertEqual(deduction.surface, test_surface)
-        self.assertEqual(deduction.account, self.eco_account)
-        self.assertEqual(deduction.intervention, self.intervention)
-
+        self.assertEqual(pre_record_log_count + 1, self.compensation.log.count())
 
diff --git a/compensation/tests/ecoaccount/__init__.py b/compensation/tests/ecoaccount/__init__.py
new file mode 100644
index 00000000..7cf7973a
--- /dev/null
+++ b/compensation/tests/ecoaccount/__init__.py
@@ -0,0 +1,7 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: michel.peltriaux@sgdnord.rlp.de
+Created on: 07.02.22
+
+"""
diff --git a/compensation/tests/ecoaccount/test_views.py b/compensation/tests/ecoaccount/test_views.py
new file mode 100644
index 00000000..c4e742fe
--- /dev/null
+++ b/compensation/tests/ecoaccount/test_views.py
@@ -0,0 +1,194 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: michel.peltriaux@sgdnord.rlp.de
+Created on: 27.10.21
+
+"""
+from django.urls import reverse
+from django.test import Client
+
+from compensation.tests.compensation.test_views import CompensationViewTestCase
+from konova.settings import DEFAULT_GROUP
+
+
+class EcoAccountViewTestCase(CompensationViewTestCase):
+    """
+    These tests focus on proper returned views depending on the user's groups privileges and login status
+
+    EcoAccounts can inherit the same tests used for compensations.
+
+    """
+    comp_state = None
+    comp_action = None
+
+    @classmethod
+    def setUpTestData(cls) -> None:
+        super().setUpTestData()
+        state = cls.create_dummy_states()
+        cls.eco_account.before_states.set([state])
+        cls.eco_account.after_states.set([state])
+
+        action = cls.create_dummy_action()
+        cls.eco_account.actions.set([action])
+
+        # Prepare urls
+        cls.index_url = reverse("compensation:acc:index", args=())
+        cls.new_url = reverse("compensation:acc:new", args=())
+        cls.new_id_url = reverse("compensation:acc:new-id", args=())
+        cls.detail_url = reverse("compensation:acc:detail", args=(cls.eco_account.id,))
+        cls.log_url = reverse("compensation:acc:log", args=(cls.eco_account.id,))
+        cls.edit_url = reverse("compensation:acc:edit", args=(cls.eco_account.id,))
+        cls.remove_url = reverse("compensation:acc:remove", args=(cls.eco_account.id,))
+        cls.report_url = reverse("compensation:acc:report", args=(cls.eco_account.id,))
+        cls.state_new_url = reverse("compensation:acc:new-state", args=(cls.eco_account.id,))
+        cls.action_new_url = reverse("compensation:acc:new-action", args=(cls.eco_account.id,))
+        cls.deadline_new_url = reverse("compensation:acc:new-deadline", args=(cls.eco_account.id,))
+        cls.new_doc_url = reverse("compensation:acc:new-doc", args=(cls.eco_account.id,))
+        cls.state_remove_url = reverse("compensation:acc:state-remove", args=(cls.eco_account.id, cls.comp_state.id,))
+        cls.action_remove_url = reverse("compensation:acc:action-remove", args=(cls.eco_account.id, cls.comp_action.id,))
+
+    def test_logged_in_no_groups_shared(self):
+        """ Check correct status code for all requests
+
+        Assumption: User logged in and has no groups and data is shared
+
+        Returns:
+
+        """
+        client = Client()
+        client.login(username=self.superuser.username, password=self.superuser_pw)
+        self.superuser.groups.set([])
+        self.eco_account.share_with_list([self.superuser])
+
+        # Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
+        # to a user without access, since the important permissions are missing
+        success_urls = [
+            self.index_url,
+            self.detail_url,
+            self.report_url,
+        ]
+        fail_urls = [
+            self.new_url,
+            self.new_id_url,
+            self.log_url,
+            self.edit_url,
+            self.remove_url,
+            self.state_new_url,
+            self.action_new_url,
+            self.deadline_new_url,
+            self.state_remove_url,
+            self.action_remove_url,
+            self.new_doc_url,
+        ]
+
+        self.assert_url_success(client, success_urls)
+        self.assert_url_fail(client, fail_urls)
+
+    def test_logged_in_no_groups_unshared(self):
+        """ Check correct status code for all requests
+
+        Assumption: User logged in and has no groups and data is shared
+
+        Returns:
+
+        """
+        client = Client()
+        client.login(username=self.superuser.username, password=self.superuser_pw)
+        self.superuser.groups.set([])
+        self.eco_account.share_with_list([])
+
+        # Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
+        # to a user having shared access, since all important permissions are missing
+        success_urls = [
+            self.index_url,
+            self.detail_url,
+            self.report_url,
+        ]
+        fail_urls = [
+            self.new_url,
+            self.new_id_url,
+            self.log_url,
+            self.edit_url,
+            self.remove_url,
+            self.state_new_url,
+            self.action_new_url,
+            self.deadline_new_url,
+            self.state_remove_url,
+            self.action_remove_url,
+            self.new_doc_url,
+        ]
+
+        self.assert_url_success(client, success_urls)
+        self.assert_url_fail(client, fail_urls)
+
+    def test_logged_in_default_group_shared(self):
+        """ Check correct status code for all requests
+
+        Assumption: User logged in, is default group member and data is shared
+        --> Default group necessary since all base functionalities depend on this group membership
+
+        Returns:
+
+        """
+        client = Client()
+        client.login(username=self.superuser.username, password=self.superuser_pw)
+        group = self.groups.get(name=DEFAULT_GROUP)
+        self.superuser.groups.set([group])
+        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
+        self.eco_account.share_with_list([self.superuser])
+
+        success_urls = [
+            self.index_url,
+            self.detail_url,
+            self.report_url,
+            self.new_url,
+            self.new_id_url,
+            self.edit_url,
+            self.state_new_url,
+            self.action_new_url,
+            self.deadline_new_url,
+            self.state_remove_url,
+            self.action_remove_url,
+            self.new_doc_url,
+            self.log_url,
+            self.remove_url,
+        ]
+        self.assert_url_success(client, success_urls)
+
+    def test_logged_in_default_group_unshared(self):
+        """ Check correct status code for all requests
+
+        Assumption: User logged in, is default group member and data is NOT shared
+        --> Default group necessary since all base functionalities depend on this group membership
+
+        Returns:
+
+        """
+        client = Client()
+        client.login(username=self.superuser.username, password=self.superuser_pw)
+        group = self.groups.get(name=DEFAULT_GROUP)
+        self.superuser.groups.set([group])
+        self.eco_account.share_with_list([])
+
+        success_urls = [
+            self.index_url,
+            self.detail_url,
+            self.report_url,
+            self.new_id_url,
+            self.new_url,
+        ]
+        fail_urls = [
+            self.edit_url,
+            self.state_new_url,
+            self.action_new_url,
+            self.deadline_new_url,
+            self.state_remove_url,
+            self.action_remove_url,
+            self.new_doc_url,
+            self.log_url,
+            self.remove_url,
+        ]
+        self.assert_url_fail(client, fail_urls)
+        self.assert_url_success(client, success_urls)
+
diff --git a/compensation/tests/ecoaccount/test_workflow.py b/compensation/tests/ecoaccount/test_workflow.py
new file mode 100644
index 00000000..92079207
--- /dev/null
+++ b/compensation/tests/ecoaccount/test_workflow.py
@@ -0,0 +1,84 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: michel.peltriaux@sgdnord.rlp.de
+Created on: 11.11.21
+
+"""
+from django.urls import reverse
+
+from konova.settings import ETS_GROUP
+from konova.tests.test_views import BaseWorkflowTestCase
+from user.models import UserAction
+
+
+class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
+    @classmethod
+    def setUpTestData(cls):
+        super().setUpTestData()
+
+        # Add user to conservation office group and give shared access to the account
+        cls.superuser.groups.add(cls.groups.get(name=ETS_GROUP))
+        cls.eco_account.share_with_list([cls.superuser])
+
+    def test_deductability(self):
+        """
+        This tests the deductability of an eco account.
+
+        An eco account should only be deductible if it is recorded.
+
+        Returns:
+
+        """
+        # Give user shared access to the dummy intervention, which will be needed here
+        self.intervention.share_with(self.superuser)
+        pre_deduction_acc_log_count = self.eco_account.log.count()
+        pre_deduction_int_log_count = self.intervention.log.count()
+
+        # Prepare data for deduction creation
+        deduct_url = reverse("compensation:acc:new-deduction", args=(self.eco_account.id,))
+        test_surface = 10.00
+        post_data = {
+            "surface": test_surface,
+            "account": self.id,
+            "intervention": self.intervention.id,
+        }
+        # Perform request --> expect to fail
+        self.client_user.post(deduct_url, post_data)
+
+        # Expect that no deduction has been created
+        self.assertEqual(0, self.eco_account.deductions.count())
+        self.assertEqual(0, self.intervention.deductions.count())
+        self.assertEqual(pre_deduction_acc_log_count, 0)
+        self.assertEqual(pre_deduction_int_log_count, 0)
+
+        # Now mock the eco account as it would be recorded (with invalid data)
+        # Make sure the deductible surface is high enough for the request
+        self.eco_account.set_recorded(self.superuser)
+        self.eco_account.refresh_from_db()
+        self.eco_account.deductable_surface = test_surface + 1.00
+        self.eco_account.save()
+        self.assertIsNotNone(self.eco_account.recorded)
+        self.assertGreater(self.eco_account.deductable_surface, test_surface)
+        # Expect the recorded entry in the log
+        self.assertEqual(pre_deduction_acc_log_count + 1, self.eco_account.log.count())
+        self.assertTrue(self.eco_account.log.first().action == UserAction.RECORDED)
+
+        # Rerun the request
+        self.client_user.post(deduct_url, post_data)
+
+        # Expect that the deduction has been created
+        self.assertEqual(1, self.eco_account.deductions.count())
+        self.assertEqual(1, self.intervention.deductions.count())
+        deduction = self.eco_account.deductions.first()
+        self.assertEqual(deduction.surface, test_surface)
+        self.assertEqual(deduction.account, self.eco_account)
+        self.assertEqual(deduction.intervention, self.intervention)
+
+        # Expect entries in the log
+        self.assertEqual(pre_deduction_acc_log_count + 2, self.eco_account.log.count())
+        self.assertTrue(self.eco_account.log.first().action == UserAction.EDITED)
+        self.assertEqual(pre_deduction_int_log_count + 1, self.intervention.log.count())
+        self.assertTrue(self.intervention.log.first().action == UserAction.EDITED)
+
+
diff --git a/ema/tests/test_views.py b/ema/tests/test_views.py
index 3d853e7b..dd37fced 100644
--- a/ema/tests/test_views.py
+++ b/ema/tests/test_views.py
@@ -5,11 +5,12 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 Created on: 26.10.21
 
 """
+
 from django.db.models import Q
 from django.urls import reverse
 from django.test.client import Client
 
-from compensation.tests.test_views import CompensationViewTestCase
+from compensation.tests.compensation.test_views import CompensationViewTestCase
 from ema.models import Ema
 from intervention.models import Responsibility
 from konova.models import Geometry
diff --git a/ema/tests/test_workflow.py b/ema/tests/test_workflow.py
new file mode 100644
index 00000000..3306a21f
--- /dev/null
+++ b/ema/tests/test_workflow.py
@@ -0,0 +1,165 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: michel.peltriaux@sgdnord.rlp.de
+Created on: 08.02.22
+
+"""
+import datetime
+
+from django.contrib.gis.geos import MultiPolygon
+from django.core.exceptions import ObjectDoesNotExist
+from django.urls import reverse
+
+from ema.models import Ema
+from konova.settings import ETS_GROUP
+from konova.tests.test_views import BaseWorkflowTestCase
+from user.models import UserAction
+
+
+class EmaWorkflowTestCase(BaseWorkflowTestCase):
+    ema = None
+
+    @classmethod
+    def setUpTestData(cls) -> None:
+        super().setUpTestData()
+
+    def setUp(self) -> None:
+        super().setUp()
+        # Create a fresh dummy (non-valid) compensation before each test
+        self.ema = self.create_dummy_ema()
+
+    def test_new(self):
+        """ Test the creation of an Ema
+
+        Returns:
+
+        """
+        self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
+        # Prepare url and form data to be posted
+        new_url = reverse("ema:new")
+        test_id = self.create_dummy_string()
+        test_title = self.create_dummy_string()
+        test_geom = self.create_dummy_geometry()
+        test_conservation_office = self.get_conservation_office_code()
+        post_data = {
+            "identifier": test_id,
+            "title": test_title,
+            "geom": test_geom.geojson,
+            "conservation_office": test_conservation_office.id
+        }
+        self.client_user.post(new_url, post_data)
+
+        try:
+            ema = Ema.objects.get(
+                identifier=test_id
+            )
+        except ObjectDoesNotExist:
+            self.fail(msg="Ema not created")
+
+        self.assertEqual(ema.identifier, test_id)
+        self.assertEqual(ema.title, test_title)
+        self.assert_equal_geometries(ema.geometry.geom, test_geom)
+        self.assertEqual(ema.log.count(), 1)
+
+        # Expect logs to be set
+        self.assertEqual(ema.log.count(), 1)
+        self.assertEqual(ema.log.first().action, UserAction.CREATED)
+
+    def test_edit(self):
+        """ Checks that the editing of an Ema works
+
+        Returns:
+
+        """
+        self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
+        self.ema.users.add(self.superuser)
+        url = reverse("ema:edit", args=(self.ema.id,))
+        self.ema = self.fill_out_ema(self.ema)
+        pre_edit_log_count = self.ema.log.count()
+
+        new_title = self.create_dummy_string()
+        new_identifier = self.create_dummy_string()
+        new_comment = self.create_dummy_string()
+        new_geometry = MultiPolygon(srid=4326)  # Create an empty geometry
+        test_conservation_office = self.get_conservation_office_code()
+
+        check_on_elements = {
+            self.ema.title: new_title,
+            self.ema.identifier: new_identifier,
+            self.ema.comment: new_comment,
+        }
+        for k, v in check_on_elements.items():
+            self.assertNotEqual(k, v)
+
+        post_data = {
+            "identifier": new_identifier,
+            "title": new_title,
+            "comment": new_comment,
+            "geom": new_geometry.geojson,
+            "conservation_office": test_conservation_office.id
+        }
+        self.client_user.post(url, post_data)
+        self.ema.refresh_from_db()
+
+        check_on_elements = {
+            self.ema.title: new_title,
+            self.ema.identifier: new_identifier,
+            self.ema.comment: new_comment,
+        }
+
+        for k, v in check_on_elements.items():
+            self.assertEqual(k, v)
+
+        self.assert_equal_geometries(self.ema.geometry.geom, new_geometry)
+
+        # Expect logs to be set
+        self.assertEqual(pre_edit_log_count + 1, self.ema.log.count())
+        self.assertEqual(self.ema.log.first().action, UserAction.EDITED)
+
+    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)
+
+        Returns:
+
+        """
+        # Add proper privilege for the user
+        self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
+        self.ema.users.add(self.superuser)
+        pre_record_log_count = self.ema.log.count()
+
+        # Prepare url and form data
+        record_url = reverse("ema:record", args=(self.ema.id,))
+        post_data = {
+            "confirm": True,
+        }
+
+        # Make sure the ema is not recorded
+        self.assertIsNone(self.ema.recorded)
+
+        # Run the request --> expect fail, since the Ema is not valid, yet
+        self.client_user.post(record_url, post_data)
+
+        # Check that the Ema is still not recorded
+        self.assertIsNone(self.ema.recorded)
+
+        # Now fill out the data for a compensation
+        self.ema = self.fill_out_ema(self.ema)
+
+        # Rerun the request
+        self.client_user.post(record_url, post_data)
+
+        # Expect the Ema now to be recorded
+        # Attention: We can only test the date part of the timestamp,
+        # since the delay in microseconds would lead to fail
+        self.ema.refresh_from_db()
+        recorded = self.ema.recorded
+        self.assertIsNotNone(recorded)
+        self.assertEqual(self.superuser, recorded.user)
+        self.assertEqual(UserAction.RECORDED, recorded.action)
+        self.assertEqual(datetime.date.today(), recorded.timestamp.date())
+
+        # Expect the user action to be in the log
+        self.assertIn(recorded, self.ema.log.all())
+        self.assertEqual(pre_record_log_count + 1, self.ema.log.count())
diff --git a/konova/models/object.py b/konova/models/object.py
index dfd5fbcc..0a802ed5 100644
--- a/konova/models/object.py
+++ b/konova/models/object.py
@@ -132,7 +132,7 @@ class BaseObject(BaseResource):
 
             self.save()
 
-    def mark_as_edited(self, performing_user: User, edit_comment: str = None):
+    def mark_as_edited(self, performing_user: User, request: HttpRequest = None, edit_comment: str = None):
         """ In case the object or a related object changed the log history needs to be updated
 
         Args:
diff --git a/konova/tests/test_views.py b/konova/tests/test_views.py
index 1bf33325..a10acf16 100644
--- a/konova/tests/test_views.py
+++ b/konova/tests/test_views.py
@@ -7,6 +7,7 @@ Created on: 26.10.21
 """
 import datetime
 
+from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
 from ema.models import Ema
 from user.models import User
 from django.contrib.auth.models import Group
@@ -15,7 +16,7 @@ from django.core.exceptions import ObjectDoesNotExist
 from django.test import TestCase, Client
 from django.urls import reverse
 
-from codelist.models import KonovaCode
+from codelist.models import KonovaCode, KonovaCodeList
 from compensation.models import Compensation, CompensationState, CompensationAction, EcoAccount, EcoAccountDeduction
 from intervention.models import Legal, Responsibility, Intervention
 from konova.management.commands.setup_data import GROUPS_DATA
@@ -236,10 +237,10 @@ class BaseTestCase(TestCase):
 
         """
         codes = KonovaCode.objects.bulk_create([
-            KonovaCode(id=1, is_selectable=True, long_name="Test1"),
-            KonovaCode(id=2, is_selectable=True, long_name="Test2"),
-            KonovaCode(id=3, is_selectable=True, long_name="Test3"),
-            KonovaCode(id=4, is_selectable=True, long_name="Test4"),
+            KonovaCode(id=1, is_selectable=True, is_archived=False, is_leaf=True, long_name="Test1"),
+            KonovaCode(id=2, is_selectable=True, is_archived=False, is_leaf=True, long_name="Test2"),
+            KonovaCode(id=3, is_selectable=True, is_archived=False, is_leaf=True, long_name="Test3"),
+            KonovaCode(id=4, is_selectable=True, is_archived=False, is_leaf=True, long_name="Test4"),
         ])
         return codes
 
@@ -298,6 +299,37 @@ class BaseTestCase(TestCase):
         compensation.geometry.save()
         return compensation
 
+    @classmethod
+    def get_conservation_office_code(cls):
+        """ Returns a dummy KonovaCode as conservation office code
+
+        Returns:
+
+        """
+        codelist = KonovaCodeList.objects.get_or_create(
+            id=CODELIST_CONSERVATION_OFFICE_ID
+        )[0]
+        code = KonovaCode.objects.get(id=2)
+        codelist.codes.add(code)
+        return code
+
+    @classmethod
+    def fill_out_ema(cls, ema):
+        """ Adds all required (dummy) data to an Ema
+
+        Returns:
+        """
+        ema.responsible.conservation_office = cls.get_conservation_office_code()
+        ema.responsible.conservation_file_number = "test"
+        ema.responsible.handler = "handler"
+        ema.responsible.save()
+        ema.after_states.add(cls.comp_state)
+        ema.before_states.add(cls.comp_state)
+        ema.actions.add(cls.comp_action)
+        ema.geometry.geom = cls.create_dummy_geometry()
+        ema.geometry.save()
+        return ema
+
     def assert_equal_geometries(self, geom1: MultiPolygon, geom2: MultiPolygon):
         """ Assert for geometries to be equal
 
-- 
2.45.2


From 5eebd42c3c9d0ec879fa0d82b4611c6c11d37626 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 11:58:43 +0100
Subject: [PATCH 04/31] Further tests ecoaccount

* adds ecoaccount workflow tests
---
 .../tests/ecoaccount/test_workflow.py         | 153 +++++++++++++++++-
 konova/tests/test_views.py                    |  22 +++
 2 files changed, 172 insertions(+), 3 deletions(-)

diff --git a/compensation/tests/ecoaccount/test_workflow.py b/compensation/tests/ecoaccount/test_workflow.py
index 92079207..f394ec7c 100644
--- a/compensation/tests/ecoaccount/test_workflow.py
+++ b/compensation/tests/ecoaccount/test_workflow.py
@@ -5,9 +5,14 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 Created on: 11.11.21
 
 """
+import datetime
+
+from django.contrib.gis.geos import MultiPolygon
+from django.core.exceptions import ObjectDoesNotExist
 from django.urls import reverse
 
-from konova.settings import ETS_GROUP
+from compensation.models import EcoAccount
+from konova.settings import ETS_GROUP, DEFAULT_GROUP
 from konova.tests.test_views import BaseWorkflowTestCase
 from user.models import UserAction
 
@@ -17,9 +22,151 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
     def setUpTestData(cls):
         super().setUpTestData()
 
+    def setUp(self) -> None:
+        super().setUp()
         # Add user to conservation office group and give shared access to the account
-        cls.superuser.groups.add(cls.groups.get(name=ETS_GROUP))
-        cls.eco_account.share_with_list([cls.superuser])
+        self.superuser.groups.add(self.groups.get(name=DEFAULT_GROUP))
+        self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
+        self.eco_account.share_with_list([self.superuser])
+
+    def test_new(self):
+        """ Test the creation of an EcoAccount
+
+        Returns:
+
+        """
+        # Prepare url and form data to be posted
+        new_url = reverse("compensation:acc:new")
+        test_id = self.create_dummy_string()
+        test_title = self.create_dummy_string()
+        test_geom = self.create_dummy_geometry()
+        test_deductable_surface = 1000
+        test_conservation_office = self.get_conservation_office_code()
+        post_data = {
+            "identifier": test_id,
+            "title": test_title,
+            "geom": test_geom.geojson,
+            "deductable_surface": test_deductable_surface,
+            "conservation_office": test_conservation_office.id
+        }
+        self.client_user.post(new_url, post_data)
+
+        try:
+            acc = EcoAccount.objects.get(
+                identifier=test_id
+            )
+        except ObjectDoesNotExist:
+            self.fail(msg="EcoAccount not created")
+
+        self.assertEqual(acc.identifier, test_id)
+        self.assertEqual(acc.title, test_title)
+        self.assert_equal_geometries(acc.geometry.geom, test_geom)
+        self.assertEqual(acc.log.count(), 1)
+
+        # Expect logs to be set
+        self.assertEqual(acc.log.count(), 1)
+        self.assertEqual(acc.log.first().action, UserAction.CREATED)
+
+    def test_edit(self):
+        """ Checks that the editing of an EcoAccount works
+
+        Returns:
+
+        """
+        self.eco_account.share_with(self.superuser)
+
+        url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
+        pre_edit_log_count = self.eco_account.log.count()
+
+        new_title = self.create_dummy_string()
+        new_identifier = self.create_dummy_string()
+        new_comment = self.create_dummy_string()
+        new_geometry = MultiPolygon(srid=4326)  # Create an empty geometry
+        test_conservation_office = self.get_conservation_office_code()
+        test_deductable_surface = 10005
+
+        check_on_elements = {
+            self.eco_account.title: new_title,
+            self.eco_account.identifier: new_identifier,
+            self.eco_account.comment: new_comment,
+            self.eco_account.deductable_surface: test_deductable_surface,
+        }
+        for k, v in check_on_elements.items():
+            self.assertNotEqual(k, v)
+
+        post_data = {
+            "identifier": new_identifier,
+            "title": new_title,
+            "comment": new_comment,
+            "geom": new_geometry.geojson,
+            "surface": test_deductable_surface,
+            "conservation_office": test_conservation_office.id
+        }
+        self.client_user.post(url, post_data)
+        self.eco_account.refresh_from_db()
+
+        check_on_elements = {
+            self.eco_account.title: new_title,
+            self.eco_account.identifier: new_identifier,
+            self.eco_account.deductable_surface: test_deductable_surface,
+            self.eco_account.comment: new_comment,
+        }
+
+        for k, v in check_on_elements.items():
+            self.assertEqual(k, v)
+
+        self.assert_equal_geometries(self.eco_account.geometry.geom, new_geometry)
+
+        # Expect logs to be set
+        self.assertEqual(pre_edit_log_count + 1, self.eco_account.log.count())
+        self.assertEqual(self.eco_account.log.first().action, UserAction.EDITED)
+
+    def test_recordability(self):
+        """
+        This tests if the recordability of the EcoAccount is triggered by the quality of it's data (e.g. not all fields filled)
+
+        Returns:
+
+        """
+        # Add proper privilege for the user
+        self.eco_account.share_with(self.superuser)
+        pre_record_log_count = self.eco_account.log.count()
+
+        # Prepare url and form data
+        record_url = reverse("compensation:acc:record", args=(self.eco_account.id,))
+        post_data = {
+            "confirm": True,
+        }
+        self.eco_account.refresh_from_db()
+
+        # Make sure the account is not recorded
+        self.assertIsNone(self.eco_account.recorded)
+
+        # Run the request --> expect fail, since the account is not valid, yet
+        self.client_user.post(record_url, post_data)
+
+        # Check that the account is still not recorded
+        self.assertIsNone(self.eco_account.recorded)
+
+        # Now fill out the data for an ecoaccount
+        self.eco_account = self.fill_out_eco_account(self.eco_account)
+
+        # Rerun the request
+        self.client_user.post(record_url, post_data)
+
+        # Expect the EcoAccount now to be recorded
+        # Attention: We can only test the date part of the timestamp,
+        # since the delay in microseconds would lead to fail
+        self.eco_account.refresh_from_db()
+        recorded = self.eco_account.recorded
+        self.assertIsNotNone(recorded)
+        self.assertEqual(self.superuser, recorded.user)
+        self.assertEqual(UserAction.RECORDED, recorded.action)
+        self.assertEqual(datetime.date.today(), recorded.timestamp.date())
+
+        # Expect the user action to be in the log
+        self.assertIn(recorded, self.eco_account.log.all())
+        self.assertEqual(pre_record_log_count + 1, self.eco_account.log.count())
 
     def test_deductability(self):
         """
diff --git a/konova/tests/test_views.py b/konova/tests/test_views.py
index a10acf16..536a7868 100644
--- a/konova/tests/test_views.py
+++ b/konova/tests/test_views.py
@@ -330,6 +330,27 @@ class BaseTestCase(TestCase):
         ema.geometry.save()
         return ema
 
+    @classmethod
+    def fill_out_eco_account(cls, eco_account):
+        """ Adds all required (dummy) data to an EcoAccount
+
+        Returns:
+        """
+        eco_account.legal.registration_date = "2022-01-01"
+        eco_account.legal.save()
+        eco_account.responsible.conservation_office = cls.get_conservation_office_code()
+        eco_account.responsible.conservation_file_number = "test"
+        eco_account.responsible.handler = "handler"
+        eco_account.responsible.save()
+        eco_account.after_states.add(cls.comp_state)
+        eco_account.before_states.add(cls.comp_state)
+        eco_account.actions.add(cls.comp_action)
+        eco_account.geometry.geom = cls.create_dummy_geometry()
+        eco_account.geometry.save()
+        eco_account.deductable_surface = eco_account.get_state_after_surface_sum()
+        eco_account.save()
+        return eco_account
+
     def assert_equal_geometries(self, geom1: MultiPolygon, geom2: MultiPolygon):
         """ Assert for geometries to be equal
 
@@ -534,6 +555,7 @@ class BaseWorkflowTestCase(BaseTestCase):
         Returns:
 
         """
+        super().setUp()
         # Set the default group as only group for the user
         default_group = self.groups.get(name=DEFAULT_GROUP)
         self.superuser.groups.set([default_group])
-- 
2.45.2


From 43bc3517ff5ca0468b918de66a5d42f82f8867fb Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 12:07:49 +0100
Subject: [PATCH 05/31] Further tests

* adds tests for intervention workflow
---
 intervention/tests/test_workflow.py | 30 +++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/intervention/tests/test_workflow.py b/intervention/tests/test_workflow.py
index fbc2f81f..7644051b 100644
--- a/intervention/tests/test_workflow.py
+++ b/intervention/tests/test_workflow.py
@@ -74,6 +74,9 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
             self.assertEqual(obj.identifier, test_id)
             self.assertEqual(obj.title, test_title)
             self.assert_equal_geometries(obj.geometry.geom, test_geom)
+            self.assertEqual(1, obj.log.count())
+            self.assertEqual(obj.log.first().action, UserAction.CREATED)
+            self.assertEqual(obj.log.first().user, self.superuser)
         except ObjectDoesNotExist:
             # Fail if there is no such object
             self.fail()
@@ -215,6 +218,8 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
         # Make sure there are no payments on the intervention, yet
         self.assertEqual(0, self.intervention.payments.count())
 
+        pre_payment_logs_count = self.intervention.log.count()
+
         # Create form data to be sent to the url
         test_amount = 10.00
         test_due = "2021-01-01"
@@ -239,6 +244,10 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
         self.assertEqual(payment.amount, test_amount)
         self.assertEqual(payment.due_on, datetime.date.fromisoformat(test_due))
         self.assertEqual(payment.comment, test_comment)
+
+        # Make sure a log entry has been created
+        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
+        self.assertEqual(pre_payment_logs_count + 1, self.intervention.log.count())
         return payment
 
     def subtest_delete_payment(self, payment: Payment):
@@ -250,6 +259,8 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
         Returns:
 
         """
+        pre_payment_logs_count = self.intervention.log.count()
+
         # Create removing url for the payment
         remove_url = reverse("compensation:pay:remove", args=(payment.id,))
         post_data = {
@@ -266,6 +277,11 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
         # Now make sure the intervention has no payments anymore
         self.assertEqual(0, self.intervention.payments.count())
 
+        # Make sure a log entry has been created
+        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
+        self.assertEqual(self.intervention.log.first().user, self.superuser)
+        self.assertEqual(pre_payment_logs_count + 1, self.intervention.log.count())
+
     def test_payments(self):
         """
         Checks a 'normal' case of adding a payment.
@@ -353,6 +369,8 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
         Returns:
 
         """
+        pre_deduction_logs_count = self.intervention.log.count()
+
         # Prepare the account for a working situation (enough deductable surface, recorded and shared)
         self.eco_account.deductable_surface = 10000.00
         if self.eco_account.recorded is None:
@@ -376,6 +394,11 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
         )
         self.assertEqual(deduction.surface, test_surface)
 
+        # Make sure a log entry has been created
+        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
+        self.assertEqual(self.intervention.log.first().user, self.superuser)
+        self.assertEqual(pre_deduction_logs_count + 1, self.intervention.log.count())
+
         # Return deduction for further usage in tests
         return deduction
 
@@ -414,6 +437,8 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
         Returns:
 
         """
+        pre_delete_logs_count = self.intervention.log.count()
+
         # Prepare url for deleting of this deduction
         delete_url = reverse("compensation:acc:remove-deduction", args=(self.eco_account.id, deduction.id,))
         post_data = {
@@ -433,6 +458,11 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
         # Expect the deduction to be totally gone
         self.assert_object_is_deleted(deduction)
 
+        # Make sure a log entry has been created
+        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
+        self.assertEqual(self.intervention.log.first().user, self.superuser)
+        self.assertEqual(pre_delete_logs_count + 1, self.intervention.log.count())
+
     def test_deduction(self):
         """
         Checks a 'normal case of adding a deduction.
-- 
2.45.2


From 5ebb3f833a7d192743985c0e03ae4288e229829e Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 13:16:20 +0100
Subject: [PATCH 06/31] #86 Log detail enhancements

* restructures removing of related data into separate sub-delete forms for easier logic handling
---
 compensation/forms/modalForms.py              |  54 +++++++++++++++++-
 compensation/models/action.py                 |  10 ----
 compensation/models/compensation.py           |  35 +++++++++++-
 compensation/models/payment.py                |   5 --
 compensation/models/state.py                  |  11 ----
 compensation/urls/payment.py                  |   4 +-
 compensation/views/compensation.py            |   8 ++-
 compensation/views/eco_account.py             |  12 ++--
 compensation/views/payment.py                 |  20 ++++---
 ema/views.py                                  |   9 ++-
 intervention/forms/modalForms.py              |  41 ++++++++++++-
 intervention/models/intervention.py           |  37 +++++++++++-
 intervention/models/revocation.py             |   5 +-
 .../detail/includes/payments.html             |   2 +-
 .../detail/includes/revocation.html           |   2 +-
 intervention/tests/test_workflow.py           |   2 +-
 intervention/urls.py                          |   6 +-
 intervention/views.py                         |  14 +++--
 konova/forms.py                               |   2 +-
 locale/de/LC_MESSAGES/django.mo               | Bin 36537 -> 36538 bytes
 locale/de/LC_MESSAGES/django.po               |   2 +-
 21 files changed, 206 insertions(+), 75 deletions(-)

diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py
index c1761736..a31dda0c 100644
--- a/compensation/forms/modalForms.py
+++ b/compensation/forms/modalForms.py
@@ -18,7 +18,7 @@ from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION
     CODELIST_COMPENSATION_ACTION_DETAIL_ID
 from compensation.models import CompensationDocument, EcoAccountDocument
 from konova.contexts import BaseContext
-from konova.forms import BaseModalForm, NewDocumentForm
+from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
 from konova.models import DeadlineType
 from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \
     ADDED_COMPENSATION_ACTION, PAYMENT_ADDED
@@ -100,10 +100,26 @@ class NewPaymentForm(BaseModalForm):
 
     def save(self):
         pay = self.instance.add_payment(self)
-        self.instance.mark_as_edited(self.user, self.request, edit_comment=PAYMENT_ADDED)
         return pay
 
 
+class PaymentRemoveModalForm(RemoveModalForm):
+    """ Removing modal form for Payment
+
+    Can be used for anything, where removing shall be confirmed by the user a second time.
+
+    """
+    payment = None
+
+    def __init__(self, *args, **kwargs):
+        payment = kwargs.pop("payment", None)
+        self.payment = payment
+        super().__init__(*args, **kwargs)
+
+    def save(self):
+        self.instance.remove_payment(self)
+
+
 class NewStateModalForm(BaseModalForm):
     """ Form handling state related input
 
@@ -219,6 +235,40 @@ class NewStateModalForm(BaseModalForm):
             raise NotImplementedError
 
 
+class CompensationStateRemoveModalForm(RemoveModalForm):
+    """ Removing modal form for CompensationState
+
+    Can be used for anything, where removing shall be confirmed by the user a second time.
+
+    """
+    state = None
+
+    def __init__(self, *args, **kwargs):
+        state = kwargs.pop("state", None)
+        self.state = state
+        super().__init__(*args, **kwargs)
+
+    def save(self):
+        self.instance.remove_state(self)
+
+
+class CompensationActionRemoveModalForm(RemoveModalForm):
+    """ Removing modal form for CompensationAction
+
+    Can be used for anything, where removing shall be confirmed by the user a second time.
+
+    """
+    action = None
+
+    def __init__(self, *args, **kwargs):
+        action = kwargs.pop("action", None)
+        self.action = action
+        super().__init__(*args, **kwargs)
+
+    def save(self):
+        self.instance.remove_action(self)
+
+
 class NewDeadlineModalForm(BaseModalForm):
     """ Form handling deadline related input
 
diff --git a/compensation/models/action.py b/compensation/models/action.py
index bd2400e7..a5579159 100644
--- a/compensation/models/action.py
+++ b/compensation/models/action.py
@@ -76,13 +76,3 @@ class CompensationAction(BaseResource):
             if choice[0] == self.unit:
                 return choice[1]
         return None
-
-    def delete(self, user=None, *args, **kwargs):
-        from compensation.models import Compensation
-        if user:
-            comps = Compensation.objects.filter(
-                actions__id__in=[self.id]
-            ).distinct()
-            for comp in comps:
-                comp.mark_as_edited(user, edit_comment=COMPENSATION_ACTION_REMOVED)
-        super().delete(*args, **kwargs)
\ No newline at end of file
diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py
index e2dc9c89..46476a67 100644
--- a/compensation/models/compensation.py
+++ b/compensation/models/compensation.py
@@ -21,7 +21,8 @@ from konova.models import BaseObject, AbstractDocument, Deadline, generate_docum
     GeoReferencedMixin
 from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
 from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, COMPENSATION_REMOVED_TEMPLATE, \
-    DOCUMENT_REMOVED_TEMPLATE, COMPENSATION_EDITED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE
+    DOCUMENT_REMOVED_TEMPLATE, COMPENSATION_EDITED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE, \
+    COMPENSATION_ACTION_REMOVED, COMPENSATION_STATE_REMOVED
 from user.models import UserActionLogEntry
 
 
@@ -114,6 +115,21 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
             self.actions.add(comp_action)
             return comp_action
 
+    def remove_action(self, form):
+        """ Removes a CompensationAction from the abstract compensation
+
+        Args:
+            form (CompensationActionRemoveModalForm): The form holding all relevant data
+
+        Returns:
+
+        """
+        action = form.action
+        user = form.user
+        with transaction.atomic():
+            action.delete()
+            self.mark_as_edited(user, edit_comment=COMPENSATION_ACTION_REMOVED)
+
     def add_state(self, form, is_before_state: bool) -> CompensationState:
         """ Adds a new compensation state to the compensation
 
@@ -138,6 +154,21 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
                 self.after_states.add(state)
             return state
 
+    def remove_state(self, form):
+        """ Removes a CompensationState from the abstract compensation
+
+        Args:
+            form (CompensationStateRemoveModalForm): The form holding all relevant data
+
+        Returns:
+
+        """
+        state = form.state
+        user = form.user
+        with transaction.atomic():
+            state.delete()
+            self.mark_as_edited(user, edit_comment=COMPENSATION_STATE_REMOVED)
+
     def get_surface_after_states(self) -> float:
         """ Calculates the compensation's/account's surface
 
@@ -346,7 +377,7 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
 
         """
         self.intervention.unrecord(user, request)
-        action = super().mark_as_edited(user, edit_comment)
+        action = super().mark_as_edited(user, edit_comment=edit_comment)
         return action
 
     def is_ready_for_publish(self) -> bool:
diff --git a/compensation/models/payment.py b/compensation/models/payment.py
index 48eec3fc..6f3f4c24 100644
--- a/compensation/models/payment.py
+++ b/compensation/models/payment.py
@@ -37,8 +37,3 @@ class Payment(BaseResource):
         ordering = [
             "-amount",
         ]
-
-    def delete(self, user=None, *args, **kwargs):
-        if user is not None:
-            self.intervention.mark_as_edited(user, edit_comment=PAYMENT_REMOVED)
-        super().delete(*args, **kwargs)
diff --git a/compensation/models/state.py b/compensation/models/state.py
index 02249145..5cb8376a 100644
--- a/compensation/models/state.py
+++ b/compensation/models/state.py
@@ -47,14 +47,3 @@ class CompensationState(UuidModel):
 
     def __str__(self):
         return f"{self.biotope_type} | {self.surface} m²"
-
-    def delete(self, user=None, *args, **kwargs):
-        from compensation.models import Compensation
-        if user:
-            comps = Compensation.objects.filter(
-                Q(before_states__id__in=[self.id]) |
-                Q(after_states__id__in=[self.id])
-            ).distinct()
-            for comp in comps:
-                comp.mark_as_edited(user, edit_comment=COMPENSATION_STATE_REMOVED)
-        super().delete(*args, **kwargs)
diff --git a/compensation/urls/payment.py b/compensation/urls/payment.py
index 2c8e5d4b..a400c636 100644
--- a/compensation/urls/payment.py
+++ b/compensation/urls/payment.py
@@ -10,6 +10,6 @@ from compensation.views.payment import *
 
 app_name = "pay"
 urlpatterns = [
-    path('<intervention_id>/new', new_payment_view, name='new'),
-    path('<id>/remove', payment_remove_view, name='remove'),
+    path('<id>/new', new_payment_view, name='new'),
+    path('<id>/remove/<payment_id>', payment_remove_view, name='remove'),
 ]
diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py
index e2456ecd..eb4dd371 100644
--- a/compensation/views/compensation.py
+++ b/compensation/views/compensation.py
@@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
 
 from compensation.forms.forms import NewCompensationForm, EditCompensationForm
 from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm, \
-    NewCompensationDocumentForm
+    NewCompensationDocumentForm, CompensationActionRemoveModalForm, CompensationStateRemoveModalForm
 from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument
 from compensation.tables import CompensationTable
 from intervention.models import Intervention
@@ -436,8 +436,9 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     Returns:
 
     """
+    comp = get_object_or_404(Compensation, id=id)
     state = get_object_or_404(CompensationState, id=state_id)
-    form = RemoveModalForm(request.POST or None, instance=state, request=request)
+    form = CompensationStateRemoveModalForm(request.POST or None, instance=comp, state=state, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_STATE_REMOVED,
@@ -459,8 +460,9 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
     Returns:
 
     """
+    comp = get_object_or_404(Compensation, id=id)
     action = get_object_or_404(CompensationAction, id=action_id)
-    form = RemoveModalForm(request.POST or None, instance=action, request=request)
+    form = CompensationActionRemoveModalForm(request.POST or None, instance=comp, action=action, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_ACTION_REMOVED,
diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py
index 34f979f0..f6cced09 100644
--- a/compensation/views/eco_account.py
+++ b/compensation/views/eco_account.py
@@ -16,10 +16,10 @@ from django.shortcuts import render, get_object_or_404, redirect
 
 from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm
 from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
-    NewEcoAccountDocumentForm
+    NewEcoAccountDocumentForm, CompensationActionRemoveModalForm, CompensationStateRemoveModalForm
 from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
 from compensation.tables import EcoAccountTable
-from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm
+from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, DeductionRemoveModalForm
 from konova.contexts import BaseContext
 from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
     shared_access_required
@@ -286,7 +286,7 @@ def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str):
     except ObjectDoesNotExist:
         raise Http404("Unknown deduction")
 
-    form = RemoveModalForm(request.POST or None, instance=eco_deduction, request=request)
+    form = DeductionRemoveModalForm(request.POST or None, instance=acc, deduction=eco_deduction, request=request)
     return form.process_request(
         request=request,
         msg_success=DEDUCTION_REMOVED,
@@ -401,8 +401,9 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     Returns:
 
     """
+    acc = get_object_or_404(EcoAccount, id=id)
     state = get_object_or_404(CompensationState, id=state_id)
-    form = RemoveModalForm(request.POST or None, instance=state, request=request)
+    form = CompensationStateRemoveModalForm(request.POST or None, instance=acc, state=state, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_STATE_REMOVED,
@@ -424,8 +425,9 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
     Returns:
 
     """
+    acc = get_object_or_404(EcoAccount, id=id)
     action = get_object_or_404(CompensationAction, id=action_id)
-    form = RemoveModalForm(request.POST or None, instance=action, request=request)
+    form = CompensationActionRemoveModalForm(request.POST or None, instance=acc, action=action, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_ACTION_REMOVED,
diff --git a/compensation/views/payment.py b/compensation/views/payment.py
index b715a3ae..d1fa91b2 100644
--- a/compensation/views/payment.py
+++ b/compensation/views/payment.py
@@ -11,7 +11,7 @@ from django.contrib.auth.decorators import login_required
 from django.http import HttpRequest
 from django.shortcuts import get_object_or_404
 
-from compensation.forms.modalForms import NewPaymentForm
+from compensation.forms.modalForms import NewPaymentForm, PaymentRemoveModalForm
 from compensation.models import Payment
 from intervention.models import Intervention
 from konova.decorators import default_group_required
@@ -21,39 +21,41 @@ from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED
 
 @login_required
 @default_group_required
-def new_payment_view(request: HttpRequest, intervention_id: str):
+def new_payment_view(request: HttpRequest, id: str):
     """ Renders a modal view for adding new payments
 
     Args:
         request (HttpRequest): The incoming request
-        intervention_id (str): The intervention's id for which a new payment shall be added
+        id (str): The intervention's id for which a new payment shall be added
 
     Returns:
 
     """
-    intervention = get_object_or_404(Intervention, id=intervention_id)
+    intervention = get_object_or_404(Intervention, id=id)
     form = NewPaymentForm(request.POST or None, instance=intervention, request=request)
     return form.process_request(
         request,
         msg_success=PAYMENT_ADDED,
-        redirect_url=reverse("intervention:detail", args=(intervention_id,)) + "#related_data"
+        redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
     )
 
 
 @login_required
 @default_group_required
-def payment_remove_view(request: HttpRequest, id: str):
+def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
     """ Renders a modal view for removing payments
 
     Args:
         request (HttpRequest): The incoming request
-        id (str): The payment's id
+        id (str): The intervention's id
+        payment_id (str): The payment's id
 
     Returns:
 
     """
-    payment = get_object_or_404(Payment, id=id)
-    form = RemoveModalForm(request.POST or None, instance=payment, request=request)
+    intervention = get_object_or_404(Intervention, id=id)
+    payment = get_object_or_404(Payment, id=payment_id)
+    form = PaymentRemoveModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
     return form.process_request(
         request=request,
         msg_success=PAYMENT_REMOVED,
diff --git a/ema/views.py b/ema/views.py
index 82bfcdc0..5f60c7a2 100644
--- a/ema/views.py
+++ b/ema/views.py
@@ -6,7 +6,8 @@ from django.shortcuts import render, get_object_or_404, redirect
 from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
 
-from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
+from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
+    CompensationActionRemoveModalForm, CompensationStateRemoveModalForm
 from compensation.models import CompensationAction, CompensationState
 from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentForm
 from ema.tables import EmaTable
@@ -425,8 +426,9 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     Returns:
 
     """
+    ema = get_object_or_404(Ema, id=id)
     state = get_object_or_404(CompensationState, id=state_id)
-    form = RemoveModalForm(request.POST or None, instance=state, request=request)
+    form = CompensationStateRemoveModalForm(request.POST or None, instance=ema, state=state, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_STATE_REMOVED,
@@ -448,8 +450,9 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
     Returns:
 
     """
+    ema = get_object_or_404(Ema, id=id)
     action = get_object_or_404(CompensationAction, id=action_id)
-    form = RemoveModalForm(request.POST or None, instance=action, request=request)
+    form = CompensationActionRemoveModalForm(request.POST or None, instance=ema, action=action, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_ACTION_REMOVED,
diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py
index e44649da..d9112b1e 100644
--- a/intervention/forms/modalForms.py
+++ b/intervention/forms/modalForms.py
@@ -7,7 +7,7 @@ Created on: 27.09.21
 """
 from dal import autocomplete
 
-from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED
+from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED
 from user.models import User, UserActionLogEntry
 from django.db import transaction
 from django import forms
@@ -16,7 +16,7 @@ from django.utils.translation import gettext_lazy as _
 from compensation.models import EcoAccount, EcoAccountDeduction
 from intervention.inputs import TextToClipboardInput
 from intervention.models import Intervention, InterventionDocument
-from konova.forms import BaseModalForm, NewDocumentForm
+from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
 from konova.utils.general import format_german_float
 from konova.utils.user_checks import is_default_group_only
 
@@ -172,6 +172,23 @@ class NewRevocationModalForm(BaseModalForm):
         return revocation
 
 
+class RevocationRemoveModalForm(RemoveModalForm):
+    """ Removing modal form for Revocation
+
+    Can be used for anything, where removing shall be confirmed by the user a second time.
+
+    """
+    revocation = None
+
+    def __init__(self, *args, **kwargs):
+        revocation = kwargs.pop("revocation", None)
+        self.revocation = revocation
+        super().__init__(*args, **kwargs)
+
+    def save(self):
+        self.instance.remove_revocation(self)
+
+
 class CheckModalForm(BaseModalForm):
     """ The modal form for running a check on interventions and their compensations
 
@@ -390,5 +407,25 @@ class NewDeductionModalForm(BaseModalForm):
         return deduction
 
 
+class DeductionRemoveModalForm(RemoveModalForm):
+    """ Removing modal form for EcoAccountDeduction
+
+    Can be used for anything, where removing shall be confirmed by the user a second time.
+
+    """
+    deduction = None
+
+    def __init__(self, *args, **kwargs):
+        deduction = kwargs.pop("deduction", None)
+        self.deduction = deduction
+        super().__init__(*args, **kwargs)
+
+    def save(self):
+        with transaction.atomic():
+            self.deduction.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
+            self.deduction.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
+            self.deduction.delete()
+
+
 class NewInterventionDocumentForm(NewDocumentForm):
     document_model = InterventionDocument
diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py
index 068594cb..79277339 100644
--- a/intervention/models/intervention.py
+++ b/intervention/models/intervention.py
@@ -16,7 +16,6 @@ from django.db import models, transaction
 from django.db.models import QuerySet
 from django.http import HttpRequest
 
-from compensation.models import EcoAccountDeduction
 from intervention.managers import InterventionManager
 from intervention.models.legal import Legal
 from intervention.models.responsibility import Responsibility
@@ -26,7 +25,8 @@ from konova.models import generate_document_file_upload_path, AbstractDocument,
     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, DEDUCTION_ADDED, DOCUMENT_REMOVED_TEMPLATE
+from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, DOCUMENT_REMOVED_TEMPLATE, \
+    PAYMENT_REMOVED, PAYMENT_ADDED, REVOCATION_REMOVED
 from user.models import UserActionLogEntry
 
 
@@ -196,6 +196,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
                 comment=form_data.get("comment", None),
                 intervention=self,
             )
+            self.mark_as_edited(user, form.request, edit_comment=PAYMENT_ADDED)
         return pay
 
     def add_revocation(self, form):
@@ -229,6 +230,21 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
                 )
         return revocation
 
+    def remove_revocation(self, form):
+        """ Removes a revocation from the intervention
+
+        Args:
+            form (RevocationRemoveModalForm): The form holding all relevant data
+
+        Returns:
+
+        """
+        revocation = form.revocation
+        user = form.user
+        with transaction.atomic():
+            revocation.delete()
+            self.mark_as_edited(user, request=form.request, edit_comment=REVOCATION_REMOVED)
+
     def mark_as_edited(self, performing_user: User, request: HttpRequest = None, edit_comment: str = None, reset_recorded: bool = True):
         """ In case the object or a related object changed, internal processes need to be started, such as
         unrecord and uncheck
@@ -242,7 +258,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
         Returns:
 
         """
-        action = super().mark_as_edited(performing_user, edit_comment)
+        action = super().mark_as_edited(performing_user, edit_comment=edit_comment)
         if reset_recorded:
             self.unrecord(performing_user, request)
         if self.checked:
@@ -289,6 +305,21 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
         """
         return reverse("intervention:share", args=(self.id, self.access_token))
 
+    def remove_payment(self, form):
+        """ Removes a Payment from the intervention
+
+        Args:
+            form (PaymentRemoveModalForm): The form holding all relevant data
+
+        Returns:
+
+        """
+        payment = form.payment
+        user = form.user
+        with transaction.atomic():
+            payment.delete()
+            self.mark_as_edited(user, request=form.request, edit_comment=PAYMENT_REMOVED)
+
 
 class InterventionDocument(AbstractDocument):
     """
diff --git a/intervention/models/revocation.py b/intervention/models/revocation.py
index da39b001..3f55bbf4 100644
--- a/intervention/models/revocation.py
+++ b/intervention/models/revocation.py
@@ -23,7 +23,7 @@ class Revocation(BaseResource):
     legal = models.ForeignKey("Legal", null=False, blank=False, on_delete=models.CASCADE, help_text="Refers to 'Widerspruch am'", related_name="revocations")
     comment = models.TextField(null=True, blank=True)
 
-    def delete(self, user=None, *args, **kwargs):
+    def delete(self, *args, **kwargs):
         # Make sure related objects are being removed as well
         try:
             self.document.delete(*args, **kwargs)
@@ -31,9 +31,6 @@ class Revocation(BaseResource):
             # No file to delete
             pass
 
-        if user is not None:
-            self.legal.intervention.mark_as_edited(user, edit_comment=REVOCATION_REMOVED)
-
         super().delete()
 
     @property
diff --git a/intervention/templates/intervention/detail/includes/payments.html b/intervention/templates/intervention/detail/includes/payments.html
index 53a17f8f..205755bc 100644
--- a/intervention/templates/intervention/detail/includes/payments.html
+++ b/intervention/templates/intervention/detail/includes/payments.html
@@ -56,7 +56,7 @@
                 </td>
                 <td class="align-middle">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:pay:remove' pay.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove payment' %}">
+                    <button data-form-url="{% url 'compensation:pay:remove' obj.id pay.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove payment' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/intervention/templates/intervention/detail/includes/revocation.html b/intervention/templates/intervention/detail/includes/revocation.html
index 9fb2989d..d6b07b72 100644
--- a/intervention/templates/intervention/detail/includes/revocation.html
+++ b/intervention/templates/intervention/detail/includes/revocation.html
@@ -65,7 +65,7 @@
                     </td>
                     <td class="align-middle">
                         {% if is_default_member and has_access  %}
-                        <button data-form-url="{% url 'intervention:remove-revocation' rev.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove revocation' %}">
+                        <button data-form-url="{% url 'intervention:remove-revocation' obj.id rev.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove revocation' %}">
                             {% fa5_icon 'trash' %}
                         </button>
                         {% endif %}
diff --git a/intervention/tests/test_workflow.py b/intervention/tests/test_workflow.py
index 7644051b..69f606f0 100644
--- a/intervention/tests/test_workflow.py
+++ b/intervention/tests/test_workflow.py
@@ -262,7 +262,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
         pre_payment_logs_count = self.intervention.log.count()
 
         # Create removing url for the payment
-        remove_url = reverse("compensation:pay:remove", args=(payment.id,))
+        remove_url = reverse("compensation:pay:remove", args=(self.intervention.id, payment.id,))
         post_data = {
             "confirm": True,
         }
diff --git a/intervention/urls.py b/intervention/urls.py
index 1c663124..7d6ff32f 100644
--- a/intervention/urls.py
+++ b/intervention/urls.py
@@ -28,7 +28,7 @@ urlpatterns = [
     path('<id>/report', report_view, name='report'),
 
     # Compensations
-    path('<id>/remove/<comp_id>', remove_compensation_view, name='remove-compensation'),
+    path('<id>/compensation/<comp_id>/remove', remove_compensation_view, name='remove-compensation'),
 
     # Documents
     path('<id>/document/new/', new_document_view, name='new-doc'),
@@ -37,10 +37,10 @@ urlpatterns = [
 
     # Deductions
     path('<id>/deduction/new', new_deduction_view, name='new-deduction'),
-    path('<id>/remove/<deduction_id>', remove_deduction_view, name='remove-deduction'),
+    path('<id>/deduction/<deduction_id>/remove', remove_deduction_view, name='remove-deduction'),
 
     # Revocation routes
     path('<id>/revocation/new', new_revocation_view, name='new-revocation'),
-    path('revocation/<id>/remove', remove_revocation_view, name='remove-revocation'),
+    path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'),
     path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
 ]
\ No newline at end of file
diff --git a/intervention/views.py b/intervention/views.py
index a518a9da..259f3e0f 100644
--- a/intervention/views.py
+++ b/intervention/views.py
@@ -6,7 +6,8 @@ from django.shortcuts import render
 
 from intervention.forms.forms import NewInterventionForm, EditInterventionForm
 from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \
-    CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm
+    CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, DeductionRemoveModalForm, \
+    RevocationRemoveModalForm
 from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
 from intervention.tables import InterventionTable
 from konova.contexts import BaseContext
@@ -340,7 +341,7 @@ def remove_view(request: HttpRequest, id: str):
 
 @login_required
 @default_group_required
-def remove_revocation_view(request: HttpRequest, id: str):
+def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
     """ Renders a remove view for a revocation
 
     Args:
@@ -350,13 +351,14 @@ def remove_revocation_view(request: HttpRequest, id: str):
     Returns:
 
     """
-    obj = Revocation.objects.get(id=id)
+    intervention = get_object_or_404(Intervention, id=id)
+    revocation = get_object_or_404(Revocation, id=revocation_id)
 
-    form = RemoveModalForm(request.POST or None, instance=obj, request=request)
+    form = RevocationRemoveModalForm(request.POST or None, instance=intervention, revocation=revocation, request=request)
     return form.process_request(
         request,
         REVOCATION_REMOVED,
-        redirect_url=reverse("intervention:detail", args=(obj.intervention.id,)) + "#related_data"
+        redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
     )
 
 
@@ -533,7 +535,7 @@ def remove_deduction_view(request: HttpRequest, id: str, deduction_id: str):
     except ObjectDoesNotExist:
         raise Http404("Unknown deduction")
 
-    form = RemoveModalForm(request.POST or None, instance=eco_deduction, request=request)
+    form = DeductionRemoveModalForm(request.POST or None, instance=intervention, deduction=eco_deduction, request=request)
     return form.process_request(
         request=request,
         msg_success=DEDUCTION_REMOVED,
diff --git a/konova/forms.py b/konova/forms.py
index 8b7539b3..66aeea70 100644
--- a/konova/forms.py
+++ b/konova/forms.py
@@ -327,7 +327,7 @@ class RemoveModalForm(BaseModalForm):
             self.instance.mark_as_deleted(self.user)
         else:
             # If the class does not provide restorable delete functionality, we must delete the entry finally
-            self.instance.delete(self.user)
+            self.instance.delete()
 
 
 class DeadlineRemoveModalForm(RemoveModalForm):
diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo
index c25c2f113b0cf7f234e21d82feaf2dcc8f869568..7fe3a4ee71d0b17b766706edd33f285e920fe645 100644
GIT binary patch
delta 3692
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

delta 3692
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

diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po
index eb1b60a4..f7a36715 100644
--- a/locale/de/LC_MESSAGES/django.po
+++ b/locale/de/LC_MESSAGES/django.po
@@ -1896,7 +1896,7 @@ msgstr "Zustand hinzugefügt"
 
 #: konova/utils/message_templates.py:44
 msgid "Added compensation action"
-msgstr "Maßnahme hinzufügen"
+msgstr "Maßnahme hinzugefügt"
 
 #: konova/utils/message_templates.py:47
 msgid "Geometry conflict detected with {}"
-- 
2.45.2


From 07c6f19d5c186d1b4863ff4b1bad4c4fef93ee30 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 13:27:42 +0100
Subject: [PATCH 07/31] Test enhancements

* adds more view tests to intervention tests
---
 intervention/tests/test_views.py | 44 ++++++++++++++++++++++++++++++++
 intervention/views.py            |  1 +
 2 files changed, 45 insertions(+)

diff --git a/intervention/tests/test_views.py b/intervention/tests/test_views.py
index 23da913a..2cdb299d 100644
--- a/intervention/tests/test_views.py
+++ b/intervention/tests/test_views.py
@@ -10,6 +10,7 @@ from django.test import Client
 from django.contrib.auth.models import Group
 from django.urls import reverse
 
+from intervention.models import Revocation
 from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 from konova.tests.test_views import BaseViewTestCase
 
@@ -34,6 +35,17 @@ class InterventionViewTestCase(BaseViewTestCase):
         cls.record_url = reverse("intervention:record", args=(cls.intervention.id,))
         cls.report_url = reverse("intervention:report", args=(cls.intervention.id,))
 
+        cls.deduction.intervention = cls.intervention
+        cls.deduction.save()
+        cls.deduction_new_url = reverse("intervention:new-deduction", args=(cls.intervention.id,))
+        cls.deduction_remove_url = reverse("intervention:remove-deduction", args=(cls.intervention.id, cls.deduction.id))
+
+        cls.revocation = Revocation.objects.create(
+            legal=cls.intervention.legal
+        )
+        cls.revocation_new_url = reverse("intervention:new-revocation", args=(cls.intervention.id,))
+        cls.revocation_remove_url = reverse("intervention:remove-revocation", args=(cls.intervention.id, cls.revocation.id))
+
     def test_views_anonymous_user(self):
         """ Check correct status code for all requests
 
@@ -61,6 +73,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.share_create_url: f"{login_redirect_base}{self.share_create_url}",
             self.run_check_url: f"{login_redirect_base}{self.run_check_url}",
             self.record_url: f"{login_redirect_base}{self.record_url}",
+            self.deduction_new_url: f"{login_redirect_base}{self.deduction_new_url}",
+            self.deduction_remove_url: f"{login_redirect_base}{self.deduction_remove_url}",
+            self.revocation_new_url: f"{login_redirect_base}{self.revocation_new_url}",
+            self.revocation_remove_url: f"{login_redirect_base}{self.revocation_remove_url}",
         }
 
         self.assert_url_success(client, success_urls)
@@ -96,6 +112,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.share_create_url,
             self.run_check_url,
             self.record_url,
+            self.revocation_new_url,
+            self.revocation_remove_url,
+            self.deduction_new_url,
+            self.deduction_remove_url,
         ]
 
         self.assert_url_success(client, success_urls)
@@ -128,6 +148,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.edit_url,
             self.remove_url,
             self.share_create_url,
+            self.revocation_new_url,
+            self.revocation_remove_url,
+            self.deduction_new_url,
+            self.deduction_remove_url,
         ]
         fail_urls = [
             self.run_check_url,
@@ -172,6 +196,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.remove_url,
             self.share_create_url,
             self.log_url,
+            self.revocation_new_url,
+            self.revocation_remove_url,
+            self.deduction_new_url,
+            self.deduction_remove_url,
         ]
         success_urls_redirect = {
             self.share_url: self.detail_url
@@ -212,6 +240,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.remove_url,
             self.share_create_url,
             self.record_url,
+            self.revocation_new_url,
+            self.revocation_remove_url,
+            self.deduction_new_url,
+            self.deduction_remove_url,
         ]
         success_urls_redirect = {
             self.share_url: self.detail_url
@@ -252,6 +284,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.share_create_url,
             self.record_url,
             self.run_check_url,
+            self.revocation_new_url,
+            self.revocation_remove_url,
+            self.deduction_new_url,
+            self.deduction_remove_url,
         ]
         success_urls_redirect = {
             self.share_url: self.detail_url
@@ -292,6 +328,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.remove_url,
             self.share_create_url,
             self.run_check_url,
+            self.revocation_new_url,
+            self.revocation_remove_url,
+            self.deduction_new_url,
+            self.deduction_remove_url,
         ]
         success_urls_redirect = {
             self.share_url: self.detail_url
@@ -332,6 +372,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.remove_url,
             self.share_create_url,
             self.run_check_url,
+            self.revocation_new_url,
+            self.revocation_remove_url,
+            self.deduction_new_url,
+            self.deduction_remove_url,
         ]
         # Define urls where a redirect to a specific location is the proper response
         success_urls_redirect = {
diff --git a/intervention/views.py b/intervention/views.py
index 259f3e0f..41f484c5 100644
--- a/intervention/views.py
+++ b/intervention/views.py
@@ -341,6 +341,7 @@ def remove_view(request: HttpRequest, id: str):
 
 @login_required
 @default_group_required
+@shared_access_required(Intervention, "id")
 def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
     """ Renders a remove view for a revocation
 
-- 
2.45.2


From 02ccb7808032113b83c22c8f615dd3e42d1b50f0 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 13:31:40 +0100
Subject: [PATCH 08/31] Remove form renaming

* renames new remove modal forms to match a more coherent style
---
 compensation/forms/modalForms.py    |  6 +++---
 compensation/models/compensation.py |  6 +++---
 compensation/views/compensation.py  | 10 +++++-----
 compensation/views/eco_account.py   | 14 +++++++-------
 compensation/views/payment.py       |  4 ++--
 ema/views.py                        | 10 +++++-----
 intervention/forms/modalForms.py    |  4 ++--
 intervention/models/intervention.py |  4 ++--
 intervention/views.py               |  8 ++++----
 konova/forms.py                     |  2 +-
 10 files changed, 34 insertions(+), 34 deletions(-)

diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py
index a31dda0c..ae5a6dad 100644
--- a/compensation/forms/modalForms.py
+++ b/compensation/forms/modalForms.py
@@ -103,7 +103,7 @@ class NewPaymentForm(BaseModalForm):
         return pay
 
 
-class PaymentRemoveModalForm(RemoveModalForm):
+class RemovePaymentModalForm(RemoveModalForm):
     """ Removing modal form for Payment
 
     Can be used for anything, where removing shall be confirmed by the user a second time.
@@ -235,7 +235,7 @@ class NewStateModalForm(BaseModalForm):
             raise NotImplementedError
 
 
-class CompensationStateRemoveModalForm(RemoveModalForm):
+class RemoveCompensationStateModalForm(RemoveModalForm):
     """ Removing modal form for CompensationState
 
     Can be used for anything, where removing shall be confirmed by the user a second time.
@@ -252,7 +252,7 @@ class CompensationStateRemoveModalForm(RemoveModalForm):
         self.instance.remove_state(self)
 
 
-class CompensationActionRemoveModalForm(RemoveModalForm):
+class RemoveCompensationActionModalForm(RemoveModalForm):
     """ Removing modal form for CompensationAction
 
     Can be used for anything, where removing shall be confirmed by the user a second time.
diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py
index 46476a67..e90e1735 100644
--- a/compensation/models/compensation.py
+++ b/compensation/models/compensation.py
@@ -79,7 +79,7 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
         """ Removes a deadline from the abstract compensation
 
         Args:
-            form (DeadlineRemoveModalForm): The form holding all relevant data
+            form (RemoveDeadlineModalForm): The form holding all relevant data
 
         Returns:
 
@@ -119,7 +119,7 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
         """ Removes a CompensationAction from the abstract compensation
 
         Args:
-            form (CompensationActionRemoveModalForm): The form holding all relevant data
+            form (RemoveCompensationActionModalForm): The form holding all relevant data
 
         Returns:
 
@@ -158,7 +158,7 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
         """ Removes a CompensationState from the abstract compensation
 
         Args:
-            form (CompensationStateRemoveModalForm): The form holding all relevant data
+            form (RemoveCompensationStateModalForm): The form holding all relevant data
 
         Returns:
 
diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py
index eb4dd371..c4b5c2f5 100644
--- a/compensation/views/compensation.py
+++ b/compensation/views/compensation.py
@@ -6,13 +6,13 @@ from django.utils.translation import gettext_lazy as _
 
 from compensation.forms.forms import NewCompensationForm, EditCompensationForm
 from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm, \
-    NewCompensationDocumentForm, CompensationActionRemoveModalForm, CompensationStateRemoveModalForm
+    NewCompensationDocumentForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
 from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument
 from compensation.tables import CompensationTable
 from intervention.models import Intervention
 from konova.contexts import BaseContext
 from konova.decorators import *
-from konova.forms import RemoveModalForm, SimpleGeomForm, DeadlineRemoveModalForm
+from konova.forms import RemoveModalForm, SimpleGeomForm, RemoveDeadlineModalForm
 from konova.models import Deadline
 from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 from konova.utils.documents import get_document, remove_document
@@ -414,7 +414,7 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
     """
     comp = get_object_or_404(Compensation, id=id)
     deadline = get_object_or_404(Deadline, id=deadline_id)
-    form = DeadlineRemoveModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
+    form = RemoveDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
     return form.process_request(
         request,
         msg_success=DEADLINE_REMOVED,
@@ -438,7 +438,7 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     """
     comp = get_object_or_404(Compensation, id=id)
     state = get_object_or_404(CompensationState, id=state_id)
-    form = CompensationStateRemoveModalForm(request.POST or None, instance=comp, state=state, request=request)
+    form = RemoveCompensationStateModalForm(request.POST or None, instance=comp, state=state, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_STATE_REMOVED,
@@ -462,7 +462,7 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
     """
     comp = get_object_or_404(Compensation, id=id)
     action = get_object_or_404(CompensationAction, id=action_id)
-    form = CompensationActionRemoveModalForm(request.POST or None, instance=comp, action=action, request=request)
+    form = RemoveCompensationActionModalForm(request.POST or None, instance=comp, action=action, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_ACTION_REMOVED,
diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py
index f6cced09..7d1e560f 100644
--- a/compensation/views/eco_account.py
+++ b/compensation/views/eco_account.py
@@ -16,14 +16,14 @@ from django.shortcuts import render, get_object_or_404, redirect
 
 from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm
 from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
-    NewEcoAccountDocumentForm, CompensationActionRemoveModalForm, CompensationStateRemoveModalForm
+    NewEcoAccountDocumentForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
 from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
 from compensation.tables import EcoAccountTable
-from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, DeductionRemoveModalForm
+from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm
 from konova.contexts import BaseContext
 from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
     shared_access_required
-from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm, DeadlineRemoveModalForm
+from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm, RemoveDeadlineModalForm
 from konova.models import Deadline
 from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
@@ -286,7 +286,7 @@ def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str):
     except ObjectDoesNotExist:
         raise Http404("Unknown deduction")
 
-    form = DeductionRemoveModalForm(request.POST or None, instance=acc, deduction=eco_deduction, request=request)
+    form = RemoveEcoAccountDeductionModalForm(request.POST or None, instance=acc, deduction=eco_deduction, request=request)
     return form.process_request(
         request=request,
         msg_success=DEDUCTION_REMOVED,
@@ -403,7 +403,7 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     """
     acc = get_object_or_404(EcoAccount, id=id)
     state = get_object_or_404(CompensationState, id=state_id)
-    form = CompensationStateRemoveModalForm(request.POST or None, instance=acc, state=state, request=request)
+    form = RemoveCompensationStateModalForm(request.POST or None, instance=acc, state=state, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_STATE_REMOVED,
@@ -427,7 +427,7 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
     """
     acc = get_object_or_404(EcoAccount, id=id)
     action = get_object_or_404(CompensationAction, id=action_id)
-    form = CompensationActionRemoveModalForm(request.POST or None, instance=acc, action=action, request=request)
+    form = RemoveCompensationActionModalForm(request.POST or None, instance=acc, action=action, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_ACTION_REMOVED,
@@ -451,7 +451,7 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
     """
     comp = get_object_or_404(EcoAccount, id=id)
     deadline = get_object_or_404(Deadline, id=deadline_id)
-    form = DeadlineRemoveModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
+    form = RemoveDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
     return form.process_request(
         request,
         msg_success=DEADLINE_REMOVED,
diff --git a/compensation/views/payment.py b/compensation/views/payment.py
index d1fa91b2..7be9bee8 100644
--- a/compensation/views/payment.py
+++ b/compensation/views/payment.py
@@ -11,7 +11,7 @@ from django.contrib.auth.decorators import login_required
 from django.http import HttpRequest
 from django.shortcuts import get_object_or_404
 
-from compensation.forms.modalForms import NewPaymentForm, PaymentRemoveModalForm
+from compensation.forms.modalForms import NewPaymentForm, RemovePaymentModalForm
 from compensation.models import Payment
 from intervention.models import Intervention
 from konova.decorators import default_group_required
@@ -55,7 +55,7 @@ def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
     """
     intervention = get_object_or_404(Intervention, id=id)
     payment = get_object_or_404(Payment, id=payment_id)
-    form = PaymentRemoveModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
+    form = RemovePaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
     return form.process_request(
         request=request,
         msg_success=PAYMENT_REMOVED,
diff --git a/ema/views.py b/ema/views.py
index 5f60c7a2..900dde7c 100644
--- a/ema/views.py
+++ b/ema/views.py
@@ -7,7 +7,7 @@ from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
 
 from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
-    CompensationActionRemoveModalForm, CompensationStateRemoveModalForm
+    RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
 from compensation.models import CompensationAction, CompensationState
 from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentForm
 from ema.tables import EmaTable
@@ -15,7 +15,7 @@ from intervention.forms.modalForms import ShareModalForm
 from konova.contexts import BaseContext
 from konova.decorators import conservation_office_group_required, shared_access_required
 from ema.models import Ema, EmaDocument
-from konova.forms import RemoveModalForm, SimpleGeomForm, RecordModalForm, DeadlineRemoveModalForm
+from konova.forms import RemoveModalForm, SimpleGeomForm, RecordModalForm, RemoveDeadlineModalForm
 from konova.models import Deadline
 from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
@@ -428,7 +428,7 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     """
     ema = get_object_or_404(Ema, id=id)
     state = get_object_or_404(CompensationState, id=state_id)
-    form = CompensationStateRemoveModalForm(request.POST or None, instance=ema, state=state, request=request)
+    form = RemoveCompensationStateModalForm(request.POST or None, instance=ema, state=state, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_STATE_REMOVED,
@@ -452,7 +452,7 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
     """
     ema = get_object_or_404(Ema, id=id)
     action = get_object_or_404(CompensationAction, id=action_id)
-    form = CompensationActionRemoveModalForm(request.POST or None, instance=ema, action=action, request=request)
+    form = RemoveCompensationActionModalForm(request.POST or None, instance=ema, action=action, request=request)
     return form.process_request(
         request,
         msg_success=COMPENSATION_ACTION_REMOVED,
@@ -595,7 +595,7 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
     """
     ema = get_object_or_404(Ema, id=id)
     deadline = get_object_or_404(Deadline, id=deadline_id)
-    form = DeadlineRemoveModalForm(request.POST or None, instance=ema, deadline=deadline, request=request)
+    form = RemoveDeadlineModalForm(request.POST or None, instance=ema, deadline=deadline, request=request)
     return form.process_request(
         request,
         msg_success=DEADLINE_REMOVED,
diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py
index d9112b1e..6e2a2a39 100644
--- a/intervention/forms/modalForms.py
+++ b/intervention/forms/modalForms.py
@@ -172,7 +172,7 @@ class NewRevocationModalForm(BaseModalForm):
         return revocation
 
 
-class RevocationRemoveModalForm(RemoveModalForm):
+class RemoveRevocationModalForm(RemoveModalForm):
     """ Removing modal form for Revocation
 
     Can be used for anything, where removing shall be confirmed by the user a second time.
@@ -407,7 +407,7 @@ class NewDeductionModalForm(BaseModalForm):
         return deduction
 
 
-class DeductionRemoveModalForm(RemoveModalForm):
+class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
     """ Removing modal form for EcoAccountDeduction
 
     Can be used for anything, where removing shall be confirmed by the user a second time.
diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py
index 79277339..821a835f 100644
--- a/intervention/models/intervention.py
+++ b/intervention/models/intervention.py
@@ -234,7 +234,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
         """ Removes a revocation from the intervention
 
         Args:
-            form (RevocationRemoveModalForm): The form holding all relevant data
+            form (RemoveRevocationModalForm): The form holding all relevant data
 
         Returns:
 
@@ -309,7 +309,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
         """ Removes a Payment from the intervention
 
         Args:
-            form (PaymentRemoveModalForm): The form holding all relevant data
+            form (RemovePaymentModalForm): The form holding all relevant data
 
         Returns:
 
diff --git a/intervention/views.py b/intervention/views.py
index 41f484c5..c0317035 100644
--- a/intervention/views.py
+++ b/intervention/views.py
@@ -6,8 +6,8 @@ from django.shortcuts import render
 
 from intervention.forms.forms import NewInterventionForm, EditInterventionForm
 from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \
-    CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, DeductionRemoveModalForm, \
-    RevocationRemoveModalForm
+    CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, RemoveEcoAccountDeductionModalForm, \
+    RemoveRevocationModalForm
 from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
 from intervention.tables import InterventionTable
 from konova.contexts import BaseContext
@@ -355,7 +355,7 @@ def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
     intervention = get_object_or_404(Intervention, id=id)
     revocation = get_object_or_404(Revocation, id=revocation_id)
 
-    form = RevocationRemoveModalForm(request.POST or None, instance=intervention, revocation=revocation, request=request)
+    form = RemoveRevocationModalForm(request.POST or None, instance=intervention, revocation=revocation, request=request)
     return form.process_request(
         request,
         REVOCATION_REMOVED,
@@ -536,7 +536,7 @@ def remove_deduction_view(request: HttpRequest, id: str, deduction_id: str):
     except ObjectDoesNotExist:
         raise Http404("Unknown deduction")
 
-    form = DeductionRemoveModalForm(request.POST or None, instance=intervention, deduction=eco_deduction, request=request)
+    form = RemoveEcoAccountDeductionModalForm(request.POST or None, instance=intervention, deduction=eco_deduction, request=request)
     return form.process_request(
         request=request,
         msg_success=DEDUCTION_REMOVED,
diff --git a/konova/forms.py b/konova/forms.py
index 66aeea70..ca7ac0e1 100644
--- a/konova/forms.py
+++ b/konova/forms.py
@@ -330,7 +330,7 @@ class RemoveModalForm(BaseModalForm):
             self.instance.delete()
 
 
-class DeadlineRemoveModalForm(RemoveModalForm):
+class RemoveDeadlineModalForm(RemoveModalForm):
     """ Removing modal form for deadlines
 
     Can be used for anything, where removing shall be confirmed by the user a second time.
-- 
2.45.2


From d5a3c707883f63e6c5215d7c8ac7b3562ace9be4 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 14:31:11 +0100
Subject: [PATCH 09/31] #86 Email enhancement

* adds object titles to email sending
---
 konova/models/object.py                       | 16 +++++++-----
 konova/tasks.py                               | 24 +++++++++---------
 konova/utils/mailer.py                        | 19 +++++++++-----
 .../email/checking/shared_data_checked.html   |  2 ++
 .../email/deleting/shared_data_deleted.html   |  2 ++
 .../email/recording/shared_data_recorded.html |  2 ++
 .../recording/shared_data_unrecorded.html     |  2 ++
 .../email/sharing/shared_access_given.html    |  2 ++
 .../email/sharing/shared_access_removed.html  |  2 ++
 user/models/user.py                           | 25 ++++++++++---------
 10 files changed, 60 insertions(+), 36 deletions(-)

diff --git a/konova/models/object.py b/konova/models/object.py
index 0a802ed5..06256abb 100644
--- a/konova/models/object.py
+++ b/konova/models/object.py
@@ -128,7 +128,7 @@ class BaseObject(BaseResource):
                 # Send mail
                 shared_users = self.shared_users.values_list("id", flat=True)
                 for user_id in shared_users:
-                    celery_send_mail_shared_data_deleted.delay(self.identifier, user_id)
+                    celery_send_mail_shared_data_deleted.delay(self.identifier, self.title, user_id)
 
             self.save()
 
@@ -217,6 +217,10 @@ class BaseObject(BaseResource):
         _str = "{}{}-{}".format(curr_month, curr_year, rand_str)
         return definitions[self.__class__]["template"].format(_str)
 
+    @abstractmethod
+    def get_detail_url(self):
+        raise NotImplementedError()
+
 
 class RecordableObjectMixin(models.Model):
     """ Wraps record related fields and functionality
@@ -253,7 +257,7 @@ class RecordableObjectMixin(models.Model):
 
         shared_users = self.users.all().values_list("id", flat=True)
         for user_id in shared_users:
-            celery_send_mail_shared_data_unrecorded.delay(self.identifier, user_id)
+            celery_send_mail_shared_data_unrecorded.delay(self.identifier, self.title, user_id)
 
         return action
 
@@ -275,7 +279,7 @@ class RecordableObjectMixin(models.Model):
 
         shared_users = self.users.all().values_list("id", flat=True)
         for user_id in shared_users:
-            celery_send_mail_shared_data_recorded.delay(self.identifier, user_id)
+            celery_send_mail_shared_data_recorded.delay(self.identifier, self.title, user_id)
 
         return action
 
@@ -360,7 +364,7 @@ class CheckableObjectMixin(models.Model):
         # Send mail
         shared_users = self.users.all().values_list("id", flat=True)
         for user_id in shared_users:
-            celery_send_mail_shared_data_checked.delay(self.identifier, user_id)
+            celery_send_mail_shared_data_checked.delay(self.identifier, self.title, user_id)
 
         self.log.add(action)
         return action
@@ -474,9 +478,9 @@ class ShareableObjectMixin(models.Model):
 
         # Send mails
         for user in removed_users:
-            celery_send_mail_shared_access_removed.delay(self.identifier, user["id"])
+            celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user["id"])
         for user in new_accessing_users:
-            celery_send_mail_shared_access_given.delay(self.identifier, user)
+            celery_send_mail_shared_access_given.delay(self.identifier, self.title,  user)
 
         # Set new shared users
         self.share_with_list(users)
diff --git a/konova/tasks.py b/konova/tasks.py
index a463374c..4c528038 100644
--- a/konova/tasks.py
+++ b/konova/tasks.py
@@ -19,42 +19,42 @@ def celery_update_parcels(geometry_id: str, recheck: bool = True):
 
 
 @shared_task
-def celery_send_mail_shared_access_removed(obj_identifier, user_id):
+def celery_send_mail_shared_access_removed(obj_identifier, obj_title=None, user_id=None):
     from user.models import User
     user = User.objects.get(id=user_id)
-    user.send_mail_shared_access_removed(obj_identifier)
+    user.send_mail_shared_access_removed(obj_identifier, obj_title)
 
 
 @shared_task
-def celery_send_mail_shared_access_given(obj_identifier, user_id):
+def celery_send_mail_shared_access_given(obj_identifier, obj_title=None, user_id=None):
     from user.models import User
     user = User.objects.get(id=user_id)
-    user.send_mail_shared_access_given(obj_identifier)
+    user.send_mail_shared_access_given(obj_identifier, obj_title)
 
 
 @shared_task
-def celery_send_mail_shared_data_recorded(obj_identifier, user_id):
+def celery_send_mail_shared_data_recorded(obj_identifier, obj_title=None, user_id=None):
     from user.models import User
     user = User.objects.get(id=user_id)
-    user.send_mail_shared_data_recorded(obj_identifier)
+    user.send_mail_shared_data_recorded(obj_identifier, obj_title)
 
 
 @shared_task
-def celery_send_mail_shared_data_unrecorded(obj_identifier, user_id):
+def celery_send_mail_shared_data_unrecorded(obj_identifier, obj_title=None, user_id=None):
     from user.models import User
     user = User.objects.get(id=user_id)
-    user.send_mail_shared_data_unrecorded(obj_identifier)
+    user.send_mail_shared_data_unrecorded(obj_identifier, obj_title)
 
 
 @shared_task
-def celery_send_mail_shared_data_deleted(obj_identifier, user_id):
+def celery_send_mail_shared_data_deleted(obj_identifier, obj_title=None, user_id=None):
     from user.models import User
     user = User.objects.get(id=user_id)
-    user.send_mail_shared_data_deleted(obj_identifier)
+    user.send_mail_shared_data_deleted(obj_identifier, obj_title)
 
 
 @shared_task
-def celery_send_mail_shared_data_checked(obj_identifier, user_id):
+def celery_send_mail_shared_data_checked(obj_identifier, obj_title=None, user_id=None):
     from user.models import User
     user = User.objects.get(id=user_id)
-    user.send_mail_shared_data_checked(obj_identifier)
+    user.send_mail_shared_data_checked(obj_identifier, obj_title)
diff --git a/konova/utils/mailer.py b/konova/utils/mailer.py
index e174ae56..33907f3c 100644
--- a/konova/utils/mailer.py
+++ b/konova/utils/mailer.py
@@ -45,11 +45,12 @@ class Mailer:
             auth_password=self.auth_password
         )
 
-    def send_mail_shared_access_removed(self, obj_identifier, user):
+    def send_mail_shared_access_removed(self, obj_identifier, obj_title, user):
         """ Send a mail if user has no access to the object anymore
 
         Args:
             obj_identifier (str): The object identifier
+            obj_title (str): The object title
 
         Returns:
 
@@ -57,6 +58,7 @@ class Mailer:
         context = {
             "user": user,
             "obj_identifier": obj_identifier,
+            "obj_title": obj_title,
             "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
         }
         msg = render_to_string("email/sharing/shared_access_removed.html", context)
@@ -67,7 +69,7 @@ class Mailer:
             msg
         )
 
-    def send_mail_shared_access_given(self, obj_identifier, user):
+    def send_mail_shared_access_given(self, obj_identifier, obj_title, user):
         """ Send a mail if user just got access to the object
 
         Args:
@@ -79,6 +81,7 @@ class Mailer:
         context = {
             "user": user,
             "obj_identifier": obj_identifier,
+            "obj_title": obj_title,
             "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
         }
         msg = render_to_string("email/sharing/shared_access_given.html", context)
@@ -89,7 +92,7 @@ class Mailer:
             msg
         )
 
-    def send_mail_shared_data_recorded(self, obj_identifier, user):
+    def send_mail_shared_data_recorded(self, obj_identifier, obj_title, user):
         """ Send a mail if the user's shared data has just been unrecorded
 
         Args:
@@ -101,6 +104,7 @@ class Mailer:
         context = {
             "user": user,
             "obj_identifier": obj_identifier,
+            "obj_title": obj_title,
             "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
         }
         msg = render_to_string("email/recording/shared_data_recorded.html", context)
@@ -111,7 +115,7 @@ class Mailer:
             msg
         )
 
-    def send_mail_shared_data_unrecorded(self, obj_identifier, user):
+    def send_mail_shared_data_unrecorded(self, obj_identifier, obj_title, user):
         """ Send a mail if the user's shared data has just been unrecorded
 
         Args:
@@ -123,6 +127,7 @@ class Mailer:
         context = {
             "user": user,
             "obj_identifier": obj_identifier,
+            "obj_title": obj_title,
             "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
         }
         msg = render_to_string("email/recording/shared_data_unrecorded.html", context)
@@ -133,7 +138,7 @@ class Mailer:
             msg
         )
 
-    def send_mail_shared_data_deleted(self, obj_identifier, user):
+    def send_mail_shared_data_deleted(self, obj_identifier, obj_title, user):
         """ Send a mail if shared data has just been deleted
 
         Args:
@@ -145,6 +150,7 @@ class Mailer:
         context = {
             "user": user,
             "obj_identifier": obj_identifier,
+            "obj_title": obj_title,
             "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
         }
         msg = render_to_string("email/deleting/shared_data_deleted.html", context)
@@ -155,7 +161,7 @@ class Mailer:
             msg
         )
 
-    def send_mail_shared_data_checked(self, obj_identifier, user):
+    def send_mail_shared_data_checked(self, obj_identifier, obj_title, user):
         """ Send a mail if shared data just has been checked
 
         Args:
@@ -167,6 +173,7 @@ class Mailer:
         context = {
             "user": user,
             "obj_identifier": obj_identifier,
+            "obj_title": obj_title,
             "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
         }
         msg = render_to_string("email/checking/shared_data_checked.html", context)
diff --git a/templates/email/checking/shared_data_checked.html b/templates/email/checking/shared_data_checked.html
index 0707cfbc..0b67ecc7 100644
--- a/templates/email/checking/shared_data_checked.html
+++ b/templates/email/checking/shared_data_checked.html
@@ -11,6 +11,8 @@
         <br>
         <strong>{{obj_identifier}}</strong>
         <br>
+        <strong>{{obj_title}}</strong>
+        <br>
         {% trans 'This means, the responsible registration office just confirmed the correctness of this dataset.' %}
         <br>
         <br>
diff --git a/templates/email/deleting/shared_data_deleted.html b/templates/email/deleting/shared_data_deleted.html
index b920f1ec..272b0fde 100644
--- a/templates/email/deleting/shared_data_deleted.html
+++ b/templates/email/deleting/shared_data_deleted.html
@@ -11,6 +11,8 @@
         <br>
         <strong>{{obj_identifier}}</strong>
         <br>
+        <strong>"{{obj_title}}"</strong>
+        <br>
         {% trans 'If this should not have been happened, please contact us. See the signature for details.' %}
         <br>
         <br>
diff --git a/templates/email/recording/shared_data_recorded.html b/templates/email/recording/shared_data_recorded.html
index f8db9182..6805c928 100644
--- a/templates/email/recording/shared_data_recorded.html
+++ b/templates/email/recording/shared_data_recorded.html
@@ -11,6 +11,8 @@
         <br>
         <strong>{{obj_identifier}}</strong>
         <br>
+        <strong>"{{obj_title}}"</strong>
+        <br>
         {% trans 'This means the data is now publicly available, e.g. in LANIS' %}
         <br>
         <br>
diff --git a/templates/email/recording/shared_data_unrecorded.html b/templates/email/recording/shared_data_unrecorded.html
index d4639c9e..1e0310ae 100644
--- a/templates/email/recording/shared_data_unrecorded.html
+++ b/templates/email/recording/shared_data_unrecorded.html
@@ -11,6 +11,8 @@
         <br>
         <strong>{{obj_identifier}}</strong>
         <br>
+        <strong>"{{obj_title}}"</strong>
+        <br>
         {% trans 'This means the data is no longer publicly available.' %}
         <br>
         <br>
diff --git a/templates/email/sharing/shared_access_given.html b/templates/email/sharing/shared_access_given.html
index b8b26b7a..140e7a88 100644
--- a/templates/email/sharing/shared_access_given.html
+++ b/templates/email/sharing/shared_access_given.html
@@ -11,6 +11,8 @@
         <br>
         <strong>{{obj_identifier}}</strong>
         <br>
+        <strong>"{{obj_title}}"</strong>
+        <br>
         {% trans 'This means you can now edit this dataset.' %}
         {% trans 'The shared dataset appears now by default on your overview for this dataset type.' %}
         <br>
diff --git a/templates/email/sharing/shared_access_removed.html b/templates/email/sharing/shared_access_removed.html
index 86d4fddd..d1cbc5bf 100644
--- a/templates/email/sharing/shared_access_removed.html
+++ b/templates/email/sharing/shared_access_removed.html
@@ -11,6 +11,8 @@
         <br>
         <strong>{{obj_identifier}}</strong>
         <br>
+        <strong>"{{obj_title}}"</strong>
+        <br>
         {% trans 'However, you are still able to view the dataset content.' %}
         {% trans 'Please use the provided search filter on the dataset`s overview pages to find them.' %}
         <br>
diff --git a/user/models/user.py b/user/models/user.py
index 6370f2d5..df63dd76 100644
--- a/user/models/user.py
+++ b/user/models/user.py
@@ -60,11 +60,12 @@ class User(AbstractUser):
             name=ETS_GROUP
         ).exists()
 
-    def send_mail_shared_access_removed(self, obj_identifier):
+    def send_mail_shared_access_removed(self, obj_identifier, obj_title):
         """ Sends a mail to the user in case of removed shared access
 
         Args:
             obj_identifier ():
+            obj_title ():
 
         Returns:
 
@@ -72,9 +73,9 @@ class User(AbstractUser):
         notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_ACCESS_REMOVED)
         if notification_set:
             mailer = Mailer()
-            mailer.send_mail_shared_access_removed(obj_identifier, self)
+            mailer.send_mail_shared_access_removed(obj_identifier, obj_title, self)
 
-    def send_mail_shared_access_given(self, obj_identifier):
+    def send_mail_shared_access_given(self, obj_identifier, obj_title):
         """ Sends a mail to the user in case of given shared access
 
         Args:
@@ -86,9 +87,9 @@ class User(AbstractUser):
         notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_ACCESS_GAINED)
         if notification_set:
             mailer = Mailer()
-            mailer.send_mail_shared_access_given(obj_identifier, self)
+            mailer.send_mail_shared_access_given(obj_identifier, obj_title, self)
 
-    def send_mail_shared_data_recorded(self, obj_identifier):
+    def send_mail_shared_data_recorded(self, obj_identifier, obj_title):
         """ Sends a mail to the user in case of shared data has been recorded
 
         Args:
@@ -100,9 +101,9 @@ class User(AbstractUser):
         notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_DATA_RECORDED)
         if notification_set:
             mailer = Mailer()
-            mailer.send_mail_shared_data_recorded(obj_identifier, self)
+            mailer.send_mail_shared_data_recorded(obj_identifier, obj_title, self)
 
-    def send_mail_shared_data_unrecorded(self, obj_identifier):
+    def send_mail_shared_data_unrecorded(self, obj_identifier, obj_title):
         """ Sends a mail to the user in case of shared data has been unrecorded
 
         Args:
@@ -114,9 +115,9 @@ class User(AbstractUser):
         notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_DATA_RECORDED)
         if notification_set:
             mailer = Mailer()
-            mailer.send_mail_shared_data_unrecorded(obj_identifier, self)
+            mailer.send_mail_shared_data_unrecorded(obj_identifier, obj_title, self)
 
-    def send_mail_shared_data_deleted(self, obj_identifier):
+    def send_mail_shared_data_deleted(self, obj_identifier, obj_title):
         """ Sends a mail to the user in case of shared data has been deleted
 
         Args:
@@ -128,9 +129,9 @@ class User(AbstractUser):
         notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_DATA_DELETED)
         if notification_set:
             mailer = Mailer()
-            mailer.send_mail_shared_data_deleted(obj_identifier, self)
+            mailer.send_mail_shared_data_deleted(obj_identifier, obj_title, self)
 
-    def send_mail_shared_data_checked(self, obj_identifier):
+    def send_mail_shared_data_checked(self, obj_identifier, obj_title):
         """ Sends a mail to the user in case of shared data has been deleted
 
         Args:
@@ -142,7 +143,7 @@ class User(AbstractUser):
         notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_DATA_CHECKED)
         if notification_set:
             mailer = Mailer()
-            mailer.send_mail_shared_data_checked(obj_identifier, self)
+            mailer.send_mail_shared_data_checked(obj_identifier, obj_title, self)
 
     def get_API_token(self):
         """ Getter for an API token
-- 
2.45.2


From 23c04c88834b4c481e7f9105cfbc6e43607d4d5b Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 14:51:53 +0100
Subject: [PATCH 10/31] #86 Parcel districts instead of revocation

* drops revocation column in favour of a parcel district column
---
 intervention/tables.py          |  60 ++--
 locale/de/LC_MESSAGES/django.mo | Bin 36538 -> 36677 bytes
 locale/de/LC_MESSAGES/django.po | 467 ++++++++++++++++----------------
 templates/table/gmrkng_col.html |   9 +
 4 files changed, 272 insertions(+), 264 deletions(-)
 create mode 100644 templates/table/gmrkng_col.html

diff --git a/intervention/tables.py b/intervention/tables.py
index 0ed003fa..223cd8a4 100644
--- a/intervention/tables.py
+++ b/intervention/tables.py
@@ -6,6 +6,7 @@ Created on: 01.12.20
 
 """
 from django.http import HttpRequest
+from django.template.loader import render_to_string
 from django.urls import reverse
 from django.utils.html import format_html
 from django.utils.timezone import localtime
@@ -29,6 +30,11 @@ class InterventionTable(BaseTable, TableRenderMixin):
         orderable=True,
         accessor="title",
     )
+    d = tables.Column(
+        verbose_name=_("Parcel gmrkng"),
+        orderable=True,
+        accessor="geometry",
+    )
     c = tables.Column(
         verbose_name=_("Checked"),
         orderable=True,
@@ -41,12 +47,6 @@ class InterventionTable(BaseTable, TableRenderMixin):
         empty_values=[],
         accessor="recorded",
     )
-    rev = tables.Column(
-        verbose_name=_("Revocation"),
-        orderable=True,
-        empty_values=[],
-        accessor="legal__revocation",
-    )
     e = tables.Column(
         verbose_name=_("Editable"),
         orderable=True,
@@ -117,6 +117,29 @@ class InterventionTable(BaseTable, TableRenderMixin):
         )
         return format_html(html)
 
+    def render_d(self, value, record: Intervention):
+        """ Renders the parcel district column for an intervention
+
+        Args:
+            value (str): The intervention geometry
+            record (Intervention): The intervention record
+
+        Returns:
+
+        """
+        parcels = value.parcels.all().values_list(
+            "gmrkng",
+            flat=True
+        ).distinct()
+        html = render_to_string(
+            "table/gmrkng_col.html",
+            {
+                "entries": parcels
+            }
+        )
+        return html
+
+
     def render_r(self, value, record: Intervention):
         """ Renders the recorded column for an intervention
 
@@ -162,28 +185,3 @@ class InterventionTable(BaseTable, TableRenderMixin):
         )
         return format_html(html)
 
-    def render_rev(self, value, record: Intervention):
-        """ Renders the revocation column for an intervention
-
-        Args:
-            value (str): The revocation value
-            record (Intervention): The intervention record
-
-        Returns:
-
-        """
-        html = ""
-        exists = value is not None
-        tooltip = _("No revocation")
-        if exists:
-            _date = value.date
-            added_ts = localtime(value.created.timestamp)
-            added_ts = added_ts.strftime(DEFAULT_DATE_TIME_FORMAT)
-            on = _date.strftime(DEFAULT_DATE_FORMAT)
-            tooltip = _("Revocation from {}, added on {} by {}").format(on, added_ts, value.created.user)
-        html += self.render_stop(
-            tooltip=tooltip,
-            icn_filled=exists,
-        )
-        return format_html(html)
-
diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo
index 7fe3a4ee71d0b17b766706edd33f285e920fe645..c9d4293ad8de176ef64a6005d1b1cb3428ecc471 100644
GIT binary patch
delta 10483
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
O82x?0oF$W^ivJgx^UF8@

delta 10561
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

diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po
index f7a36715..f280137d 100644
--- a/locale/de/LC_MESSAGES/django.po
+++ b/locale/de/LC_MESSAGES/django.po
@@ -5,7 +5,7 @@
 #
 #: compensation/filters.py:122 compensation/forms/modalForms.py:35
 #: compensation/forms/modalForms.py:46 compensation/forms/modalForms.py:62
-#: compensation/forms/modalForms.py:256 compensation/forms/modalForms.py:350
+#: compensation/forms/modalForms.py:306 compensation/forms/modalForms.py:399
 #: intervention/forms/forms.py:54 intervention/forms/forms.py:156
 #: intervention/forms/forms.py:168 intervention/forms/modalForms.py:124
 #: intervention/forms/modalForms.py:137 intervention/forms/modalForms.py:150
@@ -19,14 +19,14 @@
 #: konova/filters/mixins.py:353 konova/filters/mixins.py:354
 #: konova/filters/mixins.py:385 konova/filters/mixins.py:386
 #: konova/forms.py:140 konova/forms.py:241 konova/forms.py:312
-#: konova/forms.py:339 konova/forms.py:349 konova/forms.py:362
-#: konova/forms.py:374 konova/forms.py:392 user/forms.py:42
+#: konova/forms.py:356 konova/forms.py:366 konova/forms.py:379
+#: konova/forms.py:391 konova/forms.py:409 user/forms.py:42
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-03 13:37+0100\n"
+"POT-Creation-Date: 2022-02-08 14:48+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -95,7 +95,7 @@ msgstr ""
 #: analysis/templates/analysis/reports/includes/eco_account/amount.html:3
 #: analysis/templates/analysis/reports/includes/intervention/amount.html:3
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:3
-#: compensation/forms/modalForms.py:334
+#: compensation/forms/modalForms.py:383
 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:34
 #: intervention/templates/intervention/detail/includes/deductions.html:31
 msgid "Amount"
@@ -138,7 +138,7 @@ msgstr "Zuständigkeitsbereich"
 #: analysis/templates/analysis/reports/includes/intervention/laws.html:17
 #: compensation/tables.py:35
 #: compensation/templates/compensation/detail/compensation/view.html:63
-#: intervention/tables.py:33
+#: intervention/tables.py:39
 #: intervention/templates/intervention/detail/view.html:68
 #: user/models/user_action.py:20
 msgid "Checked"
@@ -157,7 +157,7 @@ msgstr "Geprüft"
 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31
 #: compensation/templates/compensation/detail/eco_account/view.html:44
 #: ema/tables.py:38 ema/templates/ema/detail/view.html:35
-#: intervention/tables.py:39
+#: intervention/tables.py:45
 #: intervention/templates/intervention/detail/view.html:82
 #: user/models/user_action.py:21
 msgid "Recorded"
@@ -213,14 +213,14 @@ msgstr "Abbuchungen"
 
 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:9
 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:11
-#: compensation/forms/modalForms.py:151
+#: compensation/forms/modalForms.py:167
 #: compensation/templates/compensation/detail/compensation/includes/states-after.html:36
 #: compensation/templates/compensation/detail/compensation/includes/states-before.html:36
 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:36
 #: compensation/templates/compensation/detail/eco_account/includes/states-before.html:36
 #: ema/templates/ema/detail/includes/states-after.html:36
 #: ema/templates/ema/detail/includes/states-before.html:36
-#: intervention/forms/modalForms.py:294
+#: intervention/forms/modalForms.py:311
 msgid "Surface"
 msgstr "Fläche"
 
@@ -284,7 +284,7 @@ msgid "Type"
 msgstr "Typ"
 
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:24
-#: intervention/forms/modalForms.py:305 intervention/forms/modalForms.py:312
+#: intervention/forms/modalForms.py:322 intervention/forms/modalForms.py:329
 #: intervention/tables.py:89
 #: intervention/templates/intervention/detail/view.html:19
 #: konova/templates/konova/includes/quickstart/interventions.html:4
@@ -295,7 +295,7 @@ msgstr "Eingriff"
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:34
 #: compensation/tables.py:226
 #: compensation/templates/compensation/detail/eco_account/view.html:19
-#: intervention/forms/modalForms.py:278 intervention/forms/modalForms.py:285
+#: intervention/forms/modalForms.py:295 intervention/forms/modalForms.py:302
 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4
 #: templates/navbars/navbar.html:34
 msgid "Eco-account"
@@ -315,7 +315,7 @@ msgstr "Nur unverzeichnete anzeigen"
 
 #: compensation/forms/forms.py:32 compensation/tables.py:25
 #: compensation/tables.py:167 ema/tables.py:28 intervention/forms/forms.py:28
-#: intervention/tables.py:23
+#: intervention/tables.py:24
 #: intervention/templates/intervention/detail/includes/compensations.html:30
 msgid "Identifier"
 msgstr "Kennung"
@@ -336,12 +336,12 @@ msgstr "Automatisch generiert"
 #: ema/tables.py:33 ema/templates/ema/detail/includes/documents.html:28
 #: ema/templates/ema/detail/view.html:31
 #: ema/templates/ema/report/report.html:12 intervention/forms/forms.py:40
-#: intervention/tables.py:28
+#: intervention/tables.py:29
 #: intervention/templates/intervention/detail/includes/compensations.html:33
 #: 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:338
+#: konova/forms.py:355
 msgid "Title"
 msgstr "Bezeichnung"
 
@@ -354,7 +354,7 @@ msgid "Compensation XY; Location ABC"
 msgstr "Kompensation XY; Flur ABC"
 
 #: compensation/forms/forms.py:57 compensation/forms/modalForms.py:61
-#: compensation/forms/modalForms.py:255 compensation/forms/modalForms.py:349
+#: compensation/forms/modalForms.py:305 compensation/forms/modalForms.py:398
 #: compensation/templates/compensation/detail/compensation/includes/actions.html:35
 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:34
 #: compensation/templates/compensation/detail/compensation/includes/documents.html:31
@@ -368,11 +368,11 @@ 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:373 konova/templates/konova/includes/comment_card.html:16
+#: konova/forms.py:390 konova/templates/konova/includes/comment_card.html:16
 msgid "Comment"
 msgstr "Kommentar"
 
-#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:351
+#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:400
 #: intervention/forms/forms.py:182
 msgid "Additional comment"
 msgstr "Zusätzlicher Kommentar"
@@ -432,7 +432,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:184 compensation/views/compensation.py:92
+#: compensation/forms/forms.py:184 compensation/views/compensation.py:94
 msgid "New compensation"
 msgstr "Neue Kompensation"
 
@@ -458,7 +458,7 @@ msgstr "Vereinbarungsdatum"
 msgid "When did the parties agree on this?"
 msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?"
 
-#: compensation/forms/forms.py:354 compensation/views/eco_account.py:102
+#: compensation/forms/forms.py:354 compensation/views/eco_account.py:103
 msgid "New Eco-Account"
 msgstr "Neues Ökokonto"
 
@@ -483,8 +483,8 @@ msgstr "Fällig am"
 msgid "Due on which date"
 msgstr "Zahlung wird an diesem Datum erwartet"
 
-#: compensation/forms/modalForms.py:63 compensation/forms/modalForms.py:257
-#: intervention/forms/modalForms.py:151 konova/forms.py:375
+#: compensation/forms/modalForms.py:63 compensation/forms/modalForms.py:307
+#: intervention/forms/modalForms.py:151 konova/forms.py:392
 msgid "Additional comment, maximum {} letters"
 msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
 
@@ -496,47 +496,47 @@ msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen"
 msgid "If there is no date you can enter, please explain why."
 msgstr "Falls Sie kein Datum angeben können, erklären Sie bitte weshalb."
 
-#: compensation/forms/modalForms.py:115 compensation/forms/modalForms.py:127
+#: compensation/forms/modalForms.py:131 compensation/forms/modalForms.py:143
 msgid "Biotope Type"
 msgstr "Biotoptyp"
 
-#: compensation/forms/modalForms.py:118
+#: compensation/forms/modalForms.py:134
 msgid "Select the biotope type"
 msgstr "Biotoptyp wählen"
 
-#: compensation/forms/modalForms.py:132 compensation/forms/modalForms.py:144
+#: compensation/forms/modalForms.py:148 compensation/forms/modalForms.py:160
 msgid "Biotope additional type"
 msgstr "Zusatzbezeichnung"
 
-#: compensation/forms/modalForms.py:135
+#: compensation/forms/modalForms.py:151
 msgid "Select an additional biotope type"
 msgstr "Zusatzbezeichnung wählen"
 
-#: compensation/forms/modalForms.py:154 intervention/forms/modalForms.py:296
+#: compensation/forms/modalForms.py:170 intervention/forms/modalForms.py:313
 msgid "in m²"
 msgstr ""
 
-#: compensation/forms/modalForms.py:165
+#: compensation/forms/modalForms.py:181
 msgid "New state"
 msgstr "Neuer Zustand"
 
-#: compensation/forms/modalForms.py:166
+#: compensation/forms/modalForms.py:182
 msgid "Insert data for the new state"
 msgstr "Geben Sie die Daten des neuen Zustandes ein"
 
-#: compensation/forms/modalForms.py:173 konova/forms.py:190
+#: compensation/forms/modalForms.py:189 konova/forms.py:190
 msgid "Object removed"
 msgstr "Objekt entfernt"
 
-#: compensation/forms/modalForms.py:227
+#: compensation/forms/modalForms.py:277
 msgid "Deadline Type"
 msgstr "Fristart"
 
-#: compensation/forms/modalForms.py:230
+#: compensation/forms/modalForms.py:280
 msgid "Select the deadline type"
 msgstr "Fristart wählen"
 
-#: compensation/forms/modalForms.py:239
+#: compensation/forms/modalForms.py:289
 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31
 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31
 #: ema/templates/ema/detail/includes/deadlines.html:31
@@ -544,27 +544,27 @@ msgstr "Fristart wählen"
 msgid "Date"
 msgstr "Datum"
 
-#: compensation/forms/modalForms.py:242
+#: compensation/forms/modalForms.py:292
 msgid "Select date"
 msgstr "Datum wählen"
 
-#: compensation/forms/modalForms.py:269
+#: compensation/forms/modalForms.py:319
 msgid "New deadline"
 msgstr "Neue Frist"
 
-#: compensation/forms/modalForms.py:270
+#: compensation/forms/modalForms.py:320
 msgid "Insert data for the new deadline"
 msgstr "Geben Sie die Daten der neuen Frist ein"
 
-#: compensation/forms/modalForms.py:288
+#: compensation/forms/modalForms.py:337
 msgid "Action Type"
 msgstr "Maßnahmentyp"
 
-#: compensation/forms/modalForms.py:291
+#: compensation/forms/modalForms.py:340
 msgid "Select the action type"
 msgstr "Maßnahmentyp wählen"
 
-#: compensation/forms/modalForms.py:300
+#: compensation/forms/modalForms.py:349
 #: compensation/templates/compensation/detail/compensation/includes/actions.html:40
 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:39
 #: compensation/templates/compensation/detail/compensation/includes/documents.html:36
@@ -590,62 +590,58 @@ msgstr "Maßnahmentyp wählen"
 msgid "Action"
 msgstr "Aktionen"
 
-#: compensation/forms/modalForms.py:305 compensation/forms/modalForms.py:317
+#: compensation/forms/modalForms.py:354 compensation/forms/modalForms.py:366
 msgid "Action Type detail"
 msgstr "Zusatzmerkmal"
 
-#: compensation/forms/modalForms.py:308
+#: compensation/forms/modalForms.py:357
 msgid "Select the action type detail"
 msgstr "Zusatzmerkmal wählen"
 
-#: compensation/forms/modalForms.py:322
+#: compensation/forms/modalForms.py:371
 msgid "Unit"
 msgstr "Einheit"
 
-#: compensation/forms/modalForms.py:325
+#: compensation/forms/modalForms.py:374
 msgid "Select the unit"
 msgstr "Einheit wählen"
 
-#: compensation/forms/modalForms.py:337
+#: compensation/forms/modalForms.py:386
 msgid "Insert the amount"
 msgstr "Menge eingeben"
 
-#: compensation/forms/modalForms.py:362
+#: compensation/forms/modalForms.py:411
 msgid "New action"
 msgstr "Neue Maßnahme"
 
-#: compensation/forms/modalForms.py:363
+#: compensation/forms/modalForms.py:412
 msgid "Insert data for the new action"
 msgstr "Geben Sie die Daten der neuen Maßnahme ein"
 
-#: compensation/models/action.py:21
+#: compensation/models/action.py:22
 msgid "cm"
 msgstr ""
 
-#: compensation/models/action.py:22
+#: compensation/models/action.py:23
 msgid "m"
 msgstr ""
 
-#: compensation/models/action.py:23
+#: compensation/models/action.py:24
 msgid "km"
 msgstr ""
 
-#: compensation/models/action.py:24
+#: compensation/models/action.py:25
 msgid "m²"
 msgstr ""
 
-#: compensation/models/action.py:25
+#: compensation/models/action.py:26
 msgid "ha"
 msgstr ""
 
-#: compensation/models/action.py:26
+#: compensation/models/action.py:27
 msgid "Pieces"
 msgstr "Stück"
 
-#: compensation/models/compensation.py:63 konova/utils/message_templates.py:43
-msgid "Added deadline"
-msgstr "Frist/Termin hinzugefügt"
-
 #: compensation/models/eco_account.py:56
 msgid ""
 "Deductable surface can not be larger than existing surfaces in after states"
@@ -689,23 +685,23 @@ msgstr "Am {} von {} geprüft worden"
 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:58
 #: compensation/templates/compensation/detail/eco_account/view.html:47
 #: ema/tables.py:102 ema/templates/ema/detail/view.html:38
-#: intervention/tables.py:132
+#: intervention/tables.py:146
 #: intervention/templates/intervention/detail/view.html:85
 msgid "Not recorded yet"
 msgstr "Noch nicht verzeichnet"
 
 #: compensation/tables.py:135 compensation/tables.py:264 ema/tables.py:107
-#: intervention/tables.py:137
+#: intervention/tables.py:151
 msgid "Recorded on {} by {}"
 msgstr "Am {} von {} verzeichnet worden"
 
 #: compensation/tables.py:159 compensation/tables.py:286 ema/tables.py:130
-#: intervention/tables.py:160
+#: intervention/tables.py:174
 msgid "Full access granted"
 msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden"
 
 #: compensation/tables.py:159 compensation/tables.py:286 ema/tables.py:130
-#: intervention/tables.py:160
+#: intervention/tables.py:174
 msgid "Access not granted"
 msgstr "Nicht freigegeben - Datensatz nur lesbar"
 
@@ -821,7 +817,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:391
+#: konova/forms.py:408
 msgid "Add new document"
 msgstr "Neues Dokument hinzufügen"
 
@@ -1076,111 +1072,76 @@ msgstr ""
 msgid "Responsible data"
 msgstr "Daten zu den verantwortlichen Stellen"
 
-#: compensation/views/compensation.py:48
+#: compensation/views/compensation.py:50
 msgid "Compensations - Overview"
 msgstr "Kompensationen - Übersicht"
 
-#: compensation/views/compensation.py:147
+#: compensation/views/compensation.py:149 konova/utils/message_templates.py:27
 msgid "Compensation {} edited"
 msgstr "Kompensation {} bearbeitet"
 
-#: compensation/views/compensation.py:157 compensation/views/eco_account.py:160
-#: ema/views.py:227 intervention/views.py:311
+#: compensation/views/compensation.py:159 compensation/views/eco_account.py:161
+#: ema/views.py:230 intervention/views.py:313
 msgid "Edit {}"
 msgstr "Bearbeite {}"
 
-#: compensation/views/compensation.py:236 compensation/views/eco_account.py:316
-#: ema/views.py:188 intervention/views.py:487
+#: compensation/views/compensation.py:238 compensation/views/eco_account.py:317
+#: ema/views.py:191 intervention/views.py:491
 msgid "Log"
 msgstr "Log"
 
-#: compensation/views/compensation.py:280 compensation/views/eco_account.py:496
-#: ema/views.py:359 intervention/views.py:133
-msgid "Document added"
-msgstr "Dokument hinzugefügt"
-
-#: compensation/views/compensation.py:350 compensation/views/eco_account.py:362
-#: ema/views.py:294
-msgid "State added"
-msgstr "Zustand hinzugefügt"
-
-#: compensation/views/compensation.py:372 compensation/views/eco_account.py:384
-#: ema/views.py:316
-msgid "Action added"
-msgstr "Maßnahme hinzugefügt"
-
-#: compensation/views/compensation.py:394 compensation/views/eco_account.py:475
-#: ema/views.py:338
-msgid "Deadline added"
-msgstr "Frist/Termin hinzugefügt"
-
-#: compensation/views/compensation.py:417 compensation/views/eco_account.py:453
-#: ema/views.py:595
-msgid "Deadline removed"
-msgstr "Frist/Termin gelöscht"
-
-#: compensation/views/compensation.py:440 compensation/views/eco_account.py:407
-#: ema/views.py:430
-msgid "State removed"
-msgstr "Zustand gelöscht"
-
-#: compensation/views/compensation.py:463 compensation/views/eco_account.py:430
-#: ema/views.py:453
-msgid "Action removed"
-msgstr "Maßnahme entfernt"
-
-#: compensation/views/compensation.py:482 compensation/views/eco_account.py:586
-#: ema/views.py:472 intervention/views.py:580
+#: compensation/views/compensation.py:487 compensation/views/eco_account.py:590
+#: ema/views.py:477 intervention/views.py:609
 msgid "Report {}"
 msgstr "Bericht {}"
 
-#: compensation/views/eco_account.py:59
+#: compensation/views/eco_account.py:60
 msgid "Eco-account - Overview"
 msgstr "Ökokonten - Übersicht"
 
-#: compensation/views/eco_account.py:92
+#: compensation/views/eco_account.py:93
 msgid "Eco-Account {} added"
 msgstr "Ökokonto {} hinzugefügt"
 
-#: compensation/views/eco_account.py:150
+#: compensation/views/eco_account.py:151
 msgid "Eco-Account {} edited"
 msgstr "Ökokonto {} bearbeitet"
 
-#: compensation/views/eco_account.py:263
+#: compensation/views/eco_account.py:264
 msgid "Eco-account removed"
 msgstr "Ökokonto entfernt"
 
-#: compensation/views/eco_account.py:337 ema/views.py:269
-#: intervention/views.py:558
+#: compensation/views/eco_account.py:338 ema/views.py:272
+#: intervention/views.py:562
 msgid "{} unrecorded"
 msgstr "{} entzeichnet"
 
-#: compensation/views/eco_account.py:337 ema/views.py:269
-#: intervention/views.py:558
+#: compensation/views/eco_account.py:338 ema/views.py:272
+#: intervention/views.py:562
 msgid "{} recorded"
 msgstr "{} verzeichnet"
 
-#: compensation/views/eco_account.py:659 ema/views.py:538
-#: intervention/views.py:384
+#: compensation/views/eco_account.py:663 ema/views.py:543
+#: intervention/views.py:388
 msgid "{} has already been shared with you"
 msgstr "{} wurde bereits für Sie freigegeben"
 
-#: compensation/views/eco_account.py:664 ema/views.py:543
-#: intervention/views.py:389
+#: compensation/views/eco_account.py:668 ema/views.py:548
+#: intervention/views.py:393
 msgid "{} has been shared with you"
 msgstr "{} ist nun für Sie freigegeben"
 
-#: compensation/views/eco_account.py:671 ema/views.py:550
-#: intervention/views.py:396
+#: compensation/views/eco_account.py:675 ema/views.py:555
+#: intervention/views.py:400
 msgid "Share link invalid"
 msgstr "Freigabelink ungültig"
 
-#: compensation/views/eco_account.py:694 ema/views.py:573
-#: intervention/views.py:419
+#: compensation/views/eco_account.py:698 ema/views.py:578
+#: intervention/views.py:423
 msgid "Share settings updated"
 msgstr "Freigabe Einstellungen aktualisiert"
 
-#: ema/forms.py:40 ema/views.py:92
+#: ema/forms.py:40 ema/views.py:95
 msgid "New EMA"
 msgstr "Neue EMA hinzufügen"
 
@@ -1208,19 +1169,19 @@ msgstr ""
 msgid "Payment funded compensation"
 msgstr "Ersatzzahlungsmaßnahme"
 
-#: ema/views.py:49
+#: ema/views.py:52
 msgid "EMAs - Overview"
 msgstr "EMAs - Übersicht"
 
-#: ema/views.py:82
+#: ema/views.py:85
 msgid "EMA {} added"
 msgstr "EMA {} hinzugefügt"
 
-#: ema/views.py:217
+#: ema/views.py:220
 msgid "EMA {} edited"
 msgstr "EMA {} bearbeitet"
 
-#: ema/views.py:250
+#: ema/views.py:253
 msgid "EMA removed"
 msgstr "EMA entfernt"
 
@@ -1281,7 +1242,7 @@ msgstr "Datum Zulassung bzw. Satzungsbeschluss"
 msgid "Binding on"
 msgstr "Datum Bestandskraft"
 
-#: intervention/forms/forms.py:193 intervention/views.py:92
+#: intervention/forms/forms.py:193 intervention/views.py:94
 msgid "New intervention"
 msgstr "Neuer Eingriff"
 
@@ -1335,20 +1296,20 @@ msgstr "Muss kleiner als 15 Mb sein"
 msgid "Add revocation"
 msgstr "Widerspruch hinzufügen"
 
-#: intervention/forms/modalForms.py:180
+#: intervention/forms/modalForms.py:197
 msgid "Checked intervention data"
 msgstr "Eingriffsdaten geprüft"
 
-#: intervention/forms/modalForms.py:186
+#: intervention/forms/modalForms.py:203
 msgid "Checked compensations data and payments"
 msgstr "Kompensationen und Zahlungen geprüft"
 
-#: intervention/forms/modalForms.py:195
+#: intervention/forms/modalForms.py:212
 #: intervention/templates/intervention/detail/includes/controls.html:19
 msgid "Run check"
 msgstr "Prüfung vornehmen"
 
-#: intervention/forms/modalForms.py:196 konova/forms.py:457
+#: intervention/forms/modalForms.py:213 konova/forms.py:474
 msgid ""
 "I, {} {}, confirm that all necessary control steps have been performed by "
 "myself."
@@ -1356,23 +1317,23 @@ msgstr ""
 "Ich, {} {}, bestätige, dass die notwendigen Kontrollschritte durchgeführt "
 "wurden:"
 
-#: intervention/forms/modalForms.py:280
+#: intervention/forms/modalForms.py:297
 msgid "Only recorded accounts can be selected for deductions"
 msgstr "Nur verzeichnete Ökokonten können für Abbuchungen verwendet werden."
 
-#: intervention/forms/modalForms.py:307
+#: intervention/forms/modalForms.py:324
 msgid "Only shared interventions can be selected"
 msgstr "Nur freigegebene Eingriffe können gewählt werden"
 
-#: intervention/forms/modalForms.py:320
+#: intervention/forms/modalForms.py:337
 msgid "New Deduction"
 msgstr "Neue Abbuchung"
 
-#: intervention/forms/modalForms.py:321
+#: intervention/forms/modalForms.py:338
 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:349
+#: intervention/forms/modalForms.py:366
 msgid ""
 "Eco-account {} is not recorded yet. You can only deduct from recorded "
 "accounts."
@@ -1380,7 +1341,7 @@ msgstr ""
 "Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von "
 "verzeichneten Ökokonten erfolgen."
 
-#: intervention/forms/modalForms.py:362
+#: intervention/forms/modalForms.py:379
 msgid ""
 "The account {} has not enough surface for a deduction of {} m². There are "
 "only {} m² left"
@@ -1388,18 +1349,9 @@ msgstr ""
 "Das Ökokonto {} hat für eine Abbuchung von {} m² nicht ausreichend "
 "Restfläche. Es stehen noch {} m² zur Verfügung."
 
-#: intervention/tables.py:45
-#: intervention/templates/intervention/detail/includes/revocation.html:58
-msgid "Revocation"
-msgstr "Widerspruch"
-
-#: intervention/tables.py:177
-msgid "No revocation"
-msgstr "Kein Widerspruch"
-
-#: intervention/tables.py:183
-msgid "Revocation from {}, added on {} by {}"
-msgstr "Widerspruch vom {}, am {} von {} hinzugefügt"
+#: intervention/tables.py:34 konova/filters/mixins.py:98
+msgid "Parcel gmrkng"
+msgstr "Gemarkung"
 
 #: intervention/templates/intervention/detail/includes/compensations.html:14
 msgid "Add new compensation"
@@ -1449,6 +1401,10 @@ msgctxt "Revocation"
 msgid "From"
 msgstr "Vom"
 
+#: intervention/templates/intervention/detail/includes/revocation.html:58
+msgid "Revocation"
+msgstr "Widerspruch"
+
 #: intervention/templates/intervention/detail/includes/revocation.html:69
 msgid "Remove revocation"
 msgstr "Widerspruch entfernen"
@@ -1465,6 +1421,17 @@ msgstr "Abbuchungen von Ökokonten"
 msgid "Exist"
 msgstr "Vorhanden"
 
+#: intervention/templates/intervention/table/gmrkng_col.html:6
+msgid ""
+"\n"
+"If the geometry is not empty, the parcels are currently recalculated. Please "
+"refresh this page in a few moments."
+msgstr ""
+"\n"
+"Falls die Geometrie nicht leer ist, werden die Flurstücke aktuell berechnet. "
+"Bitte laden Sie diese Seite in ein paar Augenblicken erneut... \n"
+"            "
+
 #: intervention/utils/quality.py:70
 msgid "Revocations exists"
 msgstr "Widersprüche liegen vor"
@@ -1477,37 +1444,37 @@ msgstr "Datum Bestandskraft"
 msgid "Laws"
 msgstr "Gesetze"
 
-#: intervention/utils/quality.py:98
+#: intervention/utils/quality.py:101
 msgid "No compensation of any type found (Compensation, Payment, Deduction)"
 msgstr ""
 "Kein Ausgleich jeglicher Art gefunden (Kompensation, Ersatzzahlung, "
 "Abbuchung)"
 
-#: intervention/views.py:49
+#: intervention/views.py:51
 msgid "Interventions - Overview"
 msgstr "Eingriffe - Übersicht"
 
-#: intervention/views.py:82
+#: intervention/views.py:84
 msgid "Intervention {} added"
 msgstr "Eingriff {} hinzugefügt"
 
-#: intervention/views.py:250
+#: intervention/views.py:252
 msgid "This intervention has {} revocations"
 msgstr "Dem Eingriff liegen {} Widersprüche vor"
 
-#: intervention/views.py:299
+#: intervention/views.py:301
 msgid "Intervention {} edited"
 msgstr "Eingriff {} bearbeitet"
 
-#: intervention/views.py:335
+#: intervention/views.py:337
 msgid "{} removed"
 msgstr "{} entfernt"
 
-#: intervention/views.py:440
+#: intervention/views.py:444
 msgid "Check performed"
 msgstr "Prüfung durchgeführt"
 
-#: intervention/views.py:563
+#: intervention/views.py:567
 msgid "There are errors on this intervention:"
 msgstr "Es liegen Fehler in diesem Eingriff vor:"
 
@@ -1544,10 +1511,6 @@ msgstr "Kreis"
 msgid "Search for district"
 msgstr "Nach Kreis suchen"
 
-#: konova/filters/mixins.py:98
-msgid "Parcel gmrkng"
-msgstr "Gemarkung"
-
 #: konova/filters/mixins.py:99
 msgid "Search for parcel gmrkng"
 msgstr "Nach Gemarkung suchen"
@@ -1624,52 +1587,52 @@ msgstr "Geometrie"
 msgid "Are you sure?"
 msgstr "Sind Sie sicher?"
 
-#: konova/forms.py:348
+#: konova/forms.py:365
 msgid "Created on"
 msgstr "Erstellt"
 
-#: konova/forms.py:350
+#: konova/forms.py:367
 msgid "When has this file been created? Important for photos."
 msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?"
 
-#: konova/forms.py:361
+#: konova/forms.py:378
 #: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231
 msgid "File"
 msgstr "Datei"
 
-#: konova/forms.py:363
+#: konova/forms.py:380
 msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
 msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
 
-#: konova/forms.py:409
+#: konova/forms.py:426
 msgid "Unsupported file type"
 msgstr "Dateiformat nicht unterstützt"
 
-#: konova/forms.py:416
+#: konova/forms.py:433
 msgid "File too large"
 msgstr "Datei zu groß"
 
-#: konova/forms.py:425
+#: konova/forms.py:442
 msgid "Added document"
 msgstr "Dokument hinzugefügt"
 
-#: konova/forms.py:448
+#: konova/forms.py:465
 msgid "Confirm record"
 msgstr "Verzeichnen bestätigen"
 
-#: konova/forms.py:456
+#: konova/forms.py:473
 msgid "Record data"
 msgstr "Daten verzeichnen"
 
-#: konova/forms.py:463
+#: konova/forms.py:480
 msgid "Confirm unrecord"
 msgstr "Entzeichnen bestätigen"
 
-#: konova/forms.py:464
+#: konova/forms.py:481
 msgid "Unrecord data"
 msgstr "Daten entzeichnen"
 
-#: konova/forms.py:465
+#: konova/forms.py:482
 msgid "I, {} {}, confirm that this data must be unrecorded."
 msgstr ""
 "Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
@@ -1772,35 +1735,31 @@ msgstr "In Zwischenablage kopieren"
 msgid "Copied to clipboard"
 msgstr "In Zwischenablage kopiert"
 
-#: konova/utils/documents.py:52
-msgid "Document '{}' deleted"
-msgstr "Dokument '{}' gelöscht"
-
-#: konova/utils/mailer.py:66
+#: konova/utils/mailer.py:68
 msgid "{} - Shared access removed"
 msgstr "{} - Zugriff entzogen"
 
-#: konova/utils/mailer.py:88
+#: konova/utils/mailer.py:91
 msgid "{} - Shared access given"
 msgstr "{} - Zugriff freigegeben"
 
-#: konova/utils/mailer.py:110
+#: konova/utils/mailer.py:114
 msgid "{} - Shared data recorded"
 msgstr "{} - Freigegebene Daten verzeichnet"
 
-#: konova/utils/mailer.py:132
+#: konova/utils/mailer.py:137
 msgid "{} - Shared data unrecorded"
 msgstr "{} - Freigegebene Daten entzeichnet"
 
-#: konova/utils/mailer.py:154
+#: konova/utils/mailer.py:160
 msgid "{} - Shared data deleted"
 msgstr "{} - Freigegebene Daten gelöscht"
 
-#: konova/utils/mailer.py:176
+#: konova/utils/mailer.py:183
 msgid "{} - Shared data checked"
 msgstr "{} - Freigegebene Daten geprüft"
 
-#: konova/utils/mailer.py:197 templates/email/api/verify_token.html:4
+#: konova/utils/mailer.py:204 templates/email/api/verify_token.html:4
 msgid "Request for new API token"
 msgstr "Anfrage für neuen API Token"
 
@@ -1862,43 +1821,79 @@ msgstr "Kompensation {} hinzugefügt"
 msgid "Compensation {} removed"
 msgstr "Kompensation {} entfernt"
 
-#: konova/utils/message_templates.py:29
-msgid "Deduction added"
-msgstr "Abbuchung hinzugefügt"
-
-#: konova/utils/message_templates.py:30
-msgid "Deduction removed"
-msgstr "Abbuchung entfernt"
-
-#: konova/utils/message_templates.py:33
-msgid "Payment added"
-msgstr "Zahlung hinzugefügt"
-
-#: konova/utils/message_templates.py:34
-msgid "Payment removed"
-msgstr "Zahlung gelöscht"
-
-#: konova/utils/message_templates.py:37
-msgid "Revocation added"
-msgstr "Widerspruch hinzugefügt"
-
-#: konova/utils/message_templates.py:38
-msgid "Revocation removed"
-msgstr "Widerspruch entfernt"
-
-#: konova/utils/message_templates.py:41
-msgid "Edited general data"
-msgstr "Allgemeine Daten bearbeitet"
-
-#: konova/utils/message_templates.py:42
-msgid "Added compensation state"
-msgstr "Zustand hinzugefügt"
-
-#: konova/utils/message_templates.py:44
+#: konova/utils/message_templates.py:28
 msgid "Added compensation action"
 msgstr "Maßnahme hinzugefügt"
 
-#: konova/utils/message_templates.py:47
+#: konova/utils/message_templates.py:29
+msgid "Added compensation state"
+msgstr "Zustand hinzugefügt"
+
+#: konova/utils/message_templates.py:32
+msgid "State removed"
+msgstr "Zustand gelöscht"
+
+#: konova/utils/message_templates.py:33
+msgid "State added"
+msgstr "Zustand hinzugefügt"
+
+#: konova/utils/message_templates.py:36
+msgid "Action added"
+msgstr "Maßnahme hinzugefügt"
+
+#: konova/utils/message_templates.py:37
+msgid "Action removed"
+msgstr "Maßnahme entfernt"
+
+#: konova/utils/message_templates.py:40
+msgid "Deduction added"
+msgstr "Abbuchung hinzugefügt"
+
+#: konova/utils/message_templates.py:41
+msgid "Deduction removed"
+msgstr "Abbuchung entfernt"
+
+#: konova/utils/message_templates.py:44
+msgid "Deadline added"
+msgstr "Frist/Termin hinzugefügt"
+
+#: konova/utils/message_templates.py:45
+msgid "Deadline removed"
+msgstr "Frist/Termin gelöscht"
+
+#: konova/utils/message_templates.py:48
+msgid "Payment added"
+msgstr "Zahlung hinzugefügt"
+
+#: konova/utils/message_templates.py:49
+msgid "Payment removed"
+msgstr "Zahlung gelöscht"
+
+#: konova/utils/message_templates.py:52
+msgid "Revocation added"
+msgstr "Widerspruch hinzugefügt"
+
+#: konova/utils/message_templates.py:53
+msgid "Revocation removed"
+msgstr "Widerspruch entfernt"
+
+#: konova/utils/message_templates.py:56
+msgid "Document '{}' deleted"
+msgstr "Dokument '{}' gelöscht"
+
+#: konova/utils/message_templates.py:57
+msgid "Document added"
+msgstr "Dokument hinzugefügt"
+
+#: konova/utils/message_templates.py:60
+msgid "Edited general data"
+msgstr "Allgemeine Daten bearbeitet"
+
+#: konova/utils/message_templates.py:61
+msgid "Added deadline"
+msgstr "Frist/Termin hinzugefügt"
+
+#: konova/utils/message_templates.py:64
 msgid "Geometry conflict detected with {}"
 msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}"
 
@@ -1984,12 +1979,12 @@ msgstr ""
 "Admin Backend aktiviert worden ist."
 
 #: templates/email/api/verify_token.html:18
-#: templates/email/checking/shared_data_checked.html:17
-#: templates/email/deleting/shared_data_deleted.html:17
-#: templates/email/recording/shared_data_recorded.html:17
-#: templates/email/recording/shared_data_unrecorded.html:17
-#: templates/email/sharing/shared_access_given.html:18
-#: templates/email/sharing/shared_access_removed.html:18
+#: templates/email/checking/shared_data_checked.html:19
+#: templates/email/deleting/shared_data_deleted.html:19
+#: templates/email/recording/shared_data_recorded.html:19
+#: templates/email/recording/shared_data_unrecorded.html:19
+#: templates/email/sharing/shared_access_given.html:20
+#: templates/email/sharing/shared_access_removed.html:20
 msgid "Best regards"
 msgstr "Beste Grüße"
 
@@ -2010,7 +2005,7 @@ msgstr "Hallo "
 msgid "the following dataset has just been checked"
 msgstr "der folgende Datensatz wurde soeben geprüft "
 
-#: templates/email/checking/shared_data_checked.html:14
+#: templates/email/checking/shared_data_checked.html:16
 msgid ""
 "This means, the responsible registration office just confirmed the "
 "correctness of this dataset."
@@ -2026,7 +2021,7 @@ msgstr "Freigegebene Daten gelöscht"
 msgid "the following dataset has just been deleted"
 msgstr "der folgende Datensatz wurde soeben gelöscht "
 
-#: templates/email/deleting/shared_data_deleted.html:14
+#: templates/email/deleting/shared_data_deleted.html:16
 msgid ""
 "If this should not have been happened, please contact us. See the signature "
 "for details."
@@ -2042,12 +2037,12 @@ msgstr "Freigegebene Daten verzeichnet"
 msgid "the following dataset has just been recorded"
 msgstr "der folgende Datensatz wurde soeben verzeichnet "
 
-#: templates/email/recording/shared_data_recorded.html:14
+#: templates/email/recording/shared_data_recorded.html:16
 msgid "This means the data is now publicly available, e.g. in LANIS"
 msgstr ""
 "Das bedeutet, dass die Daten nun öffentlich verfügbar sind, z.B. im LANIS."
 
-#: templates/email/recording/shared_data_recorded.html:24
+#: templates/email/recording/shared_data_recorded.html:26
 msgid ""
 "Please note: Recorded intervention means the compensations are recorded as "
 "well."
@@ -2063,11 +2058,11 @@ msgstr "Freigegebene Daten entzeichnet"
 msgid "the following dataset has just been unrecorded"
 msgstr "der folgende Datensatz wurde soeben entzeichnet "
 
-#: templates/email/recording/shared_data_unrecorded.html:14
+#: templates/email/recording/shared_data_unrecorded.html:16
 msgid "This means the data is no longer publicly available."
 msgstr "Das bedeutet, dass die Daten nicht länger öffentlich verfügbar sind."
 
-#: templates/email/recording/shared_data_unrecorded.html:24
+#: templates/email/recording/shared_data_unrecorded.html:26
 msgid ""
 "Please note: Unrecorded intervention means the compensations are unrecorded "
 "as well."
@@ -2083,11 +2078,11 @@ msgstr "Zugriff freigegeben"
 msgid "the following dataset has just been shared with you"
 msgstr "der folgende Datensatz wurde soeben für Sie freigegeben "
 
-#: templates/email/sharing/shared_access_given.html:14
+#: templates/email/sharing/shared_access_given.html:16
 msgid "This means you can now edit this dataset."
 msgstr "Das bedeutet, dass Sie diesen Datensatz nun auch bearbeiten können."
 
-#: templates/email/sharing/shared_access_given.html:15
+#: templates/email/sharing/shared_access_given.html:17
 msgid ""
 "The shared dataset appears now by default on your overview for this dataset "
 "type."
@@ -2095,7 +2090,7 @@ msgstr ""
 "Der freigegebene Datensatz ist nun standardmäßig in Ihrer Übersicht für den "
 "Datensatztyp im KSP gelistet."
 
-#: templates/email/sharing/shared_access_given.html:25
+#: templates/email/sharing/shared_access_given.html:27
 msgid ""
 "Please note: Shared access on an intervention means you automatically have "
 "editing access to related compensations."
@@ -2115,11 +2110,11 @@ msgstr ""
 "Ihnen wurde soeben der bearbeitende Zugriff auf den folgenden Datensatz "
 "entzogen: "
 
-#: templates/email/sharing/shared_access_removed.html:14
+#: templates/email/sharing/shared_access_removed.html:16
 msgid "However, you are still able to view the dataset content."
 msgstr "Sie können den Datensatz aber immer noch im KSP einsehen."
 
-#: templates/email/sharing/shared_access_removed.html:15
+#: templates/email/sharing/shared_access_removed.html:17
 msgid ""
 "Please use the provided search filter on the dataset`s overview pages to "
 "find them."
@@ -3918,6 +3913,12 @@ msgstr ""
 msgid "Unable to connect to qpid with SASL mechanism %s"
 msgstr ""
 
+#~ msgid "No revocation"
+#~ msgstr "Kein Widerspruch"
+
+#~ msgid "Revocation from {}, added on {} by {}"
+#~ msgstr "Widerspruch vom {}, am {} von {} hinzugefügt"
+
 #~ msgid "General data edited"
 #~ msgstr "Allgemeine Daten bearbeitet"
 
diff --git a/templates/table/gmrkng_col.html b/templates/table/gmrkng_col.html
new file mode 100644
index 00000000..bd8878de
--- /dev/null
+++ b/templates/table/gmrkng_col.html
@@ -0,0 +1,9 @@
+{% load i18n fontawesome_5 %}
+
+{% for entry in entries %}
+    <span class="badge pill-badge rlp-r">{{entry}}</span>
+{% empty %}
+    <span class="text-info" title="{% trans 'If the geometry is not empty, the parcels are currently recalculated. Please refresh this page in a few moments.' %}">
+        {% fa5_icon 'hourglass-half' %}
+    </span>
+{% endfor %}
\ No newline at end of file
-- 
2.45.2


From 5e79f16e1e1befd7766420c910c3f26bdb7587f6 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 15:07:05 +0100
Subject: [PATCH 11/31] #86 Revocation rendering if needed

* renders revocation warning on the index view if a revocation exists
---
 intervention/tables.py                      | 20 +++++++++++---------
 templates/table/revocation_warning_col.html | 14 ++++++++++++++
 2 files changed, 25 insertions(+), 9 deletions(-)
 create mode 100644 templates/table/revocation_warning_col.html

diff --git a/intervention/tables.py b/intervention/tables.py
index 223cd8a4..fd39ddcf 100644
--- a/intervention/tables.py
+++ b/intervention/tables.py
@@ -84,14 +84,17 @@ class InterventionTable(BaseTable, TableRenderMixin):
         Returns:
 
         """
-        html = ""
-        html += self.render_link(
-            tooltip=_("Open {}").format(_("Intervention")),
-            href=reverse("intervention:detail", args=(record.id,)),
-            txt=value,
-            new_tab=False,
+        context = {
+            "tooltip": _("Open {}").format(_("Intervention")),
+            "content": value,
+            "url": reverse("intervention:detail", args=(record.id,)),
+            "has_revocations": record.legal.revocations.exists()
+        }
+        html = render_to_string(
+            "table/revocation_warning_col.html",
+            context
         )
-        return format_html(html)
+        return html
 
     def render_c(self, value, record: Intervention):
         """ Renders the checked column for an intervention
@@ -127,7 +130,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
         Returns:
 
         """
-        parcels = value.parcels.all().values_list(
+        parcels = value.parcels.values_list(
             "gmrkng",
             flat=True
         ).distinct()
@@ -139,7 +142,6 @@ class InterventionTable(BaseTable, TableRenderMixin):
         )
         return html
 
-
     def render_r(self, value, record: Intervention):
         """ Renders the recorded column for an intervention
 
diff --git a/templates/table/revocation_warning_col.html b/templates/table/revocation_warning_col.html
new file mode 100644
index 00000000..2ed0922a
--- /dev/null
+++ b/templates/table/revocation_warning_col.html
@@ -0,0 +1,14 @@
+{% load i18n fontawesome_5 %}
+
+{% if has_revocations %}
+    <strong>
+        <a href="{{url}}" title="{% trans 'Revocations exists' %}">
+            {% fa5_icon 'ban' %}
+            {{content}}
+        </a>
+    </strong>
+{% else %}
+    <a href="{{url}}" title="{{tooltip}}">
+        {{content}}
+    </a>
+{% endif %}
\ No newline at end of file
-- 
2.45.2


From 402bc2d6f3bc1b2c7d227062701e38dd8b59a228 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 15:25:44 +0100
Subject: [PATCH 12/31] #86 Parcel district column for all

* adds parcel district column for all major data objects
* adds warning about intervention-revocation on index view of compensations
* adds warning about intervention-revocation on detail view of related compensations
---
 compensation/forms/modalForms.py    |   2 +-
 compensation/models/compensation.py |  22 ++++++-
 compensation/tables.py              |  76 ++++++++++++++++++++---
 ema/tables.py                       |  29 +++++++++
 intervention/models/intervention.py |   9 ++-
 intervention/views.py               |   8 ---
 konova/static/css/konova.css        |   7 +++
 konova/utils/message_templates.py   |   3 +
 locale/de/LC_MESSAGES/django.mo     | Bin 36677 -> 36661 bytes
 locale/de/LC_MESSAGES/django.po     |  93 ++++++++++++++--------------
 10 files changed, 184 insertions(+), 65 deletions(-)

diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py
index ae5a6dad..18e60611 100644
--- a/compensation/forms/modalForms.py
+++ b/compensation/forms/modalForms.py
@@ -21,7 +21,7 @@ from konova.contexts import BaseContext
 from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
 from konova.models import DeadlineType
 from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \
-    ADDED_COMPENSATION_ACTION, PAYMENT_ADDED
+    ADDED_COMPENSATION_ACTION
 
 
 class NewPaymentForm(BaseModalForm):
diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py
index e90e1735..491a8208 100644
--- a/compensation/models/compensation.py
+++ b/compensation/models/compensation.py
@@ -22,7 +22,7 @@ from konova.models import BaseObject, AbstractDocument, Deadline, generate_docum
 from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
 from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, COMPENSATION_REMOVED_TEMPLATE, \
     DOCUMENT_REMOVED_TEMPLATE, COMPENSATION_EDITED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE, \
-    COMPENSATION_ACTION_REMOVED, COMPENSATION_STATE_REMOVED
+    COMPENSATION_ACTION_REMOVED, COMPENSATION_STATE_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
 from user.models import UserActionLogEntry
 
 
@@ -390,6 +390,26 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
         """
         return self.intervention.is_ready_for_publish()
 
+    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 self.intervention.legal.revocations.exists():
+            messages.error(
+                request,
+                INTERVENTION_HAS_REVOCATIONS_TEMPLATE.format(self.intervention.legal.revocations.count()),
+                extra_tags="danger",
+            )
+        super().set_status_messages(request)
+        return request
+
 
 class CompensationDocument(AbstractDocument):
     """
diff --git a/compensation/tables.py b/compensation/tables.py
index b78b5aa9..6f29e0dd 100644
--- a/compensation/tables.py
+++ b/compensation/tables.py
@@ -31,6 +31,11 @@ class CompensationTable(BaseTable, TableRenderMixin):
         orderable=True,
         accessor="title",
     )
+    d = tables.Column(
+        verbose_name=_("Parcel gmrkng"),
+        orderable=True,
+        accessor="geometry",
+    )
     c = tables.Column(
         verbose_name=_("Checked"),
         orderable=True,
@@ -80,14 +85,17 @@ class CompensationTable(BaseTable, TableRenderMixin):
         Returns:
 
         """
-        html = ""
-        html += self.render_link(
-            tooltip=_("Open {}").format(_("Compensation")),
-            href=reverse("compensation:detail", args=(record.id,)),
-            txt=value,
-            new_tab=False,
+        context = {
+            "tooltip": _("Open {}").format(_("Intervention")),
+            "content": value,
+            "url": reverse("compensation:detail", args=(record.id,)),
+            "has_revocations": record.intervention.legal.revocations.exists()
+        }
+        html = render_to_string(
+            "table/revocation_warning_col.html",
+            context
         )
-        return format_html(html)
+        return html
 
     def render_c(self, value, record: Compensation):
         """ Renders the checked column for a compensation
@@ -115,6 +123,28 @@ class CompensationTable(BaseTable, TableRenderMixin):
         )
         return format_html(html)
 
+    def render_d(self, value, record: Compensation):
+        """ Renders the parcel district column for a compensation
+
+        Args:
+            value (str): The geometry
+            record (Compensation): The compensation record
+
+        Returns:
+
+        """
+        parcels = value.parcels.values_list(
+            "gmrkng",
+            flat=True
+        ).distinct()
+        html = render_to_string(
+            "table/gmrkng_col.html",
+            {
+                "entries": parcels
+            }
+        )
+        return html
+
     def render_r(self, value, record: Compensation):
         """ Renders the registered column for a compensation
 
@@ -173,10 +203,20 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
         orderable=True,
         accessor="title",
     )
+    d = tables.Column(
+        verbose_name=_("Parcel gmrkng"),
+        orderable=True,
+        accessor="geometry",
+    )
     av = tables.Column(
         verbose_name=_("Available"),
         orderable=True,
         empty_values=[],
+        attrs={
+            "th": {
+                "class": "w-20",
+            }
+        }
     )
     r = tables.Column(
         verbose_name=_("Recorded"),
@@ -244,6 +284,28 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
         html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
         return format_html(html)
 
+    def render_d(self, value, record: Compensation):
+        """ Renders the parcel district column for a compensation
+
+        Args:
+            value (str): The geometry
+            record (Compensation): The compensation record
+
+        Returns:
+
+        """
+        parcels = value.parcels.values_list(
+            "gmrkng",
+            flat=True
+        ).distinct()
+        html = render_to_string(
+            "table/gmrkng_col.html",
+            {
+                "entries": parcels
+            }
+        )
+        return html
+
     def render_r(self, value, record: EcoAccount):
         """ Renders the recorded column for an eco account
 
diff --git a/ema/tables.py b/ema/tables.py
index d30f3e36..20ceb456 100644
--- a/ema/tables.py
+++ b/ema/tables.py
@@ -6,6 +6,7 @@ Created on: 19.08.21
 
 """
 from django.http import HttpRequest
+from django.template.loader import render_to_string
 from django.utils.html import format_html
 from django.utils.timezone import localtime
 from django.utils.translation import gettext_lazy as _
@@ -34,6 +35,11 @@ class EmaTable(BaseTable, TableRenderMixin):
         orderable=True,
         accessor="title",
     )
+    d = tables.Column(
+        verbose_name=_("Parcel gmrkng"),
+        orderable=True,
+        accessor="geometry",
+    )
     r = tables.Column(
         verbose_name=_("Recorded"),
         orderable=True,
@@ -87,6 +93,29 @@ class EmaTable(BaseTable, TableRenderMixin):
         )
         return format_html(html)
 
+    def render_d(self, value, record: Ema):
+        """ Renders the parcel district column for a ema
+
+        Args:
+            value (str): The geometry
+            record (Ema): The ema record
+
+        Returns:
+
+        """
+        parcels = value.parcels.values_list(
+            "gmrkng",
+            flat=True
+        ).distinct()
+        html = render_to_string(
+            "table/gmrkng_col.html",
+            {
+                "entries": parcels
+            }
+        )
+        return html
+
+
     def render_r(self, value, record: Ema):
         """ Renders the registered column for a EMA
 
diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py
index 821a835f..8f54a2ae 100644
--- a/intervention/models/intervention.py
+++ b/intervention/models/intervention.py
@@ -26,7 +26,7 @@ from konova.models import generate_document_file_upload_path, AbstractDocument,
     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, DOCUMENT_REMOVED_TEMPLATE, \
-    PAYMENT_REMOVED, PAYMENT_ADDED, REVOCATION_REMOVED
+    PAYMENT_REMOVED, PAYMENT_ADDED, REVOCATION_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
 from user.models import UserActionLogEntry
 
 
@@ -276,6 +276,13 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
         Returns:
             request (HttpRequest): The modified request
         """
+        # Inform user about revocation
+        if self.legal.revocations.exists():
+            messages.error(
+                request,
+                INTERVENTION_HAS_REVOCATIONS_TEMPLATE.format(self.legal.revocations.count()),
+                extra_tags="danger",
+            )
         if not self.is_shared_with(request.user):
             messages.info(request, DATA_UNSHARED_EXPLANATION)
         request = self.set_geometry_conflict_message(request)
diff --git a/intervention/views.py b/intervention/views.py
index c0317035..8a59d9a9 100644
--- a/intervention/views.py
+++ b/intervention/views.py
@@ -245,14 +245,6 @@ def detail_view(request: HttpRequest, id: str):
 
     parcels = intervention.get_underlying_parcels()
 
-    # Inform user about revocation
-    if intervention.legal.revocations.exists():
-        messages.error(
-            request,
-            _("This intervention has {} revocations").format(intervention.legal.revocations.count()),
-            extra_tags="danger",
-        )
-
     context = {
         "obj": intervention,
         "compensations": compensations,
diff --git a/konova/static/css/konova.css b/konova/static/css/konova.css
index a10e124b..709d3eb3 100644
--- a/konova/static/css/konova.css
+++ b/konova/static/css/konova.css
@@ -219,6 +219,13 @@ Overwrites bootstrap .btn:focus box shadow color
     overflow: auto;
 }
 
+.w-20{
+    width: 20%;
+}
+.w-10{
+    width: 20%;
+}
+
 /*
 Extends css for django autocomplete light (dal)
 No other approach worked to get the autocomplete fields to full width of parent containers
diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py
index e13a9cae..87c22af2 100644
--- a/konova/utils/message_templates.py
+++ b/konova/utils/message_templates.py
@@ -62,3 +62,6 @@ ADDED_DEADLINE = _("Added deadline")
 
 # Geometry conflicts
 GEOMETRY_CONFLICT_WITH_TEMPLATE = _("Geometry conflict detected with {}")
+
+# INTERVENTION
+INTERVENTION_HAS_REVOCATIONS_TEMPLATE = _("This intervention has {} revocations")
diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo
index c9d4293ad8de176ef64a6005d1b1cb3428ecc471..74f794dec1833275cf54dc769ab7dab41978bbd3 100644
GIT binary patch
delta 9547
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
D)!mGV

delta 9549
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
DGI56~

diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po
index f280137d..b97e8414 100644
--- a/locale/de/LC_MESSAGES/django.po
+++ b/locale/de/LC_MESSAGES/django.po
@@ -26,7 +26,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 14:48+0100\n"
+"POT-Creation-Date: 2022-02-08 15:16+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -136,7 +136,7 @@ msgstr "Zuständigkeitsbereich"
 #: analysis/templates/analysis/reports/includes/intervention/amount.html:17
 #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:8
 #: analysis/templates/analysis/reports/includes/intervention/laws.html:17
-#: compensation/tables.py:35
+#: compensation/tables.py:40
 #: compensation/templates/compensation/detail/compensation/view.html:63
 #: intervention/tables.py:39
 #: intervention/templates/intervention/detail/view.html:68
@@ -152,11 +152,11 @@ 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:41 compensation/tables.py:182
+#: compensation/tables.py:46 compensation/tables.py:219
 #: compensation/templates/compensation/detail/compensation/view.html:77
 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31
 #: compensation/templates/compensation/detail/eco_account/view.html:44
-#: ema/tables.py:38 ema/templates/ema/detail/view.html:35
+#: ema/tables.py:44 ema/templates/ema/detail/view.html:35
 #: intervention/tables.py:45
 #: intervention/templates/intervention/detail/view.html:82
 #: user/models/user_action.py:21
@@ -196,7 +196,7 @@ msgid "Other registration office"
 msgstr "Andere Zulassungsbehörden"
 
 #: analysis/templates/analysis/reports/includes/compensation/card_compensation.html:11
-#: compensation/tables.py:62
+#: compensation/tables.py:67
 #: intervention/templates/intervention/detail/includes/compensations.html:8
 #: intervention/templates/intervention/report/report.html:49
 msgid "Compensations"
@@ -239,7 +239,7 @@ msgstr "Kompensationsart"
 
 #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:15
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:29
-#: compensation/tables.py:85
+#: compensation/tables.py:90
 #: compensation/templates/compensation/detail/compensation/view.html:19
 #: konova/templates/konova/includes/quickstart/compensations.html:4
 #: templates/navbars/navbar.html:28
@@ -285,7 +285,7 @@ msgstr "Typ"
 
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:24
 #: intervention/forms/modalForms.py:322 intervention/forms/modalForms.py:329
-#: intervention/tables.py:89
+#: intervention/tables.py:88
 #: intervention/templates/intervention/detail/view.html:19
 #: konova/templates/konova/includes/quickstart/interventions.html:4
 #: templates/navbars/navbar.html:22
@@ -293,7 +293,7 @@ msgid "Intervention"
 msgstr "Eingriff"
 
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:34
-#: compensation/tables.py:226
+#: compensation/tables.py:263
 #: compensation/templates/compensation/detail/eco_account/view.html:19
 #: intervention/forms/modalForms.py:295 intervention/forms/modalForms.py:302
 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4
@@ -314,7 +314,7 @@ msgid "Show only unrecorded"
 msgstr "Nur unverzeichnete anzeigen"
 
 #: compensation/forms/forms.py:32 compensation/tables.py:25
-#: compensation/tables.py:167 ema/tables.py:28 intervention/forms/forms.py:28
+#: compensation/tables.py:194 ema/tables.py:29 intervention/forms/forms.py:28
 #: intervention/tables.py:24
 #: intervention/templates/intervention/detail/includes/compensations.html:30
 msgid "Identifier"
@@ -326,14 +326,14 @@ msgid "Generated automatically"
 msgstr "Automatisch generiert"
 
 #: compensation/forms/forms.py:44 compensation/tables.py:30
-#: compensation/tables.py:172
+#: compensation/tables.py:199
 #: compensation/templates/compensation/detail/compensation/includes/documents.html:28
 #: compensation/templates/compensation/detail/compensation/view.html:31
 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:28
 #: compensation/templates/compensation/detail/eco_account/view.html:31
 #: compensation/templates/compensation/report/compensation/report.html:12
 #: compensation/templates/compensation/report/eco_account/report.html:12
-#: ema/tables.py:33 ema/templates/ema/detail/includes/documents.html:28
+#: ema/tables.py:34 ema/templates/ema/detail/includes/documents.html:28
 #: ema/templates/ema/detail/view.html:31
 #: ema/templates/ema/report/report.html:12 intervention/forms/forms.py:40
 #: intervention/tables.py:29
@@ -657,65 +657,70 @@ 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:47 compensation/tables.py:188 ema/tables.py:44
+#: compensation/tables.py:35 compensation/tables.py:204 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:225 ema/tables.py:50
 #: intervention/tables.py:51
 msgid "Editable"
 msgstr "Freigegeben"
 
-#: compensation/tables.py:53 compensation/tables.py:194 ema/tables.py:50
+#: compensation/tables.py:58 compensation/tables.py:231 ema/tables.py:56
 #: intervention/tables.py:57
 msgid "Last edit"
 msgstr "Zuletzt bearbeitet"
 
-#: compensation/tables.py:85 compensation/tables.py:226 ema/tables.py:83
-#: intervention/tables.py:89
+#: compensation/tables.py:90 compensation/tables.py:263 ema/tables.py:89
+#: intervention/tables.py:88
 msgid "Open {}"
 msgstr "Öffne {}"
 
-#: compensation/tables.py:106 intervention/tables.py:108
+#: compensation/tables.py:111 intervention/tables.py:111
 msgid "Not checked yet"
 msgstr "Noch nicht geprüft"
 
-#: compensation/tables.py:111 intervention/tables.py:113
+#: compensation/tables.py:116 intervention/tables.py:116
 msgid "Checked on {} by {}"
 msgstr "Am {} von {} geprüft worden"
 
-#: compensation/tables.py:130
+#: compensation/tables.py:157
 #: compensation/templates/compensation/detail/compensation/view.html:80
 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:58
 #: compensation/templates/compensation/detail/eco_account/view.html:47
-#: ema/tables.py:102 ema/templates/ema/detail/view.html:38
-#: intervention/tables.py:146
+#: ema/tables.py:131 ema/templates/ema/detail/view.html:38
+#: intervention/tables.py:157
 #: intervention/templates/intervention/detail/view.html:85
 msgid "Not recorded yet"
 msgstr "Noch nicht verzeichnet"
 
-#: compensation/tables.py:135 compensation/tables.py:264 ema/tables.py:107
-#: intervention/tables.py:151
+#: compensation/tables.py:162 compensation/tables.py:323 ema/tables.py:136
+#: intervention/tables.py:162
 msgid "Recorded on {} by {}"
 msgstr "Am {} von {} verzeichnet worden"
 
-#: compensation/tables.py:159 compensation/tables.py:286 ema/tables.py:130
-#: intervention/tables.py:174
+#: compensation/tables.py:186 compensation/tables.py:345 ema/tables.py:159
+#: intervention/tables.py:185
 msgid "Full access granted"
 msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden"
 
-#: compensation/tables.py:159 compensation/tables.py:286 ema/tables.py:130
-#: intervention/tables.py:174
+#: compensation/tables.py:186 compensation/tables.py:345 ema/tables.py:159
+#: intervention/tables.py:185
 msgid "Access not granted"
 msgstr "Nicht freigegeben - Datensatz nur lesbar"
 
-#: compensation/tables.py:177
+#: compensation/tables.py:209
 #: compensation/templates/compensation/detail/eco_account/view.html:35
 #: konova/templates/konova/widgets/progressbar.html:3
 msgid "Available"
 msgstr "Verfügbar"
 
-#: compensation/tables.py:203
+#: compensation/tables.py:240
 msgid "Eco Accounts"
 msgstr "Ökokonten"
 
-#: compensation/tables.py:259
+#: compensation/tables.py:318
 msgid "Not recorded yet. Can not be used for deductions, yet."
 msgstr ""
 "Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden."
@@ -1149,11 +1154,11 @@ msgstr "Neue EMA hinzufügen"
 msgid "Edit EMA"
 msgstr "Bearbeite EMA"
 
-#: ema/tables.py:59 templates/navbars/navbar.html:43
+#: ema/tables.py:65 templates/navbars/navbar.html:43
 msgid "Payment funded compensations"
 msgstr "Ersatzzahlungsmaßnahmen (EMA)"
 
-#: ema/tables.py:60
+#: ema/tables.py:66
 msgid "EMA explanation"
 msgstr ""
 "EMA sind Kompensationen, die durch Ersatzzahlungen finanziert wurden. "
@@ -1161,7 +1166,7 @@ msgstr ""
 "Maßnahmen aus Ersatzzahlungen, die nach 2015 rechtskräftig wurden, werden "
 "durch die Stiftung Natur und Umwelt verwaltet."
 
-#: ema/tables.py:83 templates/navbars/navbar.html:43
+#: ema/tables.py:89 templates/navbars/navbar.html:43
 msgid "EMA"
 msgstr ""
 
@@ -1349,10 +1354,6 @@ msgstr ""
 "Das Ökokonto {} hat für eine Abbuchung von {} m² nicht ausreichend "
 "Restfläche. Es stehen noch {} m² zur Verfügung."
 
-#: intervention/tables.py:34 konova/filters/mixins.py:98
-msgid "Parcel gmrkng"
-msgstr "Gemarkung"
-
 #: intervention/templates/intervention/detail/includes/compensations.html:14
 msgid "Add new compensation"
 msgstr "Neue Kompensation hinzufügen"
@@ -1421,18 +1422,8 @@ msgstr "Abbuchungen von Ökokonten"
 msgid "Exist"
 msgstr "Vorhanden"
 
-#: intervention/templates/intervention/table/gmrkng_col.html:6
-msgid ""
-"\n"
-"If the geometry is not empty, the parcels are currently recalculated. Please "
-"refresh this page in a few moments."
-msgstr ""
-"\n"
-"Falls die Geometrie nicht leer ist, werden die Flurstücke aktuell berechnet. "
-"Bitte laden Sie diese Seite in ein paar Augenblicken erneut... \n"
-"            "
-
 #: intervention/utils/quality.py:70
+#: templates/table/revocation_warning_col.html:5
 msgid "Revocations exists"
 msgstr "Widersprüche liegen vor"
 
@@ -2265,6 +2256,14 @@ msgstr ""
 "wieder vorbei. \n"
 "            "
 
+#: templates/table/gmrkng_col.html:6
+msgid ""
+"If the geometry is not empty, the parcels are currently recalculated. Please "
+"refresh this page in a few moments."
+msgstr ""
+"Falls die Geometrie nicht leer ist, werden die Flurstücke aktuell berechnet. "
+"Bitte laden Sie diese Seite in ein paar Augenblicken erneut..."
+
 #: user/forms.py:27
 msgid "Notifications"
 msgstr "Benachrichtigungen"
-- 
2.45.2


From 6ea66180d15ff71b81dec37d7d982b22f098951f Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 17:08:03 +0100
Subject: [PATCH 13/31] #86 Autocomplete enhancement

* adds support for title lookup on EcoAccounts
* adds support for title lookup on Interventions
* adds support for email lookup on User
---
 konova/autocompletes.py | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/konova/autocompletes.py b/konova/autocompletes.py
index 3c12b36d..3a79ab68 100644
--- a/konova/autocompletes.py
+++ b/konova/autocompletes.py
@@ -35,8 +35,9 @@ class EcoAccountAutocomplete(Select2QuerySetView):
         )
         if self.q:
             qs = qs.filter(
-                identifier__icontains=self.q
-            )
+                Q(identifier__icontains=self.q) |
+                Q(title__icontains=self.q)
+            ).distinct()
         return qs
 
 
@@ -57,8 +58,9 @@ class InterventionAutocomplete(Select2QuerySetView):
         )
         if self.q:
             qs = qs.filter(
-                identifier__icontains=self.q
-            )
+                Q(identifier__icontains=self.q) |
+                Q(title__icontains=self.q)
+            ).distinct()
         return qs
 
 
@@ -81,8 +83,9 @@ class ShareUserAutocomplete(Select2QuerySetView):
         if self.q:
             # Due to privacy concerns only a full username match will return the proper user entry
             qs = qs.filter(
-                username=self.q
-            )
+                Q(username=self.q) |
+                Q(email=self.q)
+            ).distinct()
         return qs
 
 
-- 
2.45.2


From a096b2a413ddfc8537105dd90e7d487edfe1679c Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 8 Feb 2022 17:14:23 +0100
Subject: [PATCH 14/31] # 86 LANIS link fix

* simplifies creation of LANIS link by refactoring into super class
---
 compensation/models/compensation.py | 22 ----------------------
 compensation/models/eco_account.py  | 22 ----------------------
 ema/models/ema.py                   | 22 ----------------------
 intervention/models/intervention.py | 28 ----------------------------
 konova/models/object.py             | 29 +++++++++++++++++++++++++++++
 5 files changed, 29 insertions(+), 94 deletions(-)

diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py
index 491a8208..4dd2b4c2 100644
--- a/compensation/models/compensation.py
+++ b/compensation/models/compensation.py
@@ -331,28 +331,6 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
         """
         return self.intervention.users.all()
 
-    def get_LANIS_link(self) -> str:
-        """ Generates a link for LANIS depending on the geometry
-
-        Returns:
-
-        """
-        try:
-            geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True)
-            x = geom.centroid.x
-            y = geom.centroid.y
-            zoom_lvl = 16
-        except AttributeError:
-            # If no geometry has been added, yet.
-            x = 1
-            y = 1
-            zoom_lvl = 6
-        return LANIS_LINK_TEMPLATE.format(
-            zoom_lvl,
-            x,
-            y,
-        )
-
     def get_documents(self) -> QuerySet:
         """ Getter for all documents of a compensation
 
diff --git a/compensation/models/eco_account.py b/compensation/models/eco_account.py
index 895d537f..6d95b399 100644
--- a/compensation/models/eco_account.py
+++ b/compensation/models/eco_account.py
@@ -123,28 +123,6 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
 
         return ret_val_total, ret_val_relative
 
-    def get_LANIS_link(self) -> str:
-        """ Generates a link for LANIS depending on the geometry
-
-        Returns:
-
-        """
-        try:
-            geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True)
-            x = geom.centroid.x
-            y = geom.centroid.y
-            zoom_lvl = 16
-        except AttributeError:
-            # If no geometry has been added, yet.
-            x = 1
-            y = 1
-            zoom_lvl = 6
-        return LANIS_LINK_TEMPLATE.format(
-            zoom_lvl,
-            x,
-            y,
-        )
-
     def quality_check(self) -> EcoAccountQualityChecker:
         """ Quality check
 
diff --git a/ema/models/ema.py b/ema/models/ema.py
index b145acba..983bdbd7 100644
--- a/ema/models/ema.py
+++ b/ema/models/ema.py
@@ -51,28 +51,6 @@ class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin):
             self.identifier = new_id
         super().save(*args, **kwargs)
 
-    def get_LANIS_link(self) -> str:
-        """ Generates a link for LANIS depending on the geometry
-
-        Returns:
-
-        """
-        try:
-            geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True)
-            x = geom.centroid.x
-            y = geom.centroid.y
-            zoom_lvl = 16
-        except AttributeError:
-            # If no geometry has been added, yet.
-            x = 1
-            y = 1
-            zoom_lvl = 6
-        return LANIS_LINK_TEMPLATE.format(
-            zoom_lvl,
-            x,
-            y,
-        )
-
     def quality_check(self) -> EmaQualityChecker:
         """ Quality check
 
diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py
index 8f54a2ae..e2e736d7 100644
--- a/intervention/models/intervention.py
+++ b/intervention/models/intervention.py
@@ -100,34 +100,6 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
         checker.run_check()
         return checker
 
-    def get_LANIS_link(self) -> str:
-        """ Generates a link for LANIS depending on the geometry
-
-        Returns:
-
-        """
-        try:
-            geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True)
-            x = geom.centroid.x
-            y = geom.centroid.y
-            area = int(geom.envelope.area)
-            z_l = 16
-            for k_area, v_zoom in LANIS_ZOOM_LUT.items():
-                if k_area < area:
-                    z_l = v_zoom
-                    break
-            zoom_lvl = z_l
-        except (AttributeError, IndexError) as e:
-            # If no geometry has been added, yet.
-            x = 1
-            y = 1
-            zoom_lvl = 6
-        return LANIS_LINK_TEMPLATE.format(
-            zoom_lvl,
-            x,
-            y,
-        )
-
     def get_documents(self) -> (QuerySet, QuerySet):
         """ Getter for all documents of an intervention
 
diff --git a/konova/models/object.py b/konova/models/object.py
index 06256abb..a6164f5a 100644
--- a/konova/models/object.py
+++ b/konova/models/object.py
@@ -12,6 +12,7 @@ from abc import abstractmethod
 from django.contrib import messages
 from django.db.models import QuerySet
 
+from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP, LANIS_ZOOM_LUT, LANIS_LINK_TEMPLATE
 from konova.tasks import celery_send_mail_shared_access_removed, celery_send_mail_shared_access_given, \
     celery_send_mail_shared_data_recorded, celery_send_mail_shared_data_unrecorded, \
     celery_send_mail_shared_data_deleted, celery_send_mail_shared_data_checked
@@ -544,3 +545,31 @@ class GeoReferencedMixin(models.Model):
             message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers)
             messages.info(request, message_str)
         return request
+
+    def get_LANIS_link(self) -> str:
+        """ Generates a link for LANIS depending on the geometry
+
+        Returns:
+
+        """
+        try:
+            geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True)
+            x = geom.centroid.x
+            y = geom.centroid.y
+            area = int(geom.envelope.area)
+            z_l = 16
+            for k_area, v_zoom in LANIS_ZOOM_LUT.items():
+                if k_area < area:
+                    z_l = v_zoom
+                    break
+            zoom_lvl = z_l
+        except (AttributeError, IndexError) as e:
+            # If no geometry has been added, yet.
+            x = 1
+            y = 1
+            zoom_lvl = 6
+        return LANIS_LINK_TEMPLATE.format(
+            zoom_lvl,
+            x,
+            y,
+        )
\ No newline at end of file
-- 
2.45.2


From aa338e5519d62dbb4831a71a907048a8aba9f3e1 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Wed, 9 Feb 2022 09:18:35 +0100
Subject: [PATCH 15/31] #86 Parcel-Geometry improvement

* improves the way parcel-geometry relations are stored on the DB
    * instead of a numerical sequence we switched to UUID, so no sequence will run out at anytime (new model: ParcelIntersection)
    * instead of dropping all M2M relations between parcel and geometry on each calculation, we keep the ones that still exist, drop the ones that do not exist and add new ones (if new ones exist)
---
 compensation/tables.py                       |  8 ++-
 ema/tables.py                                |  4 +-
 intervention/tables.py                       |  4 +-
 konova/migrations/0003_auto_20220208_1801.py | 54 ++++++++++++++++++++
 konova/migrations/0004_auto_20220209_0839.py | 17 ++++++
 konova/models/geometry.py                    | 23 +++++++--
 konova/models/parcel.py                      | 21 +++++++-
 konova/tasks.py                              | 12 +++--
 8 files changed, 132 insertions(+), 11 deletions(-)
 create mode 100644 konova/migrations/0003_auto_20220208_1801.py
 create mode 100644 konova/migrations/0004_auto_20220209_0839.py

diff --git a/compensation/tables.py b/compensation/tables.py
index 6f29e0dd..5a3da24b 100644
--- a/compensation/tables.py
+++ b/compensation/tables.py
@@ -133,7 +133,9 @@ class CompensationTable(BaseTable, TableRenderMixin):
         Returns:
 
         """
-        parcels = value.parcels.values_list(
+        parcels = value.parcels.filter(
+            parcelintersection__calculated_on__isnull=False,
+        ).values_list(
             "gmrkng",
             flat=True
         ).distinct()
@@ -294,7 +296,9 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
         Returns:
 
         """
-        parcels = value.parcels.values_list(
+        parcels = value.parcels.filter(
+            parcelintersection__calculated_on__isnull=False,
+        ).values_list(
             "gmrkng",
             flat=True
         ).distinct()
diff --git a/ema/tables.py b/ema/tables.py
index 20ceb456..bf3709de 100644
--- a/ema/tables.py
+++ b/ema/tables.py
@@ -103,7 +103,9 @@ class EmaTable(BaseTable, TableRenderMixin):
         Returns:
 
         """
-        parcels = value.parcels.values_list(
+        parcels = value.parcels.filter(
+            parcelintersection__calculated_on__isnull=False,
+        ).values_list(
             "gmrkng",
             flat=True
         ).distinct()
diff --git a/intervention/tables.py b/intervention/tables.py
index fd39ddcf..96d17c59 100644
--- a/intervention/tables.py
+++ b/intervention/tables.py
@@ -130,7 +130,9 @@ class InterventionTable(BaseTable, TableRenderMixin):
         Returns:
 
         """
-        parcels = value.parcels.values_list(
+        parcels = value.parcels.filter(
+            parcelintersection__calculated_on__isnull=False,
+        ).values_list(
             "gmrkng",
             flat=True
         ).distinct()
diff --git a/konova/migrations/0003_auto_20220208_1801.py b/konova/migrations/0003_auto_20220208_1801.py
new file mode 100644
index 00000000..d1d9b5a0
--- /dev/null
+++ b/konova/migrations/0003_auto_20220208_1801.py
@@ -0,0 +1,54 @@
+# Generated by Django 3.1.3 on 2022-02-08 17:01
+
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+def migrate_parcels(apps, schema_editor):
+    Geometry = apps.get_model('konova', 'Geometry')
+    SpatialIntersection = apps.get_model('konova', 'SpatialIntersection')
+
+    all_geoms = Geometry.objects.all()
+    for geom in all_geoms:
+        SpatialIntersection.objects.bulk_create([
+            SpatialIntersection(geometry=geom, parcel=parcel)
+            for parcel in geom.parcels.all()
+        ])
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('konova', '0002_auto_20220114_0936'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='SpatialIntersection',
+            fields=[
+                ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+                ('calculated_on', models.DateTimeField(auto_now_add=True, null=True)),
+                ('geometry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='konova.geometry')),
+                ('parcel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='konova.parcel')),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.RunPython(migrate_parcels),
+        migrations.AddField(
+            model_name='parcel',
+            name='geometries_tmp',
+            field=models.ManyToManyField(blank=True, related_name='parcels', through='konova.SpatialIntersection', to='konova.Geometry'),
+        ),
+        migrations.RemoveField(
+            model_name='parcel',
+            name='geometries',
+        ),
+        migrations.RenameField(
+            model_name='parcel',
+            old_name='geometries_tmp',
+            new_name='geometries',
+        ),
+    ]
diff --git a/konova/migrations/0004_auto_20220209_0839.py b/konova/migrations/0004_auto_20220209_0839.py
new file mode 100644
index 00000000..fe41eada
--- /dev/null
+++ b/konova/migrations/0004_auto_20220209_0839.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.1.3 on 2022-02-09 07:39
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('konova', '0003_auto_20220208_1801'),
+    ]
+
+    operations = [
+        migrations.RenameModel(
+            old_name='SpatialIntersection',
+            new_name='ParcelIntersection',
+        ),
+    ]
diff --git a/konova/models/geometry.py b/konova/models/geometry.py
index 0a380b48..bec89c39 100644
--- a/konova/models/geometry.py
+++ b/konova/models/geometry.py
@@ -99,7 +99,7 @@ class Geometry(BaseResource):
         Returns:
 
         """
-        from konova.models import Parcel, District
+        from konova.models import Parcel, District, ParcelIntersection
         parcel_fetcher = ParcelWFSFetcher(
             geometry_id=self.id,
         )
@@ -107,6 +107,7 @@ class Geometry(BaseResource):
         fetched_parcels = parcel_fetcher.get_features(
             typename
         )
+        _now = timezone.now()
         underlying_parcels = []
         for result in fetched_parcels:
             fetched_parcel = result[typename]
@@ -125,19 +126,35 @@ class Geometry(BaseResource):
                 krs=fetched_parcel["ave:kreis"],
             )[0]
             parcel_obj.district = district
-            parcel_obj.updated_on = timezone.now()
+            parcel_obj.updated_on = _now
             parcel_obj.save()
             underlying_parcels.append(parcel_obj)
 
+        # Update the linked parcels
         self.parcels.set(underlying_parcels)
 
+        # Set the calculated_on intermediate field, so this related data will be found on lookups
+        intersections_without_ts = self.parcelintersection_set.filter(
+            parcel__in=self.parcels.all(),
+            calculated_on__isnull=True,
+        )
+        for entry in intersections_without_ts:
+            entry.calculated_on = _now
+        ParcelIntersection.objects.bulk_update(
+            intersections_without_ts,
+            ["calculated_on"]
+        )
+
     def get_underlying_parcels(self):
         """ Getter for related parcels and their districts
 
         Returns:
             parcels (QuerySet): The related parcels as queryset
         """
-        parcels = self.parcels.all().prefetch_related(
+
+        parcels = self.parcels.filter(
+            parcelintersection__calculated_on__isnull=False,
+        ).prefetch_related(
             "district"
         ).order_by(
             "gmrkng",
diff --git a/konova/models/parcel.py b/konova/models/parcel.py
index 487225e6..9c887f1a 100644
--- a/konova/models/parcel.py
+++ b/konova/models/parcel.py
@@ -22,7 +22,7 @@ class Parcel(UuidModel):
     To avoid conflicts due to german Umlaute, the field names are shortened and vocals are dropped.
 
     """
-    geometries = models.ManyToManyField("konova.Geometry", related_name="parcels", blank=True)
+    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,
@@ -77,3 +77,22 @@ class District(UuidModel):
 
     def __str__(self):
         return f"{self.gmnd} | {self.krs}"
+
+
+class ParcelIntersection(UuidModel):
+    """ ParcelIntersection is an intermediary model, which is used to configure the
+    M2M relation between Parcel and Geometry.
+
+    Based on uuids, we will not have (practically) any problems on outrunning primary keys
+    and extending the model with calculated_on timestamp, we can 'hide' entries while they
+    are being recalculated and keep track on the last time they have been calculated this
+    way.
+
+    Please note: The calculated_on describes when the relation between the Parcel and the Geometry
+    has been established. The updated_on field of Parcel describes when this Parcel has been
+    changed the last time.
+
+    """
+    parcel = models.ForeignKey(Parcel, on_delete=models.CASCADE)
+    geometry = models.ForeignKey("konova.Geometry", on_delete=models.CASCADE)
+    calculated_on = models.DateTimeField(auto_now_add=True, null=True, blank=True)
diff --git a/konova/tasks.py b/konova/tasks.py
index 4c528038..c74a2bd7 100644
--- a/konova/tasks.py
+++ b/konova/tasks.py
@@ -4,13 +4,19 @@ from celery import shared_task
 from django.core.exceptions import ObjectDoesNotExist
 
 
-
 @shared_task
 def celery_update_parcels(geometry_id: str, recheck: bool = True):
-    from konova.models import Geometry
+    from konova.models import Geometry, ParcelIntersection
     try:
         geom = Geometry.objects.get(id=geometry_id)
-        geom.parcels.clear()
+        objs = geom.parcelintersection_set.all()
+        for obj in objs:
+            obj.calculated_on = None
+        ParcelIntersection.objects.bulk_update(
+            objs,
+            ["calculated_on"]
+        )
+
         geom.update_parcels()
     except ObjectDoesNotExist:
         if recheck:
-- 
2.45.2


From c5e3800c344e9b7d6184f42e5af3684aa55afa83 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Wed, 9 Feb 2022 09:30:37 +0100
Subject: [PATCH 16/31] #86 District column simplification

* simplifies the fetching of districts for district column
---
 compensation/tables.py | 8 ++------
 ema/tables.py          | 4 +---
 intervention/tables.py | 4 +---
 3 files changed, 4 insertions(+), 12 deletions(-)

diff --git a/compensation/tables.py b/compensation/tables.py
index 5a3da24b..96888cc1 100644
--- a/compensation/tables.py
+++ b/compensation/tables.py
@@ -133,9 +133,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
         Returns:
 
         """
-        parcels = value.parcels.filter(
-            parcelintersection__calculated_on__isnull=False,
-        ).values_list(
+        parcels = value.get_underlying_parcels().values_list(
             "gmrkng",
             flat=True
         ).distinct()
@@ -296,9 +294,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
         Returns:
 
         """
-        parcels = value.parcels.filter(
-            parcelintersection__calculated_on__isnull=False,
-        ).values_list(
+        parcels = value.get_underlying_parcels().values_list(
             "gmrkng",
             flat=True
         ).distinct()
diff --git a/ema/tables.py b/ema/tables.py
index bf3709de..f9689517 100644
--- a/ema/tables.py
+++ b/ema/tables.py
@@ -103,9 +103,7 @@ class EmaTable(BaseTable, TableRenderMixin):
         Returns:
 
         """
-        parcels = value.parcels.filter(
-            parcelintersection__calculated_on__isnull=False,
-        ).values_list(
+        parcels = value.get_underlying_parcels().values_list(
             "gmrkng",
             flat=True
         ).distinct()
diff --git a/intervention/tables.py b/intervention/tables.py
index 96d17c59..f535039d 100644
--- a/intervention/tables.py
+++ b/intervention/tables.py
@@ -130,9 +130,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
         Returns:
 
         """
-        parcels = value.parcels.filter(
-            parcelintersection__calculated_on__isnull=False,
-        ).values_list(
+        parcels = value.get_underlying_parcels().values_list(
             "gmrkng",
             flat=True
         ).distinct()
-- 
2.45.2


From ce9143e4b2d5adfaaecda65c2f1c49a011d08f22 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Wed, 9 Feb 2022 10:29:34 +0100
Subject: [PATCH 17/31] #86 Edit payment

* adds button for payment editing
* adds new edit form payment editing
* adds tests for views and workflow
---
 compensation/forms/modalForms.py              |  28 ++-
 compensation/tests/compensation/test_views.py |   2 +-
 compensation/tests/payment/__init__.py        |   7 +
 compensation/tests/payment/test_views.py      | 154 +++++++++++++
 compensation/tests/payment/test_workflow.py   | 125 +++++++++++
 compensation/urls/payment.py                  |   1 +
 compensation/views/payment.py                 |  32 ++-
 .../detail/includes/payments.html             |   7 +-
 konova/forms.py                               |   7 +-
 konova/utils/message_templates.py             |   1 +
 locale/de/LC_MESSAGES/django.mo               | Bin 36661 -> 36759 bytes
 locale/de/LC_MESSAGES/django.po               | 212 +++++++++---------
 12 files changed, 462 insertions(+), 114 deletions(-)
 create mode 100644 compensation/tests/payment/__init__.py
 create mode 100644 compensation/tests/payment/test_views.py
 create mode 100644 compensation/tests/payment/test_workflow.py

diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py
index 18e60611..a6e7df26 100644
--- a/compensation/forms/modalForms.py
+++ b/compensation/forms/modalForms.py
@@ -21,7 +21,7 @@ from konova.contexts import BaseContext
 from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
 from konova.models import DeadlineType
 from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \
-    ADDED_COMPENSATION_ACTION
+    ADDED_COMPENSATION_ACTION, PAYMENT_EDITED
 
 
 class NewPaymentForm(BaseModalForm):
@@ -103,6 +103,32 @@ class NewPaymentForm(BaseModalForm):
         return pay
 
 
+class EditPaymentModalForm(NewPaymentForm):
+    """ Form handling edit for Payment
+
+    """
+    payment = None
+
+    def __init__(self, *args, **kwargs):
+        self.payment = kwargs.pop("payment", None)
+        super().__init__(*args, **kwargs)
+        form_date = {
+            "amount": self.payment.amount,
+            "due": str(self.payment.due_on),
+            "comment": self.payment.comment,
+        }
+        self.load_initial_data(form_date, disabled_fields=[])
+
+    def save(self):
+        payment = self.payment
+        payment.amount = self.cleaned_data.get("amount", None)
+        payment.due_on = self.cleaned_data.get("due", None)
+        payment.comment = self.cleaned_data.get("comment", None)
+        payment.save()
+        self.instance.mark_as_edited(self.user, self.request, edit_comment=PAYMENT_EDITED)
+        return payment
+
+
 class RemovePaymentModalForm(RemoveModalForm):
     """ Removing modal form for Payment
 
diff --git a/compensation/tests/compensation/test_views.py b/compensation/tests/compensation/test_views.py
index 4039496a..465a1026 100644
--- a/compensation/tests/compensation/test_views.py
+++ b/compensation/tests/compensation/test_views.py
@@ -117,7 +117,7 @@ class CompensationViewTestCase(BaseViewTestCase):
     def test_logged_in_no_groups_unshared(self):
         """ Check correct status code for all requests
 
-        Assumption: User logged in and has no groups and data is shared
+        Assumption: User logged in and has no groups and data is not shared
 
         Returns:
 
diff --git a/compensation/tests/payment/__init__.py b/compensation/tests/payment/__init__.py
new file mode 100644
index 00000000..b90ce206
--- /dev/null
+++ b/compensation/tests/payment/__init__.py
@@ -0,0 +1,7 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: michel.peltriaux@sgdnord.rlp.de
+Created on: 09.02.22
+
+"""
diff --git a/compensation/tests/payment/test_views.py b/compensation/tests/payment/test_views.py
new file mode 100644
index 00000000..69130e86
--- /dev/null
+++ b/compensation/tests/payment/test_views.py
@@ -0,0 +1,154 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: michel.peltriaux@sgdnord.rlp.de
+Created on: 09.02.22
+
+"""
+from django.urls import reverse
+from django.test.client import Client
+
+from compensation.models import Payment
+from konova.settings import DEFAULT_GROUP
+from konova.tests.test_views import BaseViewTestCase
+
+
+class PaymentViewTestCase(BaseViewTestCase):
+
+    @classmethod
+    def setUpTestData(cls) -> None:
+        super().setUpTestData()
+
+        cls.payment = Payment.objects.get_or_create(
+            intervention=cls.intervention,
+            amount=1,
+            due_on="2020-01-01",
+            comment="Testcomment"
+        )[0]
+
+        cls.new_url = reverse("compensation:pay:new", args=(cls.intervention.id,))
+        cls.edit_url = reverse("compensation:pay:edit", args=(cls.intervention.id, cls.payment.id))
+        cls.remove_url = reverse("compensation:pay:remove", args=(cls.intervention.id, cls.payment.id))
+
+    def test_anonymous_user(self):
+        """ Check correct status code for all requests
+
+        Assumption: User not logged in
+
+        Returns:
+
+        """
+        client = Client()
+
+        success_urls = [
+        ]
+        fail_urls = [
+            self.new_url,
+            self.edit_url,
+            self.remove_url,
+        ]
+
+        self.assert_url_success(client, success_urls)
+        self.assert_url_fail(client, fail_urls)
+
+    def test_logged_in_no_groups_shared(self):
+        """ Check correct status code for all requests
+
+        Assumption: User logged in and has no groups and data is shared
+
+        Returns:
+
+        """
+        client = Client()
+        client.login(username=self.superuser.username, password=self.superuser_pw)
+        self.superuser.groups.set([])
+        self.intervention.share_with_list([self.superuser])
+
+        # Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
+        # to a user without access, since the important permissions are missing
+        success_urls = [
+        ]
+        fail_urls = [
+            self.new_url,
+            self.edit_url,
+            self.remove_url,
+        ]
+
+        self.assert_url_success(client, success_urls)
+        self.assert_url_fail(client, fail_urls)
+
+    def test_logged_in_no_groups_unshared(self):
+        """ Check correct status code for all requests
+
+        Assumption: User logged in and has no groups and data is not shared
+
+        Returns:
+
+        """
+        client = Client()
+        client.login(username=self.superuser.username, password=self.superuser_pw)
+        self.superuser.groups.set([])
+        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
+        self.intervention.share_with_list([])
+
+        # Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
+        # to a user having shared access, since all important permissions are missing
+        success_urls = [
+        ]
+        fail_urls = [
+            self.new_url,
+            self.edit_url,
+            self.remove_url,
+        ]
+
+        self.assert_url_success(client, success_urls)
+        self.assert_url_fail(client, fail_urls)
+
+    def test_logged_in_default_group_shared(self):
+        """ Check correct status code for all requests
+
+        Assumption: User logged in, is default group member and data is shared
+        --> Default group necessary since all base functionalities depend on this group membership
+
+        Returns:
+
+        """
+        client = Client()
+        client.login(username=self.superuser.username, password=self.superuser_pw)
+        group = self.groups.get(name=DEFAULT_GROUP)
+        self.superuser.groups.set([group])
+        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
+        self.intervention.share_with_list([self.superuser])
+
+        success_urls = [
+            self.new_url,
+            self.edit_url,
+            self.remove_url,
+        ]
+        self.assert_url_success(client, success_urls)
+
+    def test_logged_in_default_group_unshared(self):
+        """ Check correct status code for all requests
+
+        Assumption: User logged in, is default group member and data is NOT shared
+        --> Default group necessary since all base functionalities depend on this group membership
+
+        Returns:
+
+        """
+        client = Client()
+        client.login(username=self.superuser.username, password=self.superuser_pw)
+        group = self.groups.get(name=DEFAULT_GROUP)
+        self.superuser.groups.set([group])
+        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
+        self.intervention.share_with_list([])
+
+        success_urls = [
+        ]
+        fail_urls = [
+            self.new_url,
+            self.edit_url,
+            self.remove_url,
+        ]
+        self.assert_url_fail(client, fail_urls)
+        self.assert_url_success(client, success_urls)
diff --git a/compensation/tests/payment/test_workflow.py b/compensation/tests/payment/test_workflow.py
new file mode 100644
index 00000000..09ff0e69
--- /dev/null
+++ b/compensation/tests/payment/test_workflow.py
@@ -0,0 +1,125 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: michel.peltriaux@sgdnord.rlp.de
+Created on: 09.02.22
+
+"""
+from django.core.exceptions import ObjectDoesNotExist
+from django.urls import reverse
+
+from compensation.models import Payment
+from konova.tests.test_views import BaseWorkflowTestCase
+from user.models import UserAction
+
+
+class PaymentWorkflowTestCase(BaseWorkflowTestCase):
+    @classmethod
+    def setUpTestData(cls):
+        super().setUpTestData()
+
+        # Give the user shared access to the dummy intervention
+        cls.intervention.share_with(cls.superuser)
+
+        cls.payment = Payment.objects.get_or_create(
+            intervention=cls.intervention,
+            amount=1,
+            due_on="2020-01-01",
+            comment="Testcomment"
+        )[0]
+
+    def test_new(self):
+        """ Test the creation of a payment
+
+        Returns:
+
+        """
+        # Prepare url and form data to be posted
+        new_url = reverse("compensation:pay:new", args=(self.intervention.id,))
+        test_amount = 12345
+        test_due_on = "1970-01-01"
+        test_comment = self.create_dummy_string()
+        post_data = {
+            "amount": test_amount,
+            "due": test_due_on,
+            "comment": test_comment,
+        }
+        pre_creation_intervention_log_count = self.intervention.log.count()
+        num_payments = self.intervention.payments.count()
+
+        self.client_user.post(new_url, post_data)
+
+        self.intervention.refresh_from_db()
+
+        self.assertEqual(num_payments + 1, self.intervention.payments.count())
+        new_payment = self.intervention.payments.get(amount=test_amount)
+        self.assertEqual(new_payment.amount, test_amount)
+        self.assertEqual(str(new_payment.due_on), test_due_on)
+        self.assertEqual(new_payment.comment, test_comment)
+
+        # Expect logs to be set
+        self.assertEqual(pre_creation_intervention_log_count + 1, self.intervention.log.count())
+        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
+
+    def test_edit(self):
+        """ Test edit of a payment
+
+        Returns:
+
+        """
+        # Prepare url and form data to be posted
+        new_url = reverse("compensation:pay:edit", args=(self.intervention.id, self.payment.id))
+        test_amount = self.payment.amount * 2
+        test_due_on = "1970-01-01"
+        test_comment = self.create_dummy_string()
+        post_data = {
+            "amount": test_amount,
+            "due": test_due_on,
+            "comment": test_comment,
+        }
+        pre_edit_intervention_log_count = self.intervention.log.count()
+        num_payments = self.intervention.payments.count()
+
+        self.client_user.post(new_url, post_data)
+
+        self.intervention.refresh_from_db()
+        self.payment.refresh_from_db()
+
+        self.assertEqual(num_payments, self.intervention.payments.count())
+        self.assertEqual(self.payment.amount, test_amount)
+        self.assertEqual(str(self.payment.due_on), test_due_on)
+        self.assertEqual(self.payment.comment, test_comment)
+
+        # Expect logs to be set
+        self.assertEqual(pre_edit_intervention_log_count + 1, self.intervention.log.count())
+        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
+
+    def test_remove(self):
+        """ Test remove of a payment
+
+        Returns:
+
+        """
+        # Prepare url and form data to be posted
+        new_url = reverse("compensation:pay:remove", args=(self.intervention.id, self.payment.id))
+        post_data = {
+            "confirm": True,
+        }
+        pre_remove_intervention_log_count = self.intervention.log.count()
+        num_payments = self.intervention.payments.count()
+
+        self.client_user.post(new_url, post_data)
+
+        self.intervention.refresh_from_db()
+        try:
+            self.payment.refresh_from_db()
+            self.fail(msg="Payment still exists after delete")
+        except ObjectDoesNotExist:
+            pass
+
+        self.assertEqual(num_payments - 1, self.intervention.payments.count())
+
+        # Expect logs to be set
+        self.assertEqual(pre_remove_intervention_log_count + 1, self.intervention.log.count())
+        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
+
diff --git a/compensation/urls/payment.py b/compensation/urls/payment.py
index a400c636..b51384dd 100644
--- a/compensation/urls/payment.py
+++ b/compensation/urls/payment.py
@@ -12,4 +12,5 @@ app_name = "pay"
 urlpatterns = [
     path('<id>/new', new_payment_view, name='new'),
     path('<id>/remove/<payment_id>', payment_remove_view, name='remove'),
+    path('<id>/edit/<payment_id>', payment_edit_view, name='edit'),
 ]
diff --git a/compensation/views/payment.py b/compensation/views/payment.py
index 7be9bee8..2be5455e 100644
--- a/compensation/views/payment.py
+++ b/compensation/views/payment.py
@@ -11,16 +11,17 @@ from django.contrib.auth.decorators import login_required
 from django.http import HttpRequest
 from django.shortcuts import get_object_or_404
 
-from compensation.forms.modalForms import NewPaymentForm, RemovePaymentModalForm
+from compensation.forms.modalForms import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
 from compensation.models import Payment
 from intervention.models import Intervention
-from konova.decorators import default_group_required
+from konova.decorators import default_group_required, shared_access_required
 from konova.forms import RemoveModalForm
-from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED
+from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED
 
 
 @login_required
 @default_group_required
+@shared_access_required(Intervention, "id")
 def new_payment_view(request: HttpRequest, id: str):
     """ Renders a modal view for adding new payments
 
@@ -42,6 +43,7 @@ def new_payment_view(request: HttpRequest, id: str):
 
 @login_required
 @default_group_required
+@shared_access_required(Intervention, "id")
 def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
     """ Renders a modal view for removing payments
 
@@ -62,3 +64,27 @@ def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
         redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
     )
 
+
+@login_required
+@default_group_required
+@shared_access_required(Intervention, "id")
+def payment_edit_view(request: HttpRequest, id: str, payment_id: str):
+    """ Renders a modal view for editing payments
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The intervention's id
+        payment_id (str): The payment's id
+
+    Returns:
+
+    """
+    intervention = get_object_or_404(Intervention, id=id)
+    payment = get_object_or_404(Payment, id=payment_id)
+    form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
+    return form.process_request(
+        request=request,
+        msg_success=PAYMENT_EDITED,
+        redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
+    )
+
diff --git a/intervention/templates/intervention/detail/includes/payments.html b/intervention/templates/intervention/detail/includes/payments.html
index 205755bc..4bf00cd8 100644
--- a/intervention/templates/intervention/detail/includes/payments.html
+++ b/intervention/templates/intervention/detail/includes/payments.html
@@ -54,9 +54,12 @@
                         {{ pay.comment }}
                     </div>
                 </td>
-                <td class="align-middle">
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:pay:remove' obj.id pay.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove payment' %}">
+                    <button data-form-url="{% url 'compensation:pay:edit' obj.id pay.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit payment' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:pay:remove' obj.id pay.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove payment' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/konova/forms.py b/konova/forms.py
index ca7ac0e1..c05eb422 100644
--- a/konova/forms.py
+++ b/konova/forms.py
@@ -87,7 +87,7 @@ class BaseForm(forms.Form):
         """
         self.fields[field].widget.attrs["placeholder"] = val
 
-    def load_initial_data(self, form_data: dict, disabled_fields: list):
+    def load_initial_data(self, form_data: dict, disabled_fields: list = None):
         """ Initializes form data from instance
 
         Inserts instance data into form and disables form fields
@@ -99,8 +99,9 @@ class BaseForm(forms.Form):
             return
         for k, v in form_data.items():
             self.initialize_form_field(k, v)
-        for field in disabled_fields:
-            self.disable_form_field(field)
+        if disabled_fields:
+            for field in disabled_fields:
+                self.disable_form_field(field)
 
     def add_widget_html_class(self, field: str, cls: str):
         """ Adds a HTML class string to the widget of a field
diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py
index 87c22af2..b7eb5253 100644
--- a/konova/utils/message_templates.py
+++ b/konova/utils/message_templates.py
@@ -46,6 +46,7 @@ DEADLINE_REMOVED = _("Deadline removed")
 
 # PAYMENTS
 PAYMENT_ADDED = _("Payment added")
+PAYMENT_EDITED = _("Payment edited")
 PAYMENT_REMOVED = _("Payment removed")
 
 # REVOCATIONS
diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo
index 74f794dec1833275cf54dc769ab7dab41978bbd3..85b75fdda447f5409f7dac392a4a7ff964a0e7fe 100644
GIT binary patch
delta 10501
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

delta 10423
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

diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po
index b97e8414..3feeb6db 100644
--- a/locale/de/LC_MESSAGES/django.po
+++ b/locale/de/LC_MESSAGES/django.po
@@ -5,7 +5,7 @@
 #
 #: compensation/filters.py:122 compensation/forms/modalForms.py:35
 #: compensation/forms/modalForms.py:46 compensation/forms/modalForms.py:62
-#: compensation/forms/modalForms.py:306 compensation/forms/modalForms.py:399
+#: compensation/forms/modalForms.py:329 compensation/forms/modalForms.py:422
 #: intervention/forms/forms.py:54 intervention/forms/forms.py:156
 #: intervention/forms/forms.py:168 intervention/forms/modalForms.py:124
 #: intervention/forms/modalForms.py:137 intervention/forms/modalForms.py:150
@@ -18,15 +18,15 @@
 #: konova/filters/mixins.py:270 konova/filters/mixins.py:315
 #: konova/filters/mixins.py:353 konova/filters/mixins.py:354
 #: konova/filters/mixins.py:385 konova/filters/mixins.py:386
-#: konova/forms.py:140 konova/forms.py:241 konova/forms.py:312
-#: konova/forms.py:356 konova/forms.py:366 konova/forms.py:379
-#: konova/forms.py:391 konova/forms.py:409 user/forms.py:42
+#: konova/forms.py:141 konova/forms.py:242 konova/forms.py:313
+#: konova/forms.py:357 konova/forms.py:367 konova/forms.py:380
+#: konova/forms.py:392 konova/forms.py:410 user/forms.py:42
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 15:16+0100\n"
+"POT-Creation-Date: 2022-02-09 09:50+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -75,7 +75,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:188
+#: analysis/forms.py:69 konova/forms.py:189
 msgid "Continue"
 msgstr "Weiter"
 
@@ -95,7 +95,7 @@ msgstr ""
 #: analysis/templates/analysis/reports/includes/eco_account/amount.html:3
 #: analysis/templates/analysis/reports/includes/intervention/amount.html:3
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:3
-#: compensation/forms/modalForms.py:383
+#: compensation/forms/modalForms.py:406
 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:34
 #: intervention/templates/intervention/detail/includes/deductions.html:31
 msgid "Amount"
@@ -152,7 +152,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:219
+#: compensation/tables.py:46 compensation/tables.py:222
 #: compensation/templates/compensation/detail/compensation/view.html:77
 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31
 #: compensation/templates/compensation/detail/eco_account/view.html:44
@@ -213,7 +213,7 @@ msgstr "Abbuchungen"
 
 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:9
 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:11
-#: compensation/forms/modalForms.py:167
+#: compensation/forms/modalForms.py:190
 #: compensation/templates/compensation/detail/compensation/includes/states-after.html:36
 #: compensation/templates/compensation/detail/compensation/includes/states-before.html:36
 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:36
@@ -239,7 +239,6 @@ msgstr "Kompensationsart"
 
 #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:15
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:29
-#: compensation/tables.py:90
 #: compensation/templates/compensation/detail/compensation/view.html:19
 #: konova/templates/konova/includes/quickstart/compensations.html:4
 #: templates/navbars/navbar.html:28
@@ -284,8 +283,8 @@ msgid "Type"
 msgstr "Typ"
 
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:24
-#: intervention/forms/modalForms.py:322 intervention/forms/modalForms.py:329
-#: intervention/tables.py:88
+#: compensation/tables.py:89 intervention/forms/modalForms.py:322
+#: intervention/forms/modalForms.py:329 intervention/tables.py:88
 #: intervention/templates/intervention/detail/view.html:19
 #: konova/templates/konova/includes/quickstart/interventions.html:4
 #: templates/navbars/navbar.html:22
@@ -293,7 +292,7 @@ msgid "Intervention"
 msgstr "Eingriff"
 
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:34
-#: compensation/tables.py:263
+#: compensation/tables.py:266
 #: compensation/templates/compensation/detail/eco_account/view.html:19
 #: intervention/forms/modalForms.py:295 intervention/forms/modalForms.py:302
 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4
@@ -314,7 +313,7 @@ msgid "Show only unrecorded"
 msgstr "Nur unverzeichnete anzeigen"
 
 #: compensation/forms/forms.py:32 compensation/tables.py:25
-#: compensation/tables.py:194 ema/tables.py:29 intervention/forms/forms.py:28
+#: compensation/tables.py:197 ema/tables.py:29 intervention/forms/forms.py:28
 #: intervention/tables.py:24
 #: intervention/templates/intervention/detail/includes/compensations.html:30
 msgid "Identifier"
@@ -326,7 +325,7 @@ msgid "Generated automatically"
 msgstr "Automatisch generiert"
 
 #: compensation/forms/forms.py:44 compensation/tables.py:30
-#: compensation/tables.py:199
+#: compensation/tables.py:202
 #: compensation/templates/compensation/detail/compensation/includes/documents.html:28
 #: compensation/templates/compensation/detail/compensation/view.html:31
 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:28
@@ -341,7 +340,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:355
+#: konova/forms.py:356
 msgid "Title"
 msgstr "Bezeichnung"
 
@@ -354,7 +353,7 @@ msgid "Compensation XY; Location ABC"
 msgstr "Kompensation XY; Flur ABC"
 
 #: compensation/forms/forms.py:57 compensation/forms/modalForms.py:61
-#: compensation/forms/modalForms.py:305 compensation/forms/modalForms.py:398
+#: compensation/forms/modalForms.py:328 compensation/forms/modalForms.py:421
 #: compensation/templates/compensation/detail/compensation/includes/actions.html:35
 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:34
 #: compensation/templates/compensation/detail/compensation/includes/documents.html:31
@@ -368,11 +367,11 @@ 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:390 konova/templates/konova/includes/comment_card.html:16
+#: konova/forms.py:391 konova/templates/konova/includes/comment_card.html:16
 msgid "Comment"
 msgstr "Kommentar"
 
-#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:400
+#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:423
 #: intervention/forms/forms.py:182
 msgid "Additional comment"
 msgstr "Zusätzlicher Kommentar"
@@ -483,8 +482,8 @@ msgstr "Fällig am"
 msgid "Due on which date"
 msgstr "Zahlung wird an diesem Datum erwartet"
 
-#: compensation/forms/modalForms.py:63 compensation/forms/modalForms.py:307
-#: intervention/forms/modalForms.py:151 konova/forms.py:392
+#: compensation/forms/modalForms.py:63 compensation/forms/modalForms.py:330
+#: intervention/forms/modalForms.py:151 konova/forms.py:393
 msgid "Additional comment, maximum {} letters"
 msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
 
@@ -496,47 +495,47 @@ msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen"
 msgid "If there is no date you can enter, please explain why."
 msgstr "Falls Sie kein Datum angeben können, erklären Sie bitte weshalb."
 
-#: compensation/forms/modalForms.py:131 compensation/forms/modalForms.py:143
+#: compensation/forms/modalForms.py:154 compensation/forms/modalForms.py:166
 msgid "Biotope Type"
 msgstr "Biotoptyp"
 
-#: compensation/forms/modalForms.py:134
+#: compensation/forms/modalForms.py:157
 msgid "Select the biotope type"
 msgstr "Biotoptyp wählen"
 
-#: compensation/forms/modalForms.py:148 compensation/forms/modalForms.py:160
+#: compensation/forms/modalForms.py:171 compensation/forms/modalForms.py:183
 msgid "Biotope additional type"
 msgstr "Zusatzbezeichnung"
 
-#: compensation/forms/modalForms.py:151
+#: compensation/forms/modalForms.py:174
 msgid "Select an additional biotope type"
 msgstr "Zusatzbezeichnung wählen"
 
-#: compensation/forms/modalForms.py:170 intervention/forms/modalForms.py:313
+#: compensation/forms/modalForms.py:193 intervention/forms/modalForms.py:313
 msgid "in m²"
 msgstr ""
 
-#: compensation/forms/modalForms.py:181
+#: compensation/forms/modalForms.py:204
 msgid "New state"
 msgstr "Neuer Zustand"
 
-#: compensation/forms/modalForms.py:182
+#: compensation/forms/modalForms.py:205
 msgid "Insert data for the new state"
 msgstr "Geben Sie die Daten des neuen Zustandes ein"
 
-#: compensation/forms/modalForms.py:189 konova/forms.py:190
+#: compensation/forms/modalForms.py:212 konova/forms.py:191
 msgid "Object removed"
 msgstr "Objekt entfernt"
 
-#: compensation/forms/modalForms.py:277
+#: compensation/forms/modalForms.py:300
 msgid "Deadline Type"
 msgstr "Fristart"
 
-#: compensation/forms/modalForms.py:280
+#: compensation/forms/modalForms.py:303
 msgid "Select the deadline type"
 msgstr "Fristart wählen"
 
-#: compensation/forms/modalForms.py:289
+#: compensation/forms/modalForms.py:312
 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31
 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31
 #: ema/templates/ema/detail/includes/deadlines.html:31
@@ -544,27 +543,27 @@ msgstr "Fristart wählen"
 msgid "Date"
 msgstr "Datum"
 
-#: compensation/forms/modalForms.py:292
+#: compensation/forms/modalForms.py:315
 msgid "Select date"
 msgstr "Datum wählen"
 
-#: compensation/forms/modalForms.py:319
+#: compensation/forms/modalForms.py:342
 msgid "New deadline"
 msgstr "Neue Frist"
 
-#: compensation/forms/modalForms.py:320
+#: compensation/forms/modalForms.py:343
 msgid "Insert data for the new deadline"
 msgstr "Geben Sie die Daten der neuen Frist ein"
 
-#: compensation/forms/modalForms.py:337
+#: compensation/forms/modalForms.py:360
 msgid "Action Type"
 msgstr "Maßnahmentyp"
 
-#: compensation/forms/modalForms.py:340
+#: compensation/forms/modalForms.py:363
 msgid "Select the action type"
 msgstr "Maßnahmentyp wählen"
 
-#: compensation/forms/modalForms.py:349
+#: compensation/forms/modalForms.py:372
 #: compensation/templates/compensation/detail/compensation/includes/actions.html:40
 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:39
 #: compensation/templates/compensation/detail/compensation/includes/documents.html:36
@@ -590,31 +589,31 @@ msgstr "Maßnahmentyp wählen"
 msgid "Action"
 msgstr "Aktionen"
 
-#: compensation/forms/modalForms.py:354 compensation/forms/modalForms.py:366
+#: compensation/forms/modalForms.py:377 compensation/forms/modalForms.py:389
 msgid "Action Type detail"
 msgstr "Zusatzmerkmal"
 
-#: compensation/forms/modalForms.py:357
+#: compensation/forms/modalForms.py:380
 msgid "Select the action type detail"
 msgstr "Zusatzmerkmal wählen"
 
-#: compensation/forms/modalForms.py:371
+#: compensation/forms/modalForms.py:394
 msgid "Unit"
 msgstr "Einheit"
 
-#: compensation/forms/modalForms.py:374
+#: compensation/forms/modalForms.py:397
 msgid "Select the unit"
 msgstr "Einheit wählen"
 
-#: compensation/forms/modalForms.py:386
+#: compensation/forms/modalForms.py:409
 msgid "Insert the amount"
 msgstr "Menge eingeben"
 
-#: compensation/forms/modalForms.py:411
+#: compensation/forms/modalForms.py:434
 msgid "New action"
 msgstr "Neue Maßnahme"
 
-#: compensation/forms/modalForms.py:412
+#: compensation/forms/modalForms.py:435
 msgid "Insert data for the new action"
 msgstr "Geben Sie die Daten der neuen Maßnahme ein"
 
@@ -657,35 +656,35 @@ 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:204 ema/tables.py:39
+#: compensation/tables.py:35 compensation/tables.py:207 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:225 ema/tables.py:50
+#: compensation/tables.py:52 compensation/tables.py:228 ema/tables.py:50
 #: intervention/tables.py:51
 msgid "Editable"
 msgstr "Freigegeben"
 
-#: compensation/tables.py:58 compensation/tables.py:231 ema/tables.py:56
+#: compensation/tables.py:58 compensation/tables.py:234 ema/tables.py:56
 #: intervention/tables.py:57
 msgid "Last edit"
 msgstr "Zuletzt bearbeitet"
 
-#: compensation/tables.py:90 compensation/tables.py:263 ema/tables.py:89
+#: compensation/tables.py:89 compensation/tables.py:266 ema/tables.py:89
 #: intervention/tables.py:88
 msgid "Open {}"
 msgstr "Öffne {}"
 
-#: compensation/tables.py:111 intervention/tables.py:111
+#: compensation/tables.py:114 intervention/tables.py:111
 msgid "Not checked yet"
 msgstr "Noch nicht geprüft"
 
-#: compensation/tables.py:116 intervention/tables.py:116
+#: compensation/tables.py:119 intervention/tables.py:116
 msgid "Checked on {} by {}"
 msgstr "Am {} von {} geprüft worden"
 
-#: compensation/tables.py:157
+#: compensation/tables.py:160
 #: compensation/templates/compensation/detail/compensation/view.html:80
 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:58
 #: compensation/templates/compensation/detail/eco_account/view.html:47
@@ -695,32 +694,32 @@ msgstr "Am {} von {} geprüft worden"
 msgid "Not recorded yet"
 msgstr "Noch nicht verzeichnet"
 
-#: compensation/tables.py:162 compensation/tables.py:323 ema/tables.py:136
+#: compensation/tables.py:165 compensation/tables.py:326 ema/tables.py:136
 #: intervention/tables.py:162
 msgid "Recorded on {} by {}"
 msgstr "Am {} von {} verzeichnet worden"
 
-#: compensation/tables.py:186 compensation/tables.py:345 ema/tables.py:159
+#: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159
 #: intervention/tables.py:185
 msgid "Full access granted"
 msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden"
 
-#: compensation/tables.py:186 compensation/tables.py:345 ema/tables.py:159
+#: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159
 #: intervention/tables.py:185
 msgid "Access not granted"
 msgstr "Nicht freigegeben - Datensatz nur lesbar"
 
-#: compensation/tables.py:209
+#: compensation/tables.py:212
 #: compensation/templates/compensation/detail/eco_account/view.html:35
 #: konova/templates/konova/widgets/progressbar.html:3
 msgid "Available"
 msgstr "Verfügbar"
 
-#: compensation/tables.py:240
+#: compensation/tables.py:243
 msgid "Eco Accounts"
 msgstr "Ökokonten"
 
-#: compensation/tables.py:318
+#: compensation/tables.py:321
 msgid "Not recorded yet. Can not be used for deductions, yet."
 msgstr ""
 "Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden."
@@ -822,7 +821,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:408
+#: konova/forms.py:409
 msgid "Add new document"
 msgstr "Neues Dokument hinzufügen"
 
@@ -1086,17 +1085,17 @@ msgid "Compensation {} edited"
 msgstr "Kompensation {} bearbeitet"
 
 #: compensation/views/compensation.py:159 compensation/views/eco_account.py:161
-#: ema/views.py:230 intervention/views.py:313
+#: ema/views.py:230 intervention/views.py:305
 msgid "Edit {}"
 msgstr "Bearbeite {}"
 
 #: compensation/views/compensation.py:238 compensation/views/eco_account.py:317
-#: ema/views.py:191 intervention/views.py:491
+#: ema/views.py:191 intervention/views.py:483
 msgid "Log"
 msgstr "Log"
 
 #: compensation/views/compensation.py:487 compensation/views/eco_account.py:590
-#: ema/views.py:477 intervention/views.py:609
+#: ema/views.py:477 intervention/views.py:601
 msgid "Report {}"
 msgstr "Bericht {}"
 
@@ -1117,32 +1116,32 @@ msgid "Eco-account removed"
 msgstr "Ökokonto entfernt"
 
 #: compensation/views/eco_account.py:338 ema/views.py:272
-#: intervention/views.py:562
+#: intervention/views.py:554
 msgid "{} unrecorded"
 msgstr "{} entzeichnet"
 
 #: compensation/views/eco_account.py:338 ema/views.py:272
-#: intervention/views.py:562
+#: intervention/views.py:554
 msgid "{} recorded"
 msgstr "{} verzeichnet"
 
 #: compensation/views/eco_account.py:663 ema/views.py:543
-#: intervention/views.py:388
+#: intervention/views.py:380
 msgid "{} has already been shared with you"
 msgstr "{} wurde bereits für Sie freigegeben"
 
 #: compensation/views/eco_account.py:668 ema/views.py:548
-#: intervention/views.py:393
+#: intervention/views.py:385
 msgid "{} has been shared with you"
 msgstr "{} ist nun für Sie freigegeben"
 
 #: compensation/views/eco_account.py:675 ema/views.py:555
-#: intervention/views.py:400
+#: intervention/views.py:392
 msgid "Share link invalid"
 msgstr "Freigabelink ungültig"
 
 #: compensation/views/eco_account.py:698 ema/views.py:578
-#: intervention/views.py:423
+#: intervention/views.py:415
 msgid "Share settings updated"
 msgstr "Freigabe Einstellungen aktualisiert"
 
@@ -1314,7 +1313,7 @@ msgstr "Kompensationen und Zahlungen geprüft"
 msgid "Run check"
 msgstr "Prüfung vornehmen"
 
-#: intervention/forms/modalForms.py:213 konova/forms.py:474
+#: intervention/forms/modalForms.py:213 konova/forms.py:475
 msgid ""
 "I, {} {}, confirm that all necessary control steps have been performed by "
 "myself."
@@ -1389,6 +1388,10 @@ msgid "Amount"
 msgstr "Betrag"
 
 #: intervention/templates/intervention/detail/includes/payments.html:59
+msgid "Edit payment"
+msgstr "Zahlung bearbeitet"
+
+#: intervention/templates/intervention/detail/includes/payments.html:62
 msgid "Remove payment"
 msgstr "Zahlung entfernen"
 
@@ -1449,23 +1452,19 @@ msgstr "Eingriffe - Übersicht"
 msgid "Intervention {} added"
 msgstr "Eingriff {} hinzugefügt"
 
-#: intervention/views.py:252
-msgid "This intervention has {} revocations"
-msgstr "Dem Eingriff liegen {} Widersprüche vor"
-
-#: intervention/views.py:301
+#: intervention/views.py:293
 msgid "Intervention {} edited"
 msgstr "Eingriff {} bearbeitet"
 
-#: intervention/views.py:337
+#: intervention/views.py:329
 msgid "{} removed"
 msgstr "{} entfernt"
 
-#: intervention/views.py:444
+#: intervention/views.py:436
 msgid "Check performed"
 msgstr "Prüfung durchgeführt"
 
-#: intervention/views.py:567
+#: intervention/views.py:559
 msgid "There are errors on this intervention:"
 msgstr "Es liegen Fehler in diesem Eingriff vor:"
 
@@ -1557,73 +1556,73 @@ msgstr "Speichern"
 msgid "Not editable"
 msgstr "Nicht editierbar"
 
-#: konova/forms.py:139 konova/forms.py:311
+#: konova/forms.py:140 konova/forms.py:312
 msgid "Confirm"
 msgstr "Bestätige"
 
-#: konova/forms.py:151 konova/forms.py:320
+#: konova/forms.py:152 konova/forms.py:321
 msgid "Remove"
 msgstr "Löschen"
 
-#: konova/forms.py:153
+#: konova/forms.py:154
 msgid "You are about to remove {} {}"
 msgstr "Sie sind dabei {} {} zu löschen"
 
-#: konova/forms.py:240 konova/utils/quality.py:44 konova/utils/quality.py:46
+#: konova/forms.py:241 konova/utils/quality.py:44 konova/utils/quality.py:46
 #: templates/form/collapsable/form.html:45
 msgid "Geometry"
 msgstr "Geometrie"
 
-#: konova/forms.py:321
+#: konova/forms.py:322
 msgid "Are you sure?"
 msgstr "Sind Sie sicher?"
 
-#: konova/forms.py:365
+#: konova/forms.py:366
 msgid "Created on"
 msgstr "Erstellt"
 
-#: konova/forms.py:367
+#: konova/forms.py:368
 msgid "When has this file been created? Important for photos."
 msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?"
 
-#: konova/forms.py:378
+#: konova/forms.py:379
 #: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231
 msgid "File"
 msgstr "Datei"
 
-#: konova/forms.py:380
+#: konova/forms.py:381
 msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
 msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
 
-#: konova/forms.py:426
+#: konova/forms.py:427
 msgid "Unsupported file type"
 msgstr "Dateiformat nicht unterstützt"
 
-#: konova/forms.py:433
+#: konova/forms.py:434
 msgid "File too large"
 msgstr "Datei zu groß"
 
-#: konova/forms.py:442
+#: konova/forms.py:443
 msgid "Added document"
 msgstr "Dokument hinzugefügt"
 
-#: konova/forms.py:465
+#: konova/forms.py:466
 msgid "Confirm record"
 msgstr "Verzeichnen bestätigen"
 
-#: konova/forms.py:473
+#: konova/forms.py:474
 msgid "Record data"
 msgstr "Daten verzeichnen"
 
-#: konova/forms.py:480
+#: konova/forms.py:481
 msgid "Confirm unrecord"
 msgstr "Entzeichnen bestätigen"
 
-#: konova/forms.py:481
+#: konova/forms.py:482
 msgid "Unrecord data"
 msgstr "Daten entzeichnen"
 
-#: konova/forms.py:482
+#: konova/forms.py:483
 msgid "I, {} {}, confirm that this data must be unrecorded."
 msgstr ""
 "Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
@@ -1857,37 +1856,45 @@ msgid "Payment added"
 msgstr "Zahlung hinzugefügt"
 
 #: konova/utils/message_templates.py:49
+msgid "Payment edited"
+msgstr "Zahlung bearbeitet"
+
+#: konova/utils/message_templates.py:50
 msgid "Payment removed"
 msgstr "Zahlung gelöscht"
 
-#: konova/utils/message_templates.py:52
+#: konova/utils/message_templates.py:53
 msgid "Revocation added"
 msgstr "Widerspruch hinzugefügt"
 
-#: konova/utils/message_templates.py:53
+#: konova/utils/message_templates.py:54
 msgid "Revocation removed"
 msgstr "Widerspruch entfernt"
 
-#: konova/utils/message_templates.py:56
+#: konova/utils/message_templates.py:57
 msgid "Document '{}' deleted"
 msgstr "Dokument '{}' gelöscht"
 
-#: konova/utils/message_templates.py:57
+#: konova/utils/message_templates.py:58
 msgid "Document added"
 msgstr "Dokument hinzugefügt"
 
-#: konova/utils/message_templates.py:60
+#: konova/utils/message_templates.py:61
 msgid "Edited general data"
 msgstr "Allgemeine Daten bearbeitet"
 
-#: konova/utils/message_templates.py:61
+#: konova/utils/message_templates.py:62
 msgid "Added deadline"
 msgstr "Frist/Termin hinzugefügt"
 
-#: konova/utils/message_templates.py:64
+#: konova/utils/message_templates.py:65
 msgid "Geometry conflict detected with {}"
 msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}"
 
+#: konova/utils/message_templates.py:68
+msgid "This intervention has {} revocations"
+msgstr "Dem Eingriff liegen {} Widersprüche vor"
+
 #: konova/utils/messenger.py:70
 msgid "{} checked"
 msgstr "{} geprüft"
@@ -3942,9 +3949,6 @@ msgstr ""
 #~ msgid "No file given!"
 #~ msgstr "Keine Datei angegeben!"
 
-#~ msgid "Added payment"
-#~ msgstr "Zahlung hinzufügen"
-
 #~ msgid "Added state"
 #~ msgstr "Zustand hinzugefügt"
 
-- 
2.45.2


From d106977c34b753114b3cd7d6573d797488e68bcc Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Wed, 9 Feb 2022 14:49:56 +0100
Subject: [PATCH 18/31] #86 Edit deductions

* adds support for editing deductions
* adds tests
* improves major base test logic
---
 api/tests/v1/share/test_api_sharing.py        |  18 +-
 .../eco_account/includes/deductions.html      |   7 +-
 compensation/tests/compensation/test_views.py |  41 ++--
 .../tests/compensation/test_workflow.py       |  19 +-
 compensation/tests/ecoaccount/test_views.py   |  41 ++--
 .../tests/ecoaccount/test_workflow.py         |  77 +++++++-
 compensation/tests/payment/test_views.py      |  12 +-
 compensation/tests/payment/test_workflow.py   |   8 +-
 compensation/urls/eco_account.py              |   3 +-
 compensation/views/eco_account.py             |  34 +++-
 ema/tests/test_views.py                       |  55 +++---
 intervention/forms/modalForms.py              |  73 +++++++-
 .../detail/includes/deductions.html           |   7 +-
 intervention/tests/test_views.py              |  42 +++--
 intervention/urls.py                          |   3 +-
 intervention/views.py                         |  32 +++-
 konova/tests/test_views.py                    | 175 ++++++++----------
 konova/utils/message_templates.py             |   5 +
 locale/de/LC_MESSAGES/django.mo               | Bin 36759 -> 37109 bytes
 locale/de/LC_MESSAGES/django.po               | 162 +++++++++-------
 20 files changed, 524 insertions(+), 290 deletions(-)

diff --git a/api/tests/v1/share/test_api_sharing.py b/api/tests/v1/share/test_api_sharing.py
index 9e7c9eec..1da0ce1b 100644
--- a/api/tests/v1/share/test_api_sharing.py
+++ b/api/tests/v1/share/test_api_sharing.py
@@ -12,15 +12,17 @@ class BaseAPIV1TestCase(BaseTestCase):
     def setUpTestData(cls):
         super().setUpTestData()
 
-        cls.superuser.get_API_token()
-        cls.superuser.api_token.is_active = True
-        cls.superuser.api_token.save()
-        default_group = cls.groups.get(name=DEFAULT_GROUP)
-        cls.superuser.groups.add(default_group)
+    def setUp(self) -> None:
+        super().setUp()
+        self.superuser.get_API_token()
+        self.superuser.api_token.is_active = True
+        self.superuser.api_token.save()
+        default_group = self.groups.get(name=DEFAULT_GROUP)
+        self.superuser.groups.add(default_group)
 
-        cls.header_data = {
-            "HTTP_ksptoken": cls.superuser.api_token.token,
-            "HTTP_kspuser": cls.superuser.username,
+        self.header_data = {
+            "HTTP_ksptoken": self.superuser.api_token.token,
+            "HTTP_kspuser": self.superuser.username,
         }
 
 
diff --git a/compensation/templates/compensation/detail/eco_account/includes/deductions.html b/compensation/templates/compensation/detail/eco_account/includes/deductions.html
index e72ab2ab..76c116b2 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/deductions.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/deductions.html
@@ -60,9 +60,12 @@
                 </td>
                 <td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
                 <td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td>
-                <td>
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:acc:remove-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove Deduction' %}">
+                    <button data-form-url="{% url 'compensation:acc:edit-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit Deduction' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:acc:remove-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove Deduction' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/compensation/tests/compensation/test_views.py b/compensation/tests/compensation/test_views.py
index 465a1026..5844e264 100644
--- a/compensation/tests/compensation/test_views.py
+++ b/compensation/tests/compensation/test_views.py
@@ -21,29 +21,32 @@ class CompensationViewTestCase(BaseViewTestCase):
     @classmethod
     def setUpTestData(cls) -> None:
         super().setUpTestData()
-        state = cls.create_dummy_states()
-        cls.compensation.before_states.set([state])
-        cls.compensation.after_states.set([state])
+        
+    def setUp(self) -> None:
+        super().setUp()
+        state = self.create_dummy_states()
+        self.compensation.before_states.set([state])
+        self.compensation.after_states.set([state])
 
-        action = cls.create_dummy_action()
-        cls.compensation.actions.set([action])
+        action = self.create_dummy_action()
+        self.compensation.actions.set([action])
 
         # Prepare urls
-        cls.index_url = reverse("compensation:index", args=())
-        cls.new_url = reverse("compensation:new", args=(cls.intervention.id,))
-        cls.new_id_url = reverse("compensation:new-id", args=())
-        cls.detail_url = reverse("compensation:detail", args=(cls.compensation.id,))
-        cls.log_url = reverse("compensation:log", args=(cls.compensation.id,))
-        cls.edit_url = reverse("compensation:edit", args=(cls.compensation.id,))
-        cls.remove_url = reverse("compensation:remove", args=(cls.compensation.id,))
-        cls.report_url = reverse("compensation:report", args=(cls.compensation.id,))
-        cls.state_new_url = reverse("compensation:new-state", args=(cls.compensation.id,))
-        cls.action_new_url = reverse("compensation:new-action", args=(cls.compensation.id,))
-        cls.deadline_new_url = reverse("compensation:new-deadline", args=(cls.compensation.id,))
-        cls.new_doc_url = reverse("compensation:new-doc", args=(cls.compensation.id,))
+        self.index_url = reverse("compensation:index", args=())
+        self.new_url = reverse("compensation:new", args=(self.intervention.id,))
+        self.new_id_url = reverse("compensation:new-id", args=())
+        self.detail_url = reverse("compensation:detail", args=(self.compensation.id,))
+        self.log_url = reverse("compensation:log", args=(self.compensation.id,))
+        self.edit_url = reverse("compensation:edit", args=(self.compensation.id,))
+        self.remove_url = reverse("compensation:remove", args=(self.compensation.id,))
+        self.report_url = reverse("compensation:report", args=(self.compensation.id,))
+        self.state_new_url = reverse("compensation:new-state", args=(self.compensation.id,))
+        self.action_new_url = reverse("compensation:new-action", args=(self.compensation.id,))
+        self.deadline_new_url = reverse("compensation:new-deadline", args=(self.compensation.id,))
+        self.new_doc_url = reverse("compensation:new-doc", args=(self.compensation.id,))
 
-        cls.state_remove_url = reverse("compensation:state-remove", args=(cls.compensation.id, cls.comp_state.id,))
-        cls.action_remove_url = reverse("compensation:action-remove", args=(cls.compensation.id, cls.comp_action.id,))
+        self.state_remove_url = reverse("compensation:state-remove", args=(self.compensation.id, self.comp_state.id,))
+        self.action_remove_url = reverse("compensation:action-remove", args=(self.compensation.id, self.comp_action.id,))
 
     def test_anonymous_user(self):
         """ Check correct status code for all requests
diff --git a/compensation/tests/compensation/test_workflow.py b/compensation/tests/compensation/test_workflow.py
index d1f13782..7b73be89 100644
--- a/compensation/tests/compensation/test_workflow.py
+++ b/compensation/tests/compensation/test_workflow.py
@@ -21,17 +21,18 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
     def setUpTestData(cls):
         super().setUpTestData()
 
-        # Give the user shared access to the dummy intervention -> inherits the access to the compensation
-        cls.intervention.share_with(cls.superuser)
-
-        # Make sure the intervention itself would be fine with valid data
-        cls.intervention = cls.fill_out_intervention(cls.intervention)
-
-        # Make sure the compensation is linked to the intervention
-        cls.intervention.compensations.set([cls.compensation])
-
     def setUp(self) -> None:
         super().setUp()
+
+        # Give the user shared access to the dummy intervention -> inherits the access to the compensation
+        self.intervention.share_with(self.superuser)
+
+        # Make sure the intervention itself would be fine with valid data
+        self.intervention = self.fill_out_intervention(self.intervention)
+
+        # Make sure the compensation is linked to the intervention
+        self.intervention.compensations.set([self.compensation])
+        
         # Delete all existing compensations, which might be created by tests
         Compensation.objects.all().delete()
 
diff --git a/compensation/tests/ecoaccount/test_views.py b/compensation/tests/ecoaccount/test_views.py
index c4e742fe..670f4f0d 100644
--- a/compensation/tests/ecoaccount/test_views.py
+++ b/compensation/tests/ecoaccount/test_views.py
@@ -25,28 +25,31 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
     @classmethod
     def setUpTestData(cls) -> None:
         super().setUpTestData()
-        state = cls.create_dummy_states()
-        cls.eco_account.before_states.set([state])
-        cls.eco_account.after_states.set([state])
+        
+    def setUp(self) -> None:
+        super().setUp()
+        state = self.create_dummy_states()
+        self.eco_account.before_states.set([state])
+        self.eco_account.after_states.set([state])
 
-        action = cls.create_dummy_action()
-        cls.eco_account.actions.set([action])
+        action = self.create_dummy_action()
+        self.eco_account.actions.set([action])
 
         # Prepare urls
-        cls.index_url = reverse("compensation:acc:index", args=())
-        cls.new_url = reverse("compensation:acc:new", args=())
-        cls.new_id_url = reverse("compensation:acc:new-id", args=())
-        cls.detail_url = reverse("compensation:acc:detail", args=(cls.eco_account.id,))
-        cls.log_url = reverse("compensation:acc:log", args=(cls.eco_account.id,))
-        cls.edit_url = reverse("compensation:acc:edit", args=(cls.eco_account.id,))
-        cls.remove_url = reverse("compensation:acc:remove", args=(cls.eco_account.id,))
-        cls.report_url = reverse("compensation:acc:report", args=(cls.eco_account.id,))
-        cls.state_new_url = reverse("compensation:acc:new-state", args=(cls.eco_account.id,))
-        cls.action_new_url = reverse("compensation:acc:new-action", args=(cls.eco_account.id,))
-        cls.deadline_new_url = reverse("compensation:acc:new-deadline", args=(cls.eco_account.id,))
-        cls.new_doc_url = reverse("compensation:acc:new-doc", args=(cls.eco_account.id,))
-        cls.state_remove_url = reverse("compensation:acc:state-remove", args=(cls.eco_account.id, cls.comp_state.id,))
-        cls.action_remove_url = reverse("compensation:acc:action-remove", args=(cls.eco_account.id, cls.comp_action.id,))
+        self.index_url = reverse("compensation:acc:index", args=())
+        self.new_url = reverse("compensation:acc:new", args=())
+        self.new_id_url = reverse("compensation:acc:new-id", args=())
+        self.detail_url = reverse("compensation:acc:detail", args=(self.eco_account.id,))
+        self.log_url = reverse("compensation:acc:log", args=(self.eco_account.id,))
+        self.edit_url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
+        self.remove_url = reverse("compensation:acc:remove", args=(self.eco_account.id,))
+        self.report_url = reverse("compensation:acc:report", args=(self.eco_account.id,))
+        self.state_new_url = reverse("compensation:acc:new-state", args=(self.eco_account.id,))
+        self.action_new_url = reverse("compensation:acc:new-action", args=(self.eco_account.id,))
+        self.deadline_new_url = reverse("compensation:acc:new-deadline", args=(self.eco_account.id,))
+        self.new_doc_url = reverse("compensation:acc:new-doc", args=(self.eco_account.id,))
+        self.state_remove_url = reverse("compensation:acc:state-remove", args=(self.eco_account.id, self.comp_state.id,))
+        self.action_remove_url = reverse("compensation:acc:action-remove", args=(self.eco_account.id, self.comp_action.id,))
 
     def test_logged_in_no_groups_shared(self):
         """ Check correct status code for all requests
diff --git a/compensation/tests/ecoaccount/test_workflow.py b/compensation/tests/ecoaccount/test_workflow.py
index f394ec7c..1cdb0308 100644
--- a/compensation/tests/ecoaccount/test_workflow.py
+++ b/compensation/tests/ecoaccount/test_workflow.py
@@ -11,7 +11,7 @@ from django.contrib.gis.geos import MultiPolygon
 from django.core.exceptions import ObjectDoesNotExist
 from django.urls import reverse
 
-from compensation.models import EcoAccount
+from compensation.models import EcoAccount, EcoAccountDeduction
 from konova.settings import ETS_GROUP, DEFAULT_GROUP
 from konova.tests.test_views import BaseWorkflowTestCase
 from user.models import UserAction
@@ -168,7 +168,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
         self.assertIn(recorded, self.eco_account.log.all())
         self.assertEqual(pre_record_log_count + 1, self.eco_account.log.count())
 
-    def test_deductability(self):
+    def test_new_deduction(self):
         """
         This tests the deductability of an eco account.
 
@@ -187,7 +187,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
         test_surface = 10.00
         post_data = {
             "surface": test_surface,
-            "account": self.id,
+            "account": self.eco_account.id,
             "intervention": self.intervention.id,
         }
         # Perform request --> expect to fail
@@ -228,4 +228,75 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
         self.assertEqual(pre_deduction_int_log_count + 1, self.intervention.log.count())
         self.assertTrue(self.intervention.log.first().action == UserAction.EDITED)
 
+    def test_edit_deduction(self):
+        test_surface = self.eco_account.get_available_rest()[0]
+        self.eco_account.set_recorded(self.superuser)
+        self.eco_account.refresh_from_db()
 
+        deduction = EcoAccountDeduction.objects.create(
+            intervention=self.intervention,
+            account=self.eco_account,
+            surface=0
+        )
+        self.assertEqual(1, self.intervention.deductions.count())
+        self.assertEqual(1, self.eco_account.deductions.count())
+
+        # Prepare url and form data to be posted
+        new_url = reverse("compensation:acc:edit-deduction", args=(self.eco_account.id, deduction.id))
+        post_data = {
+            "intervention": deduction.intervention.id,
+            "account": deduction.account.id,
+            "surface": test_surface,
+        }
+        pre_edit_intervention_log_count = self.intervention.log.count()
+        pre_edit_account_log_count = self.eco_account.log.count()
+        num_deductions_intervention = self.intervention.deductions.count()
+        num_deductions_account = self.eco_account.deductions.count()
+
+        self.client_user.post(new_url, post_data)
+
+        self.intervention.refresh_from_db()
+        self.eco_account.refresh_from_db()
+        deduction.refresh_from_db()
+
+        self.assertEqual(num_deductions_intervention, self.intervention.deductions.count())
+        self.assertEqual(num_deductions_account, self.eco_account.deductions.count())
+        self.assertEqual(deduction.surface, test_surface)
+
+        # Expect logs to be set
+        self.assertEqual(pre_edit_intervention_log_count + 1, self.intervention.log.count())
+        self.assertEqual(pre_edit_account_log_count + 1, self.eco_account.log.count())
+        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
+        self.assertEqual(self.eco_account.log.first().action, UserAction.EDITED)
+
+    def test_remove_deduction(self):
+        intervention = self.deduction.intervention
+        account = self.deduction.account
+
+        # Prepare url and form data to be posted
+        new_url = reverse("compensation:acc:remove-deduction", args=(account.id, self.deduction.id))
+        post_data = {
+            "confirm": True,
+        }
+
+        intervention.share_with(self.superuser)
+        account.share_with(self.superuser)
+
+        pre_edit_intervention_log_count = intervention.log.count()
+        pre_edit_account_log_count = account.log.count()
+        num_deductions_intervention = intervention.deductions.count()
+        num_deductions_account = account.deductions.count()
+
+        self.client_user.post(new_url, post_data)
+
+        intervention.refresh_from_db()
+        account.refresh_from_db()
+
+        self.assertEqual(num_deductions_intervention - 1, intervention.deductions.count())
+        self.assertEqual(num_deductions_account - 1, account.deductions.count())
+
+        # Expect logs to be set
+        self.assertEqual(pre_edit_intervention_log_count + 1, intervention.log.count())
+        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)
diff --git a/compensation/tests/payment/test_views.py b/compensation/tests/payment/test_views.py
index 69130e86..b1eca5ae 100644
--- a/compensation/tests/payment/test_views.py
+++ b/compensation/tests/payment/test_views.py
@@ -19,16 +19,18 @@ class PaymentViewTestCase(BaseViewTestCase):
     def setUpTestData(cls) -> None:
         super().setUpTestData()
 
-        cls.payment = Payment.objects.get_or_create(
-            intervention=cls.intervention,
+    def setUp(self) -> None:
+        super().setUp()
+        self.payment = Payment.objects.get_or_create(
+            intervention=self.intervention,
             amount=1,
             due_on="2020-01-01",
             comment="Testcomment"
         )[0]
 
-        cls.new_url = reverse("compensation:pay:new", args=(cls.intervention.id,))
-        cls.edit_url = reverse("compensation:pay:edit", args=(cls.intervention.id, cls.payment.id))
-        cls.remove_url = reverse("compensation:pay:remove", args=(cls.intervention.id, cls.payment.id))
+        self.new_url = reverse("compensation:pay:new", args=(self.intervention.id,))
+        self.edit_url = reverse("compensation:pay:edit", args=(self.intervention.id, self.payment.id))
+        self.remove_url = reverse("compensation:pay:remove", args=(self.intervention.id, self.payment.id))
 
     def test_anonymous_user(self):
         """ Check correct status code for all requests
diff --git a/compensation/tests/payment/test_workflow.py b/compensation/tests/payment/test_workflow.py
index 09ff0e69..790fb619 100644
--- a/compensation/tests/payment/test_workflow.py
+++ b/compensation/tests/payment/test_workflow.py
@@ -18,11 +18,13 @@ class PaymentWorkflowTestCase(BaseWorkflowTestCase):
     def setUpTestData(cls):
         super().setUpTestData()
 
+    def setUp(self) -> None:
+        super().setUp()
         # Give the user shared access to the dummy intervention
-        cls.intervention.share_with(cls.superuser)
+        self.intervention.share_with(self.superuser)
 
-        cls.payment = Payment.objects.get_or_create(
-            intervention=cls.intervention,
+        self.payment = Payment.objects.get_or_create(
+            intervention=self.intervention,
             amount=1,
             due_on="2020-01-01",
             comment="Testcomment"
diff --git a/compensation/urls/eco_account.py b/compensation/urls/eco_account.py
index 7deb48f7..0c3a0293 100644
--- a/compensation/urls/eco_account.py
+++ b/compensation/urls/eco_account.py
@@ -34,7 +34,8 @@ urlpatterns = [
     path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
 
     # Eco-account deductions
-    path('<id>/remove/<deduction_id>', deduction_remove_view, name='remove-deduction'),
+    path('<id>/deduction/<deduction_id>/remove', deduction_remove_view, name='remove-deduction'),
+    path('<id>/deduction/<deduction_id>/edit', deduction_edit_view, name='edit-deduction'),
     path('<id>/deduct/new', new_deduction_view, name='new-deduction'),
 
 ]
\ No newline at end of file
diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py
index 7d1e560f..08e01a61 100644
--- a/compensation/views/eco_account.py
+++ b/compensation/views/eco_account.py
@@ -19,7 +19,8 @@ from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm,
     NewEcoAccountDocumentForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
 from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
 from compensation.tables import EcoAccountTable
-from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm
+from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm, \
+    EditEcoAccountDeductionModalForm
 from konova.contexts import BaseContext
 from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
     shared_access_required
@@ -31,7 +32,8 @@ from konova.utils.documents import get_document, remove_document
 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
+    COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, \
+    DEDUCTION_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -294,6 +296,34 @@ def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str):
     )
 
 
+@login_required
+@default_group_required
+@shared_access_required(EcoAccount, "id")
+def deduction_edit_view(request: HttpRequest, id: str, deduction_id: str):
+    """ Renders a modal view for editing deductions
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The eco account's id
+        deduction_id (str): The deduction's id
+
+    Returns:
+
+    """
+    acc = get_object_or_404(EcoAccount, id=id)
+    try:
+        eco_deduction = acc.deductions.get(id=deduction_id)
+    except ObjectDoesNotExist:
+        raise Http404("Unknown deduction")
+
+    form = EditEcoAccountDeductionModalForm(request.POST or None, instance=acc, deduction=eco_deduction, request=request)
+    return form.process_request(
+        request=request,
+        msg_success=DEDUCTION_EDITED,
+        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @default_group_required
 @shared_access_required(EcoAccount, "id")
diff --git a/ema/tests/test_views.py b/ema/tests/test_views.py
index dd37fced..65f4d351 100644
--- a/ema/tests/test_views.py
+++ b/ema/tests/test_views.py
@@ -31,42 +31,43 @@ class EmaViewTestCase(CompensationViewTestCase):
     def setUpTestData(cls) -> None:
         super().setUpTestData()
 
+    def setUp(self) -> None:
+        super().setUp()
         # Create dummy data and related objects, like states or actions
-        cls.create_dummy_data()
-        state = cls.create_dummy_states()
-        action = cls.create_dummy_action()
-        cls.ema.before_states.set([state])
-        cls.ema.after_states.set([state])
-        cls.ema.actions.set([action])
+        self.create_dummy_data()
+        state = self.create_dummy_states()
+        action = self.create_dummy_action()
+        self.ema.before_states.set([state])
+        self.ema.after_states.set([state])
+        self.ema.actions.set([action])
 
         # Prepare urls
-        cls.index_url = reverse("ema:index", args=())
-        cls.new_url = reverse("ema:new", args=())
-        cls.new_id_url = reverse("ema:new-id", args=())
-        cls.detail_url = reverse("ema:detail", args=(cls.ema.id,))
-        cls.log_url = reverse("ema:log", args=(cls.ema.id,))
-        cls.edit_url = reverse("ema:edit", args=(cls.ema.id,))
-        cls.remove_url = reverse("ema:remove", args=(cls.ema.id,))
-        cls.share_url = reverse("ema:share", args=(cls.ema.id, cls.ema.access_token,))
-        cls.share_create_url = reverse("ema:share-create", args=(cls.ema.id,))
-        cls.record_url = reverse("ema:record", args=(cls.ema.id,))
-        cls.report_url = reverse("ema:report", args=(cls.ema.id,))
-        cls.new_doc_url = reverse("ema:new-doc", args=(cls.ema.id,))
-        cls.state_new_url = reverse("ema:new-state", args=(cls.ema.id,))
-        cls.action_new_url = reverse("ema:new-action", args=(cls.ema.id,))
-        cls.deadline_new_url = reverse("ema:new-deadline", args=(cls.ema.id,))
-        cls.state_remove_url = reverse("ema:state-remove", args=(cls.ema.id, state.id,))
-        cls.action_remove_url = reverse("ema:action-remove", args=(cls.ema.id, action.id,))
+        self.index_url = reverse("ema:index", args=())
+        self.new_url = reverse("ema:new", args=())
+        self.new_id_url = reverse("ema:new-id", args=())
+        self.detail_url = reverse("ema:detail", args=(self.ema.id,))
+        self.log_url = reverse("ema:log", args=(self.ema.id,))
+        self.edit_url = reverse("ema:edit", args=(self.ema.id,))
+        self.remove_url = reverse("ema:remove", args=(self.ema.id,))
+        self.share_url = reverse("ema:share", args=(self.ema.id, self.ema.access_token,))
+        self.share_create_url = reverse("ema:share-create", args=(self.ema.id,))
+        self.record_url = reverse("ema:record", args=(self.ema.id,))
+        self.report_url = reverse("ema:report", args=(self.ema.id,))
+        self.new_doc_url = reverse("ema:new-doc", args=(self.ema.id,))
+        self.state_new_url = reverse("ema:new-state", args=(self.ema.id,))
+        self.action_new_url = reverse("ema:new-action", args=(self.ema.id,))
+        self.deadline_new_url = reverse("ema:new-deadline", args=(self.ema.id,))
+        self.state_remove_url = reverse("ema:state-remove", args=(self.ema.id, state.id,))
+        self.action_remove_url = reverse("ema:action-remove", args=(self.ema.id, action.id,))
 
-    @classmethod
-    def create_dummy_data(cls):
+    def create_dummy_data(self):
         # Create dummy data
         # Create log entry
-        action = UserActionLogEntry.get_created_action(cls.superuser)
+        action = UserActionLogEntry.get_created_action(self.superuser)
         # Create responsible data object
         responsibility_data = Responsibility.objects.create()
         geometry = Geometry.objects.create()
-        cls.ema = Ema.objects.create(
+        self.ema = Ema.objects.create(
             identifier="TEST",
             title="Test_title",
             created=action,
diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py
index 6e2a2a39..6a044c7c 100644
--- a/intervention/forms/modalForms.py
+++ b/intervention/forms/modalForms.py
@@ -7,7 +7,7 @@ Created on: 27.09.21
 """
 from dal import autocomplete
 
-from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED
+from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED
 from user.models import User, UserActionLogEntry
 from django.db import transaction
 from django import forms
@@ -349,6 +349,21 @@ class NewDeductionModalForm(BaseModalForm):
         else:
             raise NotImplementedError
 
+    def _get_available_surface(self, acc):
+        """ Calculates how much available surface is left on the account
+
+        Args:
+            acc (EcoAccount):
+
+        Returns:
+
+        """
+        # Calculate valid surface
+        deductable_surface = acc.deductable_surface
+        sum_surface_deductions = acc.get_deductions_surface()
+        rest_surface = deductable_surface - sum_surface_deductions
+        return rest_surface
+
     def is_valid(self):
         """ Custom validity check
 
@@ -367,10 +382,7 @@ class NewDeductionModalForm(BaseModalForm):
             )
             return False
 
-        # Calculate valid surface
-        deductable_surface = acc.deductable_surface
-        sum_surface_deductions = acc.get_deductions_surface()
-        rest_surface = deductable_surface - sum_surface_deductions
+        rest_surface = self._get_available_surface(acc)
         form_surface = float(self.cleaned_data["surface"])
         is_valid_surface = form_surface <= rest_surface
         if not is_valid_surface:
@@ -407,6 +419,57 @@ class NewDeductionModalForm(BaseModalForm):
         return deduction
 
 
+class EditEcoAccountDeductionModalForm(NewDeductionModalForm):
+    deduction = None
+
+    def __init__(self, *args, **kwargs):
+        self.deduction = kwargs.pop("deduction", None)
+        super().__init__(*args, **kwargs)
+        form_data = {
+            "account": self.deduction.account,
+            "intervention": self.deduction.intervention,
+            "surface": self.deduction.surface,
+        }
+        self.load_initial_data(form_data)
+
+    def _get_available_surface(self, acc):
+        rest_surface = super()._get_available_surface(acc)
+        # Increase available surface by the currently deducted surface, so we can 'deduct' the same amount again or
+        # increase the surface only a little, which will still be valid.
+        # Example: 200 m² left, 500 m² deducted. Entering 700 m² would fail if we would not add the 500 m² to the available
+        # surface again.
+        rest_surface += self.deduction.surface
+        return rest_surface
+
+    def save(self):
+        deduction = self.deduction
+        form_account = self.cleaned_data.get("account", None)
+        form_intervention = self.cleaned_data.get("intervention", None)
+        current_account = deduction.account
+        current_intervention = deduction.intervention
+
+
+        # If account or intervention has been changed, we put that change in the logs just as if the deduction has
+        # been removed for this entry. Act as if the deduction is newly created for the new entries
+        if current_account != form_account:
+            current_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
+            form_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
+        else:
+            current_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
+
+        if current_intervention != form_intervention:
+            current_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
+            form_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
+        else:
+            current_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
+
+        deduction.account = form_account
+        deduction.intervention = self.cleaned_data.get("intervention", None)
+        deduction.surface = self.cleaned_data.get("surface", None)
+        deduction.save()
+        return deduction
+
+
 class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
     """ Removing modal form for EcoAccountDeduction
 
diff --git a/intervention/templates/intervention/detail/includes/deductions.html b/intervention/templates/intervention/detail/includes/deductions.html
index 99f11cb8..b11817d1 100644
--- a/intervention/templates/intervention/detail/includes/deductions.html
+++ b/intervention/templates/intervention/detail/includes/deductions.html
@@ -55,9 +55,12 @@
                 </td>
                 <td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
                 <td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td>
-                <td>
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'intervention:remove-deduction' obj.id deduction.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove Deduction' %}">
+                    <button data-form-url="{% url 'intervention:edit-deduction' obj.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit Deduction' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'intervention:remove-deduction' obj.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove Deduction' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/intervention/tests/test_views.py b/intervention/tests/test_views.py
index 2cdb299d..68e4b562 100644
--- a/intervention/tests/test_views.py
+++ b/intervention/tests/test_views.py
@@ -21,30 +21,32 @@ class InterventionViewTestCase(BaseViewTestCase):
     def setUpTestData(cls) -> None:
         super().setUpTestData()
 
+    def setUp(self) -> None:
+        super().setUp()
         # Prepare urls
-        cls.index_url = reverse("intervention:index", args=())
-        cls.new_url = reverse("intervention:new", args=())
-        cls.new_id_url = reverse("intervention:new-id", args=())
-        cls.detail_url = reverse("intervention:detail", args=(cls.intervention.id,))
-        cls.log_url = reverse("intervention:log", args=(cls.intervention.id,))
-        cls.edit_url = reverse("intervention:edit", args=(cls.intervention.id,))
-        cls.remove_url = reverse("intervention:remove", args=(cls.intervention.id,))
-        cls.share_url = reverse("intervention:share", args=(cls.intervention.id, cls.intervention.access_token,))
-        cls.share_create_url = reverse("intervention:share-create", args=(cls.intervention.id,))
-        cls.run_check_url = reverse("intervention:check", args=(cls.intervention.id,))
-        cls.record_url = reverse("intervention:record", args=(cls.intervention.id,))
-        cls.report_url = reverse("intervention:report", args=(cls.intervention.id,))
+        self.index_url = reverse("intervention:index", args=())
+        self.new_url = reverse("intervention:new", args=())
+        self.new_id_url = reverse("intervention:new-id", args=())
+        self.detail_url = reverse("intervention:detail", args=(self.intervention.id,))
+        self.log_url = reverse("intervention:log", args=(self.intervention.id,))
+        self.edit_url = reverse("intervention:edit", args=(self.intervention.id,))
+        self.remove_url = reverse("intervention:remove", args=(self.intervention.id,))
+        self.share_url = reverse("intervention:share", args=(self.intervention.id, self.intervention.access_token,))
+        self.share_create_url = reverse("intervention:share-create", args=(self.intervention.id,))
+        self.run_check_url = reverse("intervention:check", args=(self.intervention.id,))
+        self.record_url = reverse("intervention:record", args=(self.intervention.id,))
+        self.report_url = reverse("intervention:report", args=(self.intervention.id,))
 
-        cls.deduction.intervention = cls.intervention
-        cls.deduction.save()
-        cls.deduction_new_url = reverse("intervention:new-deduction", args=(cls.intervention.id,))
-        cls.deduction_remove_url = reverse("intervention:remove-deduction", args=(cls.intervention.id, cls.deduction.id))
+        self.deduction.intervention = self.intervention
+        self.deduction.save()
+        self.deduction_new_url = reverse("intervention:new-deduction", args=(self.intervention.id,))
+        self.deduction_remove_url = reverse("intervention:remove-deduction", args=(self.intervention.id, self.deduction.id))
 
-        cls.revocation = Revocation.objects.create(
-            legal=cls.intervention.legal
+        self.revocation = Revocation.objects.create(
+            legal=self.intervention.legal
         )
-        cls.revocation_new_url = reverse("intervention:new-revocation", args=(cls.intervention.id,))
-        cls.revocation_remove_url = reverse("intervention:remove-revocation", args=(cls.intervention.id, cls.revocation.id))
+        self.revocation_new_url = reverse("intervention:new-revocation", args=(self.intervention.id,))
+        self.revocation_remove_url = reverse("intervention:remove-revocation", args=(self.intervention.id, self.revocation.id))
 
     def test_views_anonymous_user(self):
         """ Check correct status code for all requests
diff --git a/intervention/urls.py b/intervention/urls.py
index 7d6ff32f..8ad7f31e 100644
--- a/intervention/urls.py
+++ b/intervention/urls.py
@@ -10,7 +10,7 @@ from django.urls import path
 from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \
     create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \
     record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \
-    remove_deduction_view, remove_compensation_view
+    remove_deduction_view, remove_compensation_view, edit_deduction_view
 
 app_name = "intervention"
 urlpatterns = [
@@ -37,6 +37,7 @@ urlpatterns = [
 
     # Deductions
     path('<id>/deduction/new', new_deduction_view, name='new-deduction'),
+    path('<id>/deduction/<deduction_id>/edit', edit_deduction_view, name='edit-deduction'),
     path('<id>/deduction/<deduction_id>/remove', remove_deduction_view, name='remove-deduction'),
 
     # Revocation routes
diff --git a/intervention/views.py b/intervention/views.py
index 8a59d9a9..6db35474 100644
--- a/intervention/views.py
+++ b/intervention/views.py
@@ -7,7 +7,7 @@ from django.shortcuts import render
 from intervention.forms.forms import NewInterventionForm, EditInterventionForm
 from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \
     CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, RemoveEcoAccountDeductionModalForm, \
-    RemoveRevocationModalForm
+    RemoveRevocationModalForm, EditEcoAccountDeductionModalForm
 from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
 from intervention.tables import InterventionTable
 from konova.contexts import BaseContext
@@ -18,7 +18,7 @@ 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
+    COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -536,6 +536,34 @@ def remove_deduction_view(request: HttpRequest, id: str, deduction_id: str):
     )
 
 
+@login_required
+@default_group_required
+@shared_access_required(Intervention, "id")
+def edit_deduction_view(request: HttpRequest, id: str, deduction_id: str):
+    """ Renders a modal view for removing deductions
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The intervention's id
+        deduction_id (str): The deduction's id
+
+    Returns:
+
+    """
+    intervention = get_object_or_404(Intervention, id=id)
+    try:
+        eco_deduction = intervention.deductions.get(id=deduction_id)
+    except ObjectDoesNotExist:
+        raise Http404("Unknown deduction")
+
+    form = EditEcoAccountDeductionModalForm(request.POST or None, instance=intervention, deduction=eco_deduction, request=request)
+    return form.process_request(
+        request=request,
+        msg_success=DEDUCTION_EDITED,
+        redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @conservation_office_group_required
 @shared_access_required(Intervention, "id")
diff --git a/konova/tests/test_views.py b/konova/tests/test_views.py
index 536a7868..6218a6c0 100644
--- a/konova/tests/test_views.py
+++ b/konova/tests/test_views.py
@@ -47,43 +47,58 @@ class BaseTestCase(TestCase):
     class Meta:
         abstract = True
 
-    @classmethod
-    def setUpTestData(cls):
-        cls.create_users()
-        cls.create_groups()
-        cls.intervention = cls.create_dummy_intervention()
-        cls.compensation = cls.create_dummy_compensation()
-        cls.eco_account = cls.create_dummy_eco_account()
-        cls.ema = cls.create_dummy_ema()
-        cls.deduction = cls.create_dummy_deduction()
-        cls.create_dummy_states()
-        cls.create_dummy_action()
-        cls.codes = cls.create_dummy_codes()
+    def setUp(self) -> None:
+        """ Setup data before each test run
 
-    @classmethod
-    def create_users(cls):
+        Returns:
+
+        """
+        super().setUp()
+
+        self.create_users()
+        self.create_groups()
+        self.intervention = self.create_dummy_intervention()
+        self.compensation = self.create_dummy_compensation()
+        self.eco_account = self.create_dummy_eco_account()
+        self.ema = self.create_dummy_ema()
+        self.deduction = self.create_dummy_deduction()
+        self.create_dummy_states()
+        self.create_dummy_action()
+        self.codes = self.create_dummy_codes()
+
+        # Set the default group as only group for the user
+        default_group = self.groups.get(name=DEFAULT_GROUP)
+        self.superuser.groups.set([default_group])
+
+        # Create fresh logged in client and a non-logged in client (anon) for each test
+        self.client_user = Client()
+        self.client_user.login(username=self.superuser.username, password=self.superuser_pw)
+        self.client_anon = Client()
+
+    
+    def create_users(self):
         # Create superuser and regular user
-        cls.superuser = User.objects.create_superuser(
+        self.superuser = User.objects.create_superuser(
             username="root",
             email="root@root.com",
-            password=cls.superuser_pw,
+            password=self.superuser_pw,
         )
-        cls.user = User.objects.create_user(
+        self.user = User.objects.create_user(
             username="user1",
             email="user@root.com",
-            password=cls.user_pw
+            password=self.user_pw
         )
-        cls.users = User.objects.all()
+        self.users = User.objects.all()
 
-    @classmethod
-    def create_groups(cls):
+    
+    def create_groups(self):
         # Create groups
         for group_data in GROUPS_DATA:
             name = group_data.get("name")
             Group.objects.get_or_create(
                 name=name,
             )
-        cls.groups = Group.objects.all()
+        self.groups = Group.objects.all()
 
     @staticmethod
     def create_dummy_string(prefix: str = ""):
@@ -94,8 +109,7 @@ class BaseTestCase(TestCase):
         """
         return f"{prefix}{generate_random_string(3, True)}"
 
-    @classmethod
-    def create_dummy_intervention(cls):
+    def create_dummy_intervention(self):
         """ Creates an intervention which can be used for tests
 
         Returns:
@@ -103,7 +117,7 @@ class BaseTestCase(TestCase):
         """
         # Create dummy data
         # Create log entry
-        action = UserActionLogEntry.get_created_action(cls.superuser)
+        action = UserActionLogEntry.get_created_action(self.superuser)
         # Create legal data object (without M2M laws first)
         legal_data = Legal.objects.create()
         # Create responsible data object
@@ -122,32 +136,30 @@ class BaseTestCase(TestCase):
         intervention.generate_access_token(make_unique=True)
         return intervention
 
-    @classmethod
-    def create_dummy_compensation(cls):
+    def create_dummy_compensation(self):
         """ Creates a compensation which can be used for tests
 
         Returns:
 
         """
-        if cls.intervention is None:
-            cls.intervention = cls.create_dummy_intervention()
+        if self.intervention is None:
+            self.intervention = self.create_dummy_intervention()
         # Create dummy data
         # Create log entry
-        action = UserActionLogEntry.get_created_action(cls.superuser)
+        action = UserActionLogEntry.get_created_action(self.superuser)
         geometry = Geometry.objects.create()
         # Finally create main object, holding the other objects
         compensation = Compensation.objects.create(
             identifier="TEST",
             title="Test_title",
-            intervention=cls.intervention,
+            intervention=self.intervention,
             created=action,
             geometry=geometry,
             comment="Test",
         )
         return compensation
 
-    @classmethod
-    def create_dummy_eco_account(cls):
+    def create_dummy_eco_account(self):
         """ Creates an eco account which can be used for tests
 
         Returns:
@@ -155,7 +167,7 @@ class BaseTestCase(TestCase):
         """
         # Create dummy data
         # Create log entry
-        action = UserActionLogEntry.get_created_action(cls.superuser)
+        action = UserActionLogEntry.get_created_action(self.superuser)
         geometry = Geometry.objects.create()
         # Create responsible data object
         lega_data = Legal.objects.create()
@@ -172,8 +184,7 @@ class BaseTestCase(TestCase):
         )
         return eco_account
 
-    @classmethod
-    def create_dummy_ema(cls):
+    def create_dummy_ema(self):
         """ Creates an ema which can be used for tests
 
         Returns:
@@ -181,7 +192,7 @@ class BaseTestCase(TestCase):
         """
         # Create dummy data
         # Create log entry
-        action = UserActionLogEntry.get_created_action(cls.superuser)
+        action = UserActionLogEntry.get_created_action(self.superuser)
         geometry = Geometry.objects.create()
         # Create responsible data object
         responsible_data = Responsibility.objects.create()
@@ -196,41 +207,37 @@ class BaseTestCase(TestCase):
         )
         return ema
 
-    @classmethod
-    def create_dummy_deduction(cls):
+    def create_dummy_deduction(self):
         return EcoAccountDeduction.objects.create(
-            account=cls.create_dummy_eco_account(),
-            intervention=cls.create_dummy_intervention(),
+            account=self.create_dummy_eco_account(),
+            intervention=self.create_dummy_intervention(),
             surface=100,
         )
 
-    @classmethod
-    def create_dummy_states(cls):
+    def create_dummy_states(self):
         """ Creates an intervention which can be used for tests
 
         Returns:
 
         """
-        cls.comp_state = CompensationState.objects.create(
+        self.comp_state = CompensationState.objects.create(
             surface=10.00,
             biotope_type=None,
         )
-        return cls.comp_state
+        return self.comp_state
 
-    @classmethod
-    def create_dummy_action(cls):
+    def create_dummy_action(self):
         """ Creates an intervention which can be used for tests
 
         Returns:
 
         """
-        cls.comp_action = CompensationAction.objects.create(
+        self.comp_action = CompensationAction.objects.create(
             amount=10
         )
-        return cls.comp_action
+        return self.comp_action
 
-    @classmethod
-    def create_dummy_codes(cls):
+    def create_dummy_codes(self):
         """ Creates some dummy KonovaCodes which can be used for testing
 
         Returns:
@@ -256,8 +263,7 @@ class BaseTestCase(TestCase):
         polygon = polygon.transform(3857, clone=True)
         return MultiPolygon(polygon, srid=3857)  # 3857 is the default srid used for MultiPolygonField in the form
 
-    @classmethod
-    def fill_out_intervention(cls, intervention: Intervention) -> Intervention:
+    def fill_out_intervention(self, intervention: Intervention) -> Intervention:
         """ Adds all required (dummy) data to an intervention
 
         Args:
@@ -277,13 +283,12 @@ class BaseTestCase(TestCase):
         intervention.legal.process_type = KonovaCode.objects.get(id=3)
         intervention.legal.save()
         intervention.legal.laws.set([KonovaCode.objects.get(id=(4))])
-        intervention.geometry.geom = cls.create_dummy_geometry()
+        intervention.geometry.geom = self.create_dummy_geometry()
         intervention.geometry.save()
         intervention.save()
         return intervention
 
-    @classmethod
-    def fill_out_compensation(cls, compensation: Compensation) -> Compensation:
+    def fill_out_compensation(self, compensation: Compensation) -> Compensation:
         """ Adds all required (dummy) data to a compensation
 
         Args:
@@ -292,15 +297,14 @@ class BaseTestCase(TestCase):
         Returns:
             compensation (Compensation): The modified compensation
         """
-        compensation.after_states.add(cls.comp_state)
-        compensation.before_states.add(cls.comp_state)
-        compensation.actions.add(cls.comp_action)
-        compensation.geometry.geom = cls.create_dummy_geometry()
+        compensation.after_states.add(self.comp_state)
+        compensation.before_states.add(self.comp_state)
+        compensation.actions.add(self.comp_action)
+        compensation.geometry.geom = self.create_dummy_geometry()
         compensation.geometry.save()
         return compensation
 
-    @classmethod
-    def get_conservation_office_code(cls):
+    def get_conservation_office_code(self):
         """ Returns a dummy KonovaCode as conservation office code
 
         Returns:
@@ -313,39 +317,37 @@ class BaseTestCase(TestCase):
         codelist.codes.add(code)
         return code
 
-    @classmethod
-    def fill_out_ema(cls, ema):
+    def fill_out_ema(self, ema):
         """ Adds all required (dummy) data to an Ema
 
         Returns:
         """
-        ema.responsible.conservation_office = cls.get_conservation_office_code()
+        ema.responsible.conservation_office = self.get_conservation_office_code()
         ema.responsible.conservation_file_number = "test"
         ema.responsible.handler = "handler"
         ema.responsible.save()
-        ema.after_states.add(cls.comp_state)
-        ema.before_states.add(cls.comp_state)
-        ema.actions.add(cls.comp_action)
-        ema.geometry.geom = cls.create_dummy_geometry()
+        ema.after_states.add(self.comp_state)
+        ema.before_states.add(self.comp_state)
+        ema.actions.add(self.comp_action)
+        ema.geometry.geom = self.create_dummy_geometry()
         ema.geometry.save()
         return ema
 
-    @classmethod
-    def fill_out_eco_account(cls, eco_account):
+    def fill_out_eco_account(self, eco_account):
         """ Adds all required (dummy) data to an EcoAccount
 
         Returns:
         """
         eco_account.legal.registration_date = "2022-01-01"
         eco_account.legal.save()
-        eco_account.responsible.conservation_office = cls.get_conservation_office_code()
+        eco_account.responsible.conservation_office = self.get_conservation_office_code()
         eco_account.responsible.conservation_file_number = "test"
         eco_account.responsible.handler = "handler"
         eco_account.responsible.save()
-        eco_account.after_states.add(cls.comp_state)
-        eco_account.before_states.add(cls.comp_state)
-        eco_account.actions.add(cls.comp_action)
-        eco_account.geometry.geom = cls.create_dummy_geometry()
+        eco_account.after_states.add(self.comp_state)
+        eco_account.before_states.add(self.comp_state)
+        eco_account.actions.add(self.comp_action)
+        eco_account.geometry.geom = self.create_dummy_geometry()
         eco_account.geometry.save()
         eco_account.deductable_surface = eco_account.get_state_after_surface_sum()
         eco_account.save()
@@ -390,7 +392,10 @@ class BaseViewTestCase(BaseTestCase):
     @classmethod
     def setUpTestData(cls) -> None:
         super().setUpTestData()
-        cls.login_url = reverse("simple-sso-login")
+        
+    def setUp(self) -> None:
+        super().setUp()
+        self.login_url = reverse("simple-sso-login")
 
     def assert_url_success(self, client: Client, urls: list):
         """ Assert for all given urls a direct 200 response
@@ -549,22 +554,6 @@ class BaseWorkflowTestCase(BaseTestCase):
     def setUpTestData(cls):
         super().setUpTestData()
 
-    def setUp(self) -> None:
-        """ Setup data before each test run
-
-        Returns:
-
-        """
-        super().setUp()
-        # Set the default group as only group for the user
-        default_group = self.groups.get(name=DEFAULT_GROUP)
-        self.superuser.groups.set([default_group])
-
-        # Create fresh logged in client and a non-logged in client (anon) for each test
-        self.client_user = Client()
-        self.client_user.login(username=self.superuser.username, password=self.superuser_pw)
-        self.client_anon = Client()
-
     def assert_object_is_deleted(self, obj):
         """ Provides a quick check whether an object has been removed from the database or not
 
diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py
index b7eb5253..ea778ea2 100644
--- a/konova/utils/message_templates.py
+++ b/konova/utils/message_templates.py
@@ -30,18 +30,22 @@ ADDED_COMPENSATION_STATE = _("Added compensation state")
 
 # COMPENSATION STATE
 COMPENSATION_STATE_REMOVED = _("State removed")
+COMPENSATION_STATE_EDITED = _("State edited")
 COMPENSATION_STATE_ADDED = _("State added")
 
 # COMPENSATION ACTION
 COMPENSATION_ACTION_ADDED = _("Action added")
+COMPENSATION_ACTION_EDITED = _("Action edited")
 COMPENSATION_ACTION_REMOVED = _("Action removed")
 
 # DEDUCTIONS
 DEDUCTION_ADDED = _("Deduction added")
+DEDUCTION_EDITED = _("Deduction edited")
 DEDUCTION_REMOVED = _("Deduction removed")
 
 # DEADLINE
 DEADLINE_ADDED = _("Deadline added")
+DEADLINE_EDITED = _("Deadline edited")
 DEADLINE_REMOVED = _("Deadline removed")
 
 # PAYMENTS
@@ -51,6 +55,7 @@ PAYMENT_REMOVED = _("Payment removed")
 
 # REVOCATIONS
 REVOCATION_ADDED = _("Revocation added")
+REVOCATION_EDITED = _("Revocation edited")
 REVOCATION_REMOVED = _("Revocation removed")
 
 # DOCUMENTS
diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo
index 85b75fdda447f5409f7dac392a4a7ff964a0e7fe..86cf2739bfdda76a4b610efe974b54aee72f0ad0 100644
GIT binary patch
delta 10863
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
RAE}Y*M)*9E&B_id^M7fg6Dj}z

delta 10631
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
Qf;k5=BK>o&&xpwTf1o<gnE(I)

diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po
index 3feeb6db..67ae2338 100644
--- a/locale/de/LC_MESSAGES/django.po
+++ b/locale/de/LC_MESSAGES/django.po
@@ -5,7 +5,7 @@
 #
 #: compensation/filters.py:122 compensation/forms/modalForms.py:35
 #: compensation/forms/modalForms.py:46 compensation/forms/modalForms.py:62
-#: compensation/forms/modalForms.py:329 compensation/forms/modalForms.py:422
+#: compensation/forms/modalForms.py:332 compensation/forms/modalForms.py:425
 #: intervention/forms/forms.py:54 intervention/forms/forms.py:156
 #: intervention/forms/forms.py:168 intervention/forms/modalForms.py:124
 #: intervention/forms/modalForms.py:137 intervention/forms/modalForms.py:150
@@ -26,7 +26,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-09 09:50+0100\n"
+"POT-Creation-Date: 2022-02-09 12:52+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -95,7 +95,7 @@ msgstr ""
 #: analysis/templates/analysis/reports/includes/eco_account/amount.html:3
 #: analysis/templates/analysis/reports/includes/intervention/amount.html:3
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:3
-#: compensation/forms/modalForms.py:406
+#: compensation/forms/modalForms.py:409
 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:34
 #: intervention/templates/intervention/detail/includes/deductions.html:31
 msgid "Amount"
@@ -213,7 +213,7 @@ msgstr "Abbuchungen"
 
 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:9
 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:11
-#: compensation/forms/modalForms.py:190
+#: compensation/forms/modalForms.py:193
 #: compensation/templates/compensation/detail/compensation/includes/states-after.html:36
 #: compensation/templates/compensation/detail/compensation/includes/states-before.html:36
 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:36
@@ -353,7 +353,7 @@ msgid "Compensation XY; Location ABC"
 msgstr "Kompensation XY; Flur ABC"
 
 #: compensation/forms/forms.py:57 compensation/forms/modalForms.py:61
-#: compensation/forms/modalForms.py:328 compensation/forms/modalForms.py:421
+#: compensation/forms/modalForms.py:331 compensation/forms/modalForms.py:424
 #: compensation/templates/compensation/detail/compensation/includes/actions.html:35
 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:34
 #: compensation/templates/compensation/detail/compensation/includes/documents.html:31
@@ -371,7 +371,7 @@ msgstr "Kompensation XY; Flur ABC"
 msgid "Comment"
 msgstr "Kommentar"
 
-#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:423
+#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:426
 #: intervention/forms/forms.py:182
 msgid "Additional comment"
 msgstr "Zusätzlicher Kommentar"
@@ -457,7 +457,7 @@ msgstr "Vereinbarungsdatum"
 msgid "When did the parties agree on this?"
 msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?"
 
-#: compensation/forms/forms.py:354 compensation/views/eco_account.py:103
+#: compensation/forms/forms.py:354 compensation/views/eco_account.py:105
 msgid "New Eco-Account"
 msgstr "Neues Ökokonto"
 
@@ -482,7 +482,7 @@ msgstr "Fällig am"
 msgid "Due on which date"
 msgstr "Zahlung wird an diesem Datum erwartet"
 
-#: compensation/forms/modalForms.py:63 compensation/forms/modalForms.py:330
+#: compensation/forms/modalForms.py:63 compensation/forms/modalForms.py:333
 #: intervention/forms/modalForms.py:151 konova/forms.py:393
 msgid "Additional comment, maximum {} letters"
 msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
@@ -495,47 +495,47 @@ msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen"
 msgid "If there is no date you can enter, please explain why."
 msgstr "Falls Sie kein Datum angeben können, erklären Sie bitte weshalb."
 
-#: compensation/forms/modalForms.py:154 compensation/forms/modalForms.py:166
+#: compensation/forms/modalForms.py:157 compensation/forms/modalForms.py:169
 msgid "Biotope Type"
 msgstr "Biotoptyp"
 
-#: compensation/forms/modalForms.py:157
+#: compensation/forms/modalForms.py:160
 msgid "Select the biotope type"
 msgstr "Biotoptyp wählen"
 
-#: compensation/forms/modalForms.py:171 compensation/forms/modalForms.py:183
+#: compensation/forms/modalForms.py:174 compensation/forms/modalForms.py:186
 msgid "Biotope additional type"
 msgstr "Zusatzbezeichnung"
 
-#: compensation/forms/modalForms.py:174
+#: compensation/forms/modalForms.py:177
 msgid "Select an additional biotope type"
 msgstr "Zusatzbezeichnung wählen"
 
-#: compensation/forms/modalForms.py:193 intervention/forms/modalForms.py:313
+#: compensation/forms/modalForms.py:196 intervention/forms/modalForms.py:313
 msgid "in m²"
 msgstr ""
 
-#: compensation/forms/modalForms.py:204
+#: compensation/forms/modalForms.py:207
 msgid "New state"
 msgstr "Neuer Zustand"
 
-#: compensation/forms/modalForms.py:205
+#: compensation/forms/modalForms.py:208
 msgid "Insert data for the new state"
 msgstr "Geben Sie die Daten des neuen Zustandes ein"
 
-#: compensation/forms/modalForms.py:212 konova/forms.py:191
+#: compensation/forms/modalForms.py:215 konova/forms.py:191
 msgid "Object removed"
 msgstr "Objekt entfernt"
 
-#: compensation/forms/modalForms.py:300
+#: compensation/forms/modalForms.py:303
 msgid "Deadline Type"
 msgstr "Fristart"
 
-#: compensation/forms/modalForms.py:303
+#: compensation/forms/modalForms.py:306
 msgid "Select the deadline type"
 msgstr "Fristart wählen"
 
-#: compensation/forms/modalForms.py:312
+#: compensation/forms/modalForms.py:315
 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31
 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31
 #: ema/templates/ema/detail/includes/deadlines.html:31
@@ -543,27 +543,27 @@ msgstr "Fristart wählen"
 msgid "Date"
 msgstr "Datum"
 
-#: compensation/forms/modalForms.py:315
+#: compensation/forms/modalForms.py:318
 msgid "Select date"
 msgstr "Datum wählen"
 
-#: compensation/forms/modalForms.py:342
+#: compensation/forms/modalForms.py:345
 msgid "New deadline"
 msgstr "Neue Frist"
 
-#: compensation/forms/modalForms.py:343
+#: compensation/forms/modalForms.py:346
 msgid "Insert data for the new deadline"
 msgstr "Geben Sie die Daten der neuen Frist ein"
 
-#: compensation/forms/modalForms.py:360
+#: compensation/forms/modalForms.py:363
 msgid "Action Type"
 msgstr "Maßnahmentyp"
 
-#: compensation/forms/modalForms.py:363
+#: compensation/forms/modalForms.py:366
 msgid "Select the action type"
 msgstr "Maßnahmentyp wählen"
 
-#: compensation/forms/modalForms.py:372
+#: compensation/forms/modalForms.py:375
 #: compensation/templates/compensation/detail/compensation/includes/actions.html:40
 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:39
 #: compensation/templates/compensation/detail/compensation/includes/documents.html:36
@@ -589,31 +589,31 @@ msgstr "Maßnahmentyp wählen"
 msgid "Action"
 msgstr "Aktionen"
 
-#: compensation/forms/modalForms.py:377 compensation/forms/modalForms.py:389
+#: compensation/forms/modalForms.py:380 compensation/forms/modalForms.py:392
 msgid "Action Type detail"
 msgstr "Zusatzmerkmal"
 
-#: compensation/forms/modalForms.py:380
+#: compensation/forms/modalForms.py:383
 msgid "Select the action type detail"
 msgstr "Zusatzmerkmal wählen"
 
-#: compensation/forms/modalForms.py:394
+#: compensation/forms/modalForms.py:397
 msgid "Unit"
 msgstr "Einheit"
 
-#: compensation/forms/modalForms.py:397
+#: compensation/forms/modalForms.py:400
 msgid "Select the unit"
 msgstr "Einheit wählen"
 
-#: compensation/forms/modalForms.py:409
+#: compensation/forms/modalForms.py:412
 msgid "Insert the amount"
 msgstr "Menge eingeben"
 
-#: compensation/forms/modalForms.py:434
+#: compensation/forms/modalForms.py:437
 msgid "New action"
 msgstr "Neue Maßnahme"
 
-#: compensation/forms/modalForms.py:435
+#: compensation/forms/modalForms.py:438
 msgid "Insert data for the new action"
 msgstr "Geben Sie die Daten der neuen Maßnahme ein"
 
@@ -992,7 +992,7 @@ msgid "Recorded on"
 msgstr "Verzeichnet am"
 
 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:65
-#: intervention/templates/intervention/detail/includes/deductions.html:60
+#: intervention/templates/intervention/detail/includes/deductions.html:63
 msgid "Remove Deduction"
 msgstr "Abbuchung entfernen"
 
@@ -1084,63 +1084,63 @@ msgstr "Kompensationen - Übersicht"
 msgid "Compensation {} edited"
 msgstr "Kompensation {} bearbeitet"
 
-#: compensation/views/compensation.py:159 compensation/views/eco_account.py:161
+#: compensation/views/compensation.py:159 compensation/views/eco_account.py:163
 #: ema/views.py:230 intervention/views.py:305
 msgid "Edit {}"
 msgstr "Bearbeite {}"
 
-#: compensation/views/compensation.py:238 compensation/views/eco_account.py:317
+#: compensation/views/compensation.py:238 compensation/views/eco_account.py:347
 #: ema/views.py:191 intervention/views.py:483
 msgid "Log"
 msgstr "Log"
 
-#: compensation/views/compensation.py:487 compensation/views/eco_account.py:590
-#: ema/views.py:477 intervention/views.py:601
+#: compensation/views/compensation.py:487 compensation/views/eco_account.py:620
+#: ema/views.py:477 intervention/views.py:629
 msgid "Report {}"
 msgstr "Bericht {}"
 
-#: compensation/views/eco_account.py:60
+#: compensation/views/eco_account.py:62
 msgid "Eco-account - Overview"
 msgstr "Ökokonten - Übersicht"
 
-#: compensation/views/eco_account.py:93
+#: compensation/views/eco_account.py:95
 msgid "Eco-Account {} added"
 msgstr "Ökokonto {} hinzugefügt"
 
-#: compensation/views/eco_account.py:151
+#: compensation/views/eco_account.py:153
 msgid "Eco-Account {} edited"
 msgstr "Ökokonto {} bearbeitet"
 
-#: compensation/views/eco_account.py:264
+#: compensation/views/eco_account.py:266
 msgid "Eco-account removed"
 msgstr "Ökokonto entfernt"
 
-#: compensation/views/eco_account.py:338 ema/views.py:272
-#: intervention/views.py:554
+#: compensation/views/eco_account.py:368 ema/views.py:272
+#: intervention/views.py:582
 msgid "{} unrecorded"
 msgstr "{} entzeichnet"
 
-#: compensation/views/eco_account.py:338 ema/views.py:272
-#: intervention/views.py:554
+#: compensation/views/eco_account.py:368 ema/views.py:272
+#: intervention/views.py:582
 msgid "{} recorded"
 msgstr "{} verzeichnet"
 
-#: compensation/views/eco_account.py:663 ema/views.py:543
+#: compensation/views/eco_account.py:693 ema/views.py:543
 #: intervention/views.py:380
 msgid "{} has already been shared with you"
 msgstr "{} wurde bereits für Sie freigegeben"
 
-#: compensation/views/eco_account.py:668 ema/views.py:548
+#: compensation/views/eco_account.py:698 ema/views.py:548
 #: intervention/views.py:385
 msgid "{} has been shared with you"
 msgstr "{} ist nun für Sie freigegeben"
 
-#: compensation/views/eco_account.py:675 ema/views.py:555
+#: compensation/views/eco_account.py:705 ema/views.py:555
 #: intervention/views.py:392
 msgid "Share link invalid"
 msgstr "Freigabelink ungültig"
 
-#: compensation/views/eco_account.py:698 ema/views.py:578
+#: compensation/views/eco_account.py:728 ema/views.py:578
 #: intervention/views.py:415
 msgid "Share settings updated"
 msgstr "Freigabe Einstellungen aktualisiert"
@@ -1337,7 +1337,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:366
+#: intervention/forms/modalForms.py:381
 msgid ""
 "Eco-account {} is not recorded yet. You can only deduct from recorded "
 "accounts."
@@ -1345,7 +1345,7 @@ msgstr ""
 "Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von "
 "verzeichneten Ökokonten erfolgen."
 
-#: intervention/forms/modalForms.py:379
+#: intervention/forms/modalForms.py:391
 msgid ""
 "The account {} has not enough surface for a deduction of {} m². There are "
 "only {} m² left"
@@ -1373,6 +1373,10 @@ msgstr "Ökokonto gelöscht! Abbuchung ungültig!"
 msgid "Eco-account not recorded! Deduction invalid!"
 msgstr "Ökokonto nicht verzeichnet! Abbuchung ungültig!"
 
+#: intervention/templates/intervention/detail/includes/deductions.html:60
+msgid "Edit Deduction"
+msgstr "Abbuchung bearbeiten"
+
 #: intervention/templates/intervention/detail/includes/payments.html:8
 #: intervention/templates/intervention/report/report.html:73
 msgid "Payments"
@@ -1464,7 +1468,7 @@ msgstr "{} entfernt"
 msgid "Check performed"
 msgstr "Prüfung durchgeführt"
 
-#: intervention/views.py:559
+#: intervention/views.py:587
 msgid "There are errors on this intervention:"
 msgstr "Es liegen Fehler in diesem Eingriff vor:"
 
@@ -1824,74 +1828,94 @@ msgid "State removed"
 msgstr "Zustand gelöscht"
 
 #: konova/utils/message_templates.py:33
+msgid "State edited"
+msgstr "Zustand bearbeitet"
+
+#: konova/utils/message_templates.py:34
 msgid "State added"
 msgstr "Zustand hinzugefügt"
 
-#: konova/utils/message_templates.py:36
+#: konova/utils/message_templates.py:37
 msgid "Action added"
 msgstr "Maßnahme hinzugefügt"
 
-#: konova/utils/message_templates.py:37
+#: konova/utils/message_templates.py:38
+msgid "Action edited"
+msgstr "Maßnahme bearbeitet"
+
+#: konova/utils/message_templates.py:39
 msgid "Action removed"
 msgstr "Maßnahme entfernt"
 
-#: konova/utils/message_templates.py:40
+#: konova/utils/message_templates.py:42
 msgid "Deduction added"
 msgstr "Abbuchung hinzugefügt"
 
-#: konova/utils/message_templates.py:41
+#: konova/utils/message_templates.py:43
+msgid "Deduction edited"
+msgstr "Abbuchung bearbeitet"
+
+#: konova/utils/message_templates.py:44
 msgid "Deduction removed"
 msgstr "Abbuchung entfernt"
 
-#: konova/utils/message_templates.py:44
+#: konova/utils/message_templates.py:47
 msgid "Deadline added"
 msgstr "Frist/Termin hinzugefügt"
 
-#: konova/utils/message_templates.py:45
+#: konova/utils/message_templates.py:48
+msgid "Deadline edited"
+msgstr "Frist/Termin bearbeitet"
+
+#: konova/utils/message_templates.py:49
 msgid "Deadline removed"
 msgstr "Frist/Termin gelöscht"
 
-#: konova/utils/message_templates.py:48
+#: konova/utils/message_templates.py:52
 msgid "Payment added"
 msgstr "Zahlung hinzugefügt"
 
-#: konova/utils/message_templates.py:49
+#: konova/utils/message_templates.py:53
 msgid "Payment edited"
 msgstr "Zahlung bearbeitet"
 
-#: konova/utils/message_templates.py:50
+#: konova/utils/message_templates.py:54
 msgid "Payment removed"
 msgstr "Zahlung gelöscht"
 
-#: konova/utils/message_templates.py:53
+#: konova/utils/message_templates.py:57
 msgid "Revocation added"
 msgstr "Widerspruch hinzugefügt"
 
-#: konova/utils/message_templates.py:54
+#: konova/utils/message_templates.py:58
+msgid "Revocation edited"
+msgstr "Widerspruch bearbeitet"
+
+#: konova/utils/message_templates.py:59
 msgid "Revocation removed"
 msgstr "Widerspruch entfernt"
 
-#: konova/utils/message_templates.py:57
+#: konova/utils/message_templates.py:62
 msgid "Document '{}' deleted"
 msgstr "Dokument '{}' gelöscht"
 
-#: konova/utils/message_templates.py:58
+#: konova/utils/message_templates.py:63
 msgid "Document added"
 msgstr "Dokument hinzugefügt"
 
-#: konova/utils/message_templates.py:61
+#: konova/utils/message_templates.py:66
 msgid "Edited general data"
 msgstr "Allgemeine Daten bearbeitet"
 
-#: konova/utils/message_templates.py:62
+#: konova/utils/message_templates.py:67
 msgid "Added deadline"
 msgstr "Frist/Termin hinzugefügt"
 
-#: konova/utils/message_templates.py:65
+#: konova/utils/message_templates.py:70
 msgid "Geometry conflict detected with {}"
 msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}"
 
-#: konova/utils/message_templates.py:68
+#: konova/utils/message_templates.py:73
 msgid "This intervention has {} revocations"
 msgstr "Dem Eingriff liegen {} Widersprüche vor"
 
-- 
2.45.2


From 1b5cda648e8273dc36ab7ce47f46daed631a78e7 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Wed, 9 Feb 2022 16:02:28 +0100
Subject: [PATCH 19/31] #86 Revocation edit

* adds support for revocation edit
    * revocation document files will be replaced on an edit
---
 intervention/forms/modalForms.py              | 57 ++++++++++++++++++-
 intervention/models/intervention.py           | 37 ++++++++++++
 .../detail/includes/revocation.html           |  7 ++-
 intervention/urls.py                          |  3 +-
 intervention/views.py                         | 32 ++++++++++-
 konova/forms.py                               |  6 +-
 konova/models/document.py                     | 16 ++++++
 konova/utils/message_templates.py             |  4 ++
 8 files changed, 151 insertions(+), 11 deletions(-)

diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py
index 6a044c7c..8039b3ae 100644
--- a/intervention/forms/modalForms.py
+++ b/intervention/forms/modalForms.py
@@ -6,8 +6,11 @@ Created on: 27.09.21
 
 """
 from dal import autocomplete
+from django.core.exceptions import ObjectDoesNotExist
+from django.db.models.fields.files import FieldFile
 
-from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED
+from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \
+    REVOCATION_EDITED, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE
 from user.models import User, UserActionLogEntry
 from django.db import transaction
 from django import forms
@@ -15,7 +18,7 @@ from django.utils.translation import gettext_lazy as _
 
 from compensation.models import EcoAccount, EcoAccountDeduction
 from intervention.inputs import TextToClipboardInput
-from intervention.models import Intervention, InterventionDocument
+from intervention.models import Intervention, InterventionDocument, RevocationDocument
 from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
 from konova.utils.general import format_german_float
 from konova.utils.user_checks import is_default_group_only
@@ -157,6 +160,7 @@ class NewRevocationModalForm(BaseModalForm):
             }
         )
     )
+    document_model = RevocationDocument
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -166,12 +170,61 @@ class NewRevocationModalForm(BaseModalForm):
             "enctype": "multipart/form-data",  # important for file upload
         }
 
+    def is_valid(self):
+        super_valid = super().is_valid()
+
+        _file = self.cleaned_data.get("file", None)
+
+        if isinstance(_file, FieldFile):
+            # FieldFile declares that no new file has been uploaded and we do not need to check on the file again
+            return super_valid
+
+        mime_type_valid = self.document_model.is_mime_type_valid(_file)
+        if not mime_type_valid:
+            self.add_error(
+                "file",
+                FILE_TYPE_UNSUPPORTED
+            )
+
+        file_size_valid = self.document_model.is_file_size_valid(_file)
+        if not file_size_valid:
+            self.add_error(
+                "file",
+                FILE_SIZE_TOO_LARGE
+            )
+
+        file_valid = mime_type_valid and file_size_valid
+        return super_valid and file_valid
+
     def save(self):
         revocation = self.instance.add_revocation(self)
         self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_ADDED)
         return revocation
 
 
+class EditRevocationModalForm(NewRevocationModalForm):
+    revocation = None
+
+    def __init__(self, *args, **kwargs):
+        self.revocation = kwargs.pop("revocation", None)
+        super().__init__(*args, **kwargs)
+        try:
+            doc = self.revocation.document.file
+        except ObjectDoesNotExist:
+            doc = None
+        form_data = {
+            "date": str(self.revocation.date),
+            "file": doc,
+            "comment": self.revocation.comment,
+        }
+        self.load_initial_data(form_data)
+
+    def save(self):
+        revocation = self.instance.edit_revocation(self)
+        self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_EDITED)
+        return revocation
+
+
 class RemoveRevocationModalForm(RemoveModalForm):
     """ Removing modal form for Revocation
 
diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py
index e2e736d7..167c27af 100644
--- a/intervention/models/intervention.py
+++ b/intervention/models/intervention.py
@@ -8,6 +8,8 @@ Created on: 15.11.21
 import shutil
 
 from django.contrib import messages
+from django.core.exceptions import ObjectDoesNotExist
+from django.db.models.fields.files import FieldFile
 from django.urls import reverse
 from django.utils import timezone
 
@@ -202,6 +204,41 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
                 )
         return revocation
 
+    def edit_revocation(self, form):
+        """ Updates a revocation of the intervention
+
+        Args:
+            form (EditRevocationModalForm): The form holding the data
+
+        Returns:
+
+        """
+        form_data = form.cleaned_data
+        file = form_data.get("file", None)
+
+        revocation = form.revocation
+        revocation.date = form_data.get("date", None)
+        revocation.comment = form_data.get("comment", None)
+
+        with transaction.atomic():
+            try:
+                revocation.document.date_of_creation = revocation.date
+                revocation.document.comment = revocation.comment
+                if not isinstance(file, FieldFile):
+                    revocation.document.replace_file(file)
+                revocation.document.save()
+            except ObjectDoesNotExist:
+                revocation.document = RevocationDocument.objects.create(
+                    title="revocation_of_{}".format(self.identifier),
+                    date_of_creation=revocation.date,
+                    comment=revocation.comment,
+                    file=file,
+                    instance=revocation
+                )
+            revocation.save()
+
+        return revocation
+
     def remove_revocation(self, form):
         """ Removes a revocation from the intervention
 
diff --git a/intervention/templates/intervention/detail/includes/revocation.html b/intervention/templates/intervention/detail/includes/revocation.html
index d6b07b72..6eb68f99 100644
--- a/intervention/templates/intervention/detail/includes/revocation.html
+++ b/intervention/templates/intervention/detail/includes/revocation.html
@@ -63,9 +63,12 @@
                             {{ rev.comment }}
                         </div>
                     </td>
-                    <td class="align-middle">
+                    <td class="align-middle float-right">
                         {% if is_default_member and has_access  %}
-                        <button data-form-url="{% url 'intervention:remove-revocation' obj.id rev.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove revocation' %}">
+                        <button data-form-url="{% url 'intervention:edit-revocation' obj.id rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit revocation' %}">
+                            {% fa5_icon 'edit' %}
+                        </button>
+                        <button data-form-url="{% url 'intervention:remove-revocation' obj.id rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove revocation' %}">
                             {% fa5_icon 'trash' %}
                         </button>
                         {% endif %}
diff --git a/intervention/urls.py b/intervention/urls.py
index 8ad7f31e..45985587 100644
--- a/intervention/urls.py
+++ b/intervention/urls.py
@@ -10,7 +10,7 @@ from django.urls import path
 from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \
     create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \
     record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \
-    remove_deduction_view, remove_compensation_view, edit_deduction_view
+    remove_deduction_view, remove_compensation_view, edit_deduction_view, edit_revocation_view
 
 app_name = "intervention"
 urlpatterns = [
@@ -42,6 +42,7 @@ urlpatterns = [
 
     # Revocation routes
     path('<id>/revocation/new', new_revocation_view, name='new-revocation'),
+    path('<id>/revocation/<revocation_id>/edit', edit_revocation_view, name='edit-revocation'),
     path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'),
     path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
 ]
\ No newline at end of file
diff --git a/intervention/views.py b/intervention/views.py
index 6db35474..90447c91 100644
--- a/intervention/views.py
+++ b/intervention/views.py
@@ -7,7 +7,7 @@ from django.shortcuts import render
 from intervention.forms.forms import NewInterventionForm, EditInterventionForm
 from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \
     CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, RemoveEcoAccountDeductionModalForm, \
-    RemoveRevocationModalForm, EditEcoAccountDeductionModalForm
+    RemoveRevocationModalForm, EditEcoAccountDeductionModalForm, EditRevocationModalForm
 from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
 from intervention.tables import InterventionTable
 from konova.contexts import BaseContext
@@ -18,7 +18,7 @@ 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
+    COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -331,6 +331,31 @@ def remove_view(request: HttpRequest, id: str):
     )
 
 
+@login_required
+@default_group_required
+@shared_access_required(Intervention, "id")
+def edit_revocation_view(request: HttpRequest, id: str, revocation_id: str):
+    """ Renders a edit view for a revocation
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The intervention's id as string
+        revocation_id (str): The revocation's id as string
+
+    Returns:
+
+    """
+    intervention = get_object_or_404(Intervention, id=id)
+    revocation = get_object_or_404(Revocation, id=revocation_id)
+
+    form = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, revocation=revocation, request=request)
+    return form.process_request(
+        request,
+        REVOCATION_EDITED,
+        redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
+    )
+
+
 @login_required
 @default_group_required
 @shared_access_required(Intervention, "id")
@@ -339,7 +364,8 @@ def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
 
     Args:
         request (HttpRequest): The incoming request
-        id (str): The revocation's id as string
+        id (str): The intervention's id as string
+        revocation_id (str): The revocation's id as string
 
     Returns:
 
diff --git a/konova/forms.py b/konova/forms.py
index c05eb422..a842e411 100644
--- a/konova/forms.py
+++ b/konova/forms.py
@@ -24,7 +24,7 @@ from konova.contexts import BaseContext
 from konova.models import BaseObject, Geometry, RecordableObjectMixin
 from konova.settings import DEFAULT_SRID
 from konova.tasks import celery_update_parcels
-from konova.utils.message_templates import FORM_INVALID
+from konova.utils.message_templates import FORM_INVALID, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE
 from user.models import UserActionLogEntry
 
 
@@ -424,14 +424,14 @@ class NewDocumentForm(BaseModalForm):
         if not mime_type_valid:
             self.add_error(
                 "file",
-                _("Unsupported file type")
+                FILE_TYPE_UNSUPPORTED
             )
 
         file_size_valid = self.document_model.is_file_size_valid(_file)
         if not file_size_valid:
             self.add_error(
                 "file",
-                _("File too large")
+                FILE_SIZE_TOO_LARGE
             )
 
         file_valid = mime_type_valid and file_size_valid
diff --git a/konova/models/document.py b/konova/models/document.py
index 72b124d3..b465d70c 100644
--- a/konova/models/document.py
+++ b/konova/models/document.py
@@ -101,3 +101,19 @@ class AbstractDocument(BaseResource):
     def is_file_size_valid(cls, _file):
         max_size = cls._maximum_file_size * pow(1000, 2)
         return _file.size <= max_size
+
+    def replace_file(self, new_file):
+        """ Replaces the old file on the hard drive with the new one
+
+        Args:
+            new_file (File): The new file
+
+        Returns:
+
+        """
+        try:
+            os.remove(self.file.file.name)
+        except FileNotFoundError:
+            pass
+        self.file = new_file
+        self.save()
\ No newline at end of file
diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py
index ea778ea2..2196dd9c 100644
--- a/konova/utils/message_templates.py
+++ b/konova/utils/message_templates.py
@@ -18,6 +18,10 @@ MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
 
 CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted")
 
+# FILES
+FILE_TYPE_UNSUPPORTED = _("Unsupported file type")
+FILE_SIZE_TOO_LARGE = _("File too large")
+
 # ECO ACCOUNT
 CANCEL_ACC_RECORDED_OR_DEDUCTED = _("Action canceled. Eco account is recorded or deductions exist. Only conservation office member can perform this action.")
 
-- 
2.45.2


From c5534bcd552d67d4e095df8efcdadd9894330f4d Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 10:21:18 +0100
Subject: [PATCH 20/31] #86 Edit document

* adds support for editing of documents
* adds buttons for intervention
---
 compensation/forms/modalForms.py              |   6 +-
 compensation/views/compensation.py            |   4 +-
 compensation/views/eco_account.py             |   6 +-
 ema/forms.py                                  |   4 +-
 ema/views.py                                  |   4 +-
 intervention/forms/modalForms.py              |  30 +--
 .../detail/includes/documents.html            |  19 +-
 intervention/urls.py                          |   7 +-
 intervention/views.py                         |  44 ++-
 konova/forms.py                               |  45 +++-
 konova/utils/documents.py                     |   2 +-
 konova/utils/message_templates.py             |   1 +
 locale/de/LC_MESSAGES/django.mo               | Bin 37109 -> 37314 bytes
 locale/de/LC_MESSAGES/django.po               | 250 +++++++++---------
 14 files changed, 247 insertions(+), 175 deletions(-)

diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py
index a6e7df26..5368077c 100644
--- a/compensation/forms/modalForms.py
+++ b/compensation/forms/modalForms.py
@@ -18,7 +18,7 @@ from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION
     CODELIST_COMPENSATION_ACTION_DETAIL_ID
 from compensation.models import CompensationDocument, EcoAccountDocument
 from konova.contexts import BaseContext
-from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
+from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
 from konova.models import DeadlineType
 from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \
     ADDED_COMPENSATION_ACTION, PAYMENT_EDITED
@@ -443,9 +443,9 @@ class NewActionModalForm(BaseModalForm):
         return action
 
 
-class NewCompensationDocumentForm(NewDocumentForm):
+class NewCompensationDocumentModalForm(NewDocumentModalForm):
     document_model = CompensationDocument
 
 
-class NewEcoAccountDocumentForm(NewDocumentForm):
+class NewEcoAccountDocumentModalForm(NewDocumentModalForm):
     document_model = EcoAccountDocument
\ No newline at end of file
diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py
index c4b5c2f5..95f4ac0b 100644
--- a/compensation/views/compensation.py
+++ b/compensation/views/compensation.py
@@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
 
 from compensation.forms.forms import NewCompensationForm, EditCompensationForm
 from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm, \
-    NewCompensationDocumentForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
+    NewCompensationDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
 from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument
 from compensation.tables import CompensationTable
 from intervention.models import Intervention
@@ -276,7 +276,7 @@ def new_document_view(request: HttpRequest, id: str):
 
     """
     comp = get_object_or_404(Compensation, id=id)
-    form = NewCompensationDocumentForm(request.POST or None, request.FILES or None, instance=comp, request=request)
+    form = NewCompensationDocumentModalForm(request.POST or None, request.FILES or None, instance=comp, request=request)
     return form.process_request(
         request,
         msg_success=DOCUMENT_ADDED,
diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py
index 08e01a61..d433d551 100644
--- a/compensation/views/eco_account.py
+++ b/compensation/views/eco_account.py
@@ -16,7 +16,7 @@ from django.shortcuts import render, get_object_or_404, redirect
 
 from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm
 from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
-    NewEcoAccountDocumentForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
+    NewEcoAccountDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
 from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
 from compensation.tables import EcoAccountTable
 from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm, \
@@ -24,7 +24,7 @@ from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm,
 from konova.contexts import BaseContext
 from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
     shared_access_required
-from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm, RemoveDeadlineModalForm
+from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentModalForm, RecordModalForm, RemoveDeadlineModalForm
 from konova.models import Deadline
 from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
@@ -524,7 +524,7 @@ def new_document_view(request: HttpRequest, id: str):
 
     """
     acc = get_object_or_404(EcoAccount, id=id)
-    form = NewEcoAccountDocumentForm(request.POST or None, request.FILES or None, instance=acc, request=request)
+    form = NewEcoAccountDocumentModalForm(request.POST or None, request.FILES or None, instance=acc, request=request)
     return form.process_request(
         request,
         msg_success=DOCUMENT_ADDED,
diff --git a/ema/forms.py b/ema/forms.py
index 5dc3bcfb..2f193605 100644
--- a/ema/forms.py
+++ b/ema/forms.py
@@ -15,7 +15,7 @@ from django.utils.translation import gettext_lazy as _
 from compensation.forms.forms import AbstractCompensationForm, CompensationResponsibleFormMixin
 from ema.models import Ema, EmaDocument
 from intervention.models import Responsibility
-from konova.forms import SimpleGeomForm, NewDocumentForm
+from konova.forms import SimpleGeomForm, NewDocumentModalForm
 from user.models import UserActionLogEntry
 
 
@@ -150,5 +150,5 @@ class EditEmaForm(NewEmaForm):
         return self.instance
 
 
-class NewEmaDocumentForm(NewDocumentForm):
+class NewEmaDocumentModalForm(NewDocumentModalForm):
     document_model = EmaDocument
\ No newline at end of file
diff --git a/ema/views.py b/ema/views.py
index 900dde7c..99c80be3 100644
--- a/ema/views.py
+++ b/ema/views.py
@@ -9,7 +9,7 @@ from django.utils.translation import gettext_lazy as _
 from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
     RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
 from compensation.models import CompensationAction, CompensationState
-from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentForm
+from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentModalForm
 from ema.tables import EmaTable
 from intervention.forms.modalForms import ShareModalForm
 from konova.contexts import BaseContext
@@ -356,7 +356,7 @@ def document_new_view(request: HttpRequest, id: str):
 
     """
     ema = get_object_or_404(Ema, id=id)
-    form = NewEmaDocumentForm(request.POST or None, request.FILES or None, instance=ema, request=request)
+    form = NewEmaDocumentModalForm(request.POST or None, request.FILES or None, instance=ema, request=request)
     return form.process_request(
         request,
         msg_success=DOCUMENT_ADDED,
diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py
index 8039b3ae..a8fa98de 100644
--- a/intervention/forms/modalForms.py
+++ b/intervention/forms/modalForms.py
@@ -19,7 +19,7 @@ from django.utils.translation import gettext_lazy as _
 from compensation.models import EcoAccount, EcoAccountDeduction
 from intervention.inputs import TextToClipboardInput
 from intervention.models import Intervention, InterventionDocument, RevocationDocument
-from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
+from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
 from konova.utils.general import format_german_float
 from konova.utils.user_checks import is_default_group_only
 
@@ -170,32 +170,6 @@ class NewRevocationModalForm(BaseModalForm):
             "enctype": "multipart/form-data",  # important for file upload
         }
 
-    def is_valid(self):
-        super_valid = super().is_valid()
-
-        _file = self.cleaned_data.get("file", None)
-
-        if isinstance(_file, FieldFile):
-            # FieldFile declares that no new file has been uploaded and we do not need to check on the file again
-            return super_valid
-
-        mime_type_valid = self.document_model.is_mime_type_valid(_file)
-        if not mime_type_valid:
-            self.add_error(
-                "file",
-                FILE_TYPE_UNSUPPORTED
-            )
-
-        file_size_valid = self.document_model.is_file_size_valid(_file)
-        if not file_size_valid:
-            self.add_error(
-                "file",
-                FILE_SIZE_TOO_LARGE
-            )
-
-        file_valid = mime_type_valid and file_size_valid
-        return super_valid and file_valid
-
     def save(self):
         revocation = self.instance.add_revocation(self)
         self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_ADDED)
@@ -543,5 +517,5 @@ class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
             self.deduction.delete()
 
 
-class NewInterventionDocumentForm(NewDocumentForm):
+class NewInterventionDocumentModalForm(NewDocumentModalForm):
     document_model = InterventionDocument
diff --git a/intervention/templates/intervention/detail/includes/documents.html b/intervention/templates/intervention/detail/includes/documents.html
index f536f154..93309f5c 100644
--- a/intervention/templates/intervention/detail/includes/documents.html
+++ b/intervention/templates/intervention/detail/includes/documents.html
@@ -27,7 +27,10 @@
                 <th scope="col">
                     {% trans 'Title' %}
                 </th>
-                <th class="w-50" scope="col">
+                <th scope="col">
+                    {% trans 'Created on' %}
+                </th>
+                <th scope="col">
                     {% trans 'Comment' %}
                 </th>
                 {% if is_default_member and has_access %}
@@ -43,18 +46,26 @@
             {% for doc in obj.documents.all %}
             <tr>
                 <td>
-                    <a href="{% url 'intervention:get-doc' doc.id %}">
+                    <a href="{% url 'intervention:get-doc' obj.id doc.id %}">
                         {{ doc.title }}
                     </a>
                 </td>
+                <td>
+                    <div class="scroll-150">
+                        {{ doc.date_of_creation }}
+                    </div>
+                </td>
                 <td>
                     <div class="scroll-150">
                         {{ doc.comment }}
                     </div>
                 </td>
-                <td>
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'intervention:remove-doc' doc.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove document' %}">
+                    <button data-form-url="{% url 'intervention:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'intervention:remove-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/intervention/urls.py b/intervention/urls.py
index 45985587..2a5e6d38 100644
--- a/intervention/urls.py
+++ b/intervention/urls.py
@@ -10,7 +10,7 @@ from django.urls import path
 from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \
     create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \
     record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \
-    remove_deduction_view, remove_compensation_view, edit_deduction_view, edit_revocation_view
+    remove_deduction_view, remove_compensation_view, edit_deduction_view, edit_revocation_view, edit_document_view
 
 app_name = "intervention"
 urlpatterns = [
@@ -32,8 +32,9 @@ urlpatterns = [
 
     # Documents
     path('<id>/document/new/', new_document_view, name='new-doc'),
-    path('document/<doc_id>', get_document_view, name='get-doc'),
-    path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
+    path('<id>/document/<doc_id>', get_document_view, name='get-doc'),
+    path('<id>/document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
+    path('<id>/document/<doc_id>/edit/', edit_document_view, name='edit-doc'),
 
     # Deductions
     path('<id>/deduction/new', new_deduction_view, name='new-deduction'),
diff --git a/intervention/views.py b/intervention/views.py
index 90447c91..2945c337 100644
--- a/intervention/views.py
+++ b/intervention/views.py
@@ -6,19 +6,19 @@ from django.shortcuts import render
 
 from intervention.forms.forms import NewInterventionForm, EditInterventionForm
 from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \
-    CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, RemoveEcoAccountDeductionModalForm, \
+    CheckModalForm, NewDeductionModalForm, NewInterventionDocumentModalForm, RemoveEcoAccountDeductionModalForm, \
     RemoveRevocationModalForm, EditEcoAccountDeductionModalForm, EditRevocationModalForm
 from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
 from intervention.tables import InterventionTable
 from konova.contexts import BaseContext
 from konova.decorators import *
-from konova.forms import SimpleGeomForm, RemoveModalForm, RecordModalForm
+from konova.forms import SimpleGeomForm, RemoveModalForm, RecordModalForm, EditDocumentModalForm
 from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 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
+    COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED, DOCUMENT_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -129,7 +129,7 @@ def new_document_view(request: HttpRequest, id: str):
 
     """
     intervention = get_object_or_404(Intervention, id=id)
-    form = NewInterventionDocumentForm(request.POST or None, request.FILES or None, instance=intervention, request=request)
+    form = NewInterventionDocumentModalForm(request.POST or None, request.FILES or None, instance=intervention, request=request)
     return form.process_request(
         request,
         msg_success=DOCUMENT_ADDED,
@@ -164,18 +164,21 @@ def get_revocation_view(request: HttpRequest, doc_id: str):
 
 @login_required
 @default_group_required
-def get_document_view(request: HttpRequest, doc_id: str):
+@shared_access_required(Intervention, "id")
+def get_document_view(request: HttpRequest, id: str, doc_id: str):
     """ Returns the document as downloadable file
 
     Wraps the generic document fetcher function from konova.utils.
 
     Args:
         request (HttpRequest): The incoming request
+        id (str): The intervention id
         doc_id (str): The document id
 
     Returns:
 
     """
+    intervention = get_object_or_404(Intervention, id=id)
     doc = get_object_or_404(InterventionDocument, id=doc_id)
     user = request.user
     instance = doc.instance
@@ -191,18 +194,21 @@ def get_document_view(request: HttpRequest, doc_id: str):
 
 @login_required
 @default_group_required
-def remove_document_view(request: HttpRequest, doc_id: str):
+@shared_access_required(Intervention, "id")
+def remove_document_view(request: HttpRequest, id: str, doc_id: str):
     """ Removes the document from the database and file system
 
     Wraps the generic functionality from konova.utils.
 
     Args:
         request (HttpRequest): The incoming request
+        id (str): The intervention id
         doc_id (str): The document id
 
     Returns:
 
     """
+    intervention = get_object_or_404(Intervention, id=id)
     doc = get_object_or_404(InterventionDocument, id=doc_id)
     return remove_document(
         request,
@@ -210,6 +216,32 @@ def remove_document_view(request: HttpRequest, doc_id: str):
     )
 
 
+@login_required
+@default_group_required
+@shared_access_required(Intervention, "id")
+def edit_document_view(request: HttpRequest, id: str, doc_id: str):
+    """ Removes the document from the database and file system
+
+    Wraps the generic functionality from konova.utils.
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The intervention id
+        doc_id (str): The document id
+
+    Returns:
+
+    """
+    intervention = get_object_or_404(Intervention, id=id)
+    doc = get_object_or_404(InterventionDocument, id=doc_id)
+    form = EditDocumentModalForm(request.POST or None, request.FILES or None, instance=intervention, document=doc, request=request)
+    return form.process_request(
+        request,
+        DOCUMENT_EDITED,
+        redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
+    )
+
+
 @login_required
 @any_group_check
 def detail_view(request: HttpRequest, id: str):
diff --git a/konova/forms.py b/konova/forms.py
index a842e411..57562f71 100644
--- a/konova/forms.py
+++ b/konova/forms.py
@@ -12,6 +12,8 @@ from bootstrap_modal_forms.forms import BSModalForm
 from bootstrap_modal_forms.utils import is_ajax
 from django import forms
 from django.contrib import messages
+from django.db.models.fields.files import FieldFile
+
 from user.models import User
 from django.contrib.gis.forms import OSMWidget, MultiPolygonField
 from django.contrib.gis.geos import MultiPolygon
@@ -21,10 +23,10 @@ from django.shortcuts import render
 from django.utils.translation import gettext_lazy as _
 
 from konova.contexts import BaseContext
-from konova.models import BaseObject, Geometry, RecordableObjectMixin
+from konova.models import BaseObject, Geometry, RecordableObjectMixin, AbstractDocument
 from konova.settings import DEFAULT_SRID
 from konova.tasks import celery_update_parcels
-from konova.utils.message_templates import FORM_INVALID, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE
+from konova.utils.message_templates import FORM_INVALID, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE, DOCUMENT_EDITED
 from user.models import UserActionLogEntry
 
 
@@ -348,7 +350,7 @@ class RemoveDeadlineModalForm(RemoveModalForm):
         self.instance.remove_deadline(self)
 
 
-class NewDocumentForm(BaseModalForm):
+class NewDocumentModalForm(BaseModalForm):
     """ Modal form for new documents
 
     """
@@ -420,6 +422,10 @@ class NewDocumentForm(BaseModalForm):
 
         _file = self.cleaned_data.get("file", None)
 
+        if _file is None or isinstance(_file, FieldFile):
+            # FieldFile declares that no new file has been uploaded and we do not need to check on the file again
+            return super_valid
+
         mime_type_valid = self.document_model.is_mime_type_valid(_file)
         if not mime_type_valid:
             self.add_error(
@@ -458,6 +464,39 @@ class NewDocumentForm(BaseModalForm):
             return doc
 
 
+class EditDocumentModalForm(NewDocumentModalForm):
+    document = None
+    document_model = AbstractDocument
+
+    def __init__(self, *args, **kwargs):
+        self.document = kwargs.pop("document", None)
+        super().__init__(*args, **kwargs)
+        form_data = {
+            "title": self.document.title,
+            "comment": self.document.comment,
+            "creation_date": str(self.document.date_of_creation),
+            "file": self.document.file,
+        }
+        self.load_initial_data(form_data)
+
+
+    def save(self):
+        with transaction.atomic():
+            document = self.document
+            file = self.cleaned_data.get("file", None)
+
+            document.title = self.cleaned_data.get("title", None)
+            document.comment = self.cleaned_data.get("comment", None)
+            document.date_of_creation = self.cleaned_data.get("creation_date", None)
+            if not isinstance(file, FieldFile):
+                document.replace_file(file)
+            document.save()
+
+            self.instance.mark_as_edited(self.user, self.request, edit_comment=DOCUMENT_EDITED)
+
+            return document
+
+
 class RecordModalForm(BaseModalForm):
     """ Modal form for recording data
 
diff --git a/konova/utils/documents.py b/konova/utils/documents.py
index 63a7d328..f9b15160 100644
--- a/konova/utils/documents.py
+++ b/konova/utils/documents.py
@@ -50,5 +50,5 @@ def remove_document(request: HttpRequest, doc: AbstractDocument):
     form = RemoveModalForm(request.POST or None, instance=doc, request=request)
     return form.process_request(
         request=request,
-        msg_success=DOCUMENT_REMOVED_TEMPLATE.format(title)
+        msg_success=DOCUMENT_REMOVED_TEMPLATE.format(title),
     )
\ No newline at end of file
diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py
index 2196dd9c..c7a2be00 100644
--- a/konova/utils/message_templates.py
+++ b/konova/utils/message_templates.py
@@ -65,6 +65,7 @@ REVOCATION_REMOVED = _("Revocation removed")
 # DOCUMENTS
 DOCUMENT_REMOVED_TEMPLATE = _("Document '{}' deleted")
 DOCUMENT_ADDED = _("Document added")
+DOCUMENT_EDITED = _("Document edited")
 
 # Edited
 EDITED_GENERAL_DATA = _("Edited general data")
diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo
index 86cf2739bfdda76a4b610efe974b54aee72f0ad0..f2e51b0088fba3200fe8ff206ddd3043ff1e8a06 100644
GIT binary patch
delta 10906
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

delta 10729
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

diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po
index 67ae2338..488b3d4c 100644
--- a/locale/de/LC_MESSAGES/django.po
+++ b/locale/de/LC_MESSAGES/django.po
@@ -7,8 +7,8 @@
 #: compensation/forms/modalForms.py:46 compensation/forms/modalForms.py:62
 #: compensation/forms/modalForms.py:332 compensation/forms/modalForms.py:425
 #: intervention/forms/forms.py:54 intervention/forms/forms.py:156
-#: intervention/forms/forms.py:168 intervention/forms/modalForms.py:124
-#: intervention/forms/modalForms.py:137 intervention/forms/modalForms.py:150
+#: intervention/forms/forms.py:168 intervention/forms/modalForms.py:127
+#: intervention/forms/modalForms.py:140 intervention/forms/modalForms.py:153
 #: konova/filters/mixins.py:53 konova/filters/mixins.py:54
 #: konova/filters/mixins.py:81 konova/filters/mixins.py:82
 #: konova/filters/mixins.py:94 konova/filters/mixins.py:95
@@ -18,15 +18,15 @@
 #: konova/filters/mixins.py:270 konova/filters/mixins.py:315
 #: konova/filters/mixins.py:353 konova/filters/mixins.py:354
 #: konova/filters/mixins.py:385 konova/filters/mixins.py:386
-#: konova/forms.py:141 konova/forms.py:242 konova/forms.py:313
-#: konova/forms.py:357 konova/forms.py:367 konova/forms.py:380
-#: konova/forms.py:392 konova/forms.py:410 user/forms.py:42
+#: 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
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-09 12:52+0100\n"
+"POT-Creation-Date: 2022-02-10 10:17+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -63,7 +63,7 @@ msgstr "Verantwortliche Stelle"
 #: analysis/forms.py:58 compensation/forms/forms.py:88
 #: compensation/forms/forms.py:165 intervention/forms/forms.py:64
 #: intervention/forms/forms.py:81 intervention/forms/forms.py:97
-#: intervention/forms/forms.py:113 intervention/forms/modalForms.py:46
+#: intervention/forms/forms.py:113 intervention/forms/modalForms.py:49
 msgid "Click for selection"
 msgstr "Auswählen..."
 
@@ -75,7 +75,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:189
+#: analysis/forms.py:69 konova/forms.py:191
 msgid "Continue"
 msgstr "Weiter"
 
@@ -220,7 +220,7 @@ msgstr "Abbuchungen"
 #: compensation/templates/compensation/detail/eco_account/includes/states-before.html:36
 #: ema/templates/ema/detail/includes/states-after.html:36
 #: ema/templates/ema/detail/includes/states-before.html:36
-#: intervention/forms/modalForms.py:311
+#: intervention/forms/modalForms.py:338
 msgid "Surface"
 msgstr "Fläche"
 
@@ -283,8 +283,8 @@ msgid "Type"
 msgstr "Typ"
 
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:24
-#: compensation/tables.py:89 intervention/forms/modalForms.py:322
-#: intervention/forms/modalForms.py:329 intervention/tables.py:88
+#: compensation/tables.py:89 intervention/forms/modalForms.py:349
+#: intervention/forms/modalForms.py:356 intervention/tables.py:88
 #: intervention/templates/intervention/detail/view.html:19
 #: konova/templates/konova/includes/quickstart/interventions.html:4
 #: templates/navbars/navbar.html:22
@@ -294,7 +294,7 @@ msgstr "Eingriff"
 #: analysis/templates/analysis/reports/includes/old_data/amount.html:34
 #: compensation/tables.py:266
 #: compensation/templates/compensation/detail/eco_account/view.html:19
-#: intervention/forms/modalForms.py:295 intervention/forms/modalForms.py:302
+#: intervention/forms/modalForms.py:322 intervention/forms/modalForms.py:329
 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4
 #: templates/navbars/navbar.html:34
 msgid "Eco-account"
@@ -340,7 +340,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:356
+#: konova/forms.py:358
 msgid "Title"
 msgstr "Bezeichnung"
 
@@ -363,11 +363,11 @@ msgstr "Kompensation XY; Flur ABC"
 #: ema/templates/ema/detail/includes/actions.html:34
 #: ema/templates/ema/detail/includes/deadlines.html:34
 #: ema/templates/ema/detail/includes/documents.html:31
-#: intervention/forms/forms.py:180 intervention/forms/modalForms.py:149
-#: intervention/templates/intervention/detail/includes/documents.html:31
+#: intervention/forms/forms.py:180 intervention/forms/modalForms.py:152
+#: 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:391 konova/templates/konova/includes/comment_card.html:16
+#: konova/forms.py:393 konova/templates/konova/includes/comment_card.html:16
 msgid "Comment"
 msgstr "Kommentar"
 
@@ -483,7 +483,7 @@ msgid "Due on which date"
 msgstr "Zahlung wird an diesem Datum erwartet"
 
 #: compensation/forms/modalForms.py:63 compensation/forms/modalForms.py:333
-#: intervention/forms/modalForms.py:151 konova/forms.py:393
+#: intervention/forms/modalForms.py:154 konova/forms.py:395
 msgid "Additional comment, maximum {} letters"
 msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
 
@@ -511,7 +511,7 @@ msgstr "Zusatzbezeichnung"
 msgid "Select an additional biotope type"
 msgstr "Zusatzbezeichnung wählen"
 
-#: compensation/forms/modalForms.py:196 intervention/forms/modalForms.py:313
+#: compensation/forms/modalForms.py:196 intervention/forms/modalForms.py:340
 msgid "in m²"
 msgstr ""
 
@@ -523,7 +523,7 @@ msgstr "Neuer Zustand"
 msgid "Insert data for the new state"
 msgstr "Geben Sie die Daten des neuen Zustandes ein"
 
-#: compensation/forms/modalForms.py:215 konova/forms.py:191
+#: compensation/forms/modalForms.py:215 konova/forms.py:193
 msgid "Object removed"
 msgstr "Objekt entfernt"
 
@@ -539,7 +539,7 @@ msgstr "Fristart wählen"
 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31
 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31
 #: ema/templates/ema/detail/includes/deadlines.html:31
-#: intervention/forms/modalForms.py:123
+#: intervention/forms/modalForms.py:126
 msgid "Date"
 msgstr "Datum"
 
@@ -582,7 +582,7 @@ msgstr "Maßnahmentyp wählen"
 #: ema/templates/ema/detail/includes/states-before.html:40
 #: intervention/templates/intervention/detail/includes/compensations.html:38
 #: intervention/templates/intervention/detail/includes/deductions.html:39
-#: intervention/templates/intervention/detail/includes/documents.html:36
+#: intervention/templates/intervention/detail/includes/documents.html:39
 #: intervention/templates/intervention/detail/includes/payments.html:39
 #: intervention/templates/intervention/detail/includes/revocation.html:43
 #: templates/log.html:10
@@ -821,14 +821,14 @@ 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:409
+#: konova/forms.py:411
 msgid "Add new document"
 msgstr "Neues Dokument hinzufügen"
 
 #: compensation/templates/compensation/detail/compensation/includes/documents.html:57
 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:55
 #: ema/templates/ema/detail/includes/documents.html:55
-#: intervention/templates/intervention/detail/includes/documents.html:57
+#: intervention/templates/intervention/detail/includes/documents.html:68
 msgid "Remove document"
 msgstr "Dokument löschen"
 
@@ -943,14 +943,14 @@ msgstr "Zuletzt bearbeitet"
 
 #: compensation/templates/compensation/detail/compensation/view.html:99
 #: compensation/templates/compensation/detail/eco_account/view.html:82
-#: ema/templates/ema/detail/view.html:76 intervention/forms/modalForms.py:53
+#: ema/templates/ema/detail/view.html:76 intervention/forms/modalForms.py:56
 #: intervention/templates/intervention/detail/view.html:116
 msgid "Shared with"
 msgstr "Freigegeben für"
 
 #: compensation/templates/compensation/detail/eco_account/includes/controls.html:15
 #: ema/templates/ema/detail/includes/controls.html:15
-#: intervention/forms/modalForms.py:67
+#: intervention/forms/modalForms.py:70
 #: intervention/templates/intervention/detail/includes/controls.html:15
 msgid "Share"
 msgstr "Freigabe"
@@ -992,6 +992,11 @@ msgid "Recorded on"
 msgstr "Verzeichnet am"
 
 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:65
+#: intervention/templates/intervention/detail/includes/deductions.html:60
+msgid "Edit Deduction"
+msgstr "Abbuchung bearbeiten"
+
+#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:68
 #: intervention/templates/intervention/detail/includes/deductions.html:63
 msgid "Remove Deduction"
 msgstr "Abbuchung entfernen"
@@ -1080,22 +1085,22 @@ msgstr "Daten zu den verantwortlichen Stellen"
 msgid "Compensations - Overview"
 msgstr "Kompensationen - Übersicht"
 
-#: compensation/views/compensation.py:149 konova/utils/message_templates.py:27
+#: compensation/views/compensation.py:149 konova/utils/message_templates.py:31
 msgid "Compensation {} edited"
 msgstr "Kompensation {} bearbeitet"
 
 #: compensation/views/compensation.py:159 compensation/views/eco_account.py:163
-#: ema/views.py:230 intervention/views.py:305
+#: ema/views.py:230 intervention/views.py:337
 msgid "Edit {}"
 msgstr "Bearbeite {}"
 
 #: compensation/views/compensation.py:238 compensation/views/eco_account.py:347
-#: ema/views.py:191 intervention/views.py:483
+#: ema/views.py:191 intervention/views.py:541
 msgid "Log"
 msgstr "Log"
 
 #: compensation/views/compensation.py:487 compensation/views/eco_account.py:620
-#: ema/views.py:477 intervention/views.py:629
+#: ema/views.py:477 intervention/views.py:687
 msgid "Report {}"
 msgstr "Bericht {}"
 
@@ -1116,32 +1121,32 @@ msgid "Eco-account removed"
 msgstr "Ökokonto entfernt"
 
 #: compensation/views/eco_account.py:368 ema/views.py:272
-#: intervention/views.py:582
+#: intervention/views.py:640
 msgid "{} unrecorded"
 msgstr "{} entzeichnet"
 
 #: compensation/views/eco_account.py:368 ema/views.py:272
-#: intervention/views.py:582
+#: intervention/views.py:640
 msgid "{} recorded"
 msgstr "{} verzeichnet"
 
 #: compensation/views/eco_account.py:693 ema/views.py:543
-#: intervention/views.py:380
+#: intervention/views.py:438
 msgid "{} has already been shared with you"
 msgstr "{} wurde bereits für Sie freigegeben"
 
 #: compensation/views/eco_account.py:698 ema/views.py:548
-#: intervention/views.py:385
+#: intervention/views.py:443
 msgid "{} has been shared with you"
 msgstr "{} ist nun für Sie freigegeben"
 
 #: compensation/views/eco_account.py:705 ema/views.py:555
-#: intervention/views.py:392
+#: intervention/views.py:450
 msgid "Share link invalid"
 msgstr "Freigabelink ungültig"
 
 #: compensation/views/eco_account.py:728 ema/views.py:578
-#: intervention/views.py:415
+#: intervention/views.py:473
 msgid "Share settings updated"
 msgstr "Freigabe Einstellungen aktualisiert"
 
@@ -1254,19 +1259,19 @@ msgstr "Neuer Eingriff"
 msgid "Edit intervention"
 msgstr "Eingriff bearbeiten"
 
-#: intervention/forms/modalForms.py:26
+#: intervention/forms/modalForms.py:29
 msgid "Share link"
 msgstr "Freigabelink"
 
-#: intervention/forms/modalForms.py:28
+#: intervention/forms/modalForms.py:31
 msgid "Send this link to users who you want to have writing access on the data"
 msgstr "Andere Nutzer erhalten über diesen Link Zugriff auf die Daten"
 
-#: intervention/forms/modalForms.py:38
+#: intervention/forms/modalForms.py:41
 msgid "Add user to share with"
 msgstr "Nutzer direkt hinzufügen"
 
-#: intervention/forms/modalForms.py:40
+#: intervention/forms/modalForms.py:43
 msgid ""
 "Multiple selection possible - You can only select users which do not already "
 "have access. Enter the full username."
@@ -1274,46 +1279,46 @@ msgstr ""
 "Mehrfachauswahl möglich - Sie können nur Nutzer wählen, für die der Eintrag "
 "noch nicht freigegeben wurde. Geben Sie den ganzen Nutzernamen an."
 
-#: intervention/forms/modalForms.py:56
+#: intervention/forms/modalForms.py:59
 msgid "Remove check to remove access for this user"
 msgstr "Wählen Sie die Nutzer ab, die keinen Zugriff mehr haben sollen"
 
-#: intervention/forms/modalForms.py:68
+#: intervention/forms/modalForms.py:71
 msgid "Share settings for {}"
 msgstr "Freigabe Einstellungen für {}"
 
-#: intervention/forms/modalForms.py:125
+#: intervention/forms/modalForms.py:128
 msgid "Date of revocation"
 msgstr "Datum des Widerspruchs"
 
-#: intervention/forms/modalForms.py:136
+#: intervention/forms/modalForms.py:139
 #: intervention/templates/intervention/detail/includes/revocation.html:35
 msgid "Document"
 msgstr "Dokument"
 
-#: intervention/forms/modalForms.py:139
+#: intervention/forms/modalForms.py:142
 msgid "Must be smaller than 15 Mb"
 msgstr "Muss kleiner als 15 Mb sein"
 
-#: intervention/forms/modalForms.py:163
+#: intervention/forms/modalForms.py:167
 #: intervention/templates/intervention/detail/includes/revocation.html:18
 msgid "Add revocation"
 msgstr "Widerspruch hinzufügen"
 
-#: intervention/forms/modalForms.py:197
+#: intervention/forms/modalForms.py:224
 msgid "Checked intervention data"
 msgstr "Eingriffsdaten geprüft"
 
-#: intervention/forms/modalForms.py:203
+#: intervention/forms/modalForms.py:230
 msgid "Checked compensations data and payments"
 msgstr "Kompensationen und Zahlungen geprüft"
 
-#: intervention/forms/modalForms.py:212
+#: intervention/forms/modalForms.py:239
 #: intervention/templates/intervention/detail/includes/controls.html:19
 msgid "Run check"
 msgstr "Prüfung vornehmen"
 
-#: intervention/forms/modalForms.py:213 konova/forms.py:475
+#: intervention/forms/modalForms.py:240 konova/forms.py:514
 msgid ""
 "I, {} {}, confirm that all necessary control steps have been performed by "
 "myself."
@@ -1321,23 +1326,23 @@ msgstr ""
 "Ich, {} {}, bestätige, dass die notwendigen Kontrollschritte durchgeführt "
 "wurden:"
 
-#: intervention/forms/modalForms.py:297
+#: intervention/forms/modalForms.py:324
 msgid "Only recorded accounts can be selected for deductions"
 msgstr "Nur verzeichnete Ökokonten können für Abbuchungen verwendet werden."
 
-#: intervention/forms/modalForms.py:324
+#: intervention/forms/modalForms.py:351
 msgid "Only shared interventions can be selected"
 msgstr "Nur freigegebene Eingriffe können gewählt werden"
 
-#: intervention/forms/modalForms.py:337
+#: intervention/forms/modalForms.py:364
 msgid "New Deduction"
 msgstr "Neue Abbuchung"
 
-#: intervention/forms/modalForms.py:338
+#: intervention/forms/modalForms.py:365
 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:381
+#: intervention/forms/modalForms.py:408
 msgid ""
 "Eco-account {} is not recorded yet. You can only deduct from recorded "
 "accounts."
@@ -1345,7 +1350,7 @@ msgstr ""
 "Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von "
 "verzeichneten Ökokonten erfolgen."
 
-#: intervention/forms/modalForms.py:391
+#: intervention/forms/modalForms.py:418
 msgid ""
 "The account {} has not enough surface for a deduction of {} m². There are "
 "only {} m² left"
@@ -1373,9 +1378,14 @@ msgstr "Ökokonto gelöscht! Abbuchung ungültig!"
 msgid "Eco-account not recorded! Deduction invalid!"
 msgstr "Ökokonto nicht verzeichnet! Abbuchung ungültig!"
 
-#: intervention/templates/intervention/detail/includes/deductions.html:60
-msgid "Edit Deduction"
-msgstr "Abbuchung bearbeiten"
+#: intervention/templates/intervention/detail/includes/documents.html:31
+#: konova/forms.py:368
+msgid "Created on"
+msgstr "Erstellt"
+
+#: intervention/templates/intervention/detail/includes/documents.html:65
+msgid "Edit document"
+msgstr "Dokument bearbeitet"
 
 #: intervention/templates/intervention/detail/includes/payments.html:8
 #: intervention/templates/intervention/report/report.html:73
@@ -1414,6 +1424,10 @@ msgid "Revocation"
 msgstr "Widerspruch"
 
 #: intervention/templates/intervention/detail/includes/revocation.html:69
+msgid "Edit revocation"
+msgstr "Widerspruch bearbeiten"
+
+#: intervention/templates/intervention/detail/includes/revocation.html:72
 msgid "Remove revocation"
 msgstr "Widerspruch entfernen"
 
@@ -1456,19 +1470,19 @@ msgstr "Eingriffe - Übersicht"
 msgid "Intervention {} added"
 msgstr "Eingriff {} hinzugefügt"
 
-#: intervention/views.py:293
+#: intervention/views.py:325
 msgid "Intervention {} edited"
 msgstr "Eingriff {} bearbeitet"
 
-#: intervention/views.py:329
+#: intervention/views.py:361
 msgid "{} removed"
 msgstr "{} entfernt"
 
-#: intervention/views.py:436
+#: intervention/views.py:494
 msgid "Check performed"
 msgstr "Prüfung durchgeführt"
 
-#: intervention/views.py:587
+#: intervention/views.py:645
 msgid "There are errors on this intervention:"
 msgstr "Es liegen Fehler in diesem Eingriff vor:"
 
@@ -1552,81 +1566,69 @@ msgstr "Nach Zulassungsbehörde suchen"
 msgid "Search for conservation office"
 msgstr "Nch Eintragungsstelle suchen"
 
-#: konova/forms.py:37 templates/form/collapsable/form.html:62
+#: konova/forms.py:39 templates/form/collapsable/form.html:62
 msgid "Save"
 msgstr "Speichern"
 
-#: konova/forms.py:69
+#: konova/forms.py:71
 msgid "Not editable"
 msgstr "Nicht editierbar"
 
-#: konova/forms.py:140 konova/forms.py:312
+#: konova/forms.py:142 konova/forms.py:314
 msgid "Confirm"
 msgstr "Bestätige"
 
-#: konova/forms.py:152 konova/forms.py:321
+#: konova/forms.py:154 konova/forms.py:323
 msgid "Remove"
 msgstr "Löschen"
 
-#: konova/forms.py:154
+#: konova/forms.py:156
 msgid "You are about to remove {} {}"
 msgstr "Sie sind dabei {} {} zu löschen"
 
-#: konova/forms.py:241 konova/utils/quality.py:44 konova/utils/quality.py:46
+#: konova/forms.py:243 konova/utils/quality.py:44 konova/utils/quality.py:46
 #: templates/form/collapsable/form.html:45
 msgid "Geometry"
 msgstr "Geometrie"
 
-#: konova/forms.py:322
+#: konova/forms.py:324
 msgid "Are you sure?"
 msgstr "Sind Sie sicher?"
 
-#: konova/forms.py:366
-msgid "Created on"
-msgstr "Erstellt"
-
-#: konova/forms.py:368
+#: konova/forms.py:370
 msgid "When has this file been created? Important for photos."
 msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?"
 
-#: konova/forms.py:379
+#: konova/forms.py:381
 #: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231
 msgid "File"
 msgstr "Datei"
 
-#: konova/forms.py:381
+#: konova/forms.py:383
 msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
 msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
 
-#: konova/forms.py:427
-msgid "Unsupported file type"
-msgstr "Dateiformat nicht unterstützt"
-
-#: konova/forms.py:434
-msgid "File too large"
-msgstr "Datei zu groß"
-
-#: konova/forms.py:443
+#: konova/forms.py:449
 msgid "Added document"
 msgstr "Dokument hinzugefügt"
 
-#: konova/forms.py:466
+#: konova/forms.py:505
 msgid "Confirm record"
 msgstr "Verzeichnen bestätigen"
 
-#: konova/forms.py:474
+#: konova/forms.py:513
 msgid "Record data"
 msgstr "Daten verzeichnen"
 
-#: konova/forms.py:481
+#: konova/forms.py:520
 msgid "Confirm unrecord"
 msgstr "Entzeichnen bestätigen"
 
-#: konova/forms.py:482
+#: konova/forms.py:521
 msgid "Unrecord data"
 msgstr "Daten entzeichnen"
 
-#: konova/forms.py:483
+#: konova/forms.py:522
 msgid "I, {} {}, confirm that this data must be unrecorded."
 msgstr ""
 "Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
@@ -1800,6 +1802,14 @@ msgid "Status of Checked and Recorded reseted"
 msgstr "'Geprüft'/'Verzeichnet' wurde zurückgesetzt"
 
 #: konova/utils/message_templates.py:22
+msgid "Unsupported file type"
+msgstr "Dateiformat nicht unterstützt"
+
+#: konova/utils/message_templates.py:23
+msgid "File too large"
+msgstr "Datei zu groß"
+
+#: konova/utils/message_templates.py:26
 msgid ""
 "Action canceled. Eco account is recorded or deductions exist. Only "
 "conservation office member can perform this action."
@@ -1807,115 +1817,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:25
+#: konova/utils/message_templates.py:29
 msgid "Compensation {} added"
 msgstr "Kompensation {} hinzugefügt"
 
-#: konova/utils/message_templates.py:26
+#: konova/utils/message_templates.py:30
 msgid "Compensation {} removed"
 msgstr "Kompensation {} entfernt"
 
-#: konova/utils/message_templates.py:28
+#: konova/utils/message_templates.py:32
 msgid "Added compensation action"
 msgstr "Maßnahme hinzugefügt"
 
-#: konova/utils/message_templates.py:29
+#: konova/utils/message_templates.py:33
 msgid "Added compensation state"
 msgstr "Zustand hinzugefügt"
 
-#: konova/utils/message_templates.py:32
+#: konova/utils/message_templates.py:36
 msgid "State removed"
 msgstr "Zustand gelöscht"
 
-#: konova/utils/message_templates.py:33
+#: konova/utils/message_templates.py:37
 msgid "State edited"
 msgstr "Zustand bearbeitet"
 
-#: konova/utils/message_templates.py:34
+#: konova/utils/message_templates.py:38
 msgid "State added"
 msgstr "Zustand hinzugefügt"
 
-#: konova/utils/message_templates.py:37
+#: konova/utils/message_templates.py:41
 msgid "Action added"
 msgstr "Maßnahme hinzugefügt"
 
-#: konova/utils/message_templates.py:38
+#: konova/utils/message_templates.py:42
 msgid "Action edited"
 msgstr "Maßnahme bearbeitet"
 
-#: konova/utils/message_templates.py:39
+#: konova/utils/message_templates.py:43
 msgid "Action removed"
 msgstr "Maßnahme entfernt"
 
-#: konova/utils/message_templates.py:42
+#: konova/utils/message_templates.py:46
 msgid "Deduction added"
 msgstr "Abbuchung hinzugefügt"
 
-#: konova/utils/message_templates.py:43
+#: konova/utils/message_templates.py:47
 msgid "Deduction edited"
 msgstr "Abbuchung bearbeitet"
 
-#: konova/utils/message_templates.py:44
+#: konova/utils/message_templates.py:48
 msgid "Deduction removed"
 msgstr "Abbuchung entfernt"
 
-#: konova/utils/message_templates.py:47
+#: konova/utils/message_templates.py:51
 msgid "Deadline added"
 msgstr "Frist/Termin hinzugefügt"
 
-#: konova/utils/message_templates.py:48
+#: konova/utils/message_templates.py:52
 msgid "Deadline edited"
 msgstr "Frist/Termin bearbeitet"
 
-#: konova/utils/message_templates.py:49
+#: konova/utils/message_templates.py:53
 msgid "Deadline removed"
 msgstr "Frist/Termin gelöscht"
 
-#: konova/utils/message_templates.py:52
+#: konova/utils/message_templates.py:56
 msgid "Payment added"
 msgstr "Zahlung hinzugefügt"
 
-#: konova/utils/message_templates.py:53
+#: konova/utils/message_templates.py:57
 msgid "Payment edited"
 msgstr "Zahlung bearbeitet"
 
-#: konova/utils/message_templates.py:54
+#: konova/utils/message_templates.py:58
 msgid "Payment removed"
 msgstr "Zahlung gelöscht"
 
-#: konova/utils/message_templates.py:57
+#: konova/utils/message_templates.py:61
 msgid "Revocation added"
 msgstr "Widerspruch hinzugefügt"
 
-#: konova/utils/message_templates.py:58
+#: konova/utils/message_templates.py:62
 msgid "Revocation edited"
 msgstr "Widerspruch bearbeitet"
 
-#: konova/utils/message_templates.py:59
+#: konova/utils/message_templates.py:63
 msgid "Revocation removed"
 msgstr "Widerspruch entfernt"
 
-#: konova/utils/message_templates.py:62
+#: konova/utils/message_templates.py:66
 msgid "Document '{}' deleted"
 msgstr "Dokument '{}' gelöscht"
 
-#: konova/utils/message_templates.py:63
+#: konova/utils/message_templates.py:67
 msgid "Document added"
 msgstr "Dokument hinzugefügt"
 
-#: konova/utils/message_templates.py:66
+#: konova/utils/message_templates.py:68
+msgid "Document edited"
+msgstr "Dokument bearbeitet"
+
+#: konova/utils/message_templates.py:71
 msgid "Edited general data"
 msgstr "Allgemeine Daten bearbeitet"
 
-#: konova/utils/message_templates.py:67
+#: konova/utils/message_templates.py:72
 msgid "Added deadline"
 msgstr "Frist/Termin hinzugefügt"
 
-#: konova/utils/message_templates.py:70
+#: konova/utils/message_templates.py:75
 msgid "Geometry conflict detected with {}"
 msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}"
 
-#: konova/utils/message_templates.py:73
+#: konova/utils/message_templates.py:78
 msgid "This intervention has {} revocations"
 msgstr "Dem Eingriff liegen {} Widersprüche vor"
 
-- 
2.45.2


From 4a777e4b01c1f877ff4f86384eee296068afac67 Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 10:28:41 +0100
Subject: [PATCH 21/31] #86 Edit document EMA

* adds buttons and urls for ema
---
 .../ema/detail/includes/documents.html        | 15 +++++--
 ema/urls.py                                   |  5 ++-
 ema/views.py                                  | 41 +++++++++++++++++--
 3 files changed, 52 insertions(+), 9 deletions(-)

diff --git a/ema/templates/ema/detail/includes/documents.html b/ema/templates/ema/detail/includes/documents.html
index dbc8927b..7be632d3 100644
--- a/ema/templates/ema/detail/includes/documents.html
+++ b/ema/templates/ema/detail/includes/documents.html
@@ -27,6 +27,9 @@
                 <th scope="col">
                     {% trans 'Title' %}
                 </th>
+                <th scope="col">
+                    {% trans 'Created on' %}
+                </th>
                 <th scope="col">
                     {% trans 'Comment' %}
                 </th>
@@ -41,18 +44,24 @@
             {% for doc in obj.documents.all %}
             <tr>
                 <td class="align-middle">
-                    <a href="{% url 'ema:get-doc' doc.id %}">
+                    <a href="{% url 'ema:get-doc' obj.id doc.id %}">
                         {{ doc.title }}
                     </a>
                 </td>
+                <td class="align-middle">
+                    {{ doc.date_of_creation }}
+                </td>
                 <td class="align-middle">
                     <div class="scroll-150">
                         {{ doc.comment }}
                     </div>
                 </td>
-                <td class="align-middle">
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'ema:remove-doc' doc.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove document' %}">
+                    <button data-form-url="{% url 'ema:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'ema:remove-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/ema/urls.py b/ema/urls.py
index 1aa52780..a2ee618f 100644
--- a/ema/urls.py
+++ b/ema/urls.py
@@ -30,7 +30,8 @@ urlpatterns = [
 
     # Documents
     path('<id>/document/new/', document_new_view, name='new-doc'),
-    path('document/<doc_id>', get_document_view, name='get-doc'),
-    path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
+    path('<id>/document/<doc_id>', get_document_view, name='get-doc'),
+    path('<id>/document/<doc_id>/edit/', edit_document_view, name='edit-doc'),
+    path('<id>/document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
 
 ]
\ No newline at end of file
diff --git a/ema/views.py b/ema/views.py
index 99c80be3..343953f3 100644
--- a/ema/views.py
+++ b/ema/views.py
@@ -15,7 +15,8 @@ from intervention.forms.modalForms import ShareModalForm
 from konova.contexts import BaseContext
 from konova.decorators import conservation_office_group_required, shared_access_required
 from ema.models import Ema, EmaDocument
-from konova.forms import RemoveModalForm, SimpleGeomForm, RecordModalForm, RemoveDeadlineModalForm
+from konova.forms import RemoveModalForm, SimpleGeomForm, RecordModalForm, RemoveDeadlineModalForm, \
+    EditDocumentModalForm
 from konova.models import Deadline
 from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
@@ -23,7 +24,7 @@ from konova.utils.documents import get_document, remove_document
 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
+    COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -366,18 +367,21 @@ def document_new_view(request: HttpRequest, id: str):
 
 @login_required
 @conservation_office_group_required
-def get_document_view(request: HttpRequest, doc_id: str):
+@shared_access_required(Ema, "id")
+def get_document_view(request: HttpRequest, id: str, doc_id: str):
     """ Returns the document as downloadable file
 
     Wraps the generic document fetcher function from konova.utils.
 
     Args:
         request (HttpRequest): The incoming request
+        id (str): The EMA id
         doc_id (str): The document id
 
     Returns:
 
     """
+    ema = get_object_or_404(Ema, id=id)
     doc = get_object_or_404(EmaDocument, id=doc_id)
     user = request.user
     instance = doc.instance
@@ -393,18 +397,47 @@ def get_document_view(request: HttpRequest, doc_id: str):
 
 @login_required
 @conservation_office_group_required
-def remove_document_view(request: HttpRequest, doc_id: str):
+@shared_access_required(Ema, "id")
+def edit_document_view(request: HttpRequest, id: str, doc_id: str):
     """ Removes the document from the database and file system
 
     Wraps the generic functionality from konova.utils.
 
     Args:
         request (HttpRequest): The incoming request
+        id (str): The EMA id
         doc_id (str): The document id
 
     Returns:
 
     """
+    ema = get_object_or_404(Ema, id=id)
+    doc = get_object_or_404(EmaDocument, id=doc_id)
+    form = EditDocumentModalForm(request.POST or None, request.FILES or None, instance=ema, document=doc, request=request)
+    return form.process_request(
+        request,
+        DOCUMENT_EDITED,
+        reverse("ema:detail", args=(id,)) + "#related_data"
+    )
+
+
+@login_required
+@conservation_office_group_required
+@shared_access_required(Ema, "id")
+def remove_document_view(request: HttpRequest, id:str, doc_id: str):
+    """ Removes the document from the database and file system
+
+    Wraps the generic functionality from konova.utils.
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The EMA id
+        doc_id (str): The document id
+
+    Returns:
+
+    """
+    ema = get_object_or_404(Ema, id=id)
     doc = get_object_or_404(EmaDocument, id=doc_id)
     return remove_document(
         request,
-- 
2.45.2


From 3ded54b80f2b977ecdfb37d0c3d74d8ac7b8963c Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 10:44:44 +0100
Subject: [PATCH 22/31] #86 Edit document Compensation

* adds support for editing of documents
* adds buttons and urls for compensation
* simplifies getter for all documents
---
 .../compensation/includes/documents.html      | 15 ++++--
 compensation/urls/compensation.py             |  5 +-
 compensation/views/compensation.py            | 49 ++++++++++++++-----
 ema/views.py                                  |  9 ----
 intervention/views.py                         | 10 ----
 5 files changed, 51 insertions(+), 37 deletions(-)

diff --git a/compensation/templates/compensation/detail/compensation/includes/documents.html b/compensation/templates/compensation/detail/compensation/includes/documents.html
index 0615d4be..e12de4e9 100644
--- a/compensation/templates/compensation/detail/compensation/includes/documents.html
+++ b/compensation/templates/compensation/detail/compensation/includes/documents.html
@@ -27,6 +27,9 @@
                 <th scope="col">
                     {% trans 'Title' %}
                 </th>
+                <th scope="col">
+                    {% trans 'Created on' %}
+                </th>
                 <th scope="col">
                     {% trans 'Comment' %}
                 </th>
@@ -43,18 +46,24 @@
             {% for doc in obj.documents.all %}
             <tr>
                 <td class="align-middle">
-                    <a href="{% url 'compensation:get-doc' doc.id %}">
+                    <a href="{% url 'compensation:get-doc' obj.id doc.id %}">
                         {{ doc.title }}
                     </a>
                 </td>
+                <td class="align-middle">
+                    {{ doc.date_of_creation }}
+                </td>
                 <td class="align-middle">
                     <div class="scroll-150">
                         {{ doc.comment }}
                     </div>
                 </td>
-                <td class="align-middle">
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:remove-doc' doc.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove document' %}">
+                    <button data-form-url="{% url 'compensation:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:remove-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/compensation/urls/compensation.py b/compensation/urls/compensation.py
index 9c23117b..ea50b011 100644
--- a/compensation/urls/compensation.py
+++ b/compensation/urls/compensation.py
@@ -28,7 +28,8 @@ urlpatterns = [
 
     # Documents
     path('<id>/document/new/', new_document_view, name='new-doc'),
-    path('document/<doc_id>', get_document_view, name='get-doc'),
-    path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
+    path('<id>/document/<doc_id>', get_document_view, name='get-doc'),
+    path('<id>/document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
+    path('<id>/document/<doc_id>/edit/', edit_document_view, name='edit-doc'),
 
 ]
\ No newline at end of file
diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py
index 95f4ac0b..40ea9335 100644
--- a/compensation/views/compensation.py
+++ b/compensation/views/compensation.py
@@ -12,7 +12,7 @@ from compensation.tables import CompensationTable
 from intervention.models import Intervention
 from konova.contexts import BaseContext
 from konova.decorators import *
-from konova.forms import RemoveModalForm, SimpleGeomForm, RemoveDeadlineModalForm
+from konova.forms import RemoveModalForm, SimpleGeomForm, RemoveDeadlineModalForm, EditDocumentModalForm
 from konova.models import Deadline
 from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 from konova.utils.documents import get_document, remove_document
@@ -20,7 +20,7 @@ from konova.utils.generators import generate_qr_code
 from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DATA_UNSHARED_EXPLANATION, \
     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
+    DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -286,45 +286,42 @@ def new_document_view(request: HttpRequest, id: str):
 
 @login_required
 @default_group_required
-def get_document_view(request: HttpRequest, doc_id: str):
+@shared_access_required(Compensation, "id")
+def get_document_view(request: HttpRequest, id: str, doc_id: str):
     """ Returns the document as downloadable file
 
     Wraps the generic document fetcher function from konova.utils.
 
     Args:
         request (HttpRequest): The incoming request
+        id (str): The compensation id
         doc_id (str): The document id
 
     Returns:
 
     """
+    comp = get_object_or_404(Compensation, id=id)
     doc = get_object_or_404(CompensationDocument, id=doc_id)
-    user = request.user
-    instance = doc.instance
-    # File download only possible if related instance is shared with user
-    if not instance.users.filter(id=user.id):
-        messages.info(
-            request,
-            DATA_UNSHARED
-        )
-        return redirect("compensation:detail", id=instance.id)
     return get_document(doc)
 
 
 @login_required
 @default_group_required
-def remove_document_view(request: HttpRequest, doc_id: str):
+@shared_access_required(Compensation, "id")
+def remove_document_view(request: HttpRequest, id: str, doc_id: str):
     """ Removes the document from the database and file system
 
     Wraps the generic functionality from konova.utils.
 
     Args:
         request (HttpRequest): The incoming request
+        id (str): The compensation id
         doc_id (str): The document id
 
     Returns:
 
     """
+    comp = get_object_or_404(Compensation, id=id)
     doc = get_object_or_404(CompensationDocument, id=doc_id)
     return remove_document(
         request,
@@ -332,6 +329,32 @@ def remove_document_view(request: HttpRequest, doc_id: str):
     )
 
 
+@login_required
+@default_group_required
+@shared_access_required(Compensation, "id")
+def edit_document_view(request: HttpRequest, id: str, doc_id: str):
+    """ Removes the document from the database and file system
+
+    Wraps the generic functionality from konova.utils.
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The compensation id
+        doc_id (str): The document id
+
+    Returns:
+
+    """
+    comp = get_object_or_404(Compensation, id=id)
+    doc = get_object_or_404(CompensationDocument, id=doc_id)
+    form = EditDocumentModalForm(request.POST or None, request.FILES or None, instance=comp, document=doc, request=request)
+    return form.process_request(
+        request,
+        DOCUMENT_EDITED,
+        reverse("compensation:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @default_group_required
 @shared_access_required(Compensation, "id")
diff --git a/ema/views.py b/ema/views.py
index 343953f3..0650f4c9 100644
--- a/ema/views.py
+++ b/ema/views.py
@@ -383,15 +383,6 @@ def get_document_view(request: HttpRequest, id: str, doc_id: str):
     """
     ema = get_object_or_404(Ema, id=id)
     doc = get_object_or_404(EmaDocument, id=doc_id)
-    user = request.user
-    instance = doc.instance
-    # File download only possible if related instance is shared with user
-    if not instance.users.filter(id=user.id):
-        messages.info(
-            request,
-            DATA_UNSHARED
-        )
-        return redirect("ema:detail", id=instance.id)
     return get_document(doc)
 
 
diff --git a/intervention/views.py b/intervention/views.py
index 2945c337..00440bb8 100644
--- a/intervention/views.py
+++ b/intervention/views.py
@@ -161,7 +161,6 @@ def get_revocation_view(request: HttpRequest, doc_id: str):
         return redirect("intervention:detail", id=doc.instance.id)
     return get_document(doc)
 
-
 @login_required
 @default_group_required
 @shared_access_required(Intervention, "id")
@@ -180,15 +179,6 @@ def get_document_view(request: HttpRequest, id: str, doc_id: str):
     """
     intervention = get_object_or_404(Intervention, id=id)
     doc = get_object_or_404(InterventionDocument, id=doc_id)
-    user = request.user
-    instance = doc.instance
-    # File download only possible if related instance is shared with user
-    if not instance.users.filter(id=user.id):
-        messages.info(
-            request,
-            DATA_UNSHARED
-        )
-        return redirect("intervention:detail", id=instance.id)
     return get_document(doc)
 
 
-- 
2.45.2


From fe842cb9bcdc32e2c068c2d69b375938b612c542 Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 10:51:52 +0100
Subject: [PATCH 23/31] #86 Edit document EcoAccount

* adds support for editing of documents
* adds buttons and urls for ecoaccount
---
 .../eco_account/includes/documents.html       | 15 ++++--
 compensation/urls/eco_account.py              |  5 +-
 compensation/views/eco_account.py             | 49 ++++++++++++++-----
 3 files changed, 51 insertions(+), 18 deletions(-)

diff --git a/compensation/templates/compensation/detail/eco_account/includes/documents.html b/compensation/templates/compensation/detail/eco_account/includes/documents.html
index b3895eac..e852842b 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/documents.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/documents.html
@@ -27,6 +27,9 @@
                 <th scope="col">
                     {% trans 'Title' %}
                 </th>
+                <th scope="col">
+                    {% trans 'Created on' %}
+                </th>
                 <th scope="col">
                     {% trans 'Comment' %}
                 </th>
@@ -41,18 +44,24 @@
             {% for doc in obj.documents.all %}
             <tr>
                 <td class="align-middle">
-                    <a href="{% url 'compensation:acc:get-doc' doc.id %}">
+                    <a href="{% url 'compensation:acc:get-doc' obj.id doc.id %}">
                         {{ doc.title }}
                     </a>
                 </td>
+                <td class="align-middle">
+                    {{ doc.date_of_creation }}
+                </td>
                 <td class="align-middle">
                     <div class="scroll-150">
                         {{ doc.comment }}
                     </div>
                 </td>
-                <td class="align-middle">
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:acc:remove-doc' doc.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove document' %}">
+                    <button data-form-url="{% url 'compensation:acc:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:acc:remove-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/compensation/urls/eco_account.py b/compensation/urls/eco_account.py
index 0c3a0293..e390d4fb 100644
--- a/compensation/urls/eco_account.py
+++ b/compensation/urls/eco_account.py
@@ -30,8 +30,9 @@ urlpatterns = [
 
     # Documents
     path('<id>/document/new/', new_document_view, name='new-doc'),
-    path('document/<doc_id>', get_document_view, name='get-doc'),
-    path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
+    path('<id>/document/<doc_id>', get_document_view, name='get-doc'),
+    path('<id>/document/<doc_id>/edit', edit_document_view, name='edit-doc'),
+    path('<id>/document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
 
     # Eco-account deductions
     path('<id>/deduction/<deduction_id>/remove', deduction_remove_view, name='remove-deduction'),
diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py
index d433d551..19959f81 100644
--- a/compensation/views/eco_account.py
+++ b/compensation/views/eco_account.py
@@ -24,7 +24,8 @@ from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm,
 from konova.contexts import BaseContext
 from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
     shared_access_required
-from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentModalForm, RecordModalForm, RemoveDeadlineModalForm
+from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentModalForm, RecordModalForm, \
+    RemoveDeadlineModalForm, EditDocumentModalForm
 from konova.models import Deadline
 from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
@@ -33,7 +34,7 @@ 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
+    DEDUCTION_EDITED, DOCUMENT_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -534,46 +535,68 @@ def new_document_view(request: HttpRequest, id: str):
 
 @login_required
 @default_group_required
-def get_document_view(request: HttpRequest, doc_id: str):
+@shared_access_required(EcoAccount, "id")
+def get_document_view(request: HttpRequest, id:str, doc_id: str):
     """ Returns the document as downloadable file
 
     Wraps the generic document fetcher function from konova.utils.
 
     Args:
         request (HttpRequest): The incoming request
+        id (str): The account id
         doc_id (str): The document id
 
     Returns:
 
     """
+    acc = get_object_or_404(EcoAccount, id=id)
     doc = get_object_or_404(EcoAccountDocument, id=doc_id)
-    user = request.user
-    instance = doc.instance
-    # File download only possible if related instance is shared with user
-    if not instance.users.filter(id=user.id):
-        messages.info(
-            request,
-            DATA_UNSHARED
-        )
-        return redirect("compensation:acc:detail", id=instance.id)
     return get_document(doc)
 
 
 @login_required
 @default_group_required
 @shared_access_required(EcoAccount, "id")
-def remove_document_view(request: HttpRequest, doc_id: str):
+def edit_document_view(request: HttpRequest, id: str, doc_id: str):
     """ Removes the document from the database and file system
 
     Wraps the generic functionality from konova.utils.
 
     Args:
         request (HttpRequest): The incoming request
+        id (str): The account id
         doc_id (str): The document id
 
     Returns:
 
     """
+    acc = get_object_or_404(EcoAccount, id=id)
+    doc = get_object_or_404(EcoAccountDocument, id=doc_id)
+    form = EditDocumentModalForm(request.POST or None, request.FILES or None, instance=acc, document=doc, request=request)
+    return form.process_request(
+        request,
+        DOCUMENT_EDITED,
+        reverse("compensation:acc:detail", args=(id,)) + "#related_data"
+    )
+
+
+@login_required
+@default_group_required
+@shared_access_required(EcoAccount, "id")
+def remove_document_view(request: HttpRequest, id: str, doc_id: str):
+    """ Removes the document from the database and file system
+
+    Wraps the generic functionality from konova.utils.
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The account id
+        doc_id (str): The document id
+
+    Returns:
+
+    """
+    acc = get_object_or_404(EcoAccount, id=id)
     doc = get_object_or_404(EcoAccountDocument, id=doc_id)
     return remove_document(
         request,
-- 
2.45.2


From 0c261196a4dfc217df3c779021de537c6002070b Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 11:02:30 +0100
Subject: [PATCH 24/31] #86 Edit states compensation

* adds support for editing of states
* adds buttons and urls for compensation
---
 compensation/forms/modalForms.py              | 25 +++++++++++++++-
 .../compensation/includes/states-after.html   |  7 +++--
 .../compensation/includes/states-before.html  |  7 +++--
 compensation/urls/compensation.py             |  1 +
 compensation/views/compensation.py            | 29 +++++++++++++++++--
 5 files changed, 62 insertions(+), 7 deletions(-)

diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py
index 5368077c..ddf8502b 100644
--- a/compensation/forms/modalForms.py
+++ b/compensation/forms/modalForms.py
@@ -21,7 +21,7 @@ from konova.contexts import BaseContext
 from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
 from konova.models import DeadlineType
 from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \
-    ADDED_COMPENSATION_ACTION, PAYMENT_EDITED
+    ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED
 
 
 class NewPaymentForm(BaseModalForm):
@@ -261,6 +261,29 @@ class NewStateModalForm(BaseModalForm):
             raise NotImplementedError
 
 
+class EditCompensationStateModalForm(NewStateModalForm):
+    state = None
+
+    def __init__(self, *args, **kwargs):
+        self.state = kwargs.pop("state", None)
+        super().__init__(*args, **kwargs)
+        form_data = {
+            "biotope_type": self.state.biotope_type,
+            "biotope_extra": self.state.biotope_type_details.all(),
+            "surface": self.state.surface,
+        }
+        self.load_initial_data(form_data)
+
+    def save(self, is_before_state: bool = False):
+        state = self.state
+        state.biotope_type = self.cleaned_data.get("biotope_type", None)
+        state.biotope_type_details.set(self.cleaned_data.get("biotope_extra", []))
+        state.surface = self.cleaned_data.get("surface", None)
+        state.save()
+        self.instance.mark_as_edited(self.user, self.request, edit_comment=COMPENSATION_STATE_EDITED)
+        return state
+
+
 class RemoveCompensationStateModalForm(RemoveModalForm):
     """ Removing modal form for CompensationState
 
diff --git a/compensation/templates/compensation/detail/compensation/includes/states-after.html b/compensation/templates/compensation/detail/compensation/includes/states-after.html
index b3e86a26..c0d8b2d5 100644
--- a/compensation/templates/compensation/detail/compensation/includes/states-after.html
+++ b/compensation/templates/compensation/detail/compensation/includes/states-after.html
@@ -57,9 +57,12 @@
                     {% endif %}
                 </td>
                 <td>{{ state.surface|floatformat:2 }} m²</td>
-                <td>
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
+                    <button data-form-url="{% url 'compensation:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/compensation/templates/compensation/detail/compensation/includes/states-before.html b/compensation/templates/compensation/detail/compensation/includes/states-before.html
index c3c7e0f1..50855430 100644
--- a/compensation/templates/compensation/detail/compensation/includes/states-before.html
+++ b/compensation/templates/compensation/detail/compensation/includes/states-before.html
@@ -57,9 +57,12 @@
                     {% endif %}
                 </td>
                 <td>{{ state.surface|floatformat:2 }} m²</td>
-                <td>
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
+                    <button data-form-url="{% url 'compensation:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/compensation/urls/compensation.py b/compensation/urls/compensation.py
index ea50b011..9d644e73 100644
--- a/compensation/urls/compensation.py
+++ b/compensation/urls/compensation.py
@@ -21,6 +21,7 @@ urlpatterns = [
     path('<id>/state/new', state_new_view, name='new-state'),
     path('<id>/action/new', action_new_view, name='new-action'),
     path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
+    path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
     path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
     path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
     path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py
index 40ea9335..3baf03a8 100644
--- a/compensation/views/compensation.py
+++ b/compensation/views/compensation.py
@@ -6,7 +6,8 @@ from django.utils.translation import gettext_lazy as _
 
 from compensation.forms.forms import NewCompensationForm, EditCompensationForm
 from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm, \
-    NewCompensationDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
+    NewCompensationDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, \
+    EditCompensationStateModalForm
 from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument
 from compensation.tables import CompensationTable
 from intervention.models import Intervention
@@ -20,7 +21,7 @@ from konova.utils.generators import generate_qr_code
 from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DATA_UNSHARED_EXPLANATION, \
     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
+    DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -469,6 +470,30 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     )
 
 
+@login_required
+@default_group_required
+@shared_access_required(Compensation, "id")
+def state_edit_view(request: HttpRequest, id: str, state_id: str):
+    """ Renders a form for editing a compensation state
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The compensation's id
+        state_id (str): The state's id
+
+    Returns:
+
+    """
+    comp = get_object_or_404(Compensation, id=id)
+    state = get_object_or_404(CompensationState, id=state_id)
+    form = EditCompensationStateModalForm(request.POST or None, instance=comp, state=state, request=request)
+    return form.process_request(
+        request,
+        msg_success=COMPENSATION_STATE_EDITED,
+        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @default_group_required
 @shared_access_required(Compensation, "id")
-- 
2.45.2


From aa242e040aa5078a60fd27e7bbe9702f13a6d734 Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 11:15:01 +0100
Subject: [PATCH 25/31] #86 Edit states EMA/EcoAccount

* adds support for editing of states for EMA and EcoAccount
* adds buttons and urls
---
 .../eco_account/includes/states-after.html    |  7 +++--
 .../eco_account/includes/states-before.html   |  7 +++--
 compensation/urls/eco_account.py              |  1 +
 compensation/views/eco_account.py             | 29 +++++++++++++++++--
 .../ema/detail/includes/states-after.html     |  7 +++--
 .../ema/detail/includes/states-before.html    |  7 +++--
 ema/urls.py                                   |  1 +
 ema/views.py                                  | 28 ++++++++++++++++--
 8 files changed, 75 insertions(+), 12 deletions(-)

diff --git a/compensation/templates/compensation/detail/eco_account/includes/states-after.html b/compensation/templates/compensation/detail/eco_account/includes/states-after.html
index ea696b31..ff074769 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/states-after.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/states-after.html
@@ -57,9 +57,12 @@
                     {% endif %}
                 </td>
                 <td>{{ state.surface|floatformat:2 }} m²</td>
-                <td>
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:acc:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
+                    <button data-form-url="{% url 'compensation:acc:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:acc:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/compensation/templates/compensation/detail/eco_account/includes/states-before.html b/compensation/templates/compensation/detail/eco_account/includes/states-before.html
index af3042ea..7b5fe785 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/states-before.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/states-before.html
@@ -57,9 +57,12 @@
                     {% endif %}
                 </td>
                 <td>{{ state.surface|floatformat:2 }} m²</td>
-                <td>
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:acc:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
+                    <button data-form-url="{% url 'compensation:acc:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:acc:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/compensation/urls/eco_account.py b/compensation/urls/eco_account.py
index e390d4fb..f23f2a11 100644
--- a/compensation/urls/eco_account.py
+++ b/compensation/urls/eco_account.py
@@ -22,6 +22,7 @@ urlpatterns = [
     path('<id>/state/new', state_new_view, name='new-state'),
     path('<id>/action/new', action_new_view, name='new-action'),
     path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
+    path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
     path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
     path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
     path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py
index 19959f81..a0365068 100644
--- a/compensation/views/eco_account.py
+++ b/compensation/views/eco_account.py
@@ -16,7 +16,8 @@ from django.shortcuts import render, get_object_or_404, redirect
 
 from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm
 from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
-    NewEcoAccountDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
+    NewEcoAccountDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, \
+    EditCompensationStateModalForm
 from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
 from compensation.tables import EcoAccountTable
 from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm, \
@@ -34,7 +35,7 @@ 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
+    DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -442,6 +443,30 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     )
 
 
+@login_required
+@default_group_required
+@shared_access_required(EcoAccount, "id")
+def state_edit_view(request: HttpRequest, id: str, state_id: str):
+    """ Renders a form for editing a compensation state
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The compensation's id
+        state_id (str): The state's id
+
+    Returns:
+
+    """
+    acc = get_object_or_404(EcoAccount, id=id)
+    state = get_object_or_404(CompensationState, id=state_id)
+    form = EditCompensationStateModalForm(request.POST or None, instance=acc, state=state, request=request)
+    return form.process_request(
+        request,
+        msg_success=COMPENSATION_STATE_EDITED,
+        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @default_group_required
 @shared_access_required(EcoAccount, "id")
diff --git a/ema/templates/ema/detail/includes/states-after.html b/ema/templates/ema/detail/includes/states-after.html
index f0d3dfc4..e876a166 100644
--- a/ema/templates/ema/detail/includes/states-after.html
+++ b/ema/templates/ema/detail/includes/states-after.html
@@ -55,9 +55,12 @@
                     {% endif %}
                 </td>
                 <td>{{ state.surface|floatformat:2 }} m²</td>
-                <td>
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'ema:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
+                    <button data-form-url="{% url 'ema:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'ema:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/ema/templates/ema/detail/includes/states-before.html b/ema/templates/ema/detail/includes/states-before.html
index f479bd8d..aec3f328 100644
--- a/ema/templates/ema/detail/includes/states-before.html
+++ b/ema/templates/ema/detail/includes/states-before.html
@@ -55,9 +55,12 @@
                     {% endif %}
                 </td>
                 <td>{{ state.surface|floatformat:2 }} m²</td>
-                <td>
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'ema:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
+                    <button data-form-url="{% url 'ema:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'ema:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/ema/urls.py b/ema/urls.py
index a2ee618f..4008a69c 100644
--- a/ema/urls.py
+++ b/ema/urls.py
@@ -22,6 +22,7 @@ urlpatterns = [
     path('<id>/state/new', state_new_view, name='new-state'),
     path('<id>/action/new', action_new_view, name='new-action'),
     path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
+    path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
     path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
     path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
     path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
diff --git a/ema/views.py b/ema/views.py
index 0650f4c9..bc54631c 100644
--- a/ema/views.py
+++ b/ema/views.py
@@ -7,7 +7,7 @@ from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
 
 from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
-    RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
+    RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, EditCompensationStateModalForm
 from compensation.models import CompensationAction, CompensationState
 from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentModalForm
 from ema.tables import EmaTable
@@ -24,7 +24,7 @@ from konova.utils.documents import get_document, remove_document
 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_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -460,6 +460,30 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
     )
 
 
+@login_required
+@conservation_office_group_required
+@shared_access_required(Ema, "id")
+def state_edit_view(request: HttpRequest, id: str, state_id: str):
+    """ Renders a form for editing an EMA state
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The ema id
+        state_id (str): The state's id
+
+    Returns:
+
+    """
+    ema = get_object_or_404(Ema, id=id)
+    state = get_object_or_404(CompensationState, id=state_id)
+    form = EditCompensationStateModalForm(request.POST or None, instance=ema, state=state, request=request)
+    return form.process_request(
+        request,
+        msg_success=COMPENSATION_STATE_EDITED,
+        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @conservation_office_group_required
 @shared_access_required(Ema, "id")
-- 
2.45.2


From b581d1c4ad2d7063aebae6550442f730faade5ca Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 11:24:20 +0100
Subject: [PATCH 26/31] #86 Edit actions EMA

* adds support for editing of CompensationAction
* adds buttons and urls for EMA
---
 compensation/forms/modalForms.py              | 29 +++++++++++++++++-
 .../ema/detail/includes/actions.html          |  7 +++--
 ema/urls.py                                   |  6 +++-
 ema/views.py                                  | 30 +++++++++++++++++--
 4 files changed, 66 insertions(+), 6 deletions(-)

diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py
index ddf8502b..6436a7c8 100644
--- a/compensation/forms/modalForms.py
+++ b/compensation/forms/modalForms.py
@@ -21,7 +21,7 @@ from konova.contexts import BaseContext
 from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
 from konova.models import DeadlineType
 from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \
-    ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED
+    ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED
 
 
 class NewPaymentForm(BaseModalForm):
@@ -466,6 +466,33 @@ class NewActionModalForm(BaseModalForm):
         return action
 
 
+class EditCompensationActionModalForm(NewActionModalForm):
+    action = None
+
+    def __init__(self, *args, **kwargs):
+        self.action = kwargs.pop("action", None)
+        super().__init__(*args, **kwargs)
+        form_data = {
+            "action_type": self.action.action_type,
+            "action_type_details": self.action.action_type_details.all(),
+            "amount": self.action.amount,
+            "unit": self.action.unit,
+            "comment": self.action.comment,
+        }
+        self.load_initial_data(form_data)
+
+    def save(self):
+        action = self.action
+        action.action_type = self.cleaned_data.get("action_type", None)
+        action.action_type_details.set(self.cleaned_data.get("action_type_details", []))
+        action.amount = self.cleaned_data.get("amount", None)
+        action.unit = self.cleaned_data.get("unit", None)
+        action.comment = self.cleaned_data.get("comment", None)
+        action.save()
+        self.instance.mark_as_edited(self.user, self.request, edit_comment=COMPENSATION_ACTION_EDITED)
+        return action
+
+
 class NewCompensationDocumentModalForm(NewDocumentModalForm):
     document_model = CompensationDocument
 
diff --git a/ema/templates/ema/detail/includes/actions.html b/ema/templates/ema/detail/includes/actions.html
index 74f0564a..30e5e527 100644
--- a/ema/templates/ema/detail/includes/actions.html
+++ b/ema/templates/ema/detail/includes/actions.html
@@ -58,9 +58,12 @@
                         {{ action.comment }}
                     </div>
                 </td>
-                <td class="">
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'ema:action-remove' obj.id action.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove action' %}">
+                    <button data-form-url="{% url 'ema:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'ema:action-remove' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove action' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/ema/urls.py b/ema/urls.py
index 4008a69c..3e7092c6 100644
--- a/ema/urls.py
+++ b/ema/urls.py
@@ -19,11 +19,15 @@ urlpatterns = [
     path('<id>/remove', remove_view, name='remove'),
     path('<id>/record', record_view, name='record'),
     path('<id>/report', report_view, name='report'),
+
     path('<id>/state/new', state_new_view, name='new-state'),
-    path('<id>/action/new', action_new_view, name='new-action'),
     path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
     path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
+
+    path('<id>/action/new', action_new_view, name='new-action'),
+    path('<id>/action/<action_id>/edit', action_edit_view, name='action-edit'),
     path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
+
     path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
     path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
     path('<id>/share/<token>', share_view, name='share'),
diff --git a/ema/views.py b/ema/views.py
index bc54631c..607d58df 100644
--- a/ema/views.py
+++ b/ema/views.py
@@ -7,7 +7,8 @@ from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
 
 from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
-    RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, EditCompensationStateModalForm
+    RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, EditCompensationStateModalForm, \
+    EditCompensationActionModalForm
 from compensation.models import CompensationAction, CompensationState
 from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentModalForm
 from ema.tables import EmaTable
@@ -24,7 +25,8 @@ from konova.utils.documents import get_document, remove_document
 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_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, \
+    COMPENSATION_ACTION_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -322,6 +324,30 @@ def action_new_view(request: HttpRequest, id: str):
     )
 
 
+@login_required
+@conservation_office_group_required
+@shared_access_required(Ema, "id")
+def action_edit_view(request: HttpRequest, id: str, action_id: str):
+    """ Renders a form for editing an actions for an EMA
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The EMA's id
+        action_id (str): The action id
+
+    Returns:
+
+    """
+    ema = get_object_or_404(Ema, id=id)
+    action = get_object_or_404(CompensationAction, id=action_id)
+    form = EditCompensationActionModalForm(request.POST or None, instance=ema, action=action, request=request)
+    return form.process_request(
+        request,
+        msg_success=COMPENSATION_ACTION_EDITED,
+        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @conservation_office_group_required
 @shared_access_required(Ema, "id")
-- 
2.45.2


From 792b4a46324aa1f73e413d193a91c26006113269 Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 11:31:13 +0100
Subject: [PATCH 27/31] #86 Edit actions compensation

* adds support for editing of CompensationAction for compensation
* adds buttons and urls
---
 .../detail/compensation/includes/actions.html |  7 +++--
 compensation/urls/compensation.py             |  8 ++++--
 compensation/views/compensation.py            | 28 +++++++++++++++++--
 3 files changed, 37 insertions(+), 6 deletions(-)

diff --git a/compensation/templates/compensation/detail/compensation/includes/actions.html b/compensation/templates/compensation/detail/compensation/includes/actions.html
index d82d8c08..f8a917ee 100644
--- a/compensation/templates/compensation/detail/compensation/includes/actions.html
+++ b/compensation/templates/compensation/detail/compensation/includes/actions.html
@@ -61,9 +61,12 @@
                         {{ action.comment }}
                     </div>
                 </td>
-                <td class="">
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:action-remove' obj.id action.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove action' %}">
+                    <button data-form-url="{% url 'compensation:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:action-remove' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove action' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/compensation/urls/compensation.py b/compensation/urls/compensation.py
index 9d644e73..3b6a6822 100644
--- a/compensation/urls/compensation.py
+++ b/compensation/urls/compensation.py
@@ -18,11 +18,15 @@ urlpatterns = [
     path('<id>/log', log_view, name='log'),
     path('<id>/edit', edit_view, name='edit'),
     path('<id>/remove', remove_view, name='remove'),
+
     path('<id>/state/new', state_new_view, name='new-state'),
-    path('<id>/action/new', action_new_view, name='new-action'),
-    path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
     path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
+    path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
+
+    path('<id>/action/new', action_new_view, name='new-action'),
+    path('<id>/action/<action_id>/edit', action_edit_view, name='action-edit'),
     path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
+
     path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
     path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
     path('<id>/report', report_view, name='report'),
diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py
index 3baf03a8..65d2ebc5 100644
--- a/compensation/views/compensation.py
+++ b/compensation/views/compensation.py
@@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _
 from compensation.forms.forms import NewCompensationForm, EditCompensationForm
 from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm, \
     NewCompensationDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, \
-    EditCompensationStateModalForm
+    EditCompensationStateModalForm, EditCompensationActionModalForm
 from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument
 from compensation.tables import CompensationTable
 from intervention.models import Intervention
@@ -21,7 +21,7 @@ from konova.utils.generators import generate_qr_code
 from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DATA_UNSHARED_EXPLANATION, \
     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
+    DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -400,6 +400,30 @@ def action_new_view(request: HttpRequest, id: str):
     )
 
 
+@login_required
+@default_group_required
+@shared_access_required(Compensation, "id")
+def action_edit_view(request: HttpRequest, id: str, action_id: str):
+    """ Renders a form for editing actions for a compensation
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The compensation's id
+        action_id (str): The action's id
+
+    Returns:
+
+    """
+    comp = get_object_or_404(Compensation, id=id)
+    action = get_object_or_404(CompensationAction, id=action_id)
+    form = EditCompensationActionModalForm(request.POST or None, instance=comp, action=action, request=request)
+    return form.process_request(
+        request,
+        msg_success=COMPENSATION_ACTION_EDITED,
+        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @default_group_required
 @shared_access_required(Compensation, "id")
-- 
2.45.2


From c88bbdabbcb19ce075baa167d61256b79a91f7f1 Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 11:45:55 +0100
Subject: [PATCH 28/31] #86 Edit view tests

* extends view tests
---
 .../compensation/includes/deadlines.html      |  2 +-
 .../compensation/includes/documents.html      |  2 +-
 .../detail/eco_account/includes/actions.html  |  9 +++--
 compensation/tests/ecoaccount/test_views.py   | 33 +++++++++++++------
 compensation/urls/eco_account.py              |  8 +++--
 compensation/views/eco_account.py             | 28 ++++++++++++++--
 ema/tests/test_views.py                       | 33 +++++++++++++------
 .../detail/includes/compensations.html        |  2 +-
 .../detail/includes/deductions.html           |  2 +-
 .../detail/includes/documents.html            |  2 +-
 .../detail/includes/payments.html             |  2 +-
 .../detail/includes/revocation.html           |  2 +-
 intervention/tests/test_views.py              | 18 ++++++++++
 13 files changed, 109 insertions(+), 34 deletions(-)

diff --git a/compensation/templates/compensation/detail/compensation/includes/deadlines.html b/compensation/templates/compensation/detail/compensation/includes/deadlines.html
index da4e21d6..0af1498a 100644
--- a/compensation/templates/compensation/detail/compensation/includes/deadlines.html
+++ b/compensation/templates/compensation/detail/compensation/includes/deadlines.html
@@ -34,7 +34,7 @@
                     {% trans 'Comment' %}
                 </th>
                 {% if is_default_member and has_access %}
-                    <th scope="col">
+                    <th class="w-10" scope="col">
                         <span class="float-right">
                             {% trans 'Action' %}
                         </span>
diff --git a/compensation/templates/compensation/detail/compensation/includes/documents.html b/compensation/templates/compensation/detail/compensation/includes/documents.html
index e12de4e9..573fd865 100644
--- a/compensation/templates/compensation/detail/compensation/includes/documents.html
+++ b/compensation/templates/compensation/detail/compensation/includes/documents.html
@@ -34,7 +34,7 @@
                     {% trans 'Comment' %}
                 </th>
                 {% if is_default_member and has_access %}
-                    <th scope="col">
+                    <th class="w-10" scope="col">
                         <span class="float-right">
                             {% trans 'Action' %}
                         </span>
diff --git a/compensation/templates/compensation/detail/eco_account/includes/actions.html b/compensation/templates/compensation/detail/eco_account/includes/actions.html
index d1275c78..add698e0 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/actions.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/actions.html
@@ -34,7 +34,7 @@
                     {% trans 'Comment' %}
                 </th>
                 {% if is_default_member and has_access %}
-                    <th scope="col">
+                    <th class="w-10" scope="col">
                         <span class="float-right">
                             {% trans 'Action' %}
                         </span>
@@ -60,9 +60,12 @@
                         {{ action.comment }}
                     </div>
                 </td>
-                <td class="">
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:acc:action-remove' obj.id action.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove action' %}">
+                    <button data-form-url="{% url 'compensation:acc:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:acc:action-remove' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove action' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/compensation/tests/ecoaccount/test_views.py b/compensation/tests/ecoaccount/test_views.py
index 670f4f0d..091e86db 100644
--- a/compensation/tests/ecoaccount/test_views.py
+++ b/compensation/tests/ecoaccount/test_views.py
@@ -44,12 +44,17 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
         self.edit_url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
         self.remove_url = reverse("compensation:acc:remove", args=(self.eco_account.id,))
         self.report_url = reverse("compensation:acc:report", args=(self.eco_account.id,))
+
         self.state_new_url = reverse("compensation:acc:new-state", args=(self.eco_account.id,))
+        self.state_edit_url = reverse("compensation:acc:state-edit", args=(self.eco_account.id, self.comp_state.id))
+        self.state_remove_url = reverse("compensation:acc:state-remove", args=(self.eco_account.id, self.comp_state.id,))
+
         self.action_new_url = reverse("compensation:acc:new-action", args=(self.eco_account.id,))
+        self.action_edit_url = reverse("compensation:acc:action-edit", args=(self.eco_account.id, self.comp_action.id))
+        self.action_remove_url = reverse("compensation:acc:action-remove", args=(self.eco_account.id, self.comp_action.id,))
+
         self.deadline_new_url = reverse("compensation:acc:new-deadline", args=(self.eco_account.id,))
         self.new_doc_url = reverse("compensation:acc:new-doc", args=(self.eco_account.id,))
-        self.state_remove_url = reverse("compensation:acc:state-remove", args=(self.eco_account.id, self.comp_state.id,))
-        self.action_remove_url = reverse("compensation:acc:action-remove", args=(self.eco_account.id, self.comp_action.id,))
 
     def test_logged_in_no_groups_shared(self):
         """ Check correct status code for all requests
@@ -78,10 +83,12 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
             self.edit_url,
             self.remove_url,
             self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
+            self.state_edit_url,
             self.state_remove_url,
+            self.action_new_url,
+            self.action_edit_url,
             self.action_remove_url,
+            self.deadline_new_url,
             self.new_doc_url,
         ]
 
@@ -115,10 +122,12 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
             self.edit_url,
             self.remove_url,
             self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
+            self.state_edit_url,
             self.state_remove_url,
+            self.action_new_url,
+            self.action_edit_url,
             self.action_remove_url,
+            self.deadline_new_url,
             self.new_doc_url,
         ]
 
@@ -149,11 +158,13 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
             self.new_id_url,
             self.edit_url,
             self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
+            self.state_edit_url,
             self.state_remove_url,
+            self.action_new_url,
+            self.action_edit_url,
             self.action_remove_url,
             self.new_doc_url,
+            self.deadline_new_url,
             self.log_url,
             self.remove_url,
         ]
@@ -184,13 +195,15 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
         fail_urls = [
             self.edit_url,
             self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
+            self.state_edit_url,
             self.state_remove_url,
+            self.action_new_url,
+            self.action_edit_url,
             self.action_remove_url,
             self.new_doc_url,
             self.log_url,
             self.remove_url,
+            self.deadline_new_url,
         ]
         self.assert_url_fail(client, fail_urls)
         self.assert_url_success(client, success_urls)
diff --git a/compensation/urls/eco_account.py b/compensation/urls/eco_account.py
index f23f2a11..ccd549dd 100644
--- a/compensation/urls/eco_account.py
+++ b/compensation/urls/eco_account.py
@@ -19,11 +19,15 @@ urlpatterns = [
     path('<id>/report', report_view, name='report'),
     path('<id>/edit', edit_view, name='edit'),
     path('<id>/remove', remove_view, name='remove'),
+
     path('<id>/state/new', state_new_view, name='new-state'),
-    path('<id>/action/new', action_new_view, name='new-action'),
-    path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
     path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
+    path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
+
+    path('<id>/action/new', action_new_view, name='new-action'),
+    path('<id>/action/<action_id>/edit', action_edit_view, name='action-edit'),
     path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
+
     path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
     path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
     path('<id>/share/<token>', share_view, name='share'),
diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py
index a0365068..37223786 100644
--- a/compensation/views/eco_account.py
+++ b/compensation/views/eco_account.py
@@ -17,7 +17,7 @@ from django.shortcuts import render, get_object_or_404, redirect
 from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm
 from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
     NewEcoAccountDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, \
-    EditCompensationStateModalForm
+    EditCompensationStateModalForm, EditCompensationActionModalForm
 from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
 from compensation.tables import EcoAccountTable
 from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm, \
@@ -35,7 +35,7 @@ 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
+    DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -491,6 +491,30 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
     )
 
 
+@login_required
+@default_group_required
+@shared_access_required(EcoAccount, "id")
+def action_edit_view(request: HttpRequest, id: str, action_id: str):
+    """ Renders a form for editing a compensation action
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The compensation's id
+        id (str): The action's id
+
+    Returns:
+
+    """
+    acc = get_object_or_404(EcoAccount, id=id)
+    action = get_object_or_404(CompensationAction, id=action_id)
+    form = EditCompensationActionModalForm(request.POST or None, instance=acc, action=action, request=request)
+    return form.process_request(
+        request,
+        msg_success=COMPENSATION_ACTION_EDITED,
+        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @default_group_required
 @shared_access_required(EcoAccount, "id")
diff --git a/ema/tests/test_views.py b/ema/tests/test_views.py
index 65f4d351..70090c7d 100644
--- a/ema/tests/test_views.py
+++ b/ema/tests/test_views.py
@@ -54,12 +54,17 @@ class EmaViewTestCase(CompensationViewTestCase):
         self.record_url = reverse("ema:record", args=(self.ema.id,))
         self.report_url = reverse("ema:report", args=(self.ema.id,))
         self.new_doc_url = reverse("ema:new-doc", args=(self.ema.id,))
+
         self.state_new_url = reverse("ema:new-state", args=(self.ema.id,))
-        self.action_new_url = reverse("ema:new-action", args=(self.ema.id,))
-        self.deadline_new_url = reverse("ema:new-deadline", args=(self.ema.id,))
+        self.state_edit_url = reverse("ema:state-edit", args=(self.ema.id, state.id))
         self.state_remove_url = reverse("ema:state-remove", args=(self.ema.id, state.id,))
+
+        self.action_new_url = reverse("ema:new-action", args=(self.ema.id,))
+        self.action_edit_url = reverse("ema:action-edit", args=(self.ema.id, action.id))
         self.action_remove_url = reverse("ema:action-remove", args=(self.ema.id, action.id,))
 
+        self.deadline_new_url = reverse("ema:new-deadline", args=(self.ema.id,))
+
     def create_dummy_data(self):
         # Create dummy data
         # Create log entry
@@ -108,10 +113,12 @@ class EmaViewTestCase(CompensationViewTestCase):
             self.new_id_url,
             self.edit_url,
             self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
             self.state_remove_url,
+            self.state_edit_url,
+            self.deadline_new_url,
+            self.action_edit_url,
             self.action_remove_url,
+            self.action_new_url,
             self.new_doc_url,
             self.log_url,
             self.remove_url,
@@ -154,10 +161,12 @@ class EmaViewTestCase(CompensationViewTestCase):
             self.new_id_url,
             self.edit_url,
             self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
+            self.state_edit_url,
             self.state_remove_url,
+            self.action_new_url,
+            self.action_edit_url,
             self.action_remove_url,
+            self.deadline_new_url,
             self.new_doc_url,
             self.log_url,
             self.remove_url,
@@ -191,9 +200,11 @@ class EmaViewTestCase(CompensationViewTestCase):
             self.new_id_url,
             self.edit_url,
             self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
+            self.state_edit_url,
             self.state_remove_url,
+            self.deadline_new_url,
+            self.action_new_url,
+            self.action_edit_url,
             self.action_remove_url,
             self.new_doc_url,
             self.log_url,
@@ -229,9 +240,11 @@ class EmaViewTestCase(CompensationViewTestCase):
         fail_urls = [
             self.edit_url,
             self.state_new_url,
-            self.action_new_url,
-            self.deadline_new_url,
+            self.state_edit_url,
             self.state_remove_url,
+            self.deadline_new_url,
+            self.action_new_url,
+            self.action_edit_url,
             self.action_remove_url,
             self.new_doc_url,
             self.log_url,
diff --git a/intervention/templates/intervention/detail/includes/compensations.html b/intervention/templates/intervention/detail/includes/compensations.html
index b7a546dd..c572a344 100644
--- a/intervention/templates/intervention/detail/includes/compensations.html
+++ b/intervention/templates/intervention/detail/includes/compensations.html
@@ -33,7 +33,7 @@
                     {% trans 'Title' %}
                 </th>
                 {% if is_default_member and has_access %}
-                    <th scope="col">
+                    <th class="w-10" scope="col">
                         <span class="float-right">
                             {% trans 'Action' %}
                         </span>
diff --git a/intervention/templates/intervention/detail/includes/deductions.html b/intervention/templates/intervention/detail/includes/deductions.html
index b11817d1..04110b8b 100644
--- a/intervention/templates/intervention/detail/includes/deductions.html
+++ b/intervention/templates/intervention/detail/includes/deductions.html
@@ -34,7 +34,7 @@
                     {% trans 'Created' %}
                 </th>
                 {% if is_default_member and has_access %}
-                    <th scope="col">
+                    <th class="w-10" scope="col">
                         <span class="float-right">
                             {% trans 'Action' %}
                         </span>
diff --git a/intervention/templates/intervention/detail/includes/documents.html b/intervention/templates/intervention/detail/includes/documents.html
index 93309f5c..9fccc4b7 100644
--- a/intervention/templates/intervention/detail/includes/documents.html
+++ b/intervention/templates/intervention/detail/includes/documents.html
@@ -34,7 +34,7 @@
                     {% trans 'Comment' %}
                 </th>
                 {% if is_default_member and has_access %}
-                    <th scope="col">
+                    <th class="w-10" scope="col">
                         <span class="float-right">
                             {% trans 'Action' %}
                         </span>
diff --git a/intervention/templates/intervention/detail/includes/payments.html b/intervention/templates/intervention/detail/includes/payments.html
index 4bf00cd8..65408e47 100644
--- a/intervention/templates/intervention/detail/includes/payments.html
+++ b/intervention/templates/intervention/detail/includes/payments.html
@@ -34,7 +34,7 @@
                     {% trans 'Comment' %}
                 </th>
                 {% if is_default_member and has_access %}
-                    <th scope="col">
+                    <th class="w-10" scope="col">
                         <span class="float-right">
                             {% trans 'Action' %}
                         </span>
diff --git a/intervention/templates/intervention/detail/includes/revocation.html b/intervention/templates/intervention/detail/includes/revocation.html
index 6eb68f99..39738d24 100644
--- a/intervention/templates/intervention/detail/includes/revocation.html
+++ b/intervention/templates/intervention/detail/includes/revocation.html
@@ -37,7 +37,7 @@
                     {% trans 'Comment' %}
                 </th>
                 {% if is_default_member and has_access %}
-                    <th scope="col">
+                    <th class="w-10" scope="col">
                         <span class="float-right">
                             {% trans 'Action' %}
                         </span>
diff --git a/intervention/tests/test_views.py b/intervention/tests/test_views.py
index 68e4b562..f12ee7a3 100644
--- a/intervention/tests/test_views.py
+++ b/intervention/tests/test_views.py
@@ -40,12 +40,14 @@ class InterventionViewTestCase(BaseViewTestCase):
         self.deduction.intervention = self.intervention
         self.deduction.save()
         self.deduction_new_url = reverse("intervention:new-deduction", args=(self.intervention.id,))
+        self.deduction_edit_url = reverse("intervention:edit-deduction", args=(self.intervention.id, self.deduction.id,))
         self.deduction_remove_url = reverse("intervention:remove-deduction", args=(self.intervention.id, self.deduction.id))
 
         self.revocation = Revocation.objects.create(
             legal=self.intervention.legal
         )
         self.revocation_new_url = reverse("intervention:new-revocation", args=(self.intervention.id,))
+        self.revocation_edit_url = reverse("intervention:edit-revocation", args=(self.intervention.id, self.revocation.id))
         self.revocation_remove_url = reverse("intervention:remove-revocation", args=(self.intervention.id, self.revocation.id))
 
     def test_views_anonymous_user(self):
@@ -76,8 +78,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.run_check_url: f"{login_redirect_base}{self.run_check_url}",
             self.record_url: f"{login_redirect_base}{self.record_url}",
             self.deduction_new_url: f"{login_redirect_base}{self.deduction_new_url}",
+            self.deduction_edit_url: f"{login_redirect_base}{self.deduction_edit_url}",
             self.deduction_remove_url: f"{login_redirect_base}{self.deduction_remove_url}",
             self.revocation_new_url: f"{login_redirect_base}{self.revocation_new_url}",
+            self.revocation_edit_url: f"{login_redirect_base}{self.revocation_edit_url}",
             self.revocation_remove_url: f"{login_redirect_base}{self.revocation_remove_url}",
         }
 
@@ -115,8 +119,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.run_check_url,
             self.record_url,
             self.revocation_new_url,
+            self.revocation_edit_url,
             self.revocation_remove_url,
             self.deduction_new_url,
+            self.deduction_edit_url,
             self.deduction_remove_url,
         ]
 
@@ -151,8 +157,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.remove_url,
             self.share_create_url,
             self.revocation_new_url,
+            self.revocation_edit_url,
             self.revocation_remove_url,
             self.deduction_new_url,
+            self.deduction_edit_url,
             self.deduction_remove_url,
         ]
         fail_urls = [
@@ -199,8 +207,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.share_create_url,
             self.log_url,
             self.revocation_new_url,
+            self.revocation_edit_url,
             self.revocation_remove_url,
             self.deduction_new_url,
+            self.deduction_edit_url,
             self.deduction_remove_url,
         ]
         success_urls_redirect = {
@@ -243,8 +253,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.share_create_url,
             self.record_url,
             self.revocation_new_url,
+            self.revocation_edit_url,
             self.revocation_remove_url,
             self.deduction_new_url,
+            self.deduction_edit_url,
             self.deduction_remove_url,
         ]
         success_urls_redirect = {
@@ -287,8 +299,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.record_url,
             self.run_check_url,
             self.revocation_new_url,
+            self.revocation_edit_url,
             self.revocation_remove_url,
             self.deduction_new_url,
+            self.deduction_edit_url,
             self.deduction_remove_url,
         ]
         success_urls_redirect = {
@@ -331,8 +345,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.share_create_url,
             self.run_check_url,
             self.revocation_new_url,
+            self.revocation_edit_url,
             self.revocation_remove_url,
             self.deduction_new_url,
+            self.deduction_edit_url,
             self.deduction_remove_url,
         ]
         success_urls_redirect = {
@@ -375,8 +391,10 @@ class InterventionViewTestCase(BaseViewTestCase):
             self.share_create_url,
             self.run_check_url,
             self.revocation_new_url,
+            self.revocation_edit_url,
             self.revocation_remove_url,
             self.deduction_new_url,
+            self.deduction_edit_url,
             self.deduction_remove_url,
         ]
         # Define urls where a redirect to a specific location is the proper response
-- 
2.45.2


From 8d396c3e2b967797670504265328be427757c979 Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 12:33:22 +0100
Subject: [PATCH 29/31] #86 Edit deadlines EMA

* adds support for editing of EMA deadlines
* adds buttons and urls
---
 compensation/forms/modalForms.py              | 25 ++++++++++++++++-
 .../ema/detail/includes/deadlines.html        |  7 +++--
 ema/tests/test_views.py                       | 19 ++++++++++++-
 ema/urls.py                                   |  4 ++-
 ema/views.py                                  | 28 +++++++++++++++++--
 5 files changed, 76 insertions(+), 7 deletions(-)

diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py
index 6436a7c8..804ebad4 100644
--- a/compensation/forms/modalForms.py
+++ b/compensation/forms/modalForms.py
@@ -21,7 +21,7 @@ from konova.contexts import BaseContext
 from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
 from konova.models import DeadlineType
 from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \
-    ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED
+    ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
 
 
 class NewPaymentForm(BaseModalForm):
@@ -373,6 +373,29 @@ class NewDeadlineModalForm(BaseModalForm):
         return deadline
 
 
+class EditDeadlineModalForm(NewDeadlineModalForm):
+    deadline = None
+
+    def __init__(self, *args, **kwargs):
+        self.deadline = kwargs.pop("deadline", None)
+        super().__init__(*args, **kwargs)
+        form_data = {
+            "type": self.deadline.type,
+            "date": str(self.deadline.date),
+            "comment": self.deadline.comment,
+        }
+        self.load_initial_data(form_data)
+
+    def save(self):
+        deadline = self.deadline
+        deadline.type = self.cleaned_data.get("type", None)
+        deadline.date = self.cleaned_data.get("date", None)
+        deadline.comment = self.cleaned_data.get("comment", None)
+        deadline.save()
+        self.instance.mark_as_edited(self.user, self.request, edit_comment=DEADLINE_EDITED)
+        return deadline
+
+
 class NewActionModalForm(BaseModalForm):
     """ Form handling action related input
 
diff --git a/ema/templates/ema/detail/includes/deadlines.html b/ema/templates/ema/detail/includes/deadlines.html
index 02efaf8b..6c7214e4 100644
--- a/ema/templates/ema/detail/includes/deadlines.html
+++ b/ema/templates/ema/detail/includes/deadlines.html
@@ -52,9 +52,12 @@
                         {{ deadline.comment }}
                     </div>
                 </td>
-                <td class="align-middle">
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'ema:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove deadline' %}">
+                    <button data-form-url="{% url 'ema:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'ema:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove deadline' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/ema/tests/test_views.py b/ema/tests/test_views.py
index 70090c7d..229f8841 100644
--- a/ema/tests/test_views.py
+++ b/ema/tests/test_views.py
@@ -13,7 +13,7 @@ from django.test.client import Client
 from compensation.tests.compensation.test_views import CompensationViewTestCase
 from ema.models import Ema
 from intervention.models import Responsibility
-from konova.models import Geometry
+from konova.models import Geometry, Deadline, DeadlineType
 from konova.settings import DEFAULT_GROUP, ETS_GROUP
 from user.models import UserActionLogEntry
 
@@ -63,7 +63,16 @@ class EmaViewTestCase(CompensationViewTestCase):
         self.action_edit_url = reverse("ema:action-edit", args=(self.ema.id, action.id))
         self.action_remove_url = reverse("ema:action-remove", args=(self.ema.id, action.id,))
 
+        self.deadline = Deadline.objects.create(
+            type=DeadlineType.FINISHED,
+            date="2020-01-01",
+            comment="TESTCOMMENT",
+        )
+        self.ema.deadlines.add(self.deadline)
+
         self.deadline_new_url = reverse("ema:new-deadline", args=(self.ema.id,))
+        self.deadline_edit_url = reverse("ema:deadline-edit", args=(self.ema.id, self.deadline.id))
+        self.deadline_remove_url = reverse("ema:deadline-remove", args=(self.ema.id, self.deadline.id))
 
     def create_dummy_data(self):
         # Create dummy data
@@ -116,6 +125,8 @@ class EmaViewTestCase(CompensationViewTestCase):
             self.state_remove_url,
             self.state_edit_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.action_edit_url,
             self.action_remove_url,
             self.action_new_url,
@@ -167,6 +178,8 @@ class EmaViewTestCase(CompensationViewTestCase):
             self.action_edit_url,
             self.action_remove_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.new_doc_url,
             self.log_url,
             self.remove_url,
@@ -203,6 +216,8 @@ class EmaViewTestCase(CompensationViewTestCase):
             self.state_edit_url,
             self.state_remove_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.action_new_url,
             self.action_edit_url,
             self.action_remove_url,
@@ -243,6 +258,8 @@ class EmaViewTestCase(CompensationViewTestCase):
             self.state_edit_url,
             self.state_remove_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.action_new_url,
             self.action_edit_url,
             self.action_remove_url,
diff --git a/ema/urls.py b/ema/urls.py
index 3e7092c6..90cafb66 100644
--- a/ema/urls.py
+++ b/ema/urls.py
@@ -28,8 +28,10 @@ urlpatterns = [
     path('<id>/action/<action_id>/edit', action_edit_view, name='action-edit'),
     path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
 
-    path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
     path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
+    path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
+    path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
+
     path('<id>/share/<token>', share_view, name='share'),
     path('<id>/share', create_share_view, name='share-create'),
 
diff --git a/ema/views.py b/ema/views.py
index 607d58df..e9d0acb6 100644
--- a/ema/views.py
+++ b/ema/views.py
@@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _
 
 from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
     RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, EditCompensationStateModalForm, \
-    EditCompensationActionModalForm
+    EditCompensationActionModalForm, EditDeadlineModalForm
 from compensation.models import CompensationAction, CompensationState
 from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentModalForm
 from ema.tables import EmaTable
@@ -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
+    COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -653,6 +653,30 @@ def create_share_view(request: HttpRequest, id: str):
     )
 
 
+@login_required
+@conservation_office_group_required
+@shared_access_required(Ema, "id")
+def deadline_edit_view(request: HttpRequest, id: str, deadline_id: str):
+    """ Renders a form for editing deadlines from a compensation
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The compensation's id
+        deadline_id (str): The deadline's id
+
+    Returns:
+
+    """
+    ema = get_object_or_404(Ema, id=id)
+    deadline = get_object_or_404(Deadline, id=deadline_id)
+    form = EditDeadlineModalForm(request.POST or None, instance=ema, deadline=deadline, request=request)
+    return form.process_request(
+        request,
+        msg_success=DEADLINE_EDITED,
+        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @conservation_office_group_required
 @shared_access_required(Ema, "id")
-- 
2.45.2


From afb0cabec49df4b65f49dfd225a31adb7c98c7cc Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 12:42:41 +0100
Subject: [PATCH 30/31] #86 Edit deadline Compensation

* adds support for editing of deadlines
* adds buttons and urls
* adds w-10 as base css-class for all action columns
---
 .../detail/compensation/includes/actions.html |  2 +-
 .../compensation/includes/deadlines.html      |  7 +++--
 .../compensation/includes/states-after.html   |  2 +-
 .../compensation/includes/states-before.html  |  2 +-
 .../eco_account/includes/deadlines.html       |  2 +-
 .../eco_account/includes/deductions.html      |  2 +-
 .../eco_account/includes/documents.html       |  2 +-
 .../eco_account/includes/states-after.html    |  2 +-
 .../eco_account/includes/states-before.html   |  2 +-
 compensation/tests/compensation/test_views.py | 20 +++++++++++++
 compensation/urls/compensation.py             |  1 +
 compensation/views/compensation.py            | 29 +++++++++++++++++--
 .../ema/detail/includes/actions.html          |  2 +-
 .../ema/detail/includes/deadlines.html        |  2 +-
 .../ema/detail/includes/documents.html        |  2 +-
 .../ema/detail/includes/states-after.html     |  2 +-
 .../ema/detail/includes/states-before.html    |  2 +-
 ema/tests/test_views.py                       |  4 +--
 18 files changed, 68 insertions(+), 19 deletions(-)

diff --git a/compensation/templates/compensation/detail/compensation/includes/actions.html b/compensation/templates/compensation/detail/compensation/includes/actions.html
index f8a917ee..33037ecf 100644
--- a/compensation/templates/compensation/detail/compensation/includes/actions.html
+++ b/compensation/templates/compensation/detail/compensation/includes/actions.html
@@ -35,7 +35,7 @@
                     {% trans 'Comment' %}
                 </th>
                 {% if is_default_member and has_access %}
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/compensation/templates/compensation/detail/compensation/includes/deadlines.html b/compensation/templates/compensation/detail/compensation/includes/deadlines.html
index 0af1498a..7f445657 100644
--- a/compensation/templates/compensation/detail/compensation/includes/deadlines.html
+++ b/compensation/templates/compensation/detail/compensation/includes/deadlines.html
@@ -54,9 +54,12 @@
                         {{ deadline.comment }}
                     </div>
                 </td>
-                <td>
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove deadline' %}">
+                    <button data-form-url="{% url 'compensation:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove deadline' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/compensation/templates/compensation/detail/compensation/includes/states-after.html b/compensation/templates/compensation/detail/compensation/includes/states-after.html
index c0d8b2d5..2c95ca1a 100644
--- a/compensation/templates/compensation/detail/compensation/includes/states-after.html
+++ b/compensation/templates/compensation/detail/compensation/includes/states-after.html
@@ -36,7 +36,7 @@
                     {% trans 'Surface' %}
                 </th>
                 {% if is_default_member and has_access %}
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/compensation/templates/compensation/detail/compensation/includes/states-before.html b/compensation/templates/compensation/detail/compensation/includes/states-before.html
index 50855430..d2ba3697 100644
--- a/compensation/templates/compensation/detail/compensation/includes/states-before.html
+++ b/compensation/templates/compensation/detail/compensation/includes/states-before.html
@@ -36,7 +36,7 @@
                     {% trans 'Surface' %}
                 </th>
                 {% if is_default_member and has_access %}
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/compensation/templates/compensation/detail/eco_account/includes/deadlines.html b/compensation/templates/compensation/detail/eco_account/includes/deadlines.html
index ff8a5291..b9d664d4 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/deadlines.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/deadlines.html
@@ -33,7 +33,7 @@
                 <th scope="col">
                     {% trans 'Comment' %}
                 </th>
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/compensation/templates/compensation/detail/eco_account/includes/deductions.html b/compensation/templates/compensation/detail/eco_account/includes/deductions.html
index 76c116b2..10f177ea 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/deductions.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/deductions.html
@@ -36,7 +36,7 @@
                 <th scope="col">
                     {% trans 'Created' %}
                 </th>
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/compensation/templates/compensation/detail/eco_account/includes/documents.html b/compensation/templates/compensation/detail/eco_account/includes/documents.html
index e852842b..67881c90 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/documents.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/documents.html
@@ -33,7 +33,7 @@
                 <th scope="col">
                     {% trans 'Comment' %}
                 </th>
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/compensation/templates/compensation/detail/eco_account/includes/states-after.html b/compensation/templates/compensation/detail/eco_account/includes/states-after.html
index ff074769..bead6f2b 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/states-after.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/states-after.html
@@ -36,7 +36,7 @@
                     {% trans 'Surface' %}
                 </th>
                 {% if is_default_member and has_access %}
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/compensation/templates/compensation/detail/eco_account/includes/states-before.html b/compensation/templates/compensation/detail/eco_account/includes/states-before.html
index 7b5fe785..c19b4049 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/states-before.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/states-before.html
@@ -36,7 +36,7 @@
                     {% trans 'Surface' %}
                 </th>
                 {% if is_default_member and has_access %}
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/compensation/tests/compensation/test_views.py b/compensation/tests/compensation/test_views.py
index 5844e264..27218f2a 100644
--- a/compensation/tests/compensation/test_views.py
+++ b/compensation/tests/compensation/test_views.py
@@ -8,6 +8,7 @@ Created on: 07.02.22
 from django.test.client import Client
 from django.urls import reverse
 
+from konova.models import Deadline, DeadlineType
 from konova.settings import DEFAULT_GROUP
 from konova.tests.test_views import BaseViewTestCase
 
@@ -31,6 +32,13 @@ class CompensationViewTestCase(BaseViewTestCase):
         action = self.create_dummy_action()
         self.compensation.actions.set([action])
 
+        self.deadline = Deadline.objects.get_or_create(
+            type=DeadlineType.FINISHED,
+            date="2020-01-01",
+            comment="TESTDEADDLINECOMMENT"
+        )[0]
+        self.compensation.deadlines.add(self.deadline)
+
         # Prepare urls
         self.index_url = reverse("compensation:index", args=())
         self.new_url = reverse("compensation:new", args=(self.intervention.id,))
@@ -43,6 +51,8 @@ class CompensationViewTestCase(BaseViewTestCase):
         self.state_new_url = reverse("compensation:new-state", args=(self.compensation.id,))
         self.action_new_url = reverse("compensation:new-action", args=(self.compensation.id,))
         self.deadline_new_url = reverse("compensation:new-deadline", args=(self.compensation.id,))
+        self.deadline_edit_url = reverse("compensation:deadline-edit", args=(self.compensation.id, self.deadline.id))
+        self.deadline_remove_url = reverse("compensation:deadline-remove", args=(self.compensation.id, self.deadline.id))
         self.new_doc_url = reverse("compensation:new-doc", args=(self.compensation.id,))
 
         self.state_remove_url = reverse("compensation:state-remove", args=(self.compensation.id, self.comp_state.id,))
@@ -72,6 +82,8 @@ class CompensationViewTestCase(BaseViewTestCase):
             self.state_new_url,
             self.action_new_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.state_remove_url,
             self.action_remove_url,
             self.new_doc_url,
@@ -109,6 +121,8 @@ class CompensationViewTestCase(BaseViewTestCase):
             self.state_new_url,
             self.action_new_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.state_remove_url,
             self.action_remove_url,
             self.new_doc_url,
@@ -147,6 +161,8 @@ class CompensationViewTestCase(BaseViewTestCase):
             self.state_new_url,
             self.action_new_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.state_remove_url,
             self.action_remove_url,
             self.new_doc_url,
@@ -181,6 +197,8 @@ class CompensationViewTestCase(BaseViewTestCase):
             self.state_new_url,
             self.action_new_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.state_remove_url,
             self.action_remove_url,
             self.new_doc_url,
@@ -217,6 +235,8 @@ class CompensationViewTestCase(BaseViewTestCase):
             self.state_new_url,
             self.action_new_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.state_remove_url,
             self.action_remove_url,
             self.new_doc_url,
diff --git a/compensation/urls/compensation.py b/compensation/urls/compensation.py
index 3b6a6822..e1a41ff2 100644
--- a/compensation/urls/compensation.py
+++ b/compensation/urls/compensation.py
@@ -28,6 +28,7 @@ urlpatterns = [
     path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
 
     path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
+    path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
     path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
     path('<id>/report', report_view, name='report'),
 
diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py
index 65d2ebc5..6dcf442b 100644
--- a/compensation/views/compensation.py
+++ b/compensation/views/compensation.py
@@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _
 from compensation.forms.forms import NewCompensationForm, EditCompensationForm
 from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm, \
     NewCompensationDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, \
-    EditCompensationStateModalForm, EditCompensationActionModalForm
+    EditCompensationStateModalForm, EditCompensationActionModalForm, EditDeadlineModalForm
 from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument
 from compensation.tables import CompensationTable
 from intervention.models import Intervention
@@ -21,7 +21,8 @@ from konova.utils.generators import generate_qr_code
 from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DATA_UNSHARED_EXPLANATION, \
     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_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, \
+    DEADLINE_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -446,6 +447,30 @@ def deadline_new_view(request: HttpRequest, id: str):
     )
 
 
+@login_required
+@default_group_required
+@shared_access_required(Compensation, "id")
+def deadline_edit_view(request: HttpRequest, id: str, deadline_id: str):
+    """ Renders a form for editing deadlines from a compensation
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The compensation's id
+        deadline_id (str): The deadline's id
+
+    Returns:
+
+    """
+    comp = get_object_or_404(Compensation, id=id)
+    deadline = get_object_or_404(Deadline, id=deadline_id)
+    form = EditDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
+    return form.process_request(
+        request,
+        msg_success=DEADLINE_EDITED,
+        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @default_group_required
 @shared_access_required(Compensation, "id")
diff --git a/ema/templates/ema/detail/includes/actions.html b/ema/templates/ema/detail/includes/actions.html
index 30e5e527..02772b36 100644
--- a/ema/templates/ema/detail/includes/actions.html
+++ b/ema/templates/ema/detail/includes/actions.html
@@ -33,7 +33,7 @@
                 <th scope="col">
                     {% trans 'Comment' %}
                 </th>
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/ema/templates/ema/detail/includes/deadlines.html b/ema/templates/ema/detail/includes/deadlines.html
index 6c7214e4..761ce067 100644
--- a/ema/templates/ema/detail/includes/deadlines.html
+++ b/ema/templates/ema/detail/includes/deadlines.html
@@ -33,7 +33,7 @@
                 <th scope="col">
                     {% trans 'Comment' %}
                 </th>
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/ema/templates/ema/detail/includes/documents.html b/ema/templates/ema/detail/includes/documents.html
index 7be632d3..b38d5499 100644
--- a/ema/templates/ema/detail/includes/documents.html
+++ b/ema/templates/ema/detail/includes/documents.html
@@ -33,7 +33,7 @@
                 <th scope="col">
                     {% trans 'Comment' %}
                 </th>
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/ema/templates/ema/detail/includes/states-after.html b/ema/templates/ema/detail/includes/states-after.html
index e876a166..e09f4ba5 100644
--- a/ema/templates/ema/detail/includes/states-after.html
+++ b/ema/templates/ema/detail/includes/states-after.html
@@ -35,7 +35,7 @@
                 <th scope="col">
                     {% trans 'Surface' %}
                 </th>
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/ema/templates/ema/detail/includes/states-before.html b/ema/templates/ema/detail/includes/states-before.html
index aec3f328..1369829b 100644
--- a/ema/templates/ema/detail/includes/states-before.html
+++ b/ema/templates/ema/detail/includes/states-before.html
@@ -35,7 +35,7 @@
                 <th scope="col">
                     {% trans 'Surface' %}
                 </th>
-                <th scope="col">
+                <th class="w-10" scope="col">
                     <span class="float-right">
                         {% trans 'Action' %}
                     </span>
diff --git a/ema/tests/test_views.py b/ema/tests/test_views.py
index 229f8841..9654e8f3 100644
--- a/ema/tests/test_views.py
+++ b/ema/tests/test_views.py
@@ -63,11 +63,11 @@ class EmaViewTestCase(CompensationViewTestCase):
         self.action_edit_url = reverse("ema:action-edit", args=(self.ema.id, action.id))
         self.action_remove_url = reverse("ema:action-remove", args=(self.ema.id, action.id,))
 
-        self.deadline = Deadline.objects.create(
+        self.deadline = Deadline.objects.get_or_create(
             type=DeadlineType.FINISHED,
             date="2020-01-01",
             comment="TESTCOMMENT",
-        )
+        )[0]
         self.ema.deadlines.add(self.deadline)
 
         self.deadline_new_url = reverse("ema:new-deadline", args=(self.ema.id,))
-- 
2.45.2


From 6fbdc3c1fb5ff340e4ed2f4fc58c5ea55b5edcbd Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 10 Feb 2022 12:49:30 +0100
Subject: [PATCH 31/31] #86 Edit deadlines EcoAccount

* adds support for editing of deadlines in EcoAccount
* adds buttons and urls
---
 .../eco_account/includes/deadlines.html       |  7 +++--
 compensation/tests/ecoaccount/test_views.py   | 19 +++++++++++++
 compensation/urls/eco_account.py              |  4 ++-
 compensation/views/eco_account.py             | 28 +++++++++++++++++--
 4 files changed, 53 insertions(+), 5 deletions(-)

diff --git a/compensation/templates/compensation/detail/eco_account/includes/deadlines.html b/compensation/templates/compensation/detail/eco_account/includes/deadlines.html
index b9d664d4..beaecfda 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/deadlines.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/deadlines.html
@@ -52,9 +52,12 @@
                         {{ deadline.comment }}
                     </div>
                 </td>
-                <td class="align-middle">
+                <td class="align-middle float-right">
                     {% if is_default_member and has_access  %}
-                    <button data-form-url="{% url 'compensation:acc:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove deadline' %}">
+                    <button data-form-url="{% url 'compensation:acc:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
+                        {% fa5_icon 'edit' %}
+                    </button>
+                    <button data-form-url="{% url 'compensation:acc:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove deadline' %}">
                         {% fa5_icon 'trash' %}
                     </button>
                     {% endif %}
diff --git a/compensation/tests/ecoaccount/test_views.py b/compensation/tests/ecoaccount/test_views.py
index 091e86db..617f7437 100644
--- a/compensation/tests/ecoaccount/test_views.py
+++ b/compensation/tests/ecoaccount/test_views.py
@@ -9,6 +9,7 @@ from django.urls import reverse
 from django.test import Client
 
 from compensation.tests.compensation.test_views import CompensationViewTestCase
+from konova.models import DeadlineType, Deadline
 from konova.settings import DEFAULT_GROUP
 
 
@@ -53,7 +54,17 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
         self.action_edit_url = reverse("compensation:acc:action-edit", args=(self.eco_account.id, self.comp_action.id))
         self.action_remove_url = reverse("compensation:acc:action-remove", args=(self.eco_account.id, self.comp_action.id,))
 
+        self.deadline = Deadline.objects.get_or_create(
+            type=DeadlineType.FINISHED,
+            date="2020-01-01",
+            comment="DEADLINE COMMENT"
+        )[0]
+        self.eco_account.deadlines.add(self.deadline)
+
         self.deadline_new_url = reverse("compensation:acc:new-deadline", args=(self.eco_account.id,))
+        self.deadline_edit_url = reverse("compensation:acc:deadline-edit", args=(self.eco_account.id, self.deadline.id))
+        self.deadline_remove_url = reverse("compensation:acc:deadline-remove", args=(self.eco_account.id, self.deadline.id))
+
         self.new_doc_url = reverse("compensation:acc:new-doc", args=(self.eco_account.id,))
 
     def test_logged_in_no_groups_shared(self):
@@ -89,6 +100,8 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
             self.action_edit_url,
             self.action_remove_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.new_doc_url,
         ]
 
@@ -128,6 +141,8 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
             self.action_edit_url,
             self.action_remove_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.new_doc_url,
         ]
 
@@ -165,6 +180,8 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
             self.action_remove_url,
             self.new_doc_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
             self.log_url,
             self.remove_url,
         ]
@@ -204,6 +221,8 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
             self.log_url,
             self.remove_url,
             self.deadline_new_url,
+            self.deadline_edit_url,
+            self.deadline_remove_url,
         ]
         self.assert_url_fail(client, fail_urls)
         self.assert_url_success(client, success_urls)
diff --git a/compensation/urls/eco_account.py b/compensation/urls/eco_account.py
index ccd549dd..a3d1aa38 100644
--- a/compensation/urls/eco_account.py
+++ b/compensation/urls/eco_account.py
@@ -28,8 +28,10 @@ urlpatterns = [
     path('<id>/action/<action_id>/edit', action_edit_view, name='action-edit'),
     path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
 
-    path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
     path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
+    path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
+    path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
+
     path('<id>/share/<token>', share_view, name='share'),
     path('<id>/share', create_share_view, name='share-create'),
 
diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py
index 37223786..cf5f3288 100644
--- a/compensation/views/eco_account.py
+++ b/compensation/views/eco_account.py
@@ -17,7 +17,7 @@ from django.shortcuts import render, get_object_or_404, redirect
 from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm
 from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
     NewEcoAccountDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, \
-    EditCompensationStateModalForm, EditCompensationActionModalForm
+    EditCompensationStateModalForm, EditCompensationActionModalForm, EditDeadlineModalForm
 from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
 from compensation.tables import EcoAccountTable
 from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm, \
@@ -35,7 +35,7 @@ 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
+    DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
 from konova.utils.user_checks import in_group
 
 
@@ -515,6 +515,30 @@ def action_edit_view(request: HttpRequest, id: str, action_id: str):
     )
 
 
+@login_required
+@default_group_required
+@shared_access_required(EcoAccount, "id")
+def deadline_edit_view(request: HttpRequest, id: str, deadline_id: str):
+    """ Renders a form for editing deadlines from a compensation
+
+    Args:
+        request (HttpRequest): The incoming request
+        id (str): The compensation's id
+        deadline_id (str): The deadline's id
+
+    Returns:
+
+    """
+    comp = get_object_or_404(EcoAccount, id=id)
+    deadline = get_object_or_404(Deadline, id=deadline_id)
+    form = EditDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
+    return form.process_request(
+        request,
+        msg_success=DEADLINE_EDITED,
+        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
+    )
+
+
 @login_required
 @default_group_required
 @shared_access_required(EcoAccount, "id")
-- 
2.45.2