diff --git a/analysis/forms.py b/analysis/forms.py
index 68e217af..c9a7995a 100644
--- a/analysis/forms.py
+++ b/analysis/forms.py
@@ -55,7 +55,7 @@ class TimespanReportForm(BaseForm):
code_lists__in=[CODELIST_CONSERVATION_OFFICE_ID],
),
widget=autocomplete.ModelSelect2(
- url="codes-conservation-office-autocomplete",
+ url="codelist:conservation-office-autocomplete",
attrs={
"data-placeholder": _("Click for selection")
}
diff --git a/codelist/autocomplete/__init__.py b/codelist/autocomplete/__init__.py
new file mode 100644
index 00000000..ca978536
--- /dev/null
+++ b/codelist/autocomplete/__init__.py
@@ -0,0 +1,7 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
diff --git a/codelist/autocomplete/base.py b/codelist/autocomplete/base.py
new file mode 100644
index 00000000..7e7a8e4a
--- /dev/null
+++ b/codelist/autocomplete/base.py
@@ -0,0 +1,74 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from dal_select2.views import Select2GroupQuerySetView
+from django.db.models import Q
+
+from codelist.models import KonovaCode
+
+
+class KonovaCodeAutocomplete(Select2GroupQuerySetView):
+ """
+ Provides simple autocomplete functionality for codes
+
+ Parameter support:
+ * q: Search for a word inside long_name of a code
+ * c: Search inside a special codelist
+
+ """
+ paginate_by = 50
+
+ def order_by(self, qs):
+ """ Orders by a predefined value
+
+ Wrapped in a function to provide inheritance-based different orders
+
+ Args:
+ qs (QuerySet): The queryset to be ordered
+
+ Returns:
+ qs (QuerySet): The ordered queryset
+ """
+ return qs.order_by(
+ "long_name"
+ )
+
+ def get_queryset(self):
+ if self.request.user.is_anonymous:
+ return KonovaCode.objects.none()
+ qs = KonovaCode.objects.filter(
+ is_archived=False,
+ is_selectable=True,
+ is_leaf=True,
+ )
+ qs = self.order_by(qs)
+ if self.c:
+ qs = qs.filter(
+ code_lists__in=[self.c]
+ )
+ if self.q:
+ # Remove whitespaces from self.q and split input in all keywords (if multiple given)
+ q = dict.fromkeys(self.q.strip().split(" "))
+ # Create one filter looking up for all keys where all keywords can be found in the same result
+ _filter = Q()
+ for keyword in q:
+ q_or = Q()
+ q_or |= Q(long_name__icontains=keyword)
+ q_or |= Q(short_name__icontains=keyword)
+ q_or |= Q(parent__long_name__icontains=keyword)
+ q_or |= Q(parent__short_name__icontains=keyword)
+ q_or |= Q(parent__parent__long_name__icontains=keyword)
+ q_or |= Q(parent__parent__short_name__icontains=keyword)
+ _filter.add(q_or, Q.AND)
+ qs = qs.filter(_filter).distinct()
+ return qs
+
+ def get_result_label(self, result):
+ return f"{result.long_name}"
+
+ def get_selected_result_label(self, result):
+ return f"{result.__str__()}"
diff --git a/codelist/autocomplete/biotope.py b/codelist/autocomplete/biotope.py
new file mode 100644
index 00000000..5e56b7f3
--- /dev/null
+++ b/codelist/autocomplete/biotope.py
@@ -0,0 +1,110 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+import collections
+
+from django.core.exceptions import ImproperlyConfigured
+
+from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID
+from codelist.autocomplete.base import KonovaCodeAutocomplete
+from konova.utils.message_templates import UNGROUPED
+
+
+class BiotopeCodeAutocomplete(KonovaCodeAutocomplete):
+ """
+ Due to limitations of the django dal package, we need to subclass for each code list
+ """
+ group_by_related = "parent"
+ related_field_name = "long_name"
+
+ def __init__(self, *args, **kwargs):
+ self.c = CODELIST_BIOTOPES_ID
+ super().__init__(*args, **kwargs)
+
+ def order_by(self, qs):
+ """ Orders by a predefined value
+
+ Wrapped in a function to provide inheritance-based different orders
+
+ Args:
+ qs (QuerySet): The queryset to be ordered
+
+ Returns:
+ qs (QuerySet): The ordered queryset
+ """
+ return qs.order_by(
+ "short_name",
+ )
+
+ def get_result_label(self, result):
+ return f"{result.long_name} ({result.short_name})"
+
+ def get_results(self, context):
+ """Return the options grouped by a common related model.
+
+ Raises ImproperlyConfigured if self.group_by_name is not configured
+ """
+ if not self.group_by_related:
+ raise ImproperlyConfigured("Missing group_by_related.")
+
+ super_groups = collections.OrderedDict()
+
+ object_list = context['object_list']
+
+ for result in object_list:
+ group = result.parent if result.parent else None
+ group_name = f"{group.long_name} ({group.short_name})" if group else UNGROUPED
+ super_group = result.parent.parent if result.parent else None
+ super_group_name = f"{super_group.long_name} ({super_group.short_name})" if super_group else UNGROUPED
+ super_groups.setdefault(super_group_name, {})
+ super_groups[super_group_name].setdefault(group_name, [])
+ super_groups[super_group_name][group_name].append(result)
+
+ return [{
+ 'id': None,
+ 'text': super_group,
+ 'children': [{
+ "id": None,
+ "text": group,
+ "children": [{
+ 'id': self.get_result_value(result),
+ 'text': self.get_result_label(result),
+ 'selected_text': self.get_selected_result_label(result),
+ } for result in results]
+ } for group, results in groups.items()]
+ } for super_group, groups in super_groups.items()]
+
+
+class BiotopeExtraCodeAutocomplete(KonovaCodeAutocomplete):
+ """
+ Due to limitations of the django dal package, we need to subclass for each code list
+ """
+ group_by_related = "parent"
+ related_field_name = "long_name"
+ paginate_by = 200
+
+ def __init__(self, *args, **kwargs):
+ self.c = CODELIST_BIOTOPES_EXTRA_CODES_ID
+ super().__init__(*args, **kwargs)
+
+ def order_by(self, qs):
+ """ Orders by a predefined value
+
+ Wrapped in a function to provide inheritance-based different orders
+
+ Args:
+ qs (QuerySet): The queryset to be ordered
+
+ Returns:
+ qs (QuerySet): The ordered queryset
+ """
+ return qs.order_by(
+ "long_name",
+ )
+
+ def get_result_label(self, result):
+ return f"{result.long_name} ({result.short_name})"
diff --git a/codelist/autocomplete/compensation_action.py b/codelist/autocomplete/compensation_action.py
new file mode 100644
index 00000000..47a652cd
--- /dev/null
+++ b/codelist/autocomplete/compensation_action.py
@@ -0,0 +1,45 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID
+from codelist.autocomplete.base import KonovaCodeAutocomplete
+
+
+class CompensationActionCodeAutocomplete(KonovaCodeAutocomplete):
+ """
+ Due to limitations of the django dal package, we need to subclass for each code list
+ """
+ group_by_related = "parent"
+ related_field_name = "long_name"
+
+ def __init__(self, *args, **kwargs):
+ self.c = CODELIST_COMPENSATION_ACTION_ID
+ super().__init__(*args, **kwargs)
+
+ def order_by(self, qs):
+ return qs.order_by(
+ "parent__long_name"
+ )
+
+
+class CompensationActionDetailCodeAutocomplete(KonovaCodeAutocomplete):
+ """
+ Due to limitations of the django dal package, we need to subclass for each code list
+ """
+ group_by_related = "parent"
+ related_field_name = "long_name"
+ paginate_by = 200
+
+ def __init__(self, *args, **kwargs):
+ self.c = CODELIST_COMPENSATION_ACTION_DETAIL_ID
+ super().__init__(*args, **kwargs)
+
+ def order_by(self, qs):
+ return qs.order_by(
+ "long_name"
+ )
+
diff --git a/codelist/autocomplete/handler.py b/codelist/autocomplete/handler.py
new file mode 100644
index 00000000..480d3315
--- /dev/null
+++ b/codelist/autocomplete/handler.py
@@ -0,0 +1,24 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from codelist.settings import CODELIST_HANDLER_ID
+from codelist.autocomplete.base import KonovaCodeAutocomplete
+
+
+class HandlerCodeAutocomplete(KonovaCodeAutocomplete):
+ """
+ Due to limitations of the django dal package, we need to subclass for each code list
+ """
+ group_by_related = "parent"
+ related_field_name = "long_name"
+
+ def __init__(self, *args, **kwargs):
+ self.c = CODELIST_HANDLER_ID
+ super().__init__(*args, **kwargs)
+
+ def get_result_label(self, result):
+ return result.long_name
diff --git a/codelist/autocomplete/law.py b/codelist/autocomplete/law.py
new file mode 100644
index 00000000..cf3f4b54
--- /dev/null
+++ b/codelist/autocomplete/law.py
@@ -0,0 +1,24 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from codelist.settings import CODELIST_LAW_ID
+from codelist.autocomplete.base import KonovaCodeAutocomplete
+
+
+class LawCodeAutocomplete(KonovaCodeAutocomplete):
+ """
+ Due to limitations of the django dal package, we need to subclass for each code list
+ """
+ group_by_related = "parent"
+ related_field_name = "long_name"
+
+ def __init__(self, *args, **kwargs):
+ self.c = CODELIST_LAW_ID
+ super().__init__(*args, **kwargs)
+
+ def get_result_label(self, result):
+ return f"{result.long_name} ({result.short_name})"
diff --git a/codelist/autocomplete/office.py b/codelist/autocomplete/office.py
new file mode 100644
index 00000000..46ddbea0
--- /dev/null
+++ b/codelist/autocomplete/office.py
@@ -0,0 +1,41 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID
+from codelist.autocomplete.base import KonovaCodeAutocomplete
+
+
+class RegistrationOfficeCodeAutocomplete(KonovaCodeAutocomplete):
+ """
+ Due to limitations of the django dal package, we need to subclass for each code list
+ """
+ group_by_related = "parent"
+ related_field_name = "long_name"
+
+ def __init__(self, *args, **kwargs):
+ self.c = CODELIST_REGISTRATION_OFFICE_ID
+ super().__init__(*args, **kwargs)
+
+ def order_by(self, qs):
+ return qs.order_by(
+ "parent__long_name"
+ )
+
+
+class ConservationOfficeCodeAutocomplete(KonovaCodeAutocomplete):
+ """
+ Due to limitations of the django dal package, we need to subclass for each code list
+ """
+ group_by_related = "parent"
+ related_field_name = "long_name"
+
+ def __init__(self, *args, **kwargs):
+ self.c = CODELIST_CONSERVATION_OFFICE_ID
+ super().__init__(*args, **kwargs)
+
+ def get_result_label(self, result):
+ return f"{result.long_name} ({result.short_name})"
diff --git a/codelist/autocomplete/process_type.py b/codelist/autocomplete/process_type.py
new file mode 100644
index 00000000..3f487e41
--- /dev/null
+++ b/codelist/autocomplete/process_type.py
@@ -0,0 +1,21 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from codelist.autocomplete.base import KonovaCodeAutocomplete
+from codelist.settings import CODELIST_PROCESS_TYPE_ID
+
+
+class ProcessTypeCodeAutocomplete(KonovaCodeAutocomplete):
+ """
+ Due to limitations of the django dal package, we need to subclass for each code list
+ """
+ group_by_related = "parent"
+ related_field_name = "long_name"
+
+ def __init__(self, *args, **kwargs):
+ self.c = CODELIST_PROCESS_TYPE_ID
+ super().__init__(*args, **kwargs)
diff --git a/codelist/models.py b/codelist/models.py
index 8e5de0c9..5d06172e 100644
--- a/codelist/models.py
+++ b/codelist/models.py
@@ -50,7 +50,7 @@ class KonovaCode(models.Model):
def __str__(self, with_parent: bool = True):
ret_val = ""
- if self.parent and with_parent:
+ if self.parent and self.parent.long_name and with_parent:
ret_val += self.parent.long_name + " > "
ret_val += self.long_name
if self.short_name and self.short_name != self.long_name:
diff --git a/codelist/urls.py b/codelist/urls.py
index 7d2dbba2..bd230f91 100644
--- a/codelist/urls.py
+++ b/codelist/urls.py
@@ -7,8 +7,24 @@ Created on: 23.08.21
"""
from django.urls import path
+from codelist.autocomplete.biotope import BiotopeCodeAutocomplete, BiotopeExtraCodeAutocomplete
+from codelist.autocomplete.compensation_action import CompensationActionDetailCodeAutocomplete, \
+ CompensationActionCodeAutocomplete
+from codelist.autocomplete.handler import HandlerCodeAutocomplete
+from codelist.autocomplete.law import LawCodeAutocomplete
+from codelist.autocomplete.office import ConservationOfficeCodeAutocomplete, RegistrationOfficeCodeAutocomplete
+from codelist.autocomplete.process_type import ProcessTypeCodeAutocomplete
app_name = "codelist"
urlpatterns = [
-
+ path("atcmplt/codes/biotope", BiotopeCodeAutocomplete.as_view(), name="biotope-autocomplete"),
+ path("atcmplt/codes/biotope/extra", BiotopeExtraCodeAutocomplete.as_view(),
+ name="biotope-extra-type-autocomplete"),
+ path("atcmplt/codes/law", LawCodeAutocomplete.as_view(), name="law-autocomplete"),
+ path("atcmplt/codes/reg-off", RegistrationOfficeCodeAutocomplete.as_view(), name="registration-office-autocomplete"),
+ path("atcmplt/codes/cons-off", ConservationOfficeCodeAutocomplete.as_view(), name="conservation-office-autocomplete"),
+ path("atcmplt/codes/handler", HandlerCodeAutocomplete.as_view(), name="handler-autocomplete"),
+ path("atcmplt/codes/comp/action", CompensationActionCodeAutocomplete.as_view(), name="compensation-action-autocomplete"),
+ path("atcmplt/codes/comp/action/detail", CompensationActionDetailCodeAutocomplete.as_view(), name="compensation-action-detail-autocomplete"),
+ path("atcmplt/codes/prc-type", ProcessTypeCodeAutocomplete.as_view(), name="process-type-autocomplete"),
]
\ No newline at end of file
diff --git a/compensation/autocomplete/__init__.py b/compensation/autocomplete/__init__.py
new file mode 100644
index 00000000..ca978536
--- /dev/null
+++ b/compensation/autocomplete/__init__.py
@@ -0,0 +1,7 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
diff --git a/compensation/autocomplete/eco_account.py b/compensation/autocomplete/eco_account.py
new file mode 100644
index 00000000..d1ea90f4
--- /dev/null
+++ b/compensation/autocomplete/eco_account.py
@@ -0,0 +1,34 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from dal_select2.views import Select2QuerySetView
+from django.db.models import Q
+
+from compensation.models import EcoAccount
+
+
+class EcoAccountAutocomplete(Select2QuerySetView):
+ """ Autocomplete for ecoAccount entries
+
+ Only returns entries that are already recorded and not deleted
+
+ """
+ def get_queryset(self):
+ if self.request.user.is_anonymous:
+ return EcoAccount.objects.none()
+ qs = EcoAccount.objects.filter(
+ deleted=None,
+ recorded__isnull=False,
+ ).order_by(
+ "identifier"
+ )
+ if self.q:
+ qs = qs.filter(
+ Q(identifier__icontains=self.q) |
+ Q(title__icontains=self.q)
+ ).distinct()
+ return qs
diff --git a/compensation/filters/__init__.py b/compensation/filters/__init__.py
new file mode 100644
index 00000000..ca978536
--- /dev/null
+++ b/compensation/filters/__init__.py
@@ -0,0 +1,7 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
diff --git a/compensation/filters.py b/compensation/filters/compensation.py
similarity index 59%
rename from compensation/filters.py
rename to compensation/filters/compensation.py
index c9bf9b70..aa1c967b 100644
--- a/compensation/filters.py
+++ b/compensation/filters/compensation.py
@@ -1,17 +1,14 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
-Contact: michel.peltriaux@sgdnord.rlp.de
-Created on: 29.07.21
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
"""
-import django_filters
-from django.utils.translation import gettext_lazy as _
-from django import forms
from django.db.models import QuerySet, Q
-from konova.filters.mixins import ConservationOfficeTableFilterMixin
-from konova.filters.table_filters import QueryTableFilter, CheckboxTableFilter, SelectionTableFilter, AbstractTableFilter
+from konova.filters.table_filters import AbstractTableFilter, CheckboxTableFilter, QueryTableFilter, \
+ SelectionTableFilter
class SelectionCompensationTableFilter(SelectionTableFilter):
@@ -114,71 +111,3 @@ class CompensationTableFilter(AbstractTableFilter):
)
# Overwrite final queryset as well
self.qs = self.checkbox_filter.qs
-
-
-class CheckboxEcoAccountTableFilter(CheckboxTableFilter):
- sr = django_filters.BooleanFilter(
- method='filter_only_show_unrecorded',
- label=_("Show only unrecorded"),
- label_suffix=_(""),
- widget=forms.CheckboxInput(
- attrs={
- "class": "form-check-input",
- }
- )
- )
-
- def filter_only_show_unrecorded(self, queryset, name, value) -> QuerySet:
- """ Filters queryset depending on value of 'show_recorded' setting
-
- Args:
- queryset ():
- name ():
- value ():
-
- Returns:
-
- """
- if value:
- return queryset.filter(
- recorded=None,
- )
- else:
- return queryset
-
-
-class SelectionEcoAccountTableFilter(ConservationOfficeTableFilterMixin):
- """ Special selection table filter for eco accounts
-
- EcoAccounts only need a selection filter for conservation office
-
- """
- pass
-
-
-class EcoAccountTableFilter(AbstractTableFilter):
- """ TableFilter for eco accounts
-
- """
- def __init__(self, user=None, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.user = user
- qs = kwargs.get("queryset", None)
- request_data = kwargs.get("data", None)
-
- # Pipe the queryset through all needed filters
- self.selection_filter = SelectionEcoAccountTableFilter(
- data=request_data,
- queryset=qs,
- )
- self.query_filter = QueryTableFilter(
- data=request_data,
- queryset=self.selection_filter.qs,
- )
- self.checkbox_filter = CheckboxEcoAccountTableFilter(
- user=user,
- data=request_data,
- queryset=self.query_filter.qs,
- )
- # Overwrite the final queryset result
- self.qs = self.checkbox_filter.qs
diff --git a/compensation/filters/eco_account.py b/compensation/filters/eco_account.py
new file mode 100644
index 00000000..b71e38d8
--- /dev/null
+++ b/compensation/filters/eco_account.py
@@ -0,0 +1,82 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from django import forms
+from django.db.models import QuerySet
+from django.utils.translation import gettext_lazy as _
+import django_filters
+
+from konova.filters.mixins.office import ConservationOfficeTableFilterMixin
+from konova.filters.table_filters import AbstractTableFilter, CheckboxTableFilter, QueryTableFilter
+
+
+class CheckboxEcoAccountTableFilter(CheckboxTableFilter):
+ sr = django_filters.BooleanFilter(
+ method='filter_only_show_unrecorded',
+ label=_("Show only unrecorded"),
+ label_suffix=_(""),
+ widget=forms.CheckboxInput(
+ attrs={
+ "class": "form-check-input",
+ }
+ )
+ )
+
+ def filter_only_show_unrecorded(self, queryset, name, value) -> QuerySet:
+ """ Filters queryset depending on value of 'show_recorded' setting
+
+ Args:
+ queryset ():
+ name ():
+ value ():
+
+ Returns:
+
+ """
+ if value:
+ return queryset.filter(
+ recorded=None,
+ )
+ else:
+ return queryset
+
+
+class SelectionEcoAccountTableFilter(ConservationOfficeTableFilterMixin):
+ """ Special selection table filter for eco accounts
+
+ EcoAccounts only need a selection filter for conservation office
+
+ """
+ pass
+
+
+class EcoAccountTableFilter(AbstractTableFilter):
+ """ TableFilter for eco accounts
+
+ """
+ def __init__(self, user=None, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.user = user
+ qs = kwargs.get("queryset", None)
+ request_data = kwargs.get("data", None)
+
+ # Pipe the queryset through all needed filters
+ self.selection_filter = SelectionEcoAccountTableFilter(
+ data=request_data,
+ queryset=qs,
+ )
+ self.query_filter = QueryTableFilter(
+ data=request_data,
+ queryset=self.selection_filter.qs,
+ )
+ self.checkbox_filter = CheckboxEcoAccountTableFilter(
+ user=user,
+ data=request_data,
+ queryset=self.query_filter.qs,
+ )
+ # Overwrite the final queryset result
+ self.qs = self.checkbox_filter.qs
diff --git a/compensation/forms/__init__.py b/compensation/forms/__init__.py
new file mode 100644
index 00000000..ca978536
--- /dev/null
+++ b/compensation/forms/__init__.py
@@ -0,0 +1,7 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
diff --git a/compensation/forms/compensation.py b/compensation/forms/compensation.py
new file mode 100644
index 00000000..c5186793
--- /dev/null
+++ b/compensation/forms/compensation.py
@@ -0,0 +1,238 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from dal import autocomplete
+from django import forms
+from django.db import transaction
+from django.urls import reverse, reverse_lazy
+from django.utils.translation import gettext_lazy as _
+
+from compensation.forms.mixins import CEFCompensationFormMixin, CoherenceCompensationFormMixin, PikCompensationFormMixin
+from compensation.models import Compensation
+from intervention.inputs import GenerateInput
+from intervention.models import Intervention
+from konova.forms import BaseForm, SimpleGeomForm
+from konova.utils.message_templates import COMPENSATION_ADDED_TEMPLATE, EDITED_GENERAL_DATA
+from user.models import UserActionLogEntry, User
+
+
+class AbstractCompensationForm(BaseForm):
+ """ Abstract form for compensations
+
+ Holds all important form fields, which are used in compensation and eco account forms
+
+ """
+ identifier = forms.CharField(
+ label=_("Identifier"),
+ label_suffix="",
+ max_length=255,
+ help_text=_("Generated automatically"),
+ widget=GenerateInput(
+ attrs={
+ "class": "form-control",
+ "url": None, # Needs to be set in inheriting constructors
+ }
+ )
+ )
+ title = forms.CharField(
+ label=_("Title"),
+ label_suffix="",
+ help_text=_("An explanatory name"),
+ max_length=255,
+ widget=forms.TextInput(
+ attrs={
+ "placeholder": _("Compensation XY; Location ABC"),
+ "class": "form-control",
+ }
+ )
+ )
+ comment = forms.CharField(
+ label_suffix="",
+ label=_("Comment"),
+ required=False,
+ help_text=_("Additional comment"),
+ widget=forms.Textarea(
+ attrs={
+ "rows": 5,
+ "class": "form-control"
+ }
+ )
+ )
+
+ class Meta:
+ abstract = True
+
+
+class NewCompensationForm(AbstractCompensationForm,
+ CEFCompensationFormMixin,
+ CoherenceCompensationFormMixin,
+ PikCompensationFormMixin):
+ """ Form for creating new compensations.
+
+ Can be initialized with an intervention id for preselecting the related intervention.
+ form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
+ ...
+ The intervention id will not be resolved into the intervention ORM object but instead will be used to initialize
+ the related form field.
+
+ """
+ intervention = forms.ModelChoiceField(
+ label=_("compensates intervention"),
+ label_suffix="",
+ help_text=_("Select the intervention for which this compensation compensates"),
+ queryset=Intervention.objects.filter(
+ deleted=None,
+ ),
+ widget=autocomplete.ModelSelect2(
+ url="intervention:autocomplete",
+ attrs={
+ "data-placeholder": _("Click for selection"),
+ "data-minimum-input-length": 3,
+ }
+ ),
+ )
+
+ # Define a field order for a nicer layout instead of running with the inheritance result
+ field_order = [
+ "identifier",
+ "title",
+ "intervention",
+ "is_pik",
+ "is_cef",
+ "is_coherence_keeping",
+ "comment",
+ ]
+
+ def __init__(self, *args, **kwargs):
+ intervention_id = kwargs.pop("intervention_id", None)
+ super().__init__(*args, **kwargs)
+ self.form_title = _("New compensation")
+
+ # If the compensation shall directly be initialized from an intervention, we need to fill in the intervention id
+ # and disable the form field.
+ # Furthermore the action_url needs to be set accordingly.
+ if intervention_id is not None:
+ self.initialize_form_field("intervention", intervention_id)
+ self.disable_form_field("intervention")
+ self.action_url = reverse("compensation:new", args=(intervention_id,))
+ self.cancel_redirect = reverse("intervention:detail", args=(intervention_id,))
+ else:
+ self.action_url = reverse("compensation:new")
+ self.cancel_redirect = reverse("compensation:index")
+
+ tmp = Compensation()
+ identifier = tmp.generate_new_identifier()
+ self.initialize_form_field("identifier", identifier)
+ self.fields["identifier"].widget.attrs["url"] = reverse_lazy("compensation:new-id")
+
+ def __create_comp(self, user, geom_form) -> Compensation:
+ """ Creates the compensation from form data
+
+ Args:
+ user (User): The performing user
+ geom_form (SimpleGeomForm): The geometry form
+
+ Returns:
+ comp (Compensation): The compensation object
+ """
+ # Fetch data from cleaned POST values
+ identifier = self.cleaned_data.get("identifier", None)
+ title = self.cleaned_data.get("title", None)
+ intervention = self.cleaned_data.get("intervention", None)
+ is_cef = self.cleaned_data.get("is_cef", None)
+ is_coherence_keeping = self.cleaned_data.get("is_coherence_keeping", None)
+ is_pik = self.cleaned_data.get("is_pik", None)
+ comment = self.cleaned_data.get("comment", None)
+
+ # Create log entry
+ action = UserActionLogEntry.get_created_action(user)
+ # Process the geometry form
+ geometry = geom_form.save(action)
+
+ # Finally create main object
+ comp = Compensation.objects.create(
+ identifier=identifier,
+ title=title,
+ intervention=intervention,
+ created=action,
+ is_cef=is_cef,
+ is_coherence_keeping=is_coherence_keeping,
+ is_pik=is_pik,
+ geometry=geometry,
+ comment=comment,
+ )
+
+ # Add the log entry to the main objects log list
+ comp.log.add(action)
+ return comp
+
+ def save(self, user: User, geom_form: SimpleGeomForm):
+ with transaction.atomic():
+ comp = self.__create_comp(user, geom_form)
+ comp.intervention.mark_as_edited(user, edit_comment=COMPENSATION_ADDED_TEMPLATE.format(comp.identifier))
+ return comp
+
+
+class EditCompensationForm(NewCompensationForm):
+ """ Form for editing compensations
+
+ """
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.form_title = _("Edit compensation")
+ self.action_url = reverse("compensation:edit", args=(self.instance.id,))
+ self.cancel_redirect = reverse("compensation:detail", args=(self.instance.id,))
+
+ # Initialize form data
+ form_data = {
+ "identifier": self.instance.identifier,
+ "title": self.instance.title,
+ "intervention": self.instance.intervention,
+ "is_cef": self.instance.is_cef,
+ "is_coherence_keeping": self.instance.is_coherence_keeping,
+ "is_pik": self.instance.is_pik,
+ "comment": self.instance.comment,
+ }
+ disabled_fields = []
+ self.load_initial_data(
+ form_data,
+ disabled_fields
+ )
+
+ def save(self, user: User, geom_form: SimpleGeomForm):
+ with transaction.atomic():
+ # Fetch data from cleaned POST values
+ identifier = self.cleaned_data.get("identifier", None)
+ title = self.cleaned_data.get("title", None)
+ intervention = self.cleaned_data.get("intervention", None)
+ is_cef = self.cleaned_data.get("is_cef", None)
+ is_coherence_keeping = self.cleaned_data.get("is_coherence_keeping", None)
+ is_pik = self.cleaned_data.get("is_pik", None)
+ comment = self.cleaned_data.get("comment", None)
+
+ # Create log entry
+ action = UserActionLogEntry.get_edited_action(user)
+
+ # Process the geometry form
+ geometry = geom_form.save(action)
+
+ # Finally create main object
+ self.instance.identifier = identifier
+ self.instance.title = title
+ self.instance.intervention = intervention
+ self.instance.geometry = geometry
+ self.instance.is_cef = is_cef
+ self.instance.is_coherence_keeping = is_coherence_keeping
+ self.instance.comment = comment
+ self.instance.is_pik = is_pik
+ self.instance.modified = action
+ self.instance.save()
+
+ self.instance.log.add(action)
+
+ intervention.mark_as_edited(user, self.request, EDITED_GENERAL_DATA)
+ return self.instance
\ No newline at end of file
diff --git a/compensation/forms/eco_account.py b/compensation/forms/eco_account.py
new file mode 100644
index 00000000..6bc12618
--- /dev/null
+++ b/compensation/forms/eco_account.py
@@ -0,0 +1,212 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from django import forms
+from django.db import transaction
+from django.urls import reverse, reverse_lazy
+from django.utils.translation import gettext_lazy as _
+from compensation.forms.compensation import AbstractCompensationForm
+from compensation.forms.mixins import CompensationResponsibleFormMixin, PikCompensationFormMixin
+from compensation.models import EcoAccount
+from intervention.models import Handler, Responsibility, Legal
+from konova.forms import SimpleGeomForm
+from user.models import User, UserActionLogEntry
+
+
+class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMixin, PikCompensationFormMixin):
+ """ Form for creating eco accounts
+
+ Inherits from basic AbstractCompensationForm and further form fields from CompensationResponsibleFormMixin
+
+ """
+ surface = forms.DecimalField(
+ min_value=0.00,
+ decimal_places=2,
+ label=_("Available Surface"),
+ label_suffix="",
+ required=False,
+ help_text=_("The amount that can be used for deductions"),
+ widget=forms.NumberInput(
+ attrs={
+ "class": "form-control",
+ "placeholder": "0,00"
+ }
+ )
+ )
+ registration_date = forms.DateField(
+ label=_("Agreement date"),
+ label_suffix="",
+ help_text=_("When did the parties agree on this?"),
+ required=False,
+ widget=forms.DateInput(
+ attrs={
+ "type": "date",
+ "class": "form-control",
+ },
+ format="%d.%m.%Y"
+ )
+ )
+
+ field_order = [
+ "identifier",
+ "title",
+ "conservation_office",
+ "registration_date",
+ "surface",
+ "conservation_file_number",
+ "is_pik",
+ "handler_type",
+ "handler_detail",
+ "comment",
+ ]
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.form_title = _("New Eco-Account")
+
+ self.action_url = reverse("compensation:acc:new")
+ self.cancel_redirect = reverse("compensation:acc:index")
+
+ tmp = EcoAccount()
+ identifier = tmp.generate_new_identifier()
+ self.initialize_form_field("identifier", identifier)
+ self.fields["identifier"].widget.attrs["url"] = reverse_lazy("compensation:acc:new-id")
+ self.fields["title"].widget.attrs["placeholder"] = _("Eco-Account XY; Location ABC")
+
+ def save(self, user: User, geom_form: SimpleGeomForm):
+ with transaction.atomic():
+ # Fetch data from cleaned POST values
+ identifier = self.cleaned_data.get("identifier", None)
+ title = self.cleaned_data.get("title", None)
+ registration_date = self.cleaned_data.get("registration_date", None)
+ handler_type = self.cleaned_data.get("handler_type", None)
+ handler_detail = self.cleaned_data.get("handler_detail", None)
+ surface = self.cleaned_data.get("surface", None)
+ conservation_office = self.cleaned_data.get("conservation_office", None)
+ conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
+ is_pik = self.cleaned_data.get("is_pik", None)
+ comment = self.cleaned_data.get("comment", None)
+
+ # Create log entry
+ action = UserActionLogEntry.get_created_action(user)
+ # Process the geometry form
+ geometry = geom_form.save(action)
+
+ handler = Handler.objects.create(
+ type=handler_type,
+ detail=handler_detail,
+ )
+
+ responsible = Responsibility.objects.create(
+ handler=handler,
+ conservation_file_number=conservation_file_number,
+ conservation_office=conservation_office,
+ )
+
+ legal = Legal.objects.create(
+ registration_date=registration_date
+ )
+
+ # Finally create main object
+ acc = EcoAccount.objects.create(
+ identifier=identifier,
+ title=title,
+ responsible=responsible,
+ deductable_surface=surface,
+ created=action,
+ geometry=geometry,
+ comment=comment,
+ is_pik=is_pik,
+ legal=legal
+ )
+ acc.share_with_user(user)
+
+ # Add the log entry to the main objects log list
+ acc.log.add(action)
+ return acc
+
+
+class EditEcoAccountForm(NewEcoAccountForm):
+ """ Form for editing eco accounts
+
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.form_title = _("Edit Eco-Account")
+
+ self.action_url = reverse("compensation:acc:edit", args=(self.instance.id,))
+ self.cancel_redirect = reverse("compensation:acc:detail", args=(self.instance.id,))
+
+ # Initialize form data
+ reg_date = self.instance.legal.registration_date
+ if reg_date is not None:
+ reg_date = reg_date.isoformat()
+
+ form_data = {
+ "identifier": self.instance.identifier,
+ "title": self.instance.title,
+ "surface": self.instance.deductable_surface,
+ "handler_type": self.instance.responsible.handler.type,
+ "handler_detail": self.instance.responsible.handler.detail,
+ "registration_date": reg_date,
+ "conservation_office": self.instance.responsible.conservation_office,
+ "conservation_file_number": self.instance.responsible.conservation_file_number,
+ "is_pik": self.instance.is_pik,
+ "comment": self.instance.comment,
+ }
+ disabled_fields = []
+ self.load_initial_data(
+ form_data,
+ disabled_fields
+ )
+
+ def save(self, user: User, geom_form: SimpleGeomForm):
+ with transaction.atomic():
+ # Fetch data from cleaned POST values
+ identifier = self.cleaned_data.get("identifier", None)
+ title = self.cleaned_data.get("title", None)
+ registration_date = self.cleaned_data.get("registration_date", None)
+ handler_type = self.cleaned_data.get("handler_type", None)
+ handler_detail = self.cleaned_data.get("handler_detail", None)
+ surface = self.cleaned_data.get("surface", None)
+ conservation_office = self.cleaned_data.get("conservation_office", None)
+ conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
+ comment = self.cleaned_data.get("comment", None)
+ is_pik = self.cleaned_data.get("is_pik", None)
+
+ # Create log entry
+ action = UserActionLogEntry.get_edited_action(user)
+
+ # Process the geometry form
+ geometry = geom_form.save(action)
+
+ # Update responsible data
+ self.instance.responsible.handler.type = handler_type
+ self.instance.responsible.handler.detail = handler_detail
+ self.instance.responsible.handler.save()
+ self.instance.responsible.conservation_office = conservation_office
+ self.instance.responsible.conservation_file_number = conservation_file_number
+ self.instance.responsible.save()
+
+ # Update legal data
+ self.instance.legal.registration_date = registration_date
+ self.instance.legal.save()
+
+ # Update main oject data
+ self.instance.identifier = identifier
+ self.instance.title = title
+ self.instance.deductable_surface = surface
+ self.instance.geometry = geometry
+ self.instance.comment = comment
+ self.instance.is_pik = is_pik
+ self.instance.modified = action
+ self.instance.save()
+
+ # Add the log entry to the main objects log list
+ self.instance.log.add(action)
+ return self.instance
diff --git a/compensation/forms/forms.py b/compensation/forms/forms.py
deleted file mode 100644
index 8f28c5ff..00000000
--- a/compensation/forms/forms.py
+++ /dev/null
@@ -1,539 +0,0 @@
-"""
-Author: Michel Peltriaux
-Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
-Contact: michel.peltriaux@sgdnord.rlp.de
-Created on: 04.12.20
-
-"""
-from dal import autocomplete
-from user.models import User
-from django.db import transaction
-from django.urls import reverse_lazy, reverse
-from django.utils.translation import gettext_lazy as _
-from django import forms
-
-from codelist.models import KonovaCode
-from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_HANDLER_ID
-from compensation.models import Compensation, EcoAccount
-from intervention.inputs import GenerateInput
-from intervention.models import Intervention, Responsibility, Legal, Handler
-from konova.forms import BaseForm, SimpleGeomForm
-from konova.utils.message_templates import EDITED_GENERAL_DATA, COMPENSATION_ADDED_TEMPLATE
-from user.models import UserActionLogEntry
-
-
-class AbstractCompensationForm(BaseForm):
- """ Abstract form for compensations
-
- Holds all important form fields, which are used in compensation and eco account forms
-
- """
- identifier = forms.CharField(
- label=_("Identifier"),
- label_suffix="",
- max_length=255,
- help_text=_("Generated automatically"),
- widget=GenerateInput(
- attrs={
- "class": "form-control",
- "url": None, # Needs to be set in inheriting constructors
- }
- )
- )
- title = forms.CharField(
- label=_("Title"),
- label_suffix="",
- help_text=_("An explanatory name"),
- max_length=255,
- widget=forms.TextInput(
- attrs={
- "placeholder": _("Compensation XY; Location ABC"),
- "class": "form-control",
- }
- )
- )
- comment = forms.CharField(
- label_suffix="",
- label=_("Comment"),
- required=False,
- help_text=_("Additional comment"),
- widget=forms.Textarea(
- attrs={
- "rows": 5,
- "class": "form-control"
- }
- )
- )
-
- class Meta:
- abstract = True
-
-
-class CompensationResponsibleFormMixin(forms.Form):
- """ Encapsulates form fields used in different compensation related models like EcoAccount or EMA
-
- """
- conservation_office = forms.ModelChoiceField(
- label=_("Conservation office"),
- label_suffix="",
- help_text=_("Select the responsible office"),
- queryset=KonovaCode.objects.filter(
- is_archived=False,
- is_leaf=True,
- code_lists__in=[CODELIST_CONSERVATION_OFFICE_ID],
- ),
- widget=autocomplete.ModelSelect2(
- url="codes-conservation-office-autocomplete",
- attrs={
- "data-placeholder": _("Click for selection")
- }
- ),
- )
- conservation_file_number = forms.CharField(
- label=_("Conservation office file number"),
- label_suffix="",
- max_length=255,
- required=False,
- widget=forms.TextInput(
- attrs={
- "placeholder": _("ETS-123/ABC.456"),
- "class": "form-control",
- }
- )
- )
-
- handler_type = forms.ModelChoiceField(
- label=_("Eco-Account handler type"),
- label_suffix="",
- help_text=_("What type of handler is responsible for the ecoaccount?"),
- required=False,
- queryset=KonovaCode.objects.filter(
- is_archived=False,
- is_leaf=True,
- code_lists__in=[CODELIST_HANDLER_ID],
- ),
- widget=autocomplete.ModelSelect2(
- url="codes-handler-autocomplete",
- attrs={
- "data-placeholder": _("Click for selection"),
- }
- ),
- )
- handler_detail = forms.CharField(
- label=_("Eco-Account handler detail"),
- label_suffix="",
- max_length=255,
- required=False,
- help_text=_("Detail input on the handler"),
- widget=forms.TextInput(
- attrs={
- "placeholder": _("Company Mustermann"),
- "class": "form-control",
- }
- )
- )
-
-
-class CEFCompensationFormMixin(forms.Form):
- """ A form mixin, providing CEF compensation field
-
- """
- is_cef = forms.BooleanField(
- label_suffix="",
- label=_("Is CEF"),
- help_text=_("Optionally: Whether this compensation is a CEF compensation?"),
- required=False,
- widget=forms.CheckboxInput()
- )
-
-
-class CoherenceCompensationFormMixin(forms.Form):
- """ A form mixin, providing coherence compensation field
-
- """
- is_coherence_keeping = forms.BooleanField(
- label_suffix="",
- label=_("Is coherence keeping"),
- help_text=_("Optionally: Whether this compensation is a coherence keeping compensation?"),
- required=False,
- widget=forms.CheckboxInput()
- )
-
-
-class PikCompensationFormMixin(forms.Form):
- """ A form mixin, providing PIK compensation field
-
- """
- is_pik = forms.BooleanField(
- label_suffix="",
- label=_("Is PIK"),
- help_text=_("Optionally: Whether this compensation is a compensation integrated in production?"),
- required=False,
- widget=forms.CheckboxInput()
- )
-
-
-class NewCompensationForm(AbstractCompensationForm,
- CEFCompensationFormMixin,
- CoherenceCompensationFormMixin,
- PikCompensationFormMixin):
- """ Form for creating new compensations.
-
- Can be initialized with an intervention id for preselecting the related intervention.
- form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
- ...
- The intervention id will not be resolved into the intervention ORM object but instead will be used to initialize
- the related form field.
-
- """
- intervention = forms.ModelChoiceField(
- label=_("compensates intervention"),
- label_suffix="",
- help_text=_("Select the intervention for which this compensation compensates"),
- queryset=Intervention.objects.filter(
- deleted=None,
- ),
- widget=autocomplete.ModelSelect2(
- url="interventions-autocomplete",
- attrs={
- "data-placeholder": _("Click for selection"),
- "data-minimum-input-length": 3,
- }
- ),
- )
-
- # Define a field order for a nicer layout instead of running with the inheritance result
- field_order = [
- "identifier",
- "title",
- "intervention",
- "is_pik",
- "is_cef",
- "is_coherence_keeping",
- "comment",
- ]
-
- def __init__(self, *args, **kwargs):
- intervention_id = kwargs.pop("intervention_id", None)
- super().__init__(*args, **kwargs)
- self.form_title = _("New compensation")
-
- # If the compensation shall directly be initialized from an intervention, we need to fill in the intervention id
- # and disable the form field.
- # Furthermore the action_url needs to be set accordingly.
- if intervention_id is not None:
- self.initialize_form_field("intervention", intervention_id)
- self.disable_form_field("intervention")
- self.action_url = reverse("compensation:new", args=(intervention_id,))
- self.cancel_redirect = reverse("intervention:detail", args=(intervention_id,))
- else:
- self.action_url = reverse("compensation:new")
- self.cancel_redirect = reverse("compensation:index")
-
- tmp = Compensation()
- identifier = tmp.generate_new_identifier()
- self.initialize_form_field("identifier", identifier)
- self.fields["identifier"].widget.attrs["url"] = reverse_lazy("compensation:new-id")
-
- def __create_comp(self, user, geom_form) -> Compensation:
- """ Creates the compensation from form data
-
- Args:
- user (User): The performing user
- geom_form (SimpleGeomForm): The geometry form
-
- Returns:
- comp (Compensation): The compensation object
- """
- # Fetch data from cleaned POST values
- identifier = self.cleaned_data.get("identifier", None)
- title = self.cleaned_data.get("title", None)
- intervention = self.cleaned_data.get("intervention", None)
- is_cef = self.cleaned_data.get("is_cef", None)
- is_coherence_keeping = self.cleaned_data.get("is_coherence_keeping", None)
- is_pik = self.cleaned_data.get("is_pik", None)
- comment = self.cleaned_data.get("comment", None)
-
- # Create log entry
- action = UserActionLogEntry.get_created_action(user)
- # Process the geometry form
- geometry = geom_form.save(action)
-
- # Finally create main object
- comp = Compensation.objects.create(
- identifier=identifier,
- title=title,
- intervention=intervention,
- created=action,
- is_cef=is_cef,
- is_coherence_keeping=is_coherence_keeping,
- is_pik=is_pik,
- geometry=geometry,
- comment=comment,
- )
-
- # Add the log entry to the main objects log list
- comp.log.add(action)
- return comp
-
- def save(self, user: User, geom_form: SimpleGeomForm):
- with transaction.atomic():
- comp = self.__create_comp(user, geom_form)
- comp.intervention.mark_as_edited(user, edit_comment=COMPENSATION_ADDED_TEMPLATE.format(comp.identifier))
- return comp
-
-
-class EditCompensationForm(NewCompensationForm):
- """ Form for editing compensations
-
- """
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.form_title = _("Edit compensation")
- self.action_url = reverse("compensation:edit", args=(self.instance.id,))
- self.cancel_redirect = reverse("compensation:detail", args=(self.instance.id,))
-
- # Initialize form data
- form_data = {
- "identifier": self.instance.identifier,
- "title": self.instance.title,
- "intervention": self.instance.intervention,
- "is_cef": self.instance.is_cef,
- "is_coherence_keeping": self.instance.is_coherence_keeping,
- "is_pik": self.instance.is_pik,
- "comment": self.instance.comment,
- }
- disabled_fields = []
- self.load_initial_data(
- form_data,
- disabled_fields
- )
-
- def save(self, user: User, geom_form: SimpleGeomForm):
- with transaction.atomic():
- # Fetch data from cleaned POST values
- identifier = self.cleaned_data.get("identifier", None)
- title = self.cleaned_data.get("title", None)
- intervention = self.cleaned_data.get("intervention", None)
- is_cef = self.cleaned_data.get("is_cef", None)
- is_coherence_keeping = self.cleaned_data.get("is_coherence_keeping", None)
- is_pik = self.cleaned_data.get("is_pik", None)
- comment = self.cleaned_data.get("comment", None)
-
- # Create log entry
- action = UserActionLogEntry.get_edited_action(user)
-
- # Process the geometry form
- geometry = geom_form.save(action)
-
- # Finally create main object
- self.instance.identifier = identifier
- self.instance.title = title
- self.instance.intervention = intervention
- self.instance.geometry = geometry
- self.instance.is_cef = is_cef
- self.instance.is_coherence_keeping = is_coherence_keeping
- self.instance.comment = comment
- self.instance.is_pik = is_pik
- self.instance.modified = action
- self.instance.save()
-
- self.instance.log.add(action)
-
- intervention.mark_as_edited(user, self.request, EDITED_GENERAL_DATA)
- return self.instance
-
-
-class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMixin, PikCompensationFormMixin):
- """ Form for creating eco accounts
-
- Inherits from basic AbstractCompensationForm and further form fields from CompensationResponsibleFormMixin
-
- """
- surface = forms.DecimalField(
- min_value=0.00,
- decimal_places=2,
- label=_("Available Surface"),
- label_suffix="",
- required=False,
- help_text=_("The amount that can be used for deductions"),
- widget=forms.NumberInput(
- attrs={
- "class": "form-control",
- "placeholder": "0,00"
- }
- )
- )
- registration_date = forms.DateField(
- label=_("Agreement date"),
- label_suffix="",
- help_text=_("When did the parties agree on this?"),
- required=False,
- widget=forms.DateInput(
- attrs={
- "type": "date",
- "class": "form-control",
- },
- format="%d.%m.%Y"
- )
- )
-
- field_order = [
- "identifier",
- "title",
- "conservation_office",
- "registration_date",
- "surface",
- "conservation_file_number",
- "is_pik",
- "handler_type",
- "handler_detail",
- "comment",
- ]
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.form_title = _("New Eco-Account")
-
- self.action_url = reverse("compensation:acc:new")
- self.cancel_redirect = reverse("compensation:acc:index")
-
- tmp = EcoAccount()
- identifier = tmp.generate_new_identifier()
- self.initialize_form_field("identifier", identifier)
- self.fields["identifier"].widget.attrs["url"] = reverse_lazy("compensation:acc:new-id")
- self.fields["title"].widget.attrs["placeholder"] = _("Eco-Account XY; Location ABC")
-
- def save(self, user: User, geom_form: SimpleGeomForm):
- with transaction.atomic():
- # Fetch data from cleaned POST values
- identifier = self.cleaned_data.get("identifier", None)
- title = self.cleaned_data.get("title", None)
- registration_date = self.cleaned_data.get("registration_date", None)
- handler_type = self.cleaned_data.get("handler_type", None)
- handler_detail = self.cleaned_data.get("handler_detail", None)
- surface = self.cleaned_data.get("surface", None)
- conservation_office = self.cleaned_data.get("conservation_office", None)
- conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
- is_pik = self.cleaned_data.get("is_pik", None)
- comment = self.cleaned_data.get("comment", None)
-
- # Create log entry
- action = UserActionLogEntry.get_created_action(user)
- # Process the geometry form
- geometry = geom_form.save(action)
-
- handler = Handler.objects.create(
- type=handler_type,
- detail=handler_detail,
- )
-
- responsible = Responsibility.objects.create(
- handler=handler,
- conservation_file_number=conservation_file_number,
- conservation_office=conservation_office,
- )
-
- legal = Legal.objects.create(
- registration_date=registration_date
- )
-
- # Finally create main object
- acc = EcoAccount.objects.create(
- identifier=identifier,
- title=title,
- responsible=responsible,
- deductable_surface=surface,
- created=action,
- geometry=geometry,
- comment=comment,
- is_pik=is_pik,
- legal=legal
- )
- acc.share_with_user(user)
-
- # Add the log entry to the main objects log list
- acc.log.add(action)
- return acc
-
-
-class EditEcoAccountForm(NewEcoAccountForm):
- """ Form for editing eco accounts
-
- """
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.form_title = _("Edit Eco-Account")
-
- self.action_url = reverse("compensation:acc:edit", args=(self.instance.id,))
- self.cancel_redirect = reverse("compensation:acc:detail", args=(self.instance.id,))
-
- # Initialize form data
- reg_date = self.instance.legal.registration_date
- if reg_date is not None:
- reg_date = reg_date.isoformat()
-
- form_data = {
- "identifier": self.instance.identifier,
- "title": self.instance.title,
- "surface": self.instance.deductable_surface,
- "handler_type": self.instance.responsible.handler.type,
- "handler_detail": self.instance.responsible.handler.detail,
- "registration_date": reg_date,
- "conservation_office": self.instance.responsible.conservation_office,
- "conservation_file_number": self.instance.responsible.conservation_file_number,
- "is_pik": self.instance.is_pik,
- "comment": self.instance.comment,
- }
- disabled_fields = []
- self.load_initial_data(
- form_data,
- disabled_fields
- )
-
- def save(self, user: User, geom_form: SimpleGeomForm):
- with transaction.atomic():
- # Fetch data from cleaned POST values
- identifier = self.cleaned_data.get("identifier", None)
- title = self.cleaned_data.get("title", None)
- registration_date = self.cleaned_data.get("registration_date", None)
- handler_type = self.cleaned_data.get("handler_type", None)
- handler_detail = self.cleaned_data.get("handler_detail", None)
- surface = self.cleaned_data.get("surface", None)
- conservation_office = self.cleaned_data.get("conservation_office", None)
- conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
- comment = self.cleaned_data.get("comment", None)
- is_pik = self.cleaned_data.get("is_pik", None)
-
- # Create log entry
- action = UserActionLogEntry.get_edited_action(user)
-
- # Process the geometry form
- geometry = geom_form.save(action)
-
- # Update responsible data
- self.instance.responsible.handler.type = handler_type
- self.instance.responsible.handler.detail = handler_detail
- self.instance.responsible.handler.save()
- self.instance.responsible.conservation_office = conservation_office
- self.instance.responsible.conservation_file_number = conservation_file_number
- self.instance.responsible.save()
-
- # Update legal data
- self.instance.legal.registration_date = registration_date
- self.instance.legal.save()
-
- # Update main oject data
- self.instance.identifier = identifier
- self.instance.title = title
- self.instance.deductable_surface = surface
- self.instance.geometry = geometry
- self.instance.comment = comment
- self.instance.is_pik = is_pik
- self.instance.modified = action
- self.instance.save()
-
- # Add the log entry to the main objects log list
- self.instance.log.add(action)
- return self.instance
diff --git a/compensation/forms/mixins.py b/compensation/forms/mixins.py
new file mode 100644
index 00000000..7cb7eb25
--- /dev/null
+++ b/compensation/forms/mixins.py
@@ -0,0 +1,117 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from dal import autocomplete
+from django import forms
+from django.utils.translation import gettext_lazy as _
+
+from codelist.models import KonovaCode
+from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_HANDLER_ID
+
+
+class CompensationResponsibleFormMixin(forms.Form):
+ """ Encapsulates form fields used in different compensation related models like EcoAccount or EMA
+
+ """
+ conservation_office = forms.ModelChoiceField(
+ label=_("Conservation office"),
+ label_suffix="",
+ help_text=_("Select the responsible office"),
+ queryset=KonovaCode.objects.filter(
+ is_archived=False,
+ is_leaf=True,
+ code_lists__in=[CODELIST_CONSERVATION_OFFICE_ID],
+ ),
+ widget=autocomplete.ModelSelect2(
+ url="codelist:conservation-office-autocomplete",
+ attrs={
+ "data-placeholder": _("Click for selection")
+ }
+ ),
+ )
+ conservation_file_number = forms.CharField(
+ label=_("Conservation office file number"),
+ label_suffix="",
+ max_length=255,
+ required=False,
+ widget=forms.TextInput(
+ attrs={
+ "placeholder": _("ETS-123/ABC.456"),
+ "class": "form-control",
+ }
+ )
+ )
+
+ handler_type = forms.ModelChoiceField(
+ label=_("Eco-Account handler type"),
+ label_suffix="",
+ help_text=_("What type of handler is responsible for the ecoaccount?"),
+ required=False,
+ queryset=KonovaCode.objects.filter(
+ is_archived=False,
+ is_leaf=True,
+ code_lists__in=[CODELIST_HANDLER_ID],
+ ),
+ widget=autocomplete.ModelSelect2(
+ url="codelist:handler-autocomplete",
+ attrs={
+ "data-placeholder": _("Click for selection"),
+ }
+ ),
+ )
+ handler_detail = forms.CharField(
+ label=_("Eco-Account handler detail"),
+ label_suffix="",
+ max_length=255,
+ required=False,
+ help_text=_("Detail input on the handler"),
+ widget=forms.TextInput(
+ attrs={
+ "placeholder": _("Company Mustermann"),
+ "class": "form-control",
+ }
+ )
+ )
+
+
+class CEFCompensationFormMixin(forms.Form):
+ """ A form mixin, providing CEF compensation field
+
+ """
+ is_cef = forms.BooleanField(
+ label_suffix="",
+ label=_("Is CEF"),
+ help_text=_("Optionally: Whether this compensation is a CEF compensation?"),
+ required=False,
+ widget=forms.CheckboxInput()
+ )
+
+
+class CoherenceCompensationFormMixin(forms.Form):
+ """ A form mixin, providing coherence compensation field
+
+ """
+ is_coherence_keeping = forms.BooleanField(
+ label_suffix="",
+ label=_("Is coherence keeping"),
+ help_text=_("Optionally: Whether this compensation is a coherence keeping compensation?"),
+ required=False,
+ widget=forms.CheckboxInput()
+ )
+
+
+class PikCompensationFormMixin(forms.Form):
+ """ A form mixin, providing PIK compensation field
+
+ """
+ is_pik = forms.BooleanField(
+ label_suffix="",
+ label=_("Is PIK"),
+ help_text=_("Optionally: Whether this compensation is a compensation integrated in production?"),
+ required=False,
+ widget=forms.CheckboxInput()
+ )
\ No newline at end of file
diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py
deleted file mode 100644
index ef21aa3f..00000000
--- a/compensation/forms/modalForms.py
+++ /dev/null
@@ -1,534 +0,0 @@
-"""
-Author: Michel Peltriaux
-Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
-Contact: michel.peltriaux@sgdnord.rlp.de
-Created on: 04.10.21
-
-"""
-from bootstrap_modal_forms.utils import is_ajax
-from dal import autocomplete
-from django import forms
-from django.contrib import messages
-from django.http import HttpRequest, HttpResponseRedirect
-from django.shortcuts import render
-from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _
-
-from codelist.models import KonovaCode
-from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \
- CODELIST_COMPENSATION_ACTION_DETAIL_ID
-from compensation.models import CompensationDocument, EcoAccountDocument
-from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple, \
- CompensationStateTreeRadioSelect
-from konova.contexts import BaseContext
-from konova.forms.modals import BaseModalForm, NewDocumentModalForm, RemoveModalForm
-from konova.models import DeadlineType
-from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, \
- ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
-
-
-class NewPaymentForm(BaseModalForm):
- """ Form handling payment related input
-
- """
- amount = forms.DecimalField(
- min_value=0.00,
- decimal_places=2,
- label=_con("money", "Amount"), # contextual translation
- label_suffix=_(""),
- help_text=_("in Euro"),
- widget=forms.NumberInput(
- attrs={
- "class": "form-control",
- "placeholder": "0,00",
- }
- )
- )
- due = forms.DateField(
- label=_("Due on"),
- label_suffix=_(""),
- required=False,
- help_text=_("Due on which date"),
- widget=forms.DateInput(
- attrs={
- "type": "date",
- "data-provide": "datepicker",
- "class": "form-control",
- },
- format="%d.%m.%Y"
- )
- )
- comment = forms.CharField(
- max_length=200,
- required=False,
- label=_("Comment"),
- label_suffix=_(""),
- help_text=_("Additional comment, maximum {} letters").format(200),
- widget=forms.Textarea(
- attrs={
- "rows": 5,
- "class": "form-control"
- }
- )
- )
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.intervention = self.instance
- self.form_title = _("Payment")
- self.form_caption = _("Add a payment for intervention '{}'").format(self.intervention.title)
-
- def is_valid(self):
- """
- Checks on form validity.
-
- For this form we need to make sure that a date or a comment is set.
- If both are missing, the user needs to enter at least an explanation why
- there is no date to be entered.
-
- Returns:
- is_valid (bool): True if valid, False otherwise
- """
- super_valid = super().is_valid()
- date = self.cleaned_data["due"]
- comment = self.cleaned_data["comment"] or None
- if not date and not comment:
- # At least one needs to be set!
- self.add_error(
- "comment",
- _("If there is no date you can enter, please explain why.")
- )
- return False
- return super_valid
-
- def save(self):
- pay = self.instance.add_payment(self)
- 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)
- self.form_title = _("Edit payment")
- 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)
- self.instance.send_data_to_egon()
- return payment
-
-
-class RemovePaymentModalForm(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
-
- Compensation states refer to 'before' and 'after' states of a compensated surface. Basically it means:
- What has been on this area before changes/compensations have been applied and what will be the result ('after')?
-
- """
- biotope_type = forms.ChoiceField(
- label=_("Biotope Type"),
- label_suffix="",
- required=True,
- help_text=_("Select the biotope type"),
- widget=CompensationStateTreeRadioSelect(),
- )
- biotope_extra = forms.ModelMultipleChoiceField(
- label=_("Biotope additional type"),
- label_suffix="",
- required=False,
- help_text=_("Select an additional biotope type"),
- queryset=KonovaCode.objects.filter(
- is_archived=False,
- is_leaf=True,
- code_lists__in=[CODELIST_BIOTOPES_EXTRA_CODES_ID],
- ),
- widget=autocomplete.ModelSelect2Multiple(
- url="codes-biotope-extra-type-autocomplete",
- attrs={
- "data-placeholder": _("Biotope additional type"),
- }
- ),
- )
- surface = forms.DecimalField(
- min_value=0.00,
- decimal_places=2,
- label=_("Surface"),
- label_suffix="",
- required=True,
- help_text=_("in m²"),
- widget=forms.NumberInput(
- attrs={
- "class": "form-control",
- "placeholder": "0,00"
- }
- )
- )
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.form_title = _("New state")
- self.form_caption = _("Insert data for the new state")
- choices = KonovaCode.objects.filter(
- code_lists__in=[CODELIST_BIOTOPES_ID],
- is_archived=False,
- is_leaf=True,
- ).values_list("id", flat=True)
- choices = [
- (choice, choice)
- for choice in choices
- ]
- self.fields["biotope_type"].choices = choices
-
- def save(self, is_before_state: bool = False):
- state = self.instance.add_state(self, is_before_state)
- self.instance.mark_as_edited(self.user, self.request, ADDED_COMPENSATION_STATE)
- return state
-
- def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
- """ Generic processing of request
-
- Wraps the request processing logic, so we don't need the same code everywhere a RemoveModalForm is being used
-
- +++
- The generic method from super class can not be used, since we need to do some request parameter check in here.
- +++
-
- Args:
- request (HttpRequest): The incoming request
- msg_success (str): The message in case of successful removing
- msg_error (str): The message in case of an error
-
- Returns:
-
- """
- redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
- template = self.template
- if request.method == "POST":
- if self.is_valid():
- # Modal forms send one POST for checking on data validity. This can be used to return possible errors
- # on the form. A second POST (if no errors occured) is sent afterwards and needs to process the
- # saving/commiting of the data to the database. is_ajax() performs this check. The first request is
- # an ajax call, the second is a regular form POST.
- if not is_ajax(request.META):
- is_before_state = bool(request.GET.get("before", False))
- self.save(is_before_state=is_before_state)
- messages.success(
- request,
- msg_success
- )
- return HttpResponseRedirect(redirect_url)
- else:
- context = {
- "form": self,
- }
- context = BaseContext(request, context).context
- return render(request, template, context)
- elif request.method == "GET":
- context = {
- "form": self,
- }
- context = BaseContext(request, context).context
- return render(request, template, context)
- else:
- raise NotImplementedError
-
-
-class EditCompensationStateModalForm(NewStateModalForm):
- state = None
-
- def __init__(self, *args, **kwargs):
- self.state = kwargs.pop("state", None)
- super().__init__(*args, **kwargs)
- self.form_title = _("Edit state")
- biotope_type_id = self.state.biotope_type.id if self.state.biotope_type else None
- form_data = {
- "biotope_type": biotope_type_id,
- "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
- biotope_type_id = self.cleaned_data.get("biotope_type", None)
- state.biotope_type = KonovaCode.objects.get(id=biotope_type_id)
- 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
-
- 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 RemoveCompensationActionModalForm(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
-
- """
- type = forms.ChoiceField(
- label=_("Deadline Type"),
- label_suffix="",
- required=True,
- help_text=_("Select the deadline type"),
- choices=DeadlineType.choices,
- widget=forms.Select(
- attrs={
- "class": "form-control"
- }
- )
- )
- date = forms.DateField(
- label=_("Date"),
- label_suffix="",
- required=True,
- help_text=_("Select date"),
- widget=forms.DateInput(
- attrs={
- "type": "date",
- "data-provide": "datepicker",
- "class": "form-control",
- },
- format="%d.%m.%Y"
- )
- )
- comment = forms.CharField(
- required=False,
- max_length=200,
- label=_("Comment"),
- label_suffix=_(""),
- help_text=_("Additional comment, maximum {} letters").format(200),
- widget=forms.Textarea(
- attrs={
- "cols": 30,
- "rows": 5,
- "class": "form-control",
- }
- )
- )
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.form_title = _("New deadline")
- self.form_caption = _("Insert data for the new deadline")
-
- def save(self):
- deadline = self.instance.add_deadline(self)
- return deadline
-
-
-class EditDeadlineModalForm(NewDeadlineModalForm):
- deadline = None
-
- def __init__(self, *args, **kwargs):
- self.deadline = kwargs.pop("deadline", None)
- super().__init__(*args, **kwargs)
- self.form_title = _("Edit deadline")
- 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
-
- Compensation actions are the actions performed on the area, which shall be compensated. Actions will change the
- surface of the area, the biotopes, and have an environmental impact. With actions the before-after states can change
- (not in the process logic in Konova, but in the real world).
-
- """
- from compensation.models import UnitChoices
- action_type = forms.MultipleChoiceField(
- label=_("Action Type"),
- label_suffix="",
- required=True,
- help_text=_("An action can consist of multiple different action types. All the selected action types are expected to be performed according to the amount and unit below on this form."),
- choices=[],
- widget=CompensationActionTreeCheckboxSelectMultiple(),
- )
- action_type_details = forms.ModelMultipleChoiceField(
- label=_("Action Type detail"),
- label_suffix="",
- required=False,
- help_text=_("Select the action type detail"),
- queryset=KonovaCode.objects.filter(
- is_archived=False,
- is_leaf=True,
- code_lists__in=[CODELIST_COMPENSATION_ACTION_DETAIL_ID],
- ),
- widget=autocomplete.ModelSelect2Multiple(
- url="codes-compensation-action-detail-autocomplete",
- attrs={
- "data-placeholder": _("Action Type detail"),
- }
- ),
- )
- unit = forms.ChoiceField(
- label=_("Unit"),
- label_suffix="",
- required=True,
- help_text=_("Select the unit"),
- choices=UnitChoices.choices,
- widget=forms.Select(
- attrs={
- "class": "form-control"
- }
- )
- )
- amount = forms.DecimalField(
- label=_("Amount"),
- label_suffix="",
- required=True,
- help_text=_("Insert the amount"),
- decimal_places=2,
- min_value=0.00,
- widget=forms.NumberInput(
- attrs={
- "class": "form-control",
- "placeholder": "0,00",
- }
- )
- )
- comment = forms.CharField(
- required=False,
- label=_("Comment"),
- label_suffix=_(""),
- help_text=_("Additional comment"),
- widget=forms.Textarea(
- attrs={
- "rows": 5,
- "class": "form-control",
- }
- )
- )
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.form_title = _("New action")
- self.form_caption = _("Insert data for the new action")
- choices =KonovaCode.objects.filter(
- code_lists__in=[CODELIST_COMPENSATION_ACTION_ID],
- is_archived=False,
- is_leaf=True,
- ).values_list("id", flat=True)
- choices = [
- (choice, choice)
- for choice in choices
- ]
- self.fields["action_type"].choices = choices
-
- def save(self):
- action = self.instance.add_action(self)
- self.instance.mark_as_edited(self.user, self.request, ADDED_COMPENSATION_ACTION)
- return action
-
-
-class EditCompensationActionModalForm(NewActionModalForm):
- action = None
-
- def __init__(self, *args, **kwargs):
- self.action = kwargs.pop("action", None)
- super().__init__(*args, **kwargs)
- self.form_title = _("Edit action")
- form_data = {
- "action_type": list(self.action.action_type.values_list("id", flat=True)),
- "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.set(self.cleaned_data.get("action_type", []))
- 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
-
-
-class NewEcoAccountDocumentModalForm(NewDocumentModalForm):
- document_model = EcoAccountDocument
\ No newline at end of file
diff --git a/compensation/forms/modals/__init__.py b/compensation/forms/modals/__init__.py
new file mode 100644
index 00000000..ca978536
--- /dev/null
+++ b/compensation/forms/modals/__init__.py
@@ -0,0 +1,7 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
diff --git a/compensation/forms/modals/compensation_action.py b/compensation/forms/modals/compensation_action.py
new file mode 100644
index 00000000..6cd7279f
--- /dev/null
+++ b/compensation/forms/modals/compensation_action.py
@@ -0,0 +1,155 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from dal import autocomplete
+from django import forms
+from django.utils.translation import gettext_lazy as _
+
+from codelist.models import KonovaCode
+from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID
+from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple
+from konova.forms.modals import BaseModalForm, RemoveModalForm
+from konova.utils.message_templates import COMPENSATION_ACTION_EDITED, ADDED_COMPENSATION_ACTION
+
+
+class NewCompensationActionModalForm(BaseModalForm):
+ """ Form handling action related input
+
+ Compensation actions are the actions performed on the area, which shall be compensated. Actions will change the
+ surface of the area, the biotopes, and have an environmental impact. With actions the before-after states can change
+ (not in the process logic in Konova, but in the real world).
+
+ """
+ from compensation.models import UnitChoices
+ action_type = forms.MultipleChoiceField(
+ label=_("Action Type"),
+ label_suffix="",
+ required=True,
+ help_text=_("An action can consist of multiple different action types. All the selected action types are expected to be performed according to the amount and unit below on this form."),
+ choices=[],
+ widget=CompensationActionTreeCheckboxSelectMultiple(),
+ )
+ action_type_details = forms.ModelMultipleChoiceField(
+ label=_("Action Type detail"),
+ label_suffix="",
+ required=False,
+ help_text=_("Select the action type detail"),
+ queryset=KonovaCode.objects.filter(
+ is_archived=False,
+ is_leaf=True,
+ code_lists__in=[CODELIST_COMPENSATION_ACTION_DETAIL_ID],
+ ),
+ widget=autocomplete.ModelSelect2Multiple(
+ url="codelist:compensation-action-detail-autocomplete",
+ attrs={
+ "data-placeholder": _("Action Type detail"),
+ }
+ ),
+ )
+ unit = forms.ChoiceField(
+ label=_("Unit"),
+ label_suffix="",
+ required=True,
+ help_text=_("Select the unit"),
+ choices=UnitChoices.choices,
+ widget=forms.Select(
+ attrs={
+ "class": "form-control"
+ }
+ )
+ )
+ amount = forms.DecimalField(
+ label=_("Amount"),
+ label_suffix="",
+ required=True,
+ help_text=_("Insert the amount"),
+ decimal_places=2,
+ min_value=0.00,
+ widget=forms.NumberInput(
+ attrs={
+ "class": "form-control",
+ "placeholder": "0,00",
+ }
+ )
+ )
+ comment = forms.CharField(
+ required=False,
+ label=_("Comment"),
+ label_suffix=_(""),
+ help_text=_("Additional comment"),
+ widget=forms.Textarea(
+ attrs={
+ "rows": 5,
+ "class": "form-control",
+ }
+ )
+ )
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.form_title = _("New action")
+ self.form_caption = _("Insert data for the new action")
+ choices =KonovaCode.objects.filter(
+ code_lists__in=[CODELIST_COMPENSATION_ACTION_ID],
+ is_archived=False,
+ is_leaf=True,
+ ).values_list("id", flat=True)
+ choices = [
+ (choice, choice)
+ for choice in choices
+ ]
+ self.fields["action_type"].choices = choices
+
+ def save(self):
+ action = self.instance.add_action(self)
+ self.instance.mark_as_edited(self.user, self.request, ADDED_COMPENSATION_ACTION)
+ return action
+
+
+class EditCompensationActionModalForm(NewCompensationActionModalForm):
+ action = None
+
+ def __init__(self, *args, **kwargs):
+ self.action = kwargs.pop("action", None)
+ super().__init__(*args, **kwargs)
+ self.form_title = _("Edit action")
+ form_data = {
+ "action_type": list(self.action.action_type.values_list("id", flat=True)),
+ "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.set(self.cleaned_data.get("action_type", []))
+ 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 RemoveCompensationActionModalForm(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)
\ No newline at end of file
diff --git a/compensation/forms/modals/deadline.py b/compensation/forms/modals/deadline.py
new file mode 100644
index 00000000..330c83c8
--- /dev/null
+++ b/compensation/forms/modals/deadline.py
@@ -0,0 +1,92 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from django import forms
+from django.utils.translation import gettext_lazy as _
+
+from konova.forms.modals import BaseModalForm
+from konova.models import DeadlineType
+from konova.utils.message_templates import DEADLINE_EDITED
+
+
+class NewDeadlineModalForm(BaseModalForm):
+ """ Form handling deadline related input
+
+ """
+ type = forms.ChoiceField(
+ label=_("Deadline Type"),
+ label_suffix="",
+ required=True,
+ help_text=_("Select the deadline type"),
+ choices=DeadlineType.choices,
+ widget=forms.Select(
+ attrs={
+ "class": "form-control"
+ }
+ )
+ )
+ date = forms.DateField(
+ label=_("Date"),
+ label_suffix="",
+ required=True,
+ help_text=_("Select date"),
+ widget=forms.DateInput(
+ attrs={
+ "type": "date",
+ "data-provide": "datepicker",
+ "class": "form-control",
+ },
+ format="%d.%m.%Y"
+ )
+ )
+ comment = forms.CharField(
+ required=False,
+ max_length=200,
+ label=_("Comment"),
+ label_suffix=_(""),
+ help_text=_("Additional comment, maximum {} letters").format(200),
+ widget=forms.Textarea(
+ attrs={
+ "cols": 30,
+ "rows": 5,
+ "class": "form-control",
+ }
+ )
+ )
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.form_title = _("New deadline")
+ self.form_caption = _("Insert data for the new deadline")
+
+ def save(self):
+ deadline = self.instance.add_deadline(self)
+ return deadline
+
+
+class EditDeadlineModalForm(NewDeadlineModalForm):
+ deadline = None
+
+ def __init__(self, *args, **kwargs):
+ self.deadline = kwargs.pop("deadline", None)
+ super().__init__(*args, **kwargs)
+ self.form_title = _("Edit deadline")
+ 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
diff --git a/compensation/forms/modals/document.py b/compensation/forms/modals/document.py
new file mode 100644
index 00000000..83c2ff92
--- /dev/null
+++ b/compensation/forms/modals/document.py
@@ -0,0 +1,17 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from compensation.models import CompensationDocument, EcoAccountDocument
+from konova.forms.modals import NewDocumentModalForm
+
+
+class NewCompensationDocumentModalForm(NewDocumentModalForm):
+ document_model = CompensationDocument
+
+
+class NewEcoAccountDocumentModalForm(NewDocumentModalForm):
+ document_model = EcoAccountDocument
diff --git a/compensation/forms/modals/payment.py b/compensation/forms/modals/payment.py
new file mode 100644
index 00000000..4383a576
--- /dev/null
+++ b/compensation/forms/modals/payment.py
@@ -0,0 +1,136 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from django import forms
+from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _
+
+from konova.forms.modals import RemoveModalForm, BaseModalForm
+from konova.utils.message_templates import PAYMENT_EDITED
+
+
+class NewPaymentForm(BaseModalForm):
+ """ Form handling payment related input
+
+ """
+ amount = forms.DecimalField(
+ min_value=0.00,
+ decimal_places=2,
+ label=_con("money", "Amount"), # contextual translation
+ label_suffix=_(""),
+ help_text=_("in Euro"),
+ widget=forms.NumberInput(
+ attrs={
+ "class": "form-control",
+ "placeholder": "0,00",
+ }
+ )
+ )
+ due = forms.DateField(
+ label=_("Due on"),
+ label_suffix=_(""),
+ required=False,
+ help_text=_("Due on which date"),
+ widget=forms.DateInput(
+ attrs={
+ "type": "date",
+ "data-provide": "datepicker",
+ "class": "form-control",
+ },
+ format="%d.%m.%Y"
+ )
+ )
+ comment = forms.CharField(
+ max_length=200,
+ required=False,
+ label=_("Comment"),
+ label_suffix=_(""),
+ help_text=_("Additional comment, maximum {} letters").format(200),
+ widget=forms.Textarea(
+ attrs={
+ "rows": 5,
+ "class": "form-control"
+ }
+ )
+ )
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.intervention = self.instance
+ self.form_title = _("Payment")
+ self.form_caption = _("Add a payment for intervention '{}'").format(self.intervention.title)
+
+ def is_valid(self):
+ """
+ Checks on form validity.
+
+ For this form we need to make sure that a date or a comment is set.
+ If both are missing, the user needs to enter at least an explanation why
+ there is no date to be entered.
+
+ Returns:
+ is_valid (bool): True if valid, False otherwise
+ """
+ super_valid = super().is_valid()
+ date = self.cleaned_data["due"]
+ comment = self.cleaned_data["comment"] or None
+ if not date and not comment:
+ # At least one needs to be set!
+ self.add_error(
+ "comment",
+ _("If there is no date you can enter, please explain why.")
+ )
+ return False
+ return super_valid
+
+ def save(self):
+ pay = self.instance.add_payment(self)
+ 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)
+ self.form_title = _("Edit payment")
+ 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)
+ self.instance.send_data_to_egon()
+ return payment
+
+
+class RemovePaymentModalForm(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)
diff --git a/compensation/forms/modals/state.py b/compensation/forms/modals/state.py
new file mode 100644
index 00000000..f0c42213
--- /dev/null
+++ b/compensation/forms/modals/state.py
@@ -0,0 +1,179 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+from bootstrap_modal_forms.utils import is_ajax
+from dal import autocomplete
+from django import forms
+from django.contrib import messages
+from django.http import HttpResponseRedirect, HttpRequest
+from django.shortcuts import render
+from django.utils.translation import gettext_lazy as _
+
+from codelist.models import KonovaCode
+from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID
+from intervention.inputs import CompensationStateTreeRadioSelect
+from konova.contexts import BaseContext
+from konova.forms.modals import RemoveModalForm, BaseModalForm
+from konova.utils.message_templates import COMPENSATION_STATE_EDITED, FORM_INVALID, ADDED_COMPENSATION_STATE
+
+
+class NewCompensationStateModalForm(BaseModalForm):
+ """ Form handling state related input
+
+ Compensation states refer to 'before' and 'after' states of a compensated surface. Basically it means:
+ What has been on this area before changes/compensations have been applied and what will be the result ('after')?
+
+ """
+ biotope_type = forms.ChoiceField(
+ label=_("Biotope Type"),
+ label_suffix="",
+ required=True,
+ help_text=_("Select the biotope type"),
+ widget=CompensationStateTreeRadioSelect(),
+ )
+ biotope_extra = forms.ModelMultipleChoiceField(
+ label=_("Biotope additional type"),
+ label_suffix="",
+ required=False,
+ help_text=_("Select an additional biotope type"),
+ queryset=KonovaCode.objects.filter(
+ is_archived=False,
+ is_leaf=True,
+ code_lists__in=[CODELIST_BIOTOPES_EXTRA_CODES_ID],
+ ),
+ widget=autocomplete.ModelSelect2Multiple(
+ url="codelist:biotope-extra-type-autocomplete",
+ attrs={
+ "data-placeholder": _("Biotope additional type"),
+ }
+ ),
+ )
+ surface = forms.DecimalField(
+ min_value=0.00,
+ decimal_places=2,
+ label=_("Surface"),
+ label_suffix="",
+ required=True,
+ help_text=_("in m²"),
+ widget=forms.NumberInput(
+ attrs={
+ "class": "form-control",
+ "placeholder": "0,00"
+ }
+ )
+ )
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.form_title = _("New state")
+ self.form_caption = _("Insert data for the new state")
+ choices = KonovaCode.objects.filter(
+ code_lists__in=[CODELIST_BIOTOPES_ID],
+ is_archived=False,
+ is_leaf=True,
+ ).values_list("id", flat=True)
+ choices = [
+ (choice, choice)
+ for choice in choices
+ ]
+ self.fields["biotope_type"].choices = choices
+
+ def save(self, is_before_state: bool = False):
+ state = self.instance.add_state(self, is_before_state)
+ self.instance.mark_as_edited(self.user, self.request, ADDED_COMPENSATION_STATE)
+ return state
+
+ def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
+ """ Generic processing of request
+
+ Wraps the request processing logic, so we don't need the same code everywhere a RemoveModalForm is being used
+
+ +++
+ The generic method from super class can not be used, since we need to do some request parameter check in here.
+ +++
+
+ Args:
+ request (HttpRequest): The incoming request
+ msg_success (str): The message in case of successful removing
+ msg_error (str): The message in case of an error
+
+ Returns:
+
+ """
+ redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
+ template = self.template
+ if request.method == "POST":
+ if self.is_valid():
+ # Modal forms send one POST for checking on data validity. This can be used to return possible errors
+ # on the form. A second POST (if no errors occured) is sent afterwards and needs to process the
+ # saving/commiting of the data to the database. is_ajax() performs this check. The first request is
+ # an ajax call, the second is a regular form POST.
+ if not is_ajax(request.META):
+ is_before_state = bool(request.GET.get("before", False))
+ self.save(is_before_state=is_before_state)
+ messages.success(
+ request,
+ msg_success
+ )
+ return HttpResponseRedirect(redirect_url)
+ else:
+ context = {
+ "form": self,
+ }
+ context = BaseContext(request, context).context
+ return render(request, template, context)
+ elif request.method == "GET":
+ context = {
+ "form": self,
+ }
+ context = BaseContext(request, context).context
+ return render(request, template, context)
+ else:
+ raise NotImplementedError
+
+
+class EditCompensationStateModalForm(NewCompensationStateModalForm):
+ state = None
+
+ def __init__(self, *args, **kwargs):
+ self.state = kwargs.pop("state", None)
+ super().__init__(*args, **kwargs)
+ self.form_title = _("Edit state")
+ biotope_type_id = self.state.biotope_type.id if self.state.biotope_type else None
+ form_data = {
+ "biotope_type": biotope_type_id,
+ "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
+ biotope_type_id = self.cleaned_data.get("biotope_type", None)
+ state.biotope_type = KonovaCode.objects.get(id=biotope_type_id)
+ 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
+
+ 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)
diff --git a/compensation/models/eco_account.py b/compensation/models/eco_account.py
index 42a202f7..dc9e336f 100644
--- a/compensation/models/eco_account.py
+++ b/compensation/models/eco_account.py
@@ -160,7 +160,7 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
Returns:
"""
- return reverse("compensation:acc:share", args=(self.id, self.access_token))
+ return reverse("compensation:acc:share-token", args=(self.id, self.access_token))
def send_notification_mail_on_deduction_change(self, data_change: dict):
""" Sends notification mails for changes on the deduction
diff --git a/compensation/tables/__init__.py b/compensation/tables/__init__.py
new file mode 100644
index 00000000..ca978536
--- /dev/null
+++ b/compensation/tables/__init__.py
@@ -0,0 +1,7 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
diff --git a/compensation/tables.py b/compensation/tables/compensation.py
similarity index 54%
rename from compensation/tables.py
rename to compensation/tables/compensation.py
index 1373f2cd..032ab67e 100644
--- a/compensation/tables.py
+++ b/compensation/tables/compensation.py
@@ -1,19 +1,19 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
-Contact: michel.peltriaux@sgdnord.rlp.de
-Created on: 01.12.20
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
"""
-from konova.utils.message_templates import DATA_IS_UNCHECKED, DATA_CHECKED_ON_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE
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.translation import gettext_lazy as _
-from compensation.filters import CompensationTableFilter, EcoAccountTableFilter
-from compensation.models import Compensation, EcoAccount
+from compensation.filters.compensation import CompensationTableFilter
+from compensation.models import Compensation
+from konova.utils.message_templates import DATA_IS_UNCHECKED, DATA_CHECKED_ON_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE
from konova.utils.tables import BaseTable, TableRenderMixin
import django_tables2 as tables
@@ -187,160 +187,3 @@ class CompensationTable(BaseTable, TableRenderMixin):
icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",
)
return format_html(html)
-
-
-class EcoAccountTable(BaseTable, TableRenderMixin):
- id = tables.Column(
- verbose_name=_("Identifier"),
- orderable=True,
- accessor="identifier",
- )
- t = tables.Column(
- verbose_name=_("Title"),
- 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"),
- orderable=True,
- empty_values=[],
- accessor="recorded",
- )
- e = tables.Column(
- verbose_name=_("Editable"),
- orderable=True,
- empty_values=[],
- accessor="users",
- )
- lm = tables.Column(
- verbose_name=_("Last edit"),
- orderable=True,
- accessor="modified__timestamp",
- )
-
- class Meta(BaseTable.Meta):
- template_name = "django_tables2/bootstrap4.html"
-
- def __init__(self, request: HttpRequest, *args, **kwargs):
- self.title = _("Eco Accounts")
- self.add_new_url = reverse("compensation:acc:new")
- qs = kwargs.get("queryset", None)
- self.filter = EcoAccountTableFilter(
- user=request.user,
- data=request.GET,
- queryset=qs,
- )
- kwargs["queryset"] = self.filter.qs
- super().__init__(request, *args, **kwargs)
-
- def render_id(self, value, record: EcoAccount):
- """ Renders the id column for an eco account
-
- Args:
- value (str): The identifier value
- record (EcoAccount): The eco account record
-
- Returns:
-
- """
- html = ""
- html += self.render_link(
- tooltip=_("Open {}").format(_("Eco-account")),
- href=reverse("compensation:acc:detail", args=(record.id,)),
- txt=value,
- new_tab=False,
- )
- return format_html(html)
-
- def render_av(self, value, record: EcoAccount):
- """ Renders the available column for an eco account
-
- Args:
- value (str): The identifier value
- record (EcoAccount): The eco account record
-
- Returns:
-
- """
- value_total, value_relative = record.get_available_rest()
- 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.get_underlying_parcels().values_list(
- "parcel_group__name",
- 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
-
- Args:
- value (str): The identifier value
- record (EcoAccount): The eco account record
-
- Returns:
-
- """
- html = ""
- checked = value is not None
- tooltip = _("Not recorded yet. Can not be used for deductions, yet.")
- if checked:
- on = value.get_timestamp_str_formatted()
- tooltip = _("Recorded on {} by {}").format(on, record.recorded.user)
- html += self.render_bookmark(
- tooltip=tooltip,
- icn_filled=checked,
- )
- return format_html(html)
-
- def render_e(self, value, record: EcoAccount):
- """ Renders the editable column for an eco account
-
- Args:
- value (str): The identifier value
- record (EcoAccount): The eco account record
-
- Returns:
-
- """
- html = ""
- # Do not use value in here, since value does use unprefetched 'users' manager, where record has already
- # prefetched users data
- has_access = record.is_shared_with(self.user)
- html += self.render_icn(
- tooltip=_("Full access granted") if has_access else _("Access not granted"),
- icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",
- )
- return format_html(html)
diff --git a/compensation/tables/eco_account.py b/compensation/tables/eco_account.py
new file mode 100644
index 00000000..17b8ecaa
--- /dev/null
+++ b/compensation/tables/eco_account.py
@@ -0,0 +1,175 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 18.08.22
+
+"""
+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.translation import gettext_lazy as _
+
+from compensation.filters.eco_account import EcoAccountTableFilter
+from compensation.models import EcoAccount
+from konova.utils.tables import TableRenderMixin, BaseTable
+
+import django_tables2 as tables
+
+
+class EcoAccountTable(BaseTable, TableRenderMixin):
+ id = tables.Column(
+ verbose_name=_("Identifier"),
+ orderable=True,
+ accessor="identifier",
+ )
+ t = tables.Column(
+ verbose_name=_("Title"),
+ 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"),
+ orderable=True,
+ empty_values=[],
+ accessor="recorded",
+ )
+ e = tables.Column(
+ verbose_name=_("Editable"),
+ orderable=True,
+ empty_values=[],
+ accessor="users",
+ )
+ lm = tables.Column(
+ verbose_name=_("Last edit"),
+ orderable=True,
+ accessor="modified__timestamp",
+ )
+
+ class Meta(BaseTable.Meta):
+ template_name = "django_tables2/bootstrap4.html"
+
+ def __init__(self, request: HttpRequest, *args, **kwargs):
+ self.title = _("Eco Accounts")
+ self.add_new_url = reverse("compensation:acc:new")
+ qs = kwargs.get("queryset", None)
+ self.filter = EcoAccountTableFilter(
+ user=request.user,
+ data=request.GET,
+ queryset=qs,
+ )
+ kwargs["queryset"] = self.filter.qs
+ super().__init__(request, *args, **kwargs)
+
+ def render_id(self, value, record: EcoAccount):
+ """ Renders the id column for an eco account
+
+ Args:
+ value (str): The identifier value
+ record (EcoAccount): The eco account record
+
+ Returns:
+
+ """
+ html = ""
+ html += self.render_link(
+ tooltip=_("Open {}").format(_("Eco-account")),
+ href=reverse("compensation:acc:detail", args=(record.id,)),
+ txt=value,
+ new_tab=False,
+ )
+ return format_html(html)
+
+ def render_av(self, value, record: EcoAccount):
+ """ Renders the available column for an eco account
+
+ Args:
+ value (str): The identifier value
+ record (EcoAccount): The eco account record
+
+ Returns:
+
+ """
+ value_total, value_relative = record.get_available_rest()
+ html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
+ return format_html(html)
+
+ def render_d(self, value, record):
+ """ Renders the parcel district column for a compensation
+
+ Args:
+ value (str): The geometry
+ record (Compensation): The compensation record
+
+ Returns:
+
+ """
+ parcels = value.get_underlying_parcels().values_list(
+ "parcel_group__name",
+ 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
+
+ Args:
+ value (str): The identifier value
+ record (EcoAccount): The eco account record
+
+ Returns:
+
+ """
+ html = ""
+ checked = value is not None
+ tooltip = _("Not recorded yet. Can not be used for deductions, yet.")
+ if checked:
+ on = value.get_timestamp_str_formatted()
+ tooltip = _("Recorded on {} by {}").format(on, record.recorded.user)
+ html += self.render_bookmark(
+ tooltip=tooltip,
+ icn_filled=checked,
+ )
+ return format_html(html)
+
+ def render_e(self, value, record: EcoAccount):
+ """ Renders the editable column for an eco account
+
+ Args:
+ value (str): The identifier value
+ record (EcoAccount): The eco account record
+
+ Returns:
+
+ """
+ html = ""
+ # Do not use value in here, since value does use unprefetched 'users' manager, where record has already
+ # prefetched users data
+ has_access = record.is_shared_with(self.user)
+ html += self.render_icn(
+ tooltip=_("Full access granted") if has_access else _("Access not granted"),
+ icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",
+ )
+ return format_html(html)
diff --git a/compensation/templates/compensation/detail/eco_account/includes/controls.html b/compensation/templates/compensation/detail/eco_account/includes/controls.html
index 42ce6067..c26883aa 100644
--- a/compensation/templates/compensation/detail/eco_account/includes/controls.html
+++ b/compensation/templates/compensation/detail/eco_account/includes/controls.html
@@ -15,7 +15,7 @@
-