Merge branch 'master' into 132_Old_entries

# Conflicts:
#	codelist/models.py
#	compensation/forms/forms.py
#	konova/autocompletes.py
#	konova/models/geometry.py
#	konova/urls.py
#	konova/utils/wfs/spatial.py
This commit is contained in:
mpeltriaux 2022-10-13 07:02:41 +02:00
commit 0bd6892583
260 changed files with 16581 additions and 8001 deletions

View File

@ -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")
}

View File

@ -125,11 +125,11 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
obj.log.add(obj.created)
obj.log.add(obj.created)
celery_update_parcels.delay(obj.geometry.id)
celery_update_parcels.delay(obj.geometry.id)
return obj.id
return obj.id
def update_model_from_json(self, id, json_model, user):
""" Updates an entry for the model based on the contents of json_model
@ -165,8 +165,8 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
obj.log.add(update_action)
obj.log.add(update_action)
celery_update_parcels.delay(obj.geometry.id)
celery_update_parcels.delay(obj.geometry.id)
return obj.id
return obj.id

View File

@ -146,12 +146,12 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
obj.log.add(obj.created)
obj.users.add(user)
obj.log.add(obj.created)
obj.users.add(user)
celery_update_parcels.delay(obj.geometry.id)
celery_update_parcels.delay(obj.geometry.id)
return obj.id
return obj.id
def update_model_from_json(self, id, json_model, user):
""" Updates an entry for the model based on the contents of json_model
@ -190,8 +190,8 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
obj.log.add(update_action)
obj.log.add(update_action)
celery_update_parcels.delay(obj.geometry.id)
celery_update_parcels.delay(obj.geometry.id)
return obj.id
return obj.id

View File

@ -118,12 +118,12 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
obj.log.add(obj.created)
obj.users.add(user)
obj.log.add(obj.created)
obj.users.add(user)
celery_update_parcels.delay(obj.geometry.id)
celery_update_parcels.delay(obj.geometry.id)
return obj.id
return obj.id
def update_model_from_json(self, id, json_model, user):
""" Updates an entry for the model based on the contents of json_model
@ -159,8 +159,8 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
obj.log.add(update_action)
obj.log.add(update_action)
celery_update_parcels.delay(obj.geometry.id)
celery_update_parcels.delay(obj.geometry.id)
return obj.id
return obj.id

View File

@ -161,12 +161,12 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
obj.legal.save()
obj.save()
obj.users.add(user)
obj.log.add(obj.created)
obj.users.add(user)
obj.log.add(obj.created)
celery_update_parcels.delay(obj.geometry.id)
celery_update_parcels.delay(obj.geometry.id)
return obj.id
return obj.id
def update_model_from_json(self, id, json_model, user):
""" Updates an entry for the model based on the contents of json_model
@ -198,8 +198,8 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
obj.legal.save()
obj.save()
obj.mark_as_edited(user, edit_comment="API update")
obj.mark_as_edited(user, edit_comment="API update")
celery_update_parcels.delay(obj.geometry.id)
celery_update_parcels.delay(obj.geometry.id)
return obj.id
return obj.id

View File

@ -9,6 +9,7 @@ Created on: 24.01.22
import json
from django.contrib.gis.geos import MultiPolygon
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import QuerySet
from api.utils.serializer.serializer import AbstractModelAPISerializer
@ -80,10 +81,14 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
json_str = str(json_str)
if len(json_str) == 0:
return None
code = KonovaCode.objects.get(
atom_id=json_str,
code_lists__in=[code_list_identifier]
)
try:
code = KonovaCode.objects.get(
atom_id=json_str,
code_lists__in=[code_list_identifier]
)
except ObjectDoesNotExist as e:
msg = f"{e.args[0]} ({json_str} not found in official list {code_list_identifier})"
raise ObjectDoesNotExist(msg)
return code
def _created_on_to_json(self, entry):

View File

@ -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
"""

View File

@ -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__()}"

View File

@ -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})"

View File

@ -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"
)

View File

@ -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

View File

@ -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})"

View File

@ -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})"

View File

@ -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)

View File

@ -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 or self.parent.short_name) + " > "
ret_val += self.long_name or ""
if self.short_name and self.short_name != self.long_name:

View File

@ -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"),
]

View File

@ -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
"""

View File

@ -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

View File

@ -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
"""

View File

@ -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

View File

@ -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

View File

@ -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
"""

View File

@ -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

View File

@ -0,0 +1,230 @@
"""
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 konova.forms.modals import RemoveModalForm
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)
acc.update_deductable_rest()
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)
self.instance.update_deductable_rest()
return self.instance
class RemoveEcoAccountModalForm(RemoveModalForm):
def is_valid(self):
super_valid = super().is_valid()
has_deductions = self.instance.deductions.exists()
if has_deductions:
self.add_error(
"confirm",
_("The account can not be removed, since there are still deductions.")
)
return super_valid and not has_deductions

View File

@ -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, CODELIST_COMPENSATION_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_COMPENSATION_HANDLER_ID],
),
widget=autocomplete.ModelSelect2(
url="codes-compensation-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

View File

@ -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()
)

View File

@ -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 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

View File

@ -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
"""

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,24 @@
# Generated by Django 3.1.3 on 2022-08-15 06:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('compensation', '0007_auto_20220531_1245'),
]
operations = [
migrations.AddField(
model_name='compensation',
name='resubmission',
field=models.ManyToManyField(blank=True, null=True, related_name='_compensation_resubmission_+', to='konova.Resubmission'),
),
migrations.AddField(
model_name='ecoaccount',
name='resubmission',
field=models.ManyToManyField(blank=True, null=True, related_name='_ecoaccount_resubmission_+', to='konova.Resubmission'),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 3.1.3 on 2022-08-15 06:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('compensation', '0008_auto_20220815_0803'),
]
operations = [
migrations.RemoveField(
model_name='compensation',
name='resubmission',
),
migrations.RemoveField(
model_name='ecoaccount',
name='resubmission',
),
migrations.AddField(
model_name='compensation',
name='resubmissions',
field=models.ManyToManyField(blank=True, null=True, related_name='_compensation_resubmissions_+', to='konova.Resubmission'),
),
migrations.AddField(
model_name='ecoaccount',
name='resubmissions',
field=models.ManyToManyField(blank=True, null=True, related_name='_ecoaccount_resubmissions_+', to='konova.Resubmission'),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.1.3 on 2022-08-15 08:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('compensation', '0009_auto_20220815_0803'),
]
operations = [
migrations.AlterField(
model_name='compensation',
name='resubmissions',
field=models.ManyToManyField(blank=True, related_name='_compensation_resubmissions_+', to='konova.Resubmission'),
),
migrations.AlterField(
model_name='ecoaccount',
name='resubmissions',
field=models.ManyToManyField(blank=True, related_name='_ecoaccount_resubmissions_+', to='konova.Resubmission'),
),
]

View File

@ -0,0 +1,36 @@
# Generated by Django 3.1.3 on 2022-10-11 11:39
from django.db import migrations, models
from django.db.models import Sum
def fill_deductable_rest(apps, schema_editor):
EcoAccount = apps.get_model("compensation", "EcoAccount")
accs = EcoAccount.objects.all()
for acc in accs:
deductions = acc.deductions.filter(
intervention__deleted=None,
)
deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0
available_surfaces = acc.deductable_surface or deductions_surfaces
rest = available_surfaces - deductions_surfaces
acc.deductable_rest = rest
acc.save()
class Migration(migrations.Migration):
dependencies = [
('compensation', '0010_auto_20220815_1030'),
]
operations = [
migrations.AddField(
model_name='ecoaccount',
name='deductable_rest',
field=models.FloatField(blank=True, default=0, help_text='Amount of deductable rest', null=True),
),
migrations.RunPython(fill_deductable_rest)
]

View File

@ -14,21 +14,22 @@ from user.models import User, Team
from django.db import models, transaction
from django.db.models import QuerySet, Sum
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from compensation.managers import CompensationManager
from compensation.models import CompensationState, CompensationAction
from compensation.utils.quality import CompensationQualityChecker
from konova.models import BaseObject, AbstractDocument, Deadline, generate_document_file_upload_path, \
GeoReferencedMixin
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
GeoReferencedMixin, DeadlineType, ResubmitableObjectMixin
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, COMPENSATION_REMOVED_TEMPLATE, \
DOCUMENT_REMOVED_TEMPLATE, COMPENSATION_EDITED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE, \
DOCUMENT_REMOVED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE, \
COMPENSATION_ACTION_REMOVED, COMPENSATION_STATE_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
from user.models import UserActionLogEntry
class AbstractCompensation(BaseObject, GeoReferencedMixin):
class AbstractCompensation(BaseObject,
GeoReferencedMixin,
ResubmitableObjectMixin
):
"""
Abstract compensation model which holds basic attributes, shared by subclasses like the regular Compensation,
EMA or EcoAccount.
@ -226,6 +227,15 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
request = self.set_geometry_conflict_message(request)
return request
def get_finished_deadlines(self):
""" Getter for FINISHED-deadlines
Returns:
queryset (QuerySet): The finished deadlines
"""
return self.deadlines.filter(
type=DeadlineType.FINISHED
)
class CEFMixin(models.Model):
""" Provides CEF flag as Mixin

View File

@ -21,6 +21,7 @@ from compensation.models.compensation import AbstractCompensation, PikMixin
from compensation.utils.quality import EcoAccountQualityChecker
from konova.models import ShareableObjectMixin, RecordableObjectMixin, AbstractDocument, BaseResource, \
generate_document_file_upload_path
from konova.tasks import celery_send_mail_deduction_changed, celery_send_mail_deduction_changed_team
class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin, PikMixin):
@ -34,6 +35,12 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
help_text="Amount of deductable surface - can be lower than the total surface due to deduction limitations",
default=0,
)
deductable_rest = models.FloatField(
blank=True,
null=True,
help_text="Amount of deductable rest",
default=0,
)
legal = models.OneToOneField(
"intervention.Legal",
@ -99,28 +106,22 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
"""
return self.after_states.all().aggregate(Sum("surface"))["surface__sum"] or 0
def get_available_rest(self) -> (float, float):
def __calculate_deductable_rest(self):
""" Calculates available rest surface of the eco account
Args:
Returns:
ret_val_total (float): Total amount
ret_val_relative (float): Amount as percentage (0-100)
"""
deductions = self.deductions.filter(
intervention__deleted=None,
)
deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0
available_surfaces = self.deductable_surface or deductions_surfaces ## no division by zero
ret_val_total = available_surfaces - deductions_surfaces
ret_val = available_surfaces - deductions_surfaces
if available_surfaces > 0:
ret_val_relative = int((ret_val_total / available_surfaces) * 100)
else:
ret_val_relative = 0
return ret_val_total, ret_val_relative
return ret_val
def quality_check(self) -> EcoAccountQualityChecker:
""" Quality check
@ -159,7 +160,49 @@ 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
Args:
data_change ():
Returns:
"""
# Send mail
shared_users = self.shared_users.values_list("id", flat=True)
for user_id in shared_users:
celery_send_mail_deduction_changed.delay(self.identifier, self.title, user_id, data_change)
# Send mail
shared_teams = self.shared_teams.values_list("id", flat=True)
for team_id in shared_teams:
celery_send_mail_deduction_changed_team.delay(self.identifier, self.title, team_id, data_change)
def update_deductable_rest(self):
"""
Updates deductable_rest, which holds the amount of rest surface for this account.
Returns:
"""
self.deductable_rest = self.__calculate_deductable_rest()
self.save()
def get_deductable_rest_relative(self):
"""
Returns deductable_rest relative to deductable_surface mapped to [0,100]
Returns:
"""
try:
ret_val = int((self.deductable_rest / (self.deductable_surface or 0)) * 100)
except ZeroDivisionError:
ret_val = 0
return ret_val
class EcoAccountDocument(AbstractDocument):
@ -252,3 +295,8 @@ class EcoAccountDeduction(BaseResource):
self.intervention.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
self.account.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
super().delete(*args, **kwargs)
self.account.update_deductable_rest()
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.account.update_deductable_rest()

View File

@ -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
"""

View File

@ -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)

View File

@ -0,0 +1,178 @@
"""
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,
accessor="deductable_rest",
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 (float): The deductable_rest
record (EcoAccount): The eco account record
Returns:
"""
try:
value_relative = record.get_deductable_rest_relative()
except ZeroDivisionError:
value_relative = 0
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)

View File

@ -74,6 +74,10 @@
{% endif %}
</td>
</tr>
{% empty %}
<div class="alert alert-danger mb-0">
{% trans 'Missing' %}
</div>
{% endfor %}
</tbody>
</table>

View File

@ -12,6 +12,9 @@
</button>
</a>
{% if has_access %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:resubmission-create' obj.id %}">
{% fa5_icon 'bell' %}
</button>
{% if is_default_member %}
<a href="{% url 'compensation:edit' obj.id %}" class="mr-2">
<button class="btn btn-default" title="{% trans 'Edit' %}">

View File

@ -20,6 +20,11 @@
</div>
</div>
</div>
{% if not has_finished_deadlines %}
<div class="alert alert-danger mb-0">
{% trans 'Missing finished deadline ' %}
</div>
{% endif %}
<div class="card-body scroll-300 p-2">
<table class="table table-hover">
<thead>

View File

@ -68,6 +68,10 @@
{% endif %}
</td>
</tr>
{% empty %}
<div class="alert alert-danger mb-0">
{% trans 'Missing' %}
</div>
{% endfor %}
</tbody>
</table>

View File

@ -68,6 +68,10 @@
{% endif %}
</td>
</tr>
{% empty %}
<div class="alert alert-danger mb-0">
{% trans 'Missing' %}
</div>
{% endfor %}
</tbody>
</table>

View File

@ -73,6 +73,10 @@
{% endif %}
</td>
</tr>
{% empty %}
<div class="alert alert-danger mb-0">
{% trans 'Missing' %}
</div>
{% endfor %}
</tbody>
</table>

View File

@ -12,7 +12,10 @@
</button>
</a>
{% if has_access %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'compensation:acc:share-create' obj.id %}">
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:acc:resubmission-create' obj.id %}">
{% fa5_icon 'bell' %}
</button>
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'compensation:acc:share-form' obj.id %}">
{% fa5_icon 'share-alt' %}
</button>
{% if is_ets_member %}

View File

@ -20,6 +20,11 @@
</div>
</div>
</div>
{% if not has_finished_deadlines %}
<div class="alert alert-danger mb-0">
{% trans 'Missing finished deadline ' %}
</div>
{% endif %}
<div class="card-body scroll-300 p-2">
<table class="table table-hover">
<thead>

View File

@ -68,6 +68,10 @@
{% endif %}
</td>
</tr>
{% empty %}
<div class="alert alert-danger mb-0">
{% trans 'Missing' %}
</div>
{% endfor %}
</tbody>
</table>

View File

@ -68,6 +68,10 @@
{% endif %}
</td>
</tr>
{% empty %}
<div class="alert alert-danger mb-0">
{% trans 'Missing' %}
</div>
{% endfor %}
</tbody>
</table>

View File

@ -47,7 +47,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
"identifier": test_id,
"title": test_title,
"geom": geom_json,
"deductable_surface": test_deductable_surface,
"surface": test_deductable_surface,
"conservation_office": test_conservation_office.id
}
self.client_user.post(new_url, post_data)
@ -61,6 +61,8 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
self.assertEqual(acc.identifier, test_id)
self.assertEqual(acc.title, test_title)
self.assertEqual(acc.deductable_surface, test_deductable_surface)
self.assertEqual(acc.deductable_rest, test_deductable_surface)
self.assert_equal_geometries(acc.geometry.geom, test_geom)
self.assertEqual(acc.log.count(), 1)
@ -84,7 +86,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
new_comment = self.create_dummy_string()
new_geometry = MultiPolygon(srid=4326) # Create an empty geometry
test_conservation_office = self.get_conservation_office_code()
test_deductable_surface = 10005
test_deductable_surface = self.eco_account.deductable_surface + 100
check_on_elements = {
self.eco_account.title: new_title,
@ -110,6 +112,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
self.eco_account.title: new_title,
self.eco_account.identifier: new_identifier,
self.eco_account.deductable_surface: test_deductable_surface,
self.eco_account.deductable_rest: test_deductable_surface,
self.eco_account.comment: new_comment,
}
@ -194,14 +197,14 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
# Perform request --> expect to fail
self.client_user.post(deduct_url, post_data)
# Expect that no deduction has been created
# Expect that no deduction has been created since the eco account is not recorded, yet
self.assertEqual(0, self.eco_account.deductions.count())
self.assertEqual(0, self.intervention.deductions.count())
self.assertEqual(pre_deduction_acc_log_count, 0)
self.assertEqual(pre_deduction_int_log_count, 0)
# Now mock the eco account as it would be recorded (with invalid data)
# Make sure the deductible surface is high enough for the request
# Make sure the deductible surface is valid for the request
self.eco_account.set_recorded(self.superuser)
self.eco_account.refresh_from_db()
self.eco_account.deductable_surface = test_surface + 1.00
@ -216,10 +219,12 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
self.client_user.post(deduct_url, post_data)
# Expect that the deduction has been created
self.eco_account.refresh_from_db()
self.assertEqual(1, self.eco_account.deductions.count())
self.assertEqual(1, self.intervention.deductions.count())
deduction = self.eco_account.deductions.first()
self.assertEqual(deduction.surface, test_surface)
self.assertEqual(self.eco_account.deductable_rest, self.eco_account.deductable_surface - deduction.surface)
self.assertEqual(deduction.account, self.eco_account)
self.assertEqual(deduction.intervention, self.intervention)
@ -230,7 +235,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
self.assertTrue(self.intervention.log.first().action == UserAction.EDITED)
def test_edit_deduction(self):
test_surface = self.eco_account.get_available_rest()[0]
test_surface = self.eco_account.deductable_rest
self.eco_account.set_recorded(self.superuser)
self.intervention.share_with_user(self.superuser)
self.eco_account.refresh_from_db()
@ -262,6 +267,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
self.eco_account.refresh_from_db()
deduction.refresh_from_db()
self.assertEqual(self.eco_account.deductable_rest, self.eco_account.deductable_surface - deduction.surface)
self.assertEqual(num_deductions_intervention, self.intervention.deductions.count())
self.assertEqual(num_deductions_account, self.eco_account.deductions.count())
self.assertEqual(deduction.surface, test_surface)
@ -275,6 +281,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
def test_remove_deduction(self):
intervention = self.deduction.intervention
account = self.deduction.account
deducted_surface = self.deduction.surface
# Prepare url and form data to be posted
new_url = reverse("compensation:acc:remove-deduction", args=(account.id, self.deduction.id))
@ -287,6 +294,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
pre_edit_intervention_log_count = intervention.log.count()
pre_edit_account_log_count = account.log.count()
pre_edit_account_rest = account.deductable_rest
num_deductions_intervention = intervention.deductions.count()
num_deductions_account = account.deductions.count()
@ -297,6 +305,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
self.assertEqual(num_deductions_intervention - 1, intervention.deductions.count())
self.assertEqual(num_deductions_account - 1, account.deductions.count())
self.assertEqual(account.deductable_rest, pre_edit_account_rest + deducted_surface)
# Expect logs to be set
self.assertEqual(pre_edit_intervention_log_count + 1, intervention.log.count())

View File

@ -6,7 +6,20 @@ Created on: 24.08.21
"""
from django.urls import path
from compensation.views.compensation import *
from compensation.views.compensation.document import EditCompensationDocumentView, NewCompensationDocumentView, \
GetCompensationDocumentView, RemoveCompensationDocumentView
from compensation.views.compensation.resubmission import CompensationResubmissionView
from compensation.views.compensation.report import report_view
from compensation.views.compensation.deadline import NewCompensationDeadlineView, EditCompensationDeadlineView, \
RemoveCompensationDeadlineView
from compensation.views.compensation.action import NewCompensationActionView, EditCompensationActionView, \
RemoveCompensationActionView
from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \
RemoveCompensationStateView
from compensation.views.compensation.compensation import index_view, new_view, new_id_view, detail_view, edit_view, \
remove_view
from compensation.views.compensation.log import CompensationLogView
urlpatterns = [
# Main compensation
@ -15,27 +28,28 @@ urlpatterns = [
path('new/<intervention_id>', new_view, name='new'),
path('new', new_view, name='new'),
path('<id>', detail_view, name='detail'),
path('<id>/log', log_view, name='log'),
path('<id>/log', CompensationLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/state/new', state_new_view, name='new-state'),
path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'),
path('<id>/state/<state_id>/edit', EditCompensationStateView.as_view(), name='state-edit'),
path('<id>/state/<state_id>/remove', RemoveCompensationStateView.as_view(), name='state-remove'),
path('<id>/action/new', action_new_view, name='new-action'),
path('<id>/action/<action_id>/edit', action_edit_view, name='action-edit'),
path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
path('<id>/action/new', NewCompensationActionView.as_view(), name='new-action'),
path('<id>/action/<action_id>/edit', EditCompensationActionView.as_view(), name='action-edit'),
path('<id>/action/<action_id>/remove', RemoveCompensationActionView.as_view(), name='action-remove'),
path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
path('<id>/deadline/new', NewCompensationDeadlineView.as_view(), name="new-deadline"),
path('<id>/deadline/<deadline_id>/edit', EditCompensationDeadlineView.as_view(), name='deadline-edit'),
path('<id>/deadline/<deadline_id>/remove', RemoveCompensationDeadlineView.as_view(), name='deadline-remove'),
path('<id>/report', report_view, name='report'),
path('<id>/resub', CompensationResubmissionView.as_view(), name='resubmission-create'),
# Documents
path('<id>/document/new/', new_document_view, name='new-doc'),
path('<id>/document/<doc_id>', get_document_view, name='get-doc'),
path('<id>/document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
path('<id>/document/<doc_id>/edit/', edit_document_view, name='edit-doc'),
path('<id>/document/new/', NewCompensationDocumentView.as_view(), name='new-doc'),
path('<id>/document/<doc_id>', GetCompensationDocumentView.as_view(), name='get-doc'),
path('<id>/document/<doc_id>/remove/', RemoveCompensationDocumentView.as_view(), name='remove-doc'),
path('<id>/document/<doc_id>/edit/', EditCompensationDocumentView.as_view(), name='edit-doc'),
]

View File

@ -6,7 +6,25 @@ Created on: 24.08.21
"""
from django.urls import path
from compensation.views.eco_account import *
from compensation.autocomplete.eco_account import EcoAccountAutocomplete
from compensation.views.eco_account.eco_account import index_view, new_view, new_id_view, edit_view, remove_view, \
detail_view
from compensation.views.eco_account.log import EcoAccountLogView
from compensation.views.eco_account.record import EcoAccountRecordView
from compensation.views.eco_account.report import report_view
from compensation.views.eco_account.resubmission import EcoAccountResubmissionView
from compensation.views.eco_account.state import NewEcoAccountStateView, EditEcoAccountStateView, \
RemoveEcoAccountStateView
from compensation.views.eco_account.action import NewEcoAccountActionView, EditEcoAccountActionView, \
RemoveEcoAccountActionView
from compensation.views.eco_account.deadline import NewEcoAccountDeadlineView, EditEcoAccountDeadlineView, \
RemoveEcoAccountDeadlineView
from compensation.views.eco_account.share import EcoAccountShareByTokenView, EcoAccountShareFormView
from compensation.views.eco_account.document import GetEcoAccountDocumentView, NewEcoAccountDocumentView, \
EditEcoAccountDocumentView, RemoveEcoAccountDocumentView
from compensation.views.eco_account.deduction import NewEcoAccountDeductionView, EditEcoAccountDeductionView, \
RemoveEcoAccountDeductionView
app_name = "acc"
urlpatterns = [
@ -14,36 +32,39 @@ urlpatterns = [
path('new/', new_view, name='new'),
path('new/id', new_id_view, name='new-id'),
path('<id>', detail_view, name='detail'),
path('<id>/log', log_view, name='log'),
path('<id>/record', record_view, name='record'),
path('<id>/log', EcoAccountLogView.as_view(), name='log'),
path('<id>/record', EcoAccountRecordView.as_view(), name='record'),
path('<id>/report', report_view, name='report'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'),
path('<id>/state/new', state_new_view, name='new-state'),
path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
path('<id>/state/new', NewEcoAccountStateView.as_view(), name='new-state'),
path('<id>/state/<state_id>/edit', EditEcoAccountStateView.as_view(), name='state-edit'),
path('<id>/state/<state_id>/remove', RemoveEcoAccountStateView.as_view(), name='state-remove'),
path('<id>/action/new', action_new_view, name='new-action'),
path('<id>/action/<action_id>/edit', action_edit_view, name='action-edit'),
path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
path('<id>/action/new', NewEcoAccountActionView.as_view(), name='new-action'),
path('<id>/action/<action_id>/edit', EditEcoAccountActionView.as_view(), name='action-edit'),
path('<id>/action/<action_id>/remove', RemoveEcoAccountActionView.as_view(), name='action-remove'),
path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
path('<id>/deadline/new', NewEcoAccountDeadlineView.as_view(), name="new-deadline"),
path('<id>/deadline/<deadline_id>/edit', EditEcoAccountDeadlineView.as_view(), name='deadline-edit'),
path('<id>/deadline/<deadline_id>/remove', RemoveEcoAccountDeadlineView.as_view(), name='deadline-remove'),
path('<id>/share/<token>', share_view, name='share'),
path('<id>/share', create_share_view, name='share-create'),
path('<id>/share/<token>', EcoAccountShareByTokenView.as_view(), name='share-token'),
path('<id>/share', EcoAccountShareFormView.as_view(), name='share-form'),
# Documents
path('<id>/document/new/', new_document_view, name='new-doc'),
path('<id>/document/<doc_id>', get_document_view, name='get-doc'),
path('<id>/document/<doc_id>/edit', edit_document_view, name='edit-doc'),
path('<id>/document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
path('<id>/document/new/', NewEcoAccountDocumentView.as_view(), name='new-doc'),
path('<id>/document/<doc_id>', GetEcoAccountDocumentView.as_view(), name='get-doc'),
path('<id>/document/<doc_id>/edit', EditEcoAccountDocumentView.as_view(), name='edit-doc'),
path('<id>/document/<doc_id>/remove/', RemoveEcoAccountDocumentView.as_view(), name='remove-doc'),
# Eco-account deductions
path('<id>/deduction/<deduction_id>/remove', deduction_remove_view, name='remove-deduction'),
path('<id>/deduction/<deduction_id>/edit', deduction_edit_view, name='edit-deduction'),
path('<id>/deduct/new', new_deduction_view, name='new-deduction'),
path('<id>/deduction/<deduction_id>/remove', RemoveEcoAccountDeductionView.as_view(), name='remove-deduction'),
path('<id>/deduction/<deduction_id>/edit', EditEcoAccountDeductionView.as_view(), name='edit-deduction'),
path('<id>/deduct/new', NewEcoAccountDeductionView.as_view(), name='new-deduction'),
# Autocomplete
path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="autocomplete"),
]

View File

@ -19,6 +19,7 @@ class CompensationQualityChecker(AbstractQualityChecker):
self._check_states()
self._check_actions()
self._check_geometry()
self._check_deadlines()
self.valid = len(self.messages) == 0
def _check_states(self):
@ -47,6 +48,16 @@ class CompensationQualityChecker(AbstractQualityChecker):
if not self.obj.actions.all():
self._add_missing_attr_name(_con("Compensation", "Actions"))
def _check_deadlines(self):
""" Checks data quality for related Deadline objects
Returns:
"""
finished_deadlines = self.obj.get_finished_deadlines()
if not finished_deadlines.exists():
self._add_missing_attr_name(_("Finished deadlines"))
class EcoAccountQualityChecker(CompensationQualityChecker):
def run_check(self):

View File

@ -5,6 +5,5 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 16.11.21
"""
from .compensation import *
from .eco_account import *
from .payment import *

View File

@ -1,657 +0,0 @@
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Sum
from django.http import HttpRequest, JsonResponse
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from compensation.forms.forms import NewCompensationForm, EditCompensationForm
from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm, \
NewCompensationDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, \
EditCompensationStateModalForm, EditCompensationActionModalForm, EditDeadlineModalForm
from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument
from compensation.tables import CompensationTable
from intervention.models import Intervention
from konova.contexts import BaseContext
from konova.decorators import *
from konova.forms import RemoveModalForm, SimpleGeomForm, RemoveDeadlineModalForm, EditDocumentModalForm
from konova.models import Deadline
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.documents import get_document, remove_document
from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DATA_UNSHARED_EXPLANATION, \
CHECKED_RECORDED_RESET, COMPENSATION_ADDED_TEMPLATE, COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, \
COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, \
DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, \
DEADLINE_EDITED, RECORDED_BLOCKS_EDIT, PARAMS_INVALID, DATA_CHECKED_PREVIOUSLY_TEMPLATE
from konova.utils.user_checks import in_group
@login_required
@any_group_check
def index_view(request: HttpRequest):
"""
Renders the index view for compensation
Args:
request (HttpRequest): The incoming request
Returns:
A rendered view
"""
template = "generic_index.html"
compensations = Compensation.objects.filter(
deleted=None, # only show those which are not deleted individually
intervention__deleted=None, # and don't show the ones whose intervention has been deleted
)
table = CompensationTable(
request=request,
queryset=compensations
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("Compensations - Overview"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
@shared_access_required(Intervention, "intervention_id")
def new_view(request: HttpRequest, intervention_id: str = None):
"""
Renders a view for a new compensation creation
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
if intervention_id is not None:
try:
intervention = Intervention.objects.get(id=intervention_id)
except ObjectDoesNotExist:
messages.error(request, PARAMS_INVALID)
return redirect("home")
if intervention.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("intervention:detail", id=intervention_id)
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
generated_identifier = data_form.cleaned_data.get("identifier", None)
comp = data_form.save(request.user, geom_form)
if generated_identifier != comp.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
comp.identifier
)
)
messages.success(request, COMPENSATION_ADDED_TEMPLATE.format(comp.identifier))
return redirect("compensation:detail", id=comp.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New compensation"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
def new_id_view(request: HttpRequest):
""" JSON endpoint
Provides fetching of free identifiers for e.g. AJAX calls
"""
tmp = Compensation()
identifier = tmp.generate_new_identifier()
while Compensation.objects.filter(identifier=identifier).exists():
identifier = tmp.generate_new_identifier()
return JsonResponse(
data={
"gen_data": identifier
}
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
# Get object from db
comp = get_object_or_404(Compensation, id=id)
if comp.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("compensation:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditCompensationForm(request.POST or None, instance=comp)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
# Preserve state of intervention recorded/checked to determine whether the user must be informed or not
# about a change of the recorded/checked state
intervention_recorded = comp.intervention.recorded is not None
intervention_checked = comp.intervention.checked is not None
# The data form takes the geom form for processing, as well as the performing user
comp = data_form.save(request.user, geom_form)
if intervention_recorded or intervention_checked:
messages.info(request, CHECKED_RECORDED_RESET)
messages.success(request, _("Compensation {} edited").format(comp.identifier))
return redirect("compensation:detail", id=comp.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@any_group_check
def detail_view(request: HttpRequest, id: str):
""" Renders a detail view for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
template = "compensation/detail/compensation/view.html"
comp = get_object_or_404(Compensation, id=id)
geom_form = SimpleGeomForm(instance=comp)
parcels = comp.get_underlying_parcels()
_user = request.user
is_data_shared = comp.intervention.is_shared_with(_user)
# Order states according to surface
before_states = comp.before_states.all().prefetch_related("biotope_type").order_by("-surface")
after_states = comp.after_states.all().prefetch_related("biotope_type").order_by("-surface")
actions = comp.actions.all().prefetch_related("action_type")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = before_states.aggregate(Sum("surface"))["surface__sum"] or 0
sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
diff_states = abs(sum_before_states - sum_after_states)
request = comp.set_status_messages(request)
last_checked = comp.intervention.get_last_checked_action()
last_checked_tooltip = ""
if last_checked:
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(last_checked.get_timestamp_str_formatted(), last_checked.user)
context = {
"obj": comp,
"last_checked": last_checked,
"last_checked_tooltip": last_checked_tooltip,
"geom_form": geom_form,
"parcels": parcels,
"has_access": is_data_shared,
"actions": actions,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP),
"LANIS_LINK": comp.get_LANIS_link(),
TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}",
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def log_view(request: HttpRequest, id: str):
""" Renders a log view using modal
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
template = "modal/modal_generic.html"
body_template = "log.html"
context = {
"modal_body_template": body_template,
"log": comp.log.all(),
"modal_title": _("Log"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
form = RemoveModalForm(request.POST or None, instance=comp, request=request)
return form.process_request(
request=request,
msg_success=COMPENSATION_REMOVED_TEMPLATE.format(comp.identifier),
redirect_url=reverse("compensation:index"),
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def new_document_view(request: HttpRequest, id: str):
""" Renders a form for uploading new documents
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id to which the new document will be related
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
form = NewCompensationDocumentModalForm(request.POST or None, request.FILES or None, instance=comp, request=request)
return form.process_request(
request,
msg_success=DOCUMENT_ADDED,
redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def get_document_view(request: HttpRequest, id: str, doc_id: str):
""" Returns the document as downloadable file
Wraps the generic document fetcher function from konova.utils.
Args:
request (HttpRequest): The incoming request
id (str): The compensation id
doc_id (str): The document id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
doc = get_object_or_404(CompensationDocument, id=doc_id)
return get_document(doc)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def remove_document_view(request: HttpRequest, id: str, doc_id: str):
""" Removes the document from the database and file system
Wraps the generic functionality from konova.utils.
Args:
request (HttpRequest): The incoming request
id (str): The compensation id
doc_id (str): The document id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
doc = get_object_or_404(CompensationDocument, id=doc_id)
return remove_document(
request,
doc
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def edit_document_view(request: HttpRequest, id: str, doc_id: str):
""" Removes the document from the database and file system
Wraps the generic functionality from konova.utils.
Args:
request (HttpRequest): The incoming request
id (str): The compensation id
doc_id (str): The document id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
doc = get_object_or_404(CompensationDocument, id=doc_id)
form = EditDocumentModalForm(request.POST or None, request.FILES or None, instance=comp, document=doc, request=request)
return form.process_request(
request,
DOCUMENT_EDITED,
reverse("compensation:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def state_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new states for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id to which the new state will be related
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
form = NewStateModalForm(request.POST or None, instance=comp, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_ADDED,
redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def action_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new actions for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id to which the new state will be related
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
form = NewActionModalForm(request.POST or None, instance=comp, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_ACTION_ADDED,
redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def action_edit_view(request: HttpRequest, id: str, action_id: str):
""" Renders a form for editing actions for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
action_id (str): The action's id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
action = get_object_or_404(CompensationAction, id=action_id)
form = EditCompensationActionModalForm(request.POST or None, instance=comp, action=action, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_ACTION_EDITED,
redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def deadline_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new states for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id to which the new state will be related
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
form = NewDeadlineModalForm(request.POST or None, instance=comp, request=request)
return form.process_request(
request,
msg_success=DEADLINE_ADDED,
redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def deadline_edit_view(request: HttpRequest, id: str, deadline_id: str):
""" Renders a form for editing deadlines from a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
deadline_id (str): The deadline's id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
deadline = get_object_or_404(Deadline, id=deadline_id)
form = EditDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
return form.process_request(
request,
msg_success=DEADLINE_EDITED,
redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
""" Renders a form for removing deadlines from a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
deadline_id (str): The deadline's id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
deadline = get_object_or_404(Deadline, id=deadline_id)
form = RemoveDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
return form.process_request(
request,
msg_success=DEADLINE_REMOVED,
redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def state_remove_view(request: HttpRequest, id: str, state_id: str):
""" Renders a form for removing a compensation state
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
state_id (str): The state's id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
state = get_object_or_404(CompensationState, id=state_id)
form = RemoveCompensationStateModalForm(request.POST or None, instance=comp, state=state, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_REMOVED,
redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def state_edit_view(request: HttpRequest, id: str, state_id: str):
""" Renders a form for editing a compensation state
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
state_id (str): The state's id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
state = get_object_or_404(CompensationState, id=state_id)
form = EditCompensationStateModalForm(request.POST or None, instance=comp, state=state, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_EDITED,
redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def action_remove_view(request: HttpRequest, id: str, action_id: str):
""" Renders a form for removing a compensation action
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
id (str): The action's id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
action = get_object_or_404(CompensationAction, id=action_id)
form = RemoveCompensationActionModalForm(request.POST or None, instance=comp, action=action, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_ACTION_REMOVED,
redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
)
def report_view(request: HttpRequest, id: str):
""" Renders the public report view
Args:
request (HttpRequest): The incoming request
id (str): The id of the intervention
Returns:
"""
# Reuse the compensation report template since compensations are structurally identical
template = "compensation/report/compensation/report.html"
comp = get_object_or_404(Compensation, id=id)
tab_title = _("Report {}").format(comp.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not comp.is_ready_for_publish():
template = "report/unavailable.html"
context = {
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=comp
)
parcels = comp.get_underlying_parcels()
qrcode_url = request.build_absolute_uri(reverse("compensation:report", args=(id,)))
qrcode_img = generate_qr_code(qrcode_url, 10)
qrcode_lanis_url = comp.get_LANIS_link()
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
# Order states by surface
before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type")
actions = comp.actions.all().prefetch_related("action_type")
context = {
"obj": comp,
"qrcode": {
"img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"has_access": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
"geom_form": geom_form,
"parcels": parcels,
"actions": actions,
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)

View File

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""

View File

@ -0,0 +1,57 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.decorators import method_decorator
from compensation.forms.modals.compensation_action import RemoveCompensationActionModalForm, \
EditCompensationActionModalForm, NewCompensationActionModalForm
from compensation.models import Compensation, CompensationAction
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.utils.message_templates import COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_EDITED, \
COMPENSATION_ACTION_ADDED
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView
class NewCompensationActionView(AbstractNewCompensationActionView):
model = Compensation
redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationActionView(AbstractEditCompensationActionView):
model = Compensation
redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationActionView(AbstractRemoveCompensationActionView):
model = Compensation
redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,273 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Sum
from django.http import HttpRequest, JsonResponse
from django.shortcuts import get_object_or_404, render, redirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.forms.compensation import EditCompensationForm, NewCompensationForm
from compensation.models import Compensation
from compensation.tables.compensation import CompensationTable
from intervention.models import Intervention
from konova.contexts import BaseContext
from konova.decorators import shared_access_required, default_group_required, any_group_check, login_required_modal
from konova.forms import SimpleGeomForm
from konova.forms.modals import RemoveModalForm
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE, \
RECORDED_BLOCKS_EDIT, CHECKED_RECORDED_RESET, FORM_INVALID, PARAMS_INVALID, IDENTIFIER_REPLACED, \
COMPENSATION_ADDED_TEMPLATE
from konova.utils.user_checks import in_group
@login_required
@any_group_check
def index_view(request: HttpRequest):
"""
Renders the index view for compensation
Args:
request (HttpRequest): The incoming request
Returns:
A rendered view
"""
template = "generic_index.html"
compensations = Compensation.objects.filter(
deleted=None, # only show those which are not deleted individually
intervention__deleted=None, # and don't show the ones whose intervention has been deleted
)
table = CompensationTable(
request=request,
queryset=compensations
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("Compensations - Overview"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
@shared_access_required(Intervention, "intervention_id")
def new_view(request: HttpRequest, intervention_id: str = None):
"""
Renders a view for a new compensation creation
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
if intervention_id is not None:
try:
intervention = Intervention.objects.get(id=intervention_id)
except ObjectDoesNotExist:
messages.error(request, PARAMS_INVALID)
return redirect("home")
if intervention.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("intervention:detail", id=intervention_id)
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
generated_identifier = data_form.cleaned_data.get("identifier", None)
comp = data_form.save(request.user, geom_form)
if generated_identifier != comp.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
comp.identifier
)
)
messages.success(request, COMPENSATION_ADDED_TEMPLATE.format(comp.identifier))
return redirect("compensation:detail", id=comp.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New compensation"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
def new_id_view(request: HttpRequest):
""" JSON endpoint
Provides fetching of free identifiers for e.g. AJAX calls
"""
tmp = Compensation()
identifier = tmp.generate_new_identifier()
while Compensation.objects.filter(identifier=identifier).exists():
identifier = tmp.generate_new_identifier()
return JsonResponse(
data={
"gen_data": identifier
}
)
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
# Get object from db
comp = get_object_or_404(Compensation, id=id)
if comp.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("compensation:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditCompensationForm(request.POST or None, instance=comp)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
# Preserve state of intervention recorded/checked to determine whether the user must be informed or not
# about a change of the recorded/checked state
intervention_recorded = comp.intervention.recorded is not None
intervention_checked = comp.intervention.checked is not None
# The data form takes the geom form for processing, as well as the performing user
comp = data_form.save(request.user, geom_form)
if intervention_recorded or intervention_checked:
messages.info(request, CHECKED_RECORDED_RESET)
messages.success(request, _("Compensation {} edited").format(comp.identifier))
return redirect("compensation:detail", id=comp.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@any_group_check
def detail_view(request: HttpRequest, id: str):
""" Renders a detail view for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
template = "compensation/detail/compensation/view.html"
comp = get_object_or_404(Compensation, id=id)
geom_form = SimpleGeomForm(instance=comp)
parcels = comp.get_underlying_parcels()
_user = request.user
is_data_shared = comp.intervention.is_shared_with(_user)
# Order states according to surface
before_states = comp.before_states.all().prefetch_related("biotope_type").order_by("-surface")
after_states = comp.after_states.all().prefetch_related("biotope_type").order_by("-surface")
actions = comp.actions.all().prefetch_related("action_type")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = before_states.aggregate(Sum("surface"))["surface__sum"] or 0
sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
diff_states = abs(sum_before_states - sum_after_states)
request = comp.set_status_messages(request)
last_checked = comp.intervention.get_last_checked_action()
last_checked_tooltip = ""
if last_checked:
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(last_checked.get_timestamp_str_formatted(), last_checked.user)
context = {
"obj": comp,
"last_checked": last_checked,
"last_checked_tooltip": last_checked_tooltip,
"geom_form": geom_form,
"parcels": parcels,
"has_access": is_data_shared,
"actions": actions,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP),
"LANIS_LINK": comp.get_LANIS_link(),
TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}",
"has_finished_deadlines": comp.get_finished_deadlines().exists(),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required_modal
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
form = RemoveModalForm(request.POST or None, instance=comp, request=request)
return form.process_request(
request=request,
msg_success=COMPENSATION_REMOVED_TEMPLATE.format(comp.identifier),
redirect_url=reverse("compensation:index"),
)

View File

@ -0,0 +1,49 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.deadline import AbstractRemoveDeadlineView, AbstractEditDeadlineView, AbstractNewDeadlineView
class NewCompensationDeadlineView(AbstractNewDeadlineView):
model = Compensation
redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationDeadlineView(AbstractEditDeadlineView):
model = Compensation
redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationDeadlineView(AbstractRemoveDeadlineView):
model = Compensation
redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,66 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.forms.modals.document import NewCompensationDocumentModalForm
from compensation.models import Compensation, CompensationDocument
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.forms.modals import EditDocumentModalForm
from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \
AbstractEditDocumentView
class NewCompensationDocumentView(AbstractNewDocumentView):
model = Compensation
form = NewCompensationDocumentModalForm
redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class GetCompensationDocumentView(AbstractGetDocumentView):
model = Compensation
document_model = CompensationDocument
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationDocumentView(AbstractRemoveDocumentView):
model = Compensation
document_model = CompensationDocument
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationDocumentView(AbstractEditDocumentView):
model = Compensation
document_model = CompensationDocument
form = EditDocumentModalForm
redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,24 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.log import AbstractLogView
class CompensationLogView(AbstractLogView):
model = Compensation
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,79 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.http import HttpRequest
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.models import Compensation
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.generators import generate_qr_code
def report_view(request: HttpRequest, id: str):
""" Renders the public report view
Args:
request (HttpRequest): The incoming request
id (str): The id of the intervention
Returns:
"""
# Reuse the compensation report template since compensations are structurally identical
template = "compensation/report/compensation/report.html"
comp = get_object_or_404(Compensation, id=id)
tab_title = _("Report {}").format(comp.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not comp.is_ready_for_publish():
template = "report/unavailable.html"
context = {
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=comp
)
parcels = comp.get_underlying_parcels()
qrcode_url = request.build_absolute_uri(reverse("compensation:report", args=(id,)))
qrcode_img = generate_qr_code(qrcode_url, 10)
qrcode_lanis_url = comp.get_LANIS_link()
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
# Order states by surface
before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type")
actions = comp.actions.all().prefetch_related("action_type")
context = {
"obj": comp,
"qrcode": {
"img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"has_access": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
"geom_form": geom_form,
"parcels": parcels,
"actions": actions,
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)

View File

@ -0,0 +1,26 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView
class CompensationResubmissionView(AbstractResubmissionView):
model = Compensation
redirect_url_base = "compensation:detail"
form_action_url_base = "compensation:resubmission-create"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,50 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \
AbstractRemoveCompensationStateView
class NewCompensationStateView(AbstractNewCompensationStateView):
model = Compensation
redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationStateView(AbstractEditCompensationStateView):
model = Compensation
redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationStateView(AbstractRemoveCompensationStateView):
model = Compensation
redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -1,840 +0,0 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 09.08.21
"""
from django.contrib import messages
from django.db.models import Sum
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpRequest, Http404, JsonResponse
from django.shortcuts import render, get_object_or_404, redirect
from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm
from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
NewEcoAccountDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, \
EditCompensationStateModalForm, EditCompensationActionModalForm, EditDeadlineModalForm
from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
from compensation.tables import EcoAccountTable
from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm, \
EditEcoAccountDeductionModalForm
from konova.contexts import BaseContext
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
shared_access_required
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentModalForm, RecordModalForm, \
RemoveDeadlineModalForm, EditDocumentModalForm
from konova.models import Deadline
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.documents import get_document, remove_document
from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, \
COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, \
DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, \
RECORDED_BLOCKS_EDIT
from konova.utils.user_checks import in_group
@login_required
@any_group_check
def index_view(request: HttpRequest):
"""
Renders the index view for eco accounts
Args:
request (HttpRequest): The incoming request
Returns:
A rendered view
"""
template = "generic_index.html"
eco_accounts = EcoAccount.objects.filter(
deleted=None,
)
table = EcoAccountTable(
request=request,
queryset=eco_accounts
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("Eco-account - Overview"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
def new_view(request: HttpRequest):
"""
Renders a view for a new eco account creation
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
data_form = NewEcoAccountForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
generated_identifier = data_form.cleaned_data.get("identifier", None)
acc = data_form.save(request.user, geom_form)
if generated_identifier != acc.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
acc.identifier
)
)
messages.success(request, _("Eco-Account {} added").format(acc.identifier))
return redirect("compensation:acc:detail", id=acc.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
def new_id_view(request: HttpRequest):
""" JSON endpoint
Provides fetching of free identifiers for e.g. AJAX calls
"""
tmp = EcoAccount()
identifier = tmp.generate_new_identifier()
while EcoAccount.objects.filter(identifier=identifier).exists():
identifier = tmp.generate_new_identifier()
return JsonResponse(
data={
"gen_data": identifier
}
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
# Get object from db
acc = get_object_or_404(EcoAccount, id=id)
if acc.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("compensation:acc:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
# The data form takes the geom form for processing, as well as the performing user
acc = data_form.save(request.user, geom_form)
messages.success(request, _("Eco-Account {} edited").format(acc.identifier))
return redirect("compensation:acc:detail", id=acc.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@any_group_check
def detail_view(request: HttpRequest, id: str):
""" Renders a detail view for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
template = "compensation/detail/eco_account/view.html"
acc = get_object_or_404(
EcoAccount.objects.prefetch_related(
"deadlines",
).select_related(
'geometry',
'responsible',
),
id=id
)
geom_form = SimpleGeomForm(instance=acc)
parcels = acc.get_underlying_parcels()
_user = request.user
is_data_shared = acc.is_shared_with(_user)
# Order states according to surface
before_states = acc.before_states.order_by("-surface")
after_states = acc.after_states.order_by("-surface")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = before_states.aggregate(Sum("surface"))["surface__sum"] or 0
sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
diff_states = abs(sum_before_states - sum_after_states)
# Calculate rest of available surface for deductions
available_total, available_relative = acc.get_available_rest()
# Prefetch related data to decrease the amount of db connections
deductions = acc.deductions.filter(
intervention__deleted=None,
)
actions = acc.actions.all()
request = acc.set_status_messages(request)
context = {
"obj": acc,
"geom_form": geom_form,
"parcels": parcels,
"has_access": is_data_shared,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"available": available_relative,
"available_total": available_total,
"is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP),
"LANIS_LINK": acc.get_LANIS_link(),
"deductions": deductions,
"actions": actions,
TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}",
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the eco account
Args:
request (HttpRequest): The incoming request
id (str): The account's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
# If the eco account has already been recorded OR there are already deductions, it can not be deleted by a regular
# default group user
if acc.recorded is not None or acc.deductions.exists():
user = request.user
if not in_group(user, ETS_GROUP):
messages.info(request, CANCEL_ACC_RECORDED_OR_DEDUCTED)
return redirect("compensation:acc:detail", id=id)
form = RemoveModalForm(request.POST or None, instance=acc, request=request)
return form.process_request(
request=request,
msg_success=_("Eco-account removed"),
redirect_url=reverse("compensation:acc:index"),
)
@login_required
@default_group_required
def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str):
""" Renders a modal view for removing deductions
Args:
request (HttpRequest): The incoming request
id (str): The eco account's id
deduction_id (str): The deduction's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
try:
eco_deduction = acc.deductions.get(id=deduction_id)
if not eco_deduction.intervention.is_shared_with(request.user):
raise ObjectDoesNotExist()
except ObjectDoesNotExist:
raise Http404("Unknown deduction")
form = RemoveEcoAccountDeductionModalForm(request.POST or None, instance=acc, deduction=eco_deduction, request=request)
return form.process_request(
request=request,
msg_success=DEDUCTION_REMOVED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
def deduction_edit_view(request: HttpRequest, id: str, deduction_id: str):
""" Renders a modal view for editing deductions
Args:
request (HttpRequest): The incoming request
id (str): The eco account's id
deduction_id (str): The deduction's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
try:
eco_deduction = acc.deductions.get(id=deduction_id)
if not eco_deduction.intervention.is_shared_with(request.user):
raise ObjectDoesNotExist
except ObjectDoesNotExist:
raise Http404("Unknown deduction")
form = EditEcoAccountDeductionModalForm(request.POST or None, instance=acc, deduction=eco_deduction, request=request)
return form.process_request(
request=request,
msg_success=DEDUCTION_EDITED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def log_view(request: HttpRequest, id: str):
""" Renders a log view using modal
Args:
request (HttpRequest): The incoming request
id (str): The eco acount's id
Returns:
"""
comp = get_object_or_404(EcoAccount, id=id)
template = "modal/modal_generic.html"
body_template = "log.html"
context = {
"modal_body_template": body_template,
"log": comp.log.all(),
"modal_title": _("Log"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@conservation_office_group_required
@shared_access_required(EcoAccount, "id")
def record_view(request: HttpRequest, id:str):
""" Renders a modal form for recording an eco account
Args:
request (HttpRequest): The incoming request
id (str): The account's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
form = RecordModalForm(request.POST or None, instance=acc, request=request)
msg_succ = _("{} unrecorded") if acc.recorded else _("{} recorded")
msg_succ = msg_succ.format(acc.identifier)
return form.process_request(
request,
msg_succ
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def state_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new states for an eco account
Args:
request (HttpRequest): The incoming request
id (str): The account's id to which the new state will be related
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
form = NewStateModalForm(request.POST or None, instance=acc, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_ADDED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def action_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new actions for an eco account
Args:
request (HttpRequest): The incoming request
id (str): The account's id to which the new state will be related
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
form = NewActionModalForm(request.POST or None, instance=acc, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_ACTION_ADDED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def state_remove_view(request: HttpRequest, id: str, state_id: str):
""" Renders a form for removing a compensation state
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
state_id (str): The state's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
state = get_object_or_404(CompensationState, id=state_id)
form = RemoveCompensationStateModalForm(request.POST or None, instance=acc, state=state, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_REMOVED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def state_edit_view(request: HttpRequest, id: str, state_id: str):
""" Renders a form for editing a compensation state
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
state_id (str): The state's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
state = get_object_or_404(CompensationState, id=state_id)
form = EditCompensationStateModalForm(request.POST or None, instance=acc, state=state, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_EDITED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def action_remove_view(request: HttpRequest, id: str, action_id: str):
""" Renders a form for removing a compensation action
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
id (str): The action's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
action = get_object_or_404(CompensationAction, id=action_id)
form = RemoveCompensationActionModalForm(request.POST or None, instance=acc, action=action, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_ACTION_REMOVED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def action_edit_view(request: HttpRequest, id: str, action_id: str):
""" Renders a form for editing a compensation action
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
id (str): The action's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
action = get_object_or_404(CompensationAction, id=action_id)
form = EditCompensationActionModalForm(request.POST or None, instance=acc, action=action, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_ACTION_EDITED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def deadline_edit_view(request: HttpRequest, id: str, deadline_id: str):
""" Renders a form for editing deadlines from a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
deadline_id (str): The deadline's id
Returns:
"""
comp = get_object_or_404(EcoAccount, id=id)
deadline = get_object_or_404(Deadline, id=deadline_id)
form = EditDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
return form.process_request(
request,
msg_success=DEADLINE_EDITED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
""" Renders a form for removing deadlines from a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
deadline_id (str): The deadline's id
Returns:
"""
comp = get_object_or_404(EcoAccount, id=id)
deadline = get_object_or_404(Deadline, id=deadline_id)
form = RemoveDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
return form.process_request(
request,
msg_success=DEADLINE_REMOVED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def deadline_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new states for an eco account
Args:
request (HttpRequest): The incoming request
id (str): The account's id to which the new state will be related
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
form = NewDeadlineModalForm(request.POST or None, instance=acc, request=request)
return form.process_request(
request,
msg_success=DEADLINE_ADDED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def new_document_view(request: HttpRequest, id: str):
""" Renders a form for uploading new documents
Args:
request (HttpRequest): The incoming request
id (str): The account's id to which the new document will be related
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
form = NewEcoAccountDocumentModalForm(request.POST or None, request.FILES or None, instance=acc, request=request)
return form.process_request(
request,
msg_success=DOCUMENT_ADDED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data",
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def get_document_view(request: HttpRequest, id:str, doc_id: str):
""" Returns the document as downloadable file
Wraps the generic document fetcher function from konova.utils.
Args:
request (HttpRequest): The incoming request
id (str): The account id
doc_id (str): The document id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
doc = get_object_or_404(EcoAccountDocument, id=doc_id)
return get_document(doc)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def edit_document_view(request: HttpRequest, id: str, doc_id: str):
""" Removes the document from the database and file system
Wraps the generic functionality from konova.utils.
Args:
request (HttpRequest): The incoming request
id (str): The account id
doc_id (str): The document id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
doc = get_object_or_404(EcoAccountDocument, id=doc_id)
form = EditDocumentModalForm(request.POST or None, request.FILES or None, instance=acc, document=doc, request=request)
return form.process_request(
request,
DOCUMENT_EDITED,
reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def remove_document_view(request: HttpRequest, id: str, doc_id: str):
""" Removes the document from the database and file system
Wraps the generic functionality from konova.utils.
Args:
request (HttpRequest): The incoming request
id (str): The account id
doc_id (str): The document id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
doc = get_object_or_404(EcoAccountDocument, id=doc_id)
return remove_document(
request,
doc
)
@login_required
@default_group_required
def new_deduction_view(request: HttpRequest, id: str):
""" Renders a modal form view for creating deductions
Args:
request (HttpRequest): THe incoming request
id (str): The eco account's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
if not acc.recorded:
raise Http404()
form = NewDeductionModalForm(request.POST or None, instance=acc, request=request)
return form.process_request(
request,
msg_success=DEDUCTION_ADDED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
def report_view(request:HttpRequest, id: str):
""" Renders the public report view
Args:
request (HttpRequest): The incoming request
id (str): The id of the intervention
Returns:
"""
# Reuse the compensation report template since EcoAccounts are structurally identical
template = "compensation/report/eco_account/report.html"
acc = get_object_or_404(EcoAccount, id=id)
tab_title = _("Report {}").format(acc.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not acc.is_ready_for_publish():
template = "report/unavailable.html"
context = {
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=acc
)
parcels = acc.get_underlying_parcels()
qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,)))
qrcode_img = generate_qr_code(qrcode_url, 10)
qrcode_lanis_url = acc.get_LANIS_link()
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
# Order states by surface
before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent")
after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent")
actions = acc.actions.all().prefetch_related("action_type__parent")
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier)
deductions = acc.deductions.all()\
.distinct("intervention")\
.select_related("intervention")\
.values_list("intervention__id", "intervention__identifier", "intervention__title", named=True)
context = {
"obj": acc,
"qrcode": {
"img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"has_access": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
"geom_form": geom_form,
"parcels": parcels,
"actions": actions,
"deductions": deductions,
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
def share_view(request: HttpRequest, id: str, token: str):
""" Performs sharing of an eco account
If token given in url is not valid, the user will be redirected to the dashboard
Args:
request (HttpRequest): The incoming request
id (str): EcoAccount's id
token (str): Access token for EcoAccount
Returns:
"""
user = request.user
obj = get_object_or_404(EcoAccount, id=id)
# Check tokens
if obj.access_token == token:
# Send different messages in case user has already been added to list of sharing users
if obj.is_shared_with(user):
messages.info(
request,
_("{} has already been shared with you").format(obj.identifier)
)
else:
messages.success(
request,
_("{} has been shared with you").format(obj.identifier)
)
obj.share_with_user(user)
return redirect("compensation:acc:detail", id=id)
else:
messages.error(
request,
_("Share link invalid"),
extra_tags="danger",
)
return redirect("home")
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def create_share_view(request: HttpRequest, id: str):
""" Renders sharing form for an eco account
Args:
request (HttpRequest): The incoming request
id (str): EcoAccount's id
Returns:
"""
obj = get_object_or_404(EcoAccount, id=id)
form = ShareModalForm(request.POST or None, instance=obj, request=request)
return form.process_request(
request,
msg_success=_("Share settings updated")
)

View File

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""

View File

@ -0,0 +1,50 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView
class NewEcoAccountActionView(AbstractNewCompensationActionView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountActionView(AbstractEditCompensationActionView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountActionView(AbstractRemoveCompensationActionView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,49 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.deadline import AbstractNewDeadlineView, AbstractEditDeadlineView, AbstractRemoveDeadlineView
class NewEcoAccountDeadlineView(AbstractNewDeadlineView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountDeadlineView(AbstractEditDeadlineView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountDeadlineView(AbstractRemoveDeadlineView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,58 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.http import Http404
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount
from konova.decorators import default_group_required, login_required_modal
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
class NewEcoAccountDeductionView(AbstractNewDeductionView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def _custom_check(self, obj):
if not obj.recorded:
raise Http404()
class EditEcoAccountDeductionView(AbstractEditDeductionView):
def _custom_check(self, obj):
pass
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountDeductionView(AbstractRemoveDeductionView):
def _custom_check(self, obj):
pass
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,69 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.decorators import method_decorator
from compensation.forms.modals.document import NewEcoAccountDocumentModalForm
from compensation.models import EcoAccount, EcoAccountDocument
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.forms.modals import EditDocumentModalForm
from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \
AbstractEditDocumentView
class NewEcoAccountDocumentView(AbstractNewDocumentView):
model = EcoAccount
form = NewEcoAccountDocumentModalForm
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class GetEcoAccountDocumentView(AbstractGetDocumentView):
model = EcoAccount
document_model = EcoAccountDocument
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountDocumentView(AbstractRemoveDocumentView):
model = EcoAccount
document_model = EcoAccountDocument
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountDocumentView(AbstractEditDocumentView):
model = EcoAccount
document_model = EcoAccountDocument
form = EditDocumentModalForm
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,268 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Sum
from django.http import HttpRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm, RemoveEcoAccountModalForm
from compensation.models import EcoAccount
from compensation.tables.eco_account import EcoAccountTable
from konova.contexts import BaseContext
from konova.decorators import shared_access_required, default_group_required, any_group_check, login_required_modal
from konova.forms import SimpleGeomForm
from konova.settings import ETS_GROUP, DEFAULT_GROUP, ZB_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECORDED_BLOCKS_EDIT, FORM_INVALID, \
IDENTIFIER_REPLACED
from konova.utils.user_checks import in_group
@login_required
@any_group_check
def index_view(request: HttpRequest):
"""
Renders the index view for eco accounts
Args:
request (HttpRequest): The incoming request
Returns:
A rendered view
"""
template = "generic_index.html"
eco_accounts = EcoAccount.objects.filter(
deleted=None,
)
table = EcoAccountTable(
request=request,
queryset=eco_accounts
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("Eco-account - Overview"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
def new_view(request: HttpRequest):
"""
Renders a view for a new eco account creation
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
data_form = NewEcoAccountForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
generated_identifier = data_form.cleaned_data.get("identifier", None)
acc = data_form.save(request.user, geom_form)
if generated_identifier != acc.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
acc.identifier
)
)
messages.success(request, _("Eco-Account {} added").format(acc.identifier))
return redirect("compensation:acc:detail", id=acc.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
def new_id_view(request: HttpRequest):
""" JSON endpoint
Provides fetching of free identifiers for e.g. AJAX calls
"""
tmp = EcoAccount()
identifier = tmp.generate_new_identifier()
while EcoAccount.objects.filter(identifier=identifier).exists():
identifier = tmp.generate_new_identifier()
return JsonResponse(
data={
"gen_data": identifier
}
)
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
# Get object from db
acc = get_object_or_404(EcoAccount, id=id)
if acc.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("compensation:acc:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
# The data form takes the geom form for processing, as well as the performing user
acc = data_form.save(request.user, geom_form)
messages.success(request, _("Eco-Account {} edited").format(acc.identifier))
return redirect("compensation:acc:detail", id=acc.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@any_group_check
def detail_view(request: HttpRequest, id: str):
""" Renders a detail view for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
template = "compensation/detail/eco_account/view.html"
acc = get_object_or_404(
EcoAccount.objects.prefetch_related(
"deadlines",
).select_related(
'geometry',
'responsible',
),
id=id
)
geom_form = SimpleGeomForm(instance=acc)
parcels = acc.get_underlying_parcels()
_user = request.user
is_data_shared = acc.is_shared_with(_user)
# Order states according to surface
before_states = acc.before_states.order_by("-surface")
after_states = acc.after_states.order_by("-surface")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = before_states.aggregate(Sum("surface"))["surface__sum"] or 0
sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
diff_states = abs(sum_before_states - sum_after_states)
# Calculate rest of available surface for deductions
available_total = acc.deductable_rest
available_relative = acc.get_deductable_rest_relative()
# Prefetch related data to decrease the amount of db connections
deductions = acc.deductions.filter(
intervention__deleted=None,
)
actions = acc.actions.all()
request = acc.set_status_messages(request)
context = {
"obj": acc,
"geom_form": geom_form,
"parcels": parcels,
"has_access": is_data_shared,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"available": available_relative,
"available_total": available_total,
"is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP),
"LANIS_LINK": acc.get_LANIS_link(),
"deductions": deductions,
"actions": actions,
TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}",
"has_finished_deadlines": acc.get_finished_deadlines().exists(),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required_modal
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the eco account
Args:
request (HttpRequest): The incoming request
id (str): The account's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
# If the eco account has already been recorded OR there are already deductions, it can not be deleted by a regular
# default group user
if acc.recorded is not None or acc.deductions.exists():
user = request.user
if not in_group(user, ETS_GROUP):
messages.info(request, CANCEL_ACC_RECORDED_OR_DEDUCTED)
return redirect("compensation:acc:detail", id=id)
form = RemoveEcoAccountModalForm(request.POST or None, instance=acc, request=request)
return form.process_request(
request=request,
msg_success=_("Eco-account removed"),
redirect_url=reverse("compensation:acc:index"),
)

View File

@ -0,0 +1,24 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.log import AbstractLogView
class EcoAccountLogView(AbstractLogView):
model = EcoAccount
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,24 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.record import AbstractRecordView
class EcoAccountRecordView(AbstractRecordView):
model = EcoAccount
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,86 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.http import HttpRequest
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.generators import generate_qr_code
def report_view(request: HttpRequest, id: str):
""" Renders the public report view
Args:
request (HttpRequest): The incoming request
id (str): The id of the intervention
Returns:
"""
# Reuse the compensation report template since EcoAccounts are structurally identical
template = "compensation/report/eco_account/report.html"
acc = get_object_or_404(EcoAccount, id=id)
tab_title = _("Report {}").format(acc.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not acc.is_ready_for_publish():
template = "report/unavailable.html"
context = {
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=acc
)
parcels = acc.get_underlying_parcels()
qrcode_url = request.build_absolute_uri(reverse("compensation:acc:report", args=(id,)))
qrcode_img = generate_qr_code(qrcode_url, 10)
qrcode_lanis_url = acc.get_LANIS_link()
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
# Order states by surface
before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent")
after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent")
actions = acc.actions.all().prefetch_related("action_type__parent")
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier)
deductions = acc.deductions.all()\
.distinct("intervention")\
.select_related("intervention")\
.values_list("intervention__id", "intervention__identifier", "intervention__title", named=True)
context = {
"obj": acc,
"qrcode": {
"img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"has_access": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
"geom_form": geom_form,
"parcels": parcels,
"actions": actions,
"deductions": deductions,
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)

View File

@ -0,0 +1,26 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView
class EcoAccountResubmissionView(AbstractResubmissionView):
model = EcoAccount
redirect_url_base = "compensation:acc:detail"
form_action_url_base = "compensation:acc:resubmission-create"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,33 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.share import AbstractShareByTokenView, AbstractShareFormView
class EcoAccountShareByTokenView(AbstractShareByTokenView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EcoAccountShareFormView(AbstractShareFormView):
model = EcoAccount
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,50 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \
AbstractRemoveCompensationStateView
class NewEcoAccountStateView(AbstractNewCompensationStateView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountStateView(AbstractEditCompensationStateView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountStateView(AbstractRemoveCompensationStateView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -6,16 +6,14 @@ Created on: 09.08.21
"""
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from compensation.forms.modalForms import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
from compensation.forms.modals.payment import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
from compensation.models import Payment
from intervention.models import Intervention
from konova.decorators import default_group_required, shared_access_required
from konova.forms import RemoveModalForm
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED

View File

@ -5,7 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 19.08.21
"""
from compensation.filters import EcoAccountTableFilter
from compensation.filters.eco_account import EcoAccountTableFilter
class EmaTableFilter(EcoAccountTableFilter):

View File

@ -5,18 +5,17 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 06.10.21
"""
from dal import autocomplete
from django import forms
from user.models import User
from django.db import transaction
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
from compensation.forms.forms import AbstractCompensationForm, CompensationResponsibleFormMixin, \
PikCompensationFormMixin
from compensation.forms.mixins import CompensationResponsibleFormMixin, PikCompensationFormMixin
from compensation.forms.compensation import AbstractCompensationForm
from ema.models import Ema, EmaDocument
from intervention.models import Responsibility, Handler
from konova.forms import SimpleGeomForm, NewDocumentModalForm
from konova.forms import SimpleGeomForm
from konova.forms.modals import NewDocumentModalForm
from user.models import UserActionLogEntry

View File

@ -0,0 +1,19 @@
# Generated by Django 3.1.3 on 2022-08-15 06:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('ema', '0004_ema_is_pik'),
]
operations = [
migrations.AddField(
model_name='ema',
name='resubmission',
field=models.ManyToManyField(blank=True, null=True, related_name='_ema_resubmission_+', to='konova.Resubmission'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1.3 on 2022-08-15 06:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('ema', '0005_ema_resubmission'),
]
operations = [
migrations.RemoveField(
model_name='ema',
name='resubmission',
),
migrations.AddField(
model_name='ema',
name='resubmissions',
field=models.ManyToManyField(blank=True, null=True, related_name='_ema_resubmissions_+', to='konova.Resubmission'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.1.3 on 2022-08-15 08:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('konova', '0014_resubmission'),
('ema', '0006_auto_20220815_0803'),
]
operations = [
migrations.AlterField(
model_name='ema',
name='resubmissions',
field=models.ManyToManyField(blank=True, related_name='_ema_resubmissions_+', to='konova.Resubmission'),
),
]

View File

@ -103,7 +103,7 @@ class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin, Pik
Returns:
"""
return reverse("ema:share", args=(self.id, self.access_token))
return reverse("ema:share-token", args=(self.id, self.access_token))
class EmaDocument(AbstractDocument):

View File

@ -71,6 +71,10 @@
{% endif %}
</td>
</tr>
{% empty %}
<div class="alert alert-danger mb-0">
{% trans 'Missing' %}
</div>
{% endfor %}
</tbody>
</table>

View File

@ -12,7 +12,10 @@
</button>
</a>
{% if has_access %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-create' obj.id %}">
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
{% fa5_icon 'bell' %}
</button>
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-form' obj.id %}">
{% fa5_icon 'share-alt' %}
</button>
{% if is_ets_member %}

View File

@ -20,6 +20,11 @@
</div>
</div>
</div>
{% if not has_finished_deadlines %}
<div class="alert alert-danger mb-0">
{% trans 'Missing finished deadline ' %}
</div>
{% endif %}
<div class="card-body scroll-300 p-2">
<table class="table table-hover">
<thead>

View File

@ -66,6 +66,10 @@
{% endif %}
</td>
</tr>
{% empty %}
<div class="alert alert-danger mb-0">
{% trans 'Missing' %}
</div>
{% endfor %}
</tbody>
</table>

View File

@ -66,6 +66,10 @@
{% endif %}
</td>
</tr>
{% empty %}
<div class="alert alert-danger mb-0">
{% trans 'Missing' %}
</div>
{% endfor %}
</tbody>
</table>

View File

@ -49,8 +49,8 @@ class EmaViewTestCase(CompensationViewTestCase):
self.log_url = reverse("ema:log", args=(self.ema.id,))
self.edit_url = reverse("ema:edit", args=(self.ema.id,))
self.remove_url = reverse("ema:remove", args=(self.ema.id,))
self.share_url = reverse("ema:share", args=(self.ema.id, self.ema.access_token,))
self.share_create_url = reverse("ema:share-create", args=(self.ema.id,))
self.share_url = reverse("ema:share-token", args=(self.ema.id, self.ema.access_token,))
self.share_create_url = reverse("ema:share-form", args=(self.ema.id,))
self.record_url = reverse("ema:record", args=(self.ema.id,))
self.report_url = reverse("ema:report", args=(self.ema.id,))
self.new_doc_url = reverse("ema:new-doc", args=(self.ema.id,))

View File

@ -6,7 +6,17 @@ Created on: 19.08.21
"""
from django.urls import path
from ema.views import *
from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActionView
from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView
from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView
from ema.views.ema import index_view, new_view, new_id_view, detail_view, edit_view, remove_view
from ema.views.log import EmaLogView
from ema.views.record import EmaRecordView
from ema.views.report import report_view
from ema.views.resubmission import EmaResubmissionView
from ema.views.share import EmaShareFormView, EmaShareByTokenView
from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateView
app_name = "ema"
urlpatterns = [
@ -14,31 +24,32 @@ urlpatterns = [
path("new/", new_view, name="new"),
path("new/id", new_id_view, name="new-id"),
path("<id>", detail_view, name="detail"),
path('<id>/log', log_view, name='log'),
path('<id>/log', EmaLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/record', record_view, name='record'),
path('<id>/record', EmaRecordView.as_view(), name='record'),
path('<id>/report', report_view, name='report'),
path('<id>/resub', EmaResubmissionView.as_view(), name='resubmission-create'),
path('<id>/state/new', state_new_view, name='new-state'),
path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
path('<id>/state/new', NewEmaStateView.as_view(), name='new-state'),
path('<id>/state/<state_id>/remove', RemoveEmaStateView.as_view(), name='state-remove'),
path('<id>/state/<state_id>/edit', EditEmaStateView.as_view(), name='state-edit'),
path('<id>/action/new', action_new_view, name='new-action'),
path('<id>/action/<action_id>/edit', action_edit_view, name='action-edit'),
path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
path('<id>/action/new', NewEmaActionView.as_view(), name='new-action'),
path('<id>/action/<action_id>/edit', EditEmaActionView.as_view(), name='action-edit'),
path('<id>/action/<action_id>/remove', RemoveEmaActionView.as_view(), name='action-remove'),
path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
path('<id>/deadline/new', NewEmaDeadlineView.as_view(), name="new-deadline"),
path('<id>/deadline/<deadline_id>/edit', EditEmaDeadlineView.as_view(), name='deadline-edit'),
path('<id>/deadline/<deadline_id>/remove', RemoveEmaDeadlineView.as_view(), name='deadline-remove'),
path('<id>/share/<token>', share_view, name='share'),
path('<id>/share', create_share_view, name='share-create'),
path('<id>/share/<token>', EmaShareByTokenView.as_view(), name='share-token'),
path('<id>/share', EmaShareFormView.as_view(), name='share-form'),
# Documents
path('<id>/document/new/', document_new_view, name='new-doc'),
path('<id>/document/<doc_id>', get_document_view, name='get-doc'),
path('<id>/document/<doc_id>/edit/', edit_document_view, name='edit-doc'),
path('<id>/document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
path('<id>/document/new/', NewEmaDocumentView.as_view(), name='new-doc'),
path('<id>/document/<doc_id>', GetEmaDocumentView.as_view(), name='get-doc'),
path('<id>/document/<doc_id>/edit/', EditEmaDocumentView.as_view(), name='edit-doc'),
path('<id>/document/<doc_id>/remove/', RemoveEmaDocumentView.as_view(), name='remove-doc'),
]

View File

@ -1,712 +0,0 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Sum
from django.http import HttpRequest, JsonResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, EditCompensationStateModalForm, \
EditCompensationActionModalForm, EditDeadlineModalForm
from compensation.models import CompensationAction, CompensationState
from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentModalForm
from ema.tables import EmaTable
from intervention.forms.modalForms import ShareModalForm
from konova.contexts import BaseContext
from konova.decorators import conservation_office_group_required, shared_access_required
from ema.models import Ema, EmaDocument
from konova.forms import RemoveModalForm, SimpleGeomForm, RecordModalForm, RemoveDeadlineModalForm, \
EditDocumentModalForm
from konova.models import Deadline
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.documents import get_document, remove_document
from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, \
COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, \
COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, RECORDED_BLOCKS_EDIT
from konova.utils.user_checks import in_group
@login_required
def index_view(request: HttpRequest):
""" Renders the index view for EMAs
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "generic_index.html"
emas = Ema.objects.filter(
deleted=None,
).order_by(
"-modified"
)
table = EmaTable(
request,
queryset=emas
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("EMAs - Overview"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@conservation_office_group_required
def new_view(request: HttpRequest):
"""
Renders a view for a new eco account creation
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "ema/form/view.html"
data_form = NewEmaForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
generated_identifier = data_form.cleaned_data.get("identifier", None)
ema = data_form.save(request.user, geom_form)
if generated_identifier != ema.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
ema.identifier
)
)
messages.success(request, _("EMA {} added").format(ema.identifier))
return redirect("ema:detail", id=ema.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New EMA"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@conservation_office_group_required
def new_id_view(request: HttpRequest):
""" JSON endpoint
Provides fetching of free identifiers for e.g. AJAX calls
"""
tmp = Ema()
identifier = tmp.generate_new_identifier()
while Ema.objects.filter(identifier=identifier).exists():
identifier = tmp.generate_new_identifier()
return JsonResponse(
data={
"gen_data": identifier
}
)
@login_required
def detail_view(request: HttpRequest, id: str):
""" Renders the detail view of an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA id
Returns:
"""
template = "ema/detail/view.html"
ema = get_object_or_404(Ema, id=id, deleted=None)
geom_form = SimpleGeomForm(instance=ema)
parcels = ema.get_underlying_parcels()
_user = request.user
is_data_shared = ema.is_shared_with(_user)
# Order states according to surface
before_states = ema.before_states.all().order_by("-surface")
after_states = ema.after_states.all().order_by("-surface")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = before_states.aggregate(Sum("surface"))["surface__sum"] or 0
sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
diff_states = abs(sum_before_states - sum_after_states)
ema.set_status_messages(request)
context = {
"obj": ema,
"geom_form": geom_form,
"parcels": parcels,
"has_access": is_data_shared,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP),
"LANIS_LINK": ema.get_LANIS_link(),
TAB_TITLE_IDENTIFIER: f"{ema.identifier} - {ema.title}",
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def log_view(request: HttpRequest, id: str):
""" Renders a log view using modal
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
template = "modal/modal_generic.html"
body_template = "log.html"
context = {
"modal_body_template": body_template,
"log": ema.log.all(),
"modal_title": _("Log"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
# Get object from db
ema = get_object_or_404(Ema, id=id)
if ema.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("ema:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditEmaForm(request.POST or None, instance=ema)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
# The data form takes the geom form for processing, as well as the performing user
ema = data_form.save(request.user, geom_form)
messages.success(request, _("EMA {} edited").format(ema.identifier))
return redirect("ema:detail", id=ema.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = RemoveModalForm(request.POST or None, instance=ema, request=request)
return form.process_request(
request=request,
msg_success=_("EMA removed"),
redirect_url=reverse("ema:index"),
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def record_view(request: HttpRequest, id: str):
""" Renders a modal view for recording the EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
msg_succ = _("{} unrecorded") if ema.recorded else _("{} recorded")
form = RecordModalForm(request.POST or None, instance=ema, request=request)
return form.process_request(
request=request,
msg_success=msg_succ.format("EMA"),
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def state_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new states for an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id to which the new state will be related
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = NewStateModalForm(request.POST or None, instance=ema, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_ADDED,
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def action_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new actions for an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id to which the new state will be related
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = NewActionModalForm(request.POST or None, instance=ema, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_ACTION_ADDED,
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def action_edit_view(request: HttpRequest, id: str, action_id: str):
""" Renders a form for editing an actions for an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id
action_id (str): The action id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
action = get_object_or_404(CompensationAction, id=action_id)
form = EditCompensationActionModalForm(request.POST or None, instance=ema, action=action, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_ACTION_EDITED,
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def deadline_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new states for an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id to which the new state will be related
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = NewDeadlineModalForm(request.POST or None, instance=ema, request=request)
return form.process_request(
request,
msg_success=DEADLINE_ADDED,
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def document_new_view(request: HttpRequest, id: str):
""" Renders a form for uploading new documents
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id to which the new document will be related
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = NewEmaDocumentModalForm(request.POST or None, request.FILES or None, instance=ema, request=request)
return form.process_request(
request,
msg_success=DOCUMENT_ADDED,
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def get_document_view(request: HttpRequest, id: str, doc_id: str):
""" Returns the document as downloadable file
Wraps the generic document fetcher function from konova.utils.
Args:
request (HttpRequest): The incoming request
id (str): The EMA id
doc_id (str): The document id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
doc = get_object_or_404(EmaDocument, id=doc_id)
return get_document(doc)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def edit_document_view(request: HttpRequest, id: str, doc_id: str):
""" Removes the document from the database and file system
Wraps the generic functionality from konova.utils.
Args:
request (HttpRequest): The incoming request
id (str): The EMA id
doc_id (str): The document id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
doc = get_object_or_404(EmaDocument, id=doc_id)
form = EditDocumentModalForm(request.POST or None, request.FILES or None, instance=ema, document=doc, request=request)
return form.process_request(
request,
DOCUMENT_EDITED,
reverse("ema:detail", args=(id,)) + "#related_data"
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def remove_document_view(request: HttpRequest, id:str, doc_id: str):
""" Removes the document from the database and file system
Wraps the generic functionality from konova.utils.
Args:
request (HttpRequest): The incoming request
id (str): The EMA id
doc_id (str): The document id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
doc = get_object_or_404(EmaDocument, id=doc_id)
return remove_document(
request,
doc
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def state_remove_view(request: HttpRequest, id: str, state_id: str):
""" Renders a form for removing an EMA state
Args:
request (HttpRequest): The incoming request
id (str): The ema id
state_id (str): The state's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
state = get_object_or_404(CompensationState, id=state_id)
form = RemoveCompensationStateModalForm(request.POST or None, instance=ema, state=state, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_REMOVED,
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def state_edit_view(request: HttpRequest, id: str, state_id: str):
""" Renders a form for editing an EMA state
Args:
request (HttpRequest): The incoming request
id (str): The ema id
state_id (str): The state's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
state = get_object_or_404(CompensationState, id=state_id)
form = EditCompensationStateModalForm(request.POST or None, instance=ema, state=state, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_EDITED,
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def action_remove_view(request: HttpRequest, id: str, action_id: str):
""" Renders a form for removing an EMA action
Args:
request (HttpRequest): The incoming request
id (str): The ema id
id (str): The action's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
action = get_object_or_404(CompensationAction, id=action_id)
form = RemoveCompensationActionModalForm(request.POST or None, instance=ema, action=action, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_ACTION_REMOVED,
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
)
def report_view(request:HttpRequest, id: str):
""" Renders the public report view
Args:
request (HttpRequest): The incoming request
id (str): The id of the intervention
Returns:
"""
# Reuse the compensation report template since EMAs are structurally identical
template = "ema/report/report.html"
ema = get_object_or_404(Ema, id=id)
tab_title = _("Report {}").format(ema.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not ema.is_ready_for_publish():
template = "report/unavailable.html"
context = {
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=ema,
)
parcels = ema.get_underlying_parcels()
qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,)))
qrcode_img = generate_qr_code(qrcode_url, 10)
qrcode_lanis_url = ema.get_LANIS_link()
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
# Order states by surface
before_states = ema.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = ema.after_states.all().order_by("-surface").prefetch_related("biotope_type")
actions = ema.actions.all().prefetch_related("action_type")
context = {
"obj": ema,
"qrcode": {
"img": qrcode_img,
"url": qrcode_url
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url
},
"has_access": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
"geom_form": geom_form,
"parcels": parcels,
"actions": actions,
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
def share_view(request: HttpRequest, id: str, token: str):
""" Performs sharing of an ema
If token given in url is not valid, the user will be redirected to the dashboard
Args:
request (HttpRequest): The incoming request
id (str): EMA's id
token (str): Access token for EMA
Returns:
"""
user = request.user
obj = get_object_or_404(Ema, id=id)
# Check tokens
if obj.access_token == token:
# Send different messages in case user has already been added to list of sharing users
if obj.is_shared_with(user):
messages.info(
request,
_("{} has already been shared with you").format(obj.identifier)
)
else:
messages.success(
request,
_("{} has been shared with you").format(obj.identifier)
)
obj.share_with_user(user)
return redirect("ema:detail", id=id)
else:
messages.error(
request,
_("Share link invalid"),
extra_tags="danger",
)
return redirect("home")
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def create_share_view(request: HttpRequest, id: str):
""" Renders sharing form for an Ema
Args:
request (HttpRequest): The incoming request
id (str): Ema's id
Returns:
"""
obj = get_object_or_404(Ema, id=id)
form = ShareModalForm(request.POST or None, instance=obj, request=request)
return form.process_request(
request,
msg_success=_("Share settings updated")
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def deadline_edit_view(request: HttpRequest, id: str, deadline_id: str):
""" Renders a form for editing deadlines from a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
deadline_id (str): The deadline's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
deadline = get_object_or_404(Deadline, id=deadline_id)
form = EditDeadlineModalForm(request.POST or None, instance=ema, deadline=deadline, request=request)
return form.process_request(
request,
msg_success=DEADLINE_EDITED,
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
""" Renders a form for removing deadlines from a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
deadline_id (str): The deadline's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
deadline = get_object_or_404(Deadline, id=deadline_id)
form = RemoveDeadlineModalForm(request.POST or None, instance=ema, deadline=deadline, request=request)
return form.process_request(
request,
msg_success=DEADLINE_REMOVED,
redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
)

7
ema/views/__init__.py Normal file
View File

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""

50
ema/views/action.py Normal file
View File

@ -0,0 +1,50 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView
class NewEmaActionView(AbstractNewCompensationActionView):
model = Ema
redirect_url = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEmaActionView(AbstractEditCompensationActionView):
model = Ema
redirect_url = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEmaActionView(AbstractRemoveCompensationActionView):
model = Ema
redirect_url = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

50
ema/views/deadline.py Normal file
View File

@ -0,0 +1,50 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.deadline import AbstractNewDeadlineView, AbstractRemoveDeadlineView, AbstractEditDeadlineView
class NewEmaDeadlineView(AbstractNewDeadlineView):
model = Ema
redirect_url = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEmaDeadlineView(AbstractEditDeadlineView):
model = Ema
redirect_url = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEmaDeadlineView(AbstractRemoveDeadlineView):
model = Ema
redirect_url = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

66
ema/views/document.py Normal file
View File

@ -0,0 +1,66 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.forms import NewEmaDocumentModalForm
from ema.models import Ema, EmaDocument
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.forms.modals import EditDocumentModalForm
from konova.views.document import AbstractEditDocumentView, AbstractRemoveDocumentView, AbstractGetDocumentView, \
AbstractNewDocumentView
class NewEmaDocumentView(AbstractNewDocumentView):
model = Ema
form = NewEmaDocumentModalForm
redirect_url = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class GetEmaDocumentView(AbstractGetDocumentView):
model = Ema
document_model = EmaDocument
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEmaDocumentView(AbstractRemoveDocumentView):
model = Ema
document_model = EmaDocument
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEmaDocumentView(AbstractEditDocumentView):
model = Ema
document_model = EmaDocument
form = EditDocumentModalForm
redirect_url = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

238
ema/views/ema.py Normal file
View File

@ -0,0 +1,238 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Sum
from django.http import HttpRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from ema.forms import NewEmaForm, EditEmaForm
from ema.models import Ema
from ema.tables import EmaTable
from konova.contexts import BaseContext
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.forms import SimpleGeomForm
from konova.forms.modals import RemoveModalForm
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID
from konova.utils.user_checks import in_group
@login_required
def index_view(request: HttpRequest):
""" Renders the index view for EMAs
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "generic_index.html"
emas = Ema.objects.filter(
deleted=None,
).order_by(
"-modified"
)
table = EmaTable(
request,
queryset=emas
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("EMAs - Overview"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@conservation_office_group_required
def new_view(request: HttpRequest):
"""
Renders a view for a new eco account creation
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "ema/form/view.html"
data_form = NewEmaForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
generated_identifier = data_form.cleaned_data.get("identifier", None)
ema = data_form.save(request.user, geom_form)
if generated_identifier != ema.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
ema.identifier
)
)
messages.success(request, _("EMA {} added").format(ema.identifier))
return redirect("ema:detail", id=ema.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New EMA"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@conservation_office_group_required
def new_id_view(request: HttpRequest):
""" JSON endpoint
Provides fetching of free identifiers for e.g. AJAX calls
"""
tmp = Ema()
identifier = tmp.generate_new_identifier()
while Ema.objects.filter(identifier=identifier).exists():
identifier = tmp.generate_new_identifier()
return JsonResponse(
data={
"gen_data": identifier
}
)
@login_required
def detail_view(request: HttpRequest, id: str):
""" Renders the detail view of an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA id
Returns:
"""
template = "ema/detail/view.html"
ema = get_object_or_404(Ema, id=id, deleted=None)
geom_form = SimpleGeomForm(instance=ema)
parcels = ema.get_underlying_parcels()
_user = request.user
is_data_shared = ema.is_shared_with(_user)
# Order states according to surface
before_states = ema.before_states.all().order_by("-surface")
after_states = ema.after_states.all().order_by("-surface")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = before_states.aggregate(Sum("surface"))["surface__sum"] or 0
sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
diff_states = abs(sum_before_states - sum_after_states)
ema.set_status_messages(request)
context = {
"obj": ema,
"geom_form": geom_form,
"parcels": parcels,
"has_access": is_data_shared,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP),
"LANIS_LINK": ema.get_LANIS_link(),
TAB_TITLE_IDENTIFIER: f"{ema.identifier} - {ema.title}",
"has_finished_deadlines": ema.get_finished_deadlines().exists(),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
# Get object from db
ema = get_object_or_404(Ema, id=id)
if ema.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("ema:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditEmaForm(request.POST or None, instance=ema)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
# The data form takes the geom form for processing, as well as the performing user
ema = data_form.save(request.user, geom_form)
messages.success(request, _("EMA {} edited").format(ema.identifier))
return redirect("ema:detail", id=ema.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required_modal
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = RemoveModalForm(request.POST or None, instance=ema, request=request)
return form.process_request(
request=request,
msg_success=_("EMA removed"),
redirect_url=reverse("ema:index"),
)

Some files were not shown because too many files have changed in this diff Show More