7_New_forms #28
@ -13,7 +13,7 @@ from codelist.models import KonovaCode, KonovaCodeList
 | 
			
		||||
from codelist.settings import CODELIST_INTERVENTION_HANDLER_ID, CODELIST_CONSERVATION_OFFICE_ID, \
 | 
			
		||||
    CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, CODELIST_COMPENSATION_HANDLER_ID, \
 | 
			
		||||
    CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_CLASS_ID, CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID, \
 | 
			
		||||
    CODELIST_COMPENSATION_COMBINATION_ID, CODELIST_BASE_URL, CODELIST_PROCESS_TYPE_ID
 | 
			
		||||
    CODELIST_COMPENSATION_FUNDING_ID, CODELIST_BASE_URL, CODELIST_PROCESS_TYPE_ID
 | 
			
		||||
 | 
			
		||||
bool_map = {
 | 
			
		||||
    "true": True,
 | 
			
		||||
@ -35,7 +35,7 @@ class Command(BaseCommand):
 | 
			
		||||
                CODELIST_COMPENSATION_ACTION_ID,
 | 
			
		||||
                CODELIST_COMPENSATION_ACTION_CLASS_ID,
 | 
			
		||||
                CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID,
 | 
			
		||||
                CODELIST_COMPENSATION_COMBINATION_ID,
 | 
			
		||||
                CODELIST_COMPENSATION_FUNDING_ID,
 | 
			
		||||
                CODELIST_PROCESS_TYPE_ID,
 | 
			
		||||
            ]
 | 
			
		||||
            self._write_warning("Fetching codes...")
 | 
			
		||||
 | 
			
		||||
@ -53,8 +53,9 @@ class KonovaCode(models.Model):
 | 
			
		||||
        if self.parent:
 | 
			
		||||
            ret_val += self.parent.long_name + " > "
 | 
			
		||||
        ret_val += self.long_name
 | 
			
		||||
        if self.short_name:
 | 
			
		||||
            ret_val += " ({})".format(self.short_name)
 | 
			
		||||
        if self.short_name and self.short_name != self.long_name:
 | 
			
		||||
            # Only add short name, if we won't have stupid repition like 'thing a (thing a)' due to misused long-short names
 | 
			
		||||
            ret_val += f" ({self.short_name})"
 | 
			
		||||
        return ret_val
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
 | 
			
		||||
@ -21,4 +21,4 @@ CODELIST_COMPENSATION_HANDLER_ID = 1052  # CLEingreifer
 | 
			
		||||
CODELIST_COMPENSATION_ACTION_ID = 1026  # CLMassnahmedetail
 | 
			
		||||
CODELIST_COMPENSATION_ACTION_CLASS_ID = 1034  # CLMassnahmeklasse
 | 
			
		||||
CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID = 1028  # CLMassnahmetyp, CEF and stuff
 | 
			
		||||
CODELIST_COMPENSATION_COMBINATION_ID = 1049  # CLKombimassnahme
 | 
			
		||||
CODELIST_COMPENSATION_FUNDING_ID = 1049  # CLKombimassnahme
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ from compensation.views.eco_account_views import *
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path("", index_view, name="acc-index"),
 | 
			
		||||
    path('new/', new_view, name='acc-new'),
 | 
			
		||||
    path('new/id', new_id_view, name='acc-new-id'),
 | 
			
		||||
    path('<id>', open_view, name='acc-open'),
 | 
			
		||||
    path('<id>/log', log_view, name='acc-log'),
 | 
			
		||||
    path('<id>/record', record_view, name='acc-record'),
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@ from compensation.views.compensation_views import *
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    # Main compensation
 | 
			
		||||
    path("", index_view, name="index"),
 | 
			
		||||
    path('new/id', new_id_view, name='new-id'),
 | 
			
		||||
    path('new/<intervention_id>', new_view, name='new'),
 | 
			
		||||
    path('new', new_view, name='new'),
 | 
			
		||||
    path('<id>', open_view, name='open'),
 | 
			
		||||
    path('<id>/log', log_view, name='log'),
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										444
									
								
								compensation/forms/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										444
									
								
								compensation/forms/forms.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,444 @@
 | 
			
		||||
"""
 | 
			
		||||
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 django.contrib.auth.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_COMPENSATION_FUNDING_ID, CODELIST_CONSERVATION_OFFICE_ID
 | 
			
		||||
from compensation.models import Compensation, EcoAccount
 | 
			
		||||
from intervention.inputs import GenerateInput
 | 
			
		||||
from intervention.models import Intervention, ResponsibilityData
 | 
			
		||||
from konova.forms import BaseForm, SimpleGeomForm
 | 
			
		||||
from user.models import UserActionLogEntry, UserAction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    fundings = forms.ModelMultipleChoiceField(
 | 
			
		||||
        label=_("Fundings"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        required=False,
 | 
			
		||||
        help_text=_("Select fundings for this compensation"),
 | 
			
		||||
        queryset=KonovaCode.objects.filter(
 | 
			
		||||
            is_archived=False,
 | 
			
		||||
            is_leaf=True,
 | 
			
		||||
            code_lists__in=[CODELIST_COMPENSATION_FUNDING_ID],
 | 
			
		||||
        ),
 | 
			
		||||
        widget=autocomplete.ModelSelect2Multiple(
 | 
			
		||||
            url="codes-compensation-funding-autocomplete",
 | 
			
		||||
            attrs={
 | 
			
		||||
                "data-placeholder": _("Click for selection"),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    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 = forms.CharField(
 | 
			
		||||
        label=_("Eco-account handler"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        required=False,
 | 
			
		||||
        help_text=_("Who handles the eco-account"),
 | 
			
		||||
        widget=forms.TextInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "placeholder": _("Company Mustermann"),
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewCompensationForm(AbstractCompensationForm):
 | 
			
		||||
    """ 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",
 | 
			
		||||
        "fundings",
 | 
			
		||||
        "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:open", 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 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)
 | 
			
		||||
            fundings = self.cleaned_data.get("fundings", None)
 | 
			
		||||
            intervention = self.cleaned_data.get("intervention", None)
 | 
			
		||||
            comment = self.cleaned_data.get("comment", None)
 | 
			
		||||
 | 
			
		||||
            # Create log entry
 | 
			
		||||
            action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=user,
 | 
			
		||||
                action=UserAction.CREATED,
 | 
			
		||||
            )
 | 
			
		||||
            # 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,
 | 
			
		||||
                geometry=geometry,
 | 
			
		||||
                comment=comment,
 | 
			
		||||
            )
 | 
			
		||||
            comp.fundings.set(fundings)
 | 
			
		||||
 | 
			
		||||
            # Add the log entry to the main objects log list
 | 
			
		||||
            comp.log.add(action)
 | 
			
		||||
        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:open", args=(self.instance.id,))
 | 
			
		||||
 | 
			
		||||
        # Initialize form data
 | 
			
		||||
        form_data = {
 | 
			
		||||
            "identifier": self.instance.identifier,
 | 
			
		||||
            "title": self.instance.title,
 | 
			
		||||
            "intervention": self.instance.intervention,
 | 
			
		||||
            "fundings": self.instance.fundings.all(),
 | 
			
		||||
            "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)
 | 
			
		||||
            fundings = self.cleaned_data.get("fundings", None)
 | 
			
		||||
            intervention = self.cleaned_data.get("intervention", None)
 | 
			
		||||
            comment = self.cleaned_data.get("comment", None)
 | 
			
		||||
 | 
			
		||||
            # Create log entry
 | 
			
		||||
            action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=user,
 | 
			
		||||
                action=UserAction.EDITED,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # 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.comment = comment
 | 
			
		||||
            self.instance.modified = action
 | 
			
		||||
            self.instance.fundings.set(fundings)
 | 
			
		||||
            self.instance.save()
 | 
			
		||||
 | 
			
		||||
            self.instance.log.add(action)
 | 
			
		||||
        return self.instance
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
 | 
			
		||||
    """ Form for creating eco accounts
 | 
			
		||||
 | 
			
		||||
    Inherits from basic AbstractCompensationForm and further form fields from CompensationResponsibleFormMixin
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    field_order = [
 | 
			
		||||
        "identifier",
 | 
			
		||||
        "title",
 | 
			
		||||
        "conservation_office",
 | 
			
		||||
        "conservation_file_number",
 | 
			
		||||
        "handler",
 | 
			
		||||
        "fundings",
 | 
			
		||||
        "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)
 | 
			
		||||
            fundings = self.cleaned_data.get("fundings", None)
 | 
			
		||||
            handler = self.cleaned_data.get("handler", 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)
 | 
			
		||||
 | 
			
		||||
            # Create log entry
 | 
			
		||||
            action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=user,
 | 
			
		||||
                action=UserAction.CREATED,
 | 
			
		||||
            )
 | 
			
		||||
            # Process the geometry form
 | 
			
		||||
            geometry = geom_form.save(action)
 | 
			
		||||
 | 
			
		||||
            responsible = ResponsibilityData.objects.create(
 | 
			
		||||
                handler=handler,
 | 
			
		||||
                conservation_file_number=conservation_file_number,
 | 
			
		||||
                conservation_office=conservation_office,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Finally create main object
 | 
			
		||||
            acc = EcoAccount.objects.create(
 | 
			
		||||
                identifier=identifier,
 | 
			
		||||
                title=title,
 | 
			
		||||
                responsible=responsible,
 | 
			
		||||
                deductable_surface=0.00,
 | 
			
		||||
                created=action,
 | 
			
		||||
                geometry=geometry,
 | 
			
		||||
                comment=comment,
 | 
			
		||||
            )
 | 
			
		||||
            acc.fundings.set(fundings)
 | 
			
		||||
            acc.users.add(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
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    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"
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    field_order = [
 | 
			
		||||
        "identifier",
 | 
			
		||||
        "title",
 | 
			
		||||
        "conservation_office",
 | 
			
		||||
        "surface",
 | 
			
		||||
        "conservation_file_number",
 | 
			
		||||
        "handler",
 | 
			
		||||
        "fundings",
 | 
			
		||||
        "comment",
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    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-open", args=(self.instance.id,))
 | 
			
		||||
 | 
			
		||||
        # Initialize form data
 | 
			
		||||
        form_data = {
 | 
			
		||||
            "identifier": self.instance.identifier,
 | 
			
		||||
            "title": self.instance.title,
 | 
			
		||||
            "surface": self.instance.deductable_surface,
 | 
			
		||||
            "handler": self.instance.responsible.handler,
 | 
			
		||||
            "conservation_office": self.instance.responsible.conservation_office,
 | 
			
		||||
            "conservation_file_number": self.instance.responsible.conservation_file_number,
 | 
			
		||||
            "fundings": self.instance.fundings.all(),
 | 
			
		||||
            "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)
 | 
			
		||||
            fundings = self.cleaned_data.get("fundings", None)
 | 
			
		||||
            handler = self.cleaned_data.get("handler", 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)
 | 
			
		||||
 | 
			
		||||
            # Create log entry
 | 
			
		||||
            action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=user,
 | 
			
		||||
                action=UserAction.EDITED,
 | 
			
		||||
            )
 | 
			
		||||
            # Process the geometry form
 | 
			
		||||
            geometry = geom_form.save(action)
 | 
			
		||||
 | 
			
		||||
            # Update responsible data
 | 
			
		||||
            self.instance.responsible.handler = handler
 | 
			
		||||
            self.instance.responsible.conservation_office = conservation_office
 | 
			
		||||
            self.instance.responsible.conservation_file_number = conservation_file_number
 | 
			
		||||
            self.instance.responsible.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.modified = action
 | 
			
		||||
            self.instance.save()
 | 
			
		||||
            self.instance.fundings.set(fundings)
 | 
			
		||||
 | 
			
		||||
            # Add the log entry to the main objects log list
 | 
			
		||||
            self.instance.log.add(action)
 | 
			
		||||
        return self.instance
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 04.12.20
 | 
			
		||||
Created on: 04.10.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from bootstrap_modal_forms.utils import is_ajax
 | 
			
		||||
@ -12,40 +12,34 @@ from django.contrib import messages
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.http import HttpRequest, HttpResponseRedirect
 | 
			
		||||
from django.shortcuts import render
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.utils.translation import pgettext_lazy as _con
 | 
			
		||||
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
 | 
			
		||||
from compensation.models import Payment, CompensationState, CompensationAction, UnitChoices
 | 
			
		||||
from compensation.models import Payment, CompensationState, UnitChoices, CompensationAction
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.forms import BaseForm, BaseModalForm
 | 
			
		||||
from konova.models import Deadline, DeadlineType
 | 
			
		||||
from konova.forms import BaseModalForm
 | 
			
		||||
from konova.models import DeadlineType, Deadline
 | 
			
		||||
from konova.utils.message_templates import FORM_INVALID
 | 
			
		||||
from user.models import UserActionLogEntry, UserAction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewCompensationForm(BaseForm):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            user_action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=self.user,
 | 
			
		||||
                action=UserAction.CREATED
 | 
			
		||||
            )
 | 
			
		||||
            # Save action to log
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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"),
 | 
			
		||||
@ -56,6 +50,7 @@ class NewPaymentForm(BaseModalForm):
 | 
			
		||||
            attrs={
 | 
			
		||||
                "type": "date",
 | 
			
		||||
                "data-provide": "datepicker",
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            },
 | 
			
		||||
            format="%d.%m.%Y"
 | 
			
		||||
        )
 | 
			
		||||
@ -69,7 +64,7 @@ class NewPaymentForm(BaseModalForm):
 | 
			
		||||
        widget=forms.Textarea(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "rows": 5,
 | 
			
		||||
                "class": "w-100"
 | 
			
		||||
                "class": "form-control"
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
@ -79,7 +74,6 @@ class NewPaymentForm(BaseModalForm):
 | 
			
		||||
        self.intervention = self.instance
 | 
			
		||||
        self.form_title = _("Payment")
 | 
			
		||||
        self.form_caption = _("Add a payment for intervention '{}'").format(self.intervention.title)
 | 
			
		||||
        self.add_placeholder_for_field("amount", "0,00")
 | 
			
		||||
 | 
			
		||||
    def is_valid(self):
 | 
			
		||||
        """
 | 
			
		||||
@ -129,6 +123,12 @@ class NewPaymentForm(BaseModalForm):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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.ModelChoiceField(
 | 
			
		||||
        label=_("Biotope Type"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
@ -152,14 +152,19 @@ class NewStateModalForm(BaseModalForm):
 | 
			
		||||
        label=_("Surface"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        required=True,
 | 
			
		||||
        help_text=_("in m²")
 | 
			
		||||
        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")
 | 
			
		||||
        self.add_placeholder_for_field("surface", "0,00")
 | 
			
		||||
 | 
			
		||||
    def save(self, is_before_state: bool = False):
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
@ -232,6 +237,9 @@ class NewStateModalForm(BaseModalForm):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewDeadlineModalForm(BaseModalForm):
 | 
			
		||||
    """ Form handling deadline related input
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    type = forms.ChoiceField(
 | 
			
		||||
        label=_("Deadline Type"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
@ -240,7 +248,7 @@ class NewDeadlineModalForm(BaseModalForm):
 | 
			
		||||
        choices=DeadlineType.choices,
 | 
			
		||||
        widget=forms.Select(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "class": "custom-select"
 | 
			
		||||
                "class": "form-control"
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
@ -253,6 +261,7 @@ class NewDeadlineModalForm(BaseModalForm):
 | 
			
		||||
            attrs={
 | 
			
		||||
                "type": "date",
 | 
			
		||||
                "data-provide": "datepicker",
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            },
 | 
			
		||||
            format="%d.%m.%Y"
 | 
			
		||||
        )
 | 
			
		||||
@ -267,6 +276,7 @@ class NewDeadlineModalForm(BaseModalForm):
 | 
			
		||||
            attrs={
 | 
			
		||||
                "cols": 30,
 | 
			
		||||
                "rows": 5,
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
@ -301,6 +311,13 @@ class NewDeadlineModalForm(BaseModalForm):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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).
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    action_type = forms.ModelChoiceField(
 | 
			
		||||
        label=_("Action Type"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
@ -326,7 +343,7 @@ class NewActionModalForm(BaseModalForm):
 | 
			
		||||
        choices=UnitChoices.choices,
 | 
			
		||||
        widget=forms.Select(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "class": "custom-select"
 | 
			
		||||
                "class": "form-control"
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
@ -337,6 +354,12 @@ class NewActionModalForm(BaseModalForm):
 | 
			
		||||
        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,
 | 
			
		||||
@ -347,7 +370,7 @@ class NewActionModalForm(BaseModalForm):
 | 
			
		||||
        widget=forms.Textarea(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "rows": 5,
 | 
			
		||||
                "class": "w-100"
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
@ -356,7 +379,6 @@ class NewActionModalForm(BaseModalForm):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.form_title = _("New action")
 | 
			
		||||
        self.form_caption = _("Insert data for the new action")
 | 
			
		||||
        self.add_placeholder_for_field("amount", "0,00")
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
@ -380,5 +402,4 @@ class NewActionModalForm(BaseModalForm):
 | 
			
		||||
            self.instance.save()
 | 
			
		||||
            self.instance.log.add(edited_action)
 | 
			
		||||
            self.instance.actions.add(comp_action)
 | 
			
		||||
        return comp_action
 | 
			
		||||
 | 
			
		||||
        return comp_action
 | 
			
		||||
@ -16,7 +16,7 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from codelist.models import KonovaCode
 | 
			
		||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, \
 | 
			
		||||
    CODELIST_COMPENSATION_COMBINATION_ID
 | 
			
		||||
    CODELIST_COMPENSATION_FUNDING_ID
 | 
			
		||||
from intervention.models import Intervention, ResponsibilityData
 | 
			
		||||
from konova.models import BaseObject, BaseResource, Geometry, UuidModel, AbstractDocument, \
 | 
			
		||||
    generate_document_file_upload_path
 | 
			
		||||
@ -144,7 +144,7 @@ class AbstractCompensation(BaseObject):
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
        limit_choices_to={
 | 
			
		||||
            "code_lists__in": [CODELIST_COMPENSATION_COMBINATION_ID],
 | 
			
		||||
            "code_lists__in": [CODELIST_COMPENSATION_FUNDING_ID],
 | 
			
		||||
            "is_selectable": True,
 | 
			
		||||
            "is_archived": False,
 | 
			
		||||
        },
 | 
			
		||||
@ -185,9 +185,9 @@ class Compensation(AbstractCompensation):
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        if self.identifier is None or len(self.identifier) == 0:
 | 
			
		||||
            # Create new identifier
 | 
			
		||||
            new_id = self._generate_new_identifier()
 | 
			
		||||
            new_id = self.generate_new_identifier()
 | 
			
		||||
            while Compensation.objects.filter(identifier=new_id).exists():
 | 
			
		||||
                new_id = self._generate_new_identifier()
 | 
			
		||||
                new_id = self.generate_new_identifier()
 | 
			
		||||
            self.identifier = new_id
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
@ -322,9 +322,9 @@ class EcoAccount(AbstractCompensation):
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        if self.identifier is None or len(self.identifier) == 0:
 | 
			
		||||
            # Create new identifier
 | 
			
		||||
            new_id = self._generate_new_identifier()
 | 
			
		||||
            new_id = self.generate_new_identifier()
 | 
			
		||||
            while EcoAccount.objects.filter(identifier=new_id).exists():
 | 
			
		||||
                new_id = self._generate_new_identifier()
 | 
			
		||||
                new_id = self.generate_new_identifier()
 | 
			
		||||
            self.identifier = new_id
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
@ -355,28 +355,28 @@ class EcoAccount(AbstractCompensation):
 | 
			
		||||
        """
 | 
			
		||||
        return self.after_states.all().aggregate(Sum("surface"))["surface__sum"] or 0
 | 
			
		||||
 | 
			
		||||
    def get_available_rest(self, as_percentage: bool = False) -> float:
 | 
			
		||||
    def get_available_rest(self) -> (float, float):
 | 
			
		||||
        """ Calculates available rest surface of the eco account
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            as_percentage (bool): Whether to return the result as m² or %
 | 
			
		||||
 | 
			
		||||
        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 = available_surfaces - deductions_surfaces
 | 
			
		||||
        ret_val_total = available_surfaces - deductions_surfaces
 | 
			
		||||
 | 
			
		||||
        if as_percentage:
 | 
			
		||||
            if available_surfaces > 0:
 | 
			
		||||
                ret_val = int((ret_val / available_surfaces) * 100)
 | 
			
		||||
            else:
 | 
			
		||||
                ret_val = 0
 | 
			
		||||
        return ret_val
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
    def get_LANIS_link(self) -> str:
 | 
			
		||||
        """ Generates a link for LANIS depending on the geometry
 | 
			
		||||
 | 
			
		||||
@ -5,8 +5,8 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 18.12.20
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
COMPENSATION_IDENTIFIER_LENGTH = 10
 | 
			
		||||
COMPENSATION_IDENTIFIER_LENGTH = 6
 | 
			
		||||
COMPENSATION_IDENTIFIER_TEMPLATE = "KOM-{}"
 | 
			
		||||
 | 
			
		||||
ECO_ACCOUNT_IDENTIFIER_LENGTH = 10
 | 
			
		||||
ECO_ACCOUNT_IDENTIFIER_LENGTH = 6
 | 
			
		||||
ECO_ACCOUNT_IDENTIFIER_TEMPLATE = "OEK-{}"
 | 
			
		||||
@ -5,6 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 01.12.20
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
@ -148,6 +149,8 @@ class CompensationTable(BaseTable):
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        html = ""
 | 
			
		||||
        if value is None:
 | 
			
		||||
            value = User.objects.none()
 | 
			
		||||
        has_access = value.filter(
 | 
			
		||||
            username=self.user.username
 | 
			
		||||
        ).exists()
 | 
			
		||||
@ -236,8 +239,8 @@ class EcoAccountTable(BaseTable):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        value = record.get_available_rest(as_percentage=True)
 | 
			
		||||
        html = render_to_string("konova/custom_widgets/progressbar.html", {"value": value})
 | 
			
		||||
        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_r(self, value, record: EcoAccount):
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
{% load i18n fontawesome_5 %}
 | 
			
		||||
 | 
			
		||||
{% if obj.comment %}
 | 
			
		||||
<div class="w-100">
 | 
			
		||||
    <div class="card mt-3">
 | 
			
		||||
        <div class="card-header rlp-gd">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-sm-12 col-md-12 col-lg-12">
 | 
			
		||||
                    <h5 class="card-title">
 | 
			
		||||
                        {% fa5_icon 'info-circle' %}
 | 
			
		||||
                        {% trans 'Comment' %}
 | 
			
		||||
                    </h5>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
            <div class="card-text font-italic">
 | 
			
		||||
                {{obj.comment}}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
@ -13,7 +13,7 @@
 | 
			
		||||
    </a>
 | 
			
		||||
    {% if has_access %}
 | 
			
		||||
        {% if is_default_member %}
 | 
			
		||||
        <a href="{% url 'home' %}" class="mr-2">
 | 
			
		||||
        <a href="{% url 'compensation:edit' obj.id %}" class="mr-2">
 | 
			
		||||
            <button class="btn btn-default" title="{% trans 'Edit' %}">
 | 
			
		||||
                {% fa5_icon 'edit' %}
 | 
			
		||||
            </button>
 | 
			
		||||
 | 
			
		||||
@ -75,7 +75,7 @@
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <br>
 | 
			
		||||
                            {% empty %}
 | 
			
		||||
                            None
 | 
			
		||||
                            {% trans 'None' %}
 | 
			
		||||
                            {% endfor %}
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
@ -99,7 +99,12 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-sm-12 col-md-12 col-lg-6">
 | 
			
		||||
            {% include 'map/geom_form.html' %}
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                {% include 'map/geom_form.html' %}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                {% include 'compensation/detail/compensation/includes/comment.html' %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr>
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
{% load i18n fontawesome_5 %}
 | 
			
		||||
 | 
			
		||||
{% if obj.comment %}
 | 
			
		||||
<div class="w-100">
 | 
			
		||||
    <div class="card mt-3">
 | 
			
		||||
        <div class="card-header rlp-gd">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-sm-12 col-md-12 col-lg-12">
 | 
			
		||||
                    <h5 class="card-title">
 | 
			
		||||
                        {% fa5_icon 'info-circle' %}
 | 
			
		||||
                        {% trans 'Comment' %}
 | 
			
		||||
                    </h5>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
            <div class="card-text font-italic">
 | 
			
		||||
                {{obj.comment}}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
@ -24,7 +24,7 @@
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% if is_default_member %}
 | 
			
		||||
        <a href="{% url 'home' %}" class="mr-2">
 | 
			
		||||
        <a href="{% url 'compensation:acc-edit' obj.id %}" class="mr-2">
 | 
			
		||||
            <button class="btn btn-default" title="{% trans 'Edit' %}">
 | 
			
		||||
                {% fa5_icon 'edit' %}
 | 
			
		||||
            </button>
 | 
			
		||||
 | 
			
		||||
@ -30,12 +30,12 @@
 | 
			
		||||
                        <th class="w-25" scope="row">{% trans 'Title' %}</th>
 | 
			
		||||
                        <td class="align-middle">{{obj.title}}</td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                    <tr {% if not obj.deductable_surface %}class="alert alert-danger" title="{% trans 'No surface deductable' %}" {% endif %}>
 | 
			
		||||
                        <th scope="row">{% trans 'Available' %}</th>
 | 
			
		||||
                        <td class="align-middle">
 | 
			
		||||
                            {{obj.deductions_surface_sum|floatformat:2}} / {{obj.deductable_surface|floatformat:2}} m²
 | 
			
		||||
                            {{available_total|floatformat:2}} / {{obj.deductable_surface|default_if_none:0.00|floatformat:2}} m²
 | 
			
		||||
                            {% with available as value %}
 | 
			
		||||
                                {% include 'konova/custom_widgets/progressbar.html' %}
 | 
			
		||||
                                {% include 'konova/widgets/progressbar.html' %}
 | 
			
		||||
                            {% endwith %}
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
@ -58,7 +58,7 @@
 | 
			
		||||
                        <td class="align-middle">{{obj.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr {% if not obj.responsible.conservation_file_number %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
 | 
			
		||||
                        <th scope="row">{% trans 'Conversation office file number' %}</th>
 | 
			
		||||
                        <th scope="row">{% trans 'Conservation office file number' %}</th>
 | 
			
		||||
                        <td class="align-middle">{{obj.responsible.conservation_file_number|default_if_none:""}}</td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr {% if not obj.responsible.handler %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
 | 
			
		||||
@ -98,7 +98,12 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-sm-12 col-md-12 col-lg-6">
 | 
			
		||||
            {% include 'map/geom_form.html' %}
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                {% include 'map/geom_form.html' %}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                {% include 'compensation/detail/compensation/includes/comment.html' %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								compensation/templates/compensation/form/view.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								compensation/templates/compensation/form/view.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
{% extends 'base.html' %}
 | 
			
		||||
{% load i18n l10n %}
 | 
			
		||||
 | 
			
		||||
{% block body %}
 | 
			
		||||
    {% include 'form/collapsable/form.html' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -1,16 +1,19 @@
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.db.models import Sum
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
from django.shortcuts import render, get_object_or_404
 | 
			
		||||
from django.http import HttpRequest, JsonResponse
 | 
			
		||||
from django.shortcuts import render
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from compensation.forms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm
 | 
			
		||||
from compensation.forms.forms import NewCompensationForm, EditCompensationForm
 | 
			
		||||
from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm
 | 
			
		||||
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, NewDocumentForm
 | 
			
		||||
from konova.utils.documents import get_document, remove_document
 | 
			
		||||
from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED
 | 
			
		||||
from konova.utils.user_checks import in_group
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -44,16 +47,100 @@ def index_view(request: HttpRequest):
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def new_view(request: HttpRequest):
 | 
			
		||||
    # ToDo
 | 
			
		||||
    pass
 | 
			
		||||
@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"
 | 
			
		||||
    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").format(comp.identifier))
 | 
			
		||||
            return redirect("compensation:open", id=comp.id)
 | 
			
		||||
        else:
 | 
			
		||||
            messages.error(request, FORM_INVALID)
 | 
			
		||||
    else:
 | 
			
		||||
        # For clarification: nothing in this case
 | 
			
		||||
        pass
 | 
			
		||||
    context = {
 | 
			
		||||
        "form": data_form,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
    return render(request, template, context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_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={
 | 
			
		||||
            "identifier": identifier
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def edit_view(request: HttpRequest, id: str):
 | 
			
		||||
    # ToDo
 | 
			
		||||
    pass
 | 
			
		||||
    """
 | 
			
		||||
    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)
 | 
			
		||||
    # 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():
 | 
			
		||||
            # The data form takes the geom form for processing, as well as the performing user
 | 
			
		||||
            comp = data_form.save(request.user, geom_form)
 | 
			
		||||
            messages.success(request, _("Compensation {} edited").format(comp.identifier))
 | 
			
		||||
            return redirect("compensation:open", id=comp.id)
 | 
			
		||||
        else:
 | 
			
		||||
            messages.error(request, FORM_INVALID)
 | 
			
		||||
    else:
 | 
			
		||||
        # For clarification: nothing in this case
 | 
			
		||||
        pass
 | 
			
		||||
    context = {
 | 
			
		||||
        "form": data_form,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
    return render(request, template, context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
 | 
			
		||||
@ -5,23 +5,26 @@ 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
 | 
			
		||||
from django.shortcuts import render, get_object_or_404
 | 
			
		||||
from django.http import HttpRequest, Http404, JsonResponse
 | 
			
		||||
from django.shortcuts import render, get_object_or_404, redirect
 | 
			
		||||
 | 
			
		||||
from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
 | 
			
		||||
from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm
 | 
			
		||||
from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
 | 
			
		||||
from compensation.models import EcoAccount, EcoAccountDocument
 | 
			
		||||
from compensation.tables import EcoAccountTable
 | 
			
		||||
from intervention.forms import NewDeductionForm
 | 
			
		||||
from intervention.forms.modalForms import NewDeductionModalForm
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required
 | 
			
		||||
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm
 | 
			
		||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 | 
			
		||||
from konova.utils.documents import get_document, remove_document
 | 
			
		||||
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID
 | 
			
		||||
from konova.utils.user_checks import in_group
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -56,15 +59,98 @@ def index_view(request: HttpRequest):
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def new_view(request: HttpRequest):
 | 
			
		||||
    # ToDo
 | 
			
		||||
    pass
 | 
			
		||||
    """
 | 
			
		||||
    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-open", id=acc.id)
 | 
			
		||||
        else:
 | 
			
		||||
            messages.error(request, FORM_INVALID)
 | 
			
		||||
    else:
 | 
			
		||||
        # For clarification: nothing in this case
 | 
			
		||||
        pass
 | 
			
		||||
    context = {
 | 
			
		||||
        "form": data_form,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
    return render(request, template, context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_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={
 | 
			
		||||
            "identifier": identifier
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def edit_view(request: HttpRequest, id: str):
 | 
			
		||||
    # ToDo
 | 
			
		||||
    pass
 | 
			
		||||
    """
 | 
			
		||||
    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)
 | 
			
		||||
    # 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-open", id=acc.id)
 | 
			
		||||
        else:
 | 
			
		||||
            messages.error(request, FORM_INVALID)
 | 
			
		||||
    else:
 | 
			
		||||
        # For clarification: nothing in this case
 | 
			
		||||
        pass
 | 
			
		||||
    context = {
 | 
			
		||||
        "form": data_form,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
    return render(request, template, context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@ -96,7 +182,7 @@ def open_view(request: HttpRequest, id: str):
 | 
			
		||||
    diff_states = abs(sum_before_states - sum_after_states)
 | 
			
		||||
 | 
			
		||||
    # Calculate rest of available surface for deductions
 | 
			
		||||
    available = acc.get_available_rest(as_percentage=True)
 | 
			
		||||
    available_total, available_relative = acc.get_available_rest()
 | 
			
		||||
 | 
			
		||||
    deductions = acc.deductions.filter(
 | 
			
		||||
        intervention__deleted=None,
 | 
			
		||||
@ -111,7 +197,8 @@ def open_view(request: HttpRequest, id: str):
 | 
			
		||||
        "sum_before_states": sum_before_states,
 | 
			
		||||
        "sum_after_states": sum_after_states,
 | 
			
		||||
        "diff_states": diff_states,
 | 
			
		||||
        "available": available,
 | 
			
		||||
        "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),
 | 
			
		||||
@ -340,7 +427,7 @@ def new_deduction_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    acc = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    form = NewDeductionForm(request.POST or None, instance=acc, user=request.user)
 | 
			
		||||
    form = NewDeductionModalForm(request.POST or None, instance=acc, user=request.user)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Deduction added")
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
from django.shortcuts import get_object_or_404
 | 
			
		||||
 | 
			
		||||
from compensation.forms import NewPaymentForm
 | 
			
		||||
from compensation.forms.modalForms import NewPaymentForm
 | 
			
		||||
from compensation.models import Payment
 | 
			
		||||
from intervention.models import Intervention
 | 
			
		||||
from konova.decorators import default_group_required
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										159
									
								
								ema/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								ema/forms.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,159 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 06.10.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.contrib.auth.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
 | 
			
		||||
from ema.models import Ema
 | 
			
		||||
from intervention.models import ResponsibilityData
 | 
			
		||||
from konova.forms import SimpleGeomForm
 | 
			
		||||
from user.models import UserActionLogEntry, UserAction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
 | 
			
		||||
    """ Form for creating new EMA objects.
 | 
			
		||||
 | 
			
		||||
    Inherits basic form fields from AbstractCompensationForm and additional from CompensationResponsibleFormMixin.
 | 
			
		||||
    Second holds self.instance.response related fields
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    field_order = [
 | 
			
		||||
        "identifier",
 | 
			
		||||
        "title",
 | 
			
		||||
        "conservation_office",
 | 
			
		||||
        "conservation_file_number",
 | 
			
		||||
        "handler",
 | 
			
		||||
        "fundings",
 | 
			
		||||
        "comment",
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.form_title = _("New EMA")
 | 
			
		||||
 | 
			
		||||
        self.action_url = reverse("ema:new")
 | 
			
		||||
        self.cancel_redirect = reverse("ema:index")
 | 
			
		||||
 | 
			
		||||
        tmp = Ema()
 | 
			
		||||
        identifier = tmp.generate_new_identifier()
 | 
			
		||||
        self.initialize_form_field("identifier", identifier)
 | 
			
		||||
        self.fields["identifier"].widget.attrs["url"] = reverse_lazy("ema:new-id")
 | 
			
		||||
        self.fields["title"].widget.attrs["placeholder"] = _("Compensation 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)
 | 
			
		||||
            fundings = self.cleaned_data.get("fundings", None)
 | 
			
		||||
            handler = self.cleaned_data.get("handler", 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)
 | 
			
		||||
 | 
			
		||||
            # Create log entry
 | 
			
		||||
            action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=user,
 | 
			
		||||
                action=UserAction.CREATED,
 | 
			
		||||
            )
 | 
			
		||||
            # Process the geometry form
 | 
			
		||||
            geometry = geom_form.save(action)
 | 
			
		||||
 | 
			
		||||
            responsible = ResponsibilityData.objects.create(
 | 
			
		||||
                handler=handler,
 | 
			
		||||
                conservation_file_number=conservation_file_number,
 | 
			
		||||
                conservation_office=conservation_office,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Finally create main object
 | 
			
		||||
            acc = Ema.objects.create(
 | 
			
		||||
                identifier=identifier,
 | 
			
		||||
                title=title,
 | 
			
		||||
                responsible=responsible,
 | 
			
		||||
                created=action,
 | 
			
		||||
                geometry=geometry,
 | 
			
		||||
                comment=comment,
 | 
			
		||||
            )
 | 
			
		||||
            acc.fundings.set(fundings)
 | 
			
		||||
 | 
			
		||||
            # Add the creating user to the list of shared users
 | 
			
		||||
            acc.users.add(user)
 | 
			
		||||
 | 
			
		||||
            # Add the log entry to the main objects log list
 | 
			
		||||
            acc.log.add(action)
 | 
			
		||||
        return acc
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditEmaForm(NewEmaForm):
 | 
			
		||||
    """ Form for editing EMAs
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.form_title = _("Edit EMA")
 | 
			
		||||
 | 
			
		||||
        self.action_url = reverse("ema:edit", args=(self.instance.id,))
 | 
			
		||||
        self.cancel_redirect = reverse("ema:open", args=(self.instance.id,))
 | 
			
		||||
 | 
			
		||||
        self.fields["identifier"].widget.attrs["url"] = reverse_lazy("ema:new-id")
 | 
			
		||||
        self.fields["title"].widget.attrs["placeholder"] = _("Compensation XY; Location ABC")
 | 
			
		||||
 | 
			
		||||
        # Initialize form data
 | 
			
		||||
        form_data = {
 | 
			
		||||
            "identifier": self.instance.identifier,
 | 
			
		||||
            "title": self.instance.title,
 | 
			
		||||
            "handler": self.instance.responsible.handler,
 | 
			
		||||
            "conservation_office": self.instance.responsible.conservation_office,
 | 
			
		||||
            "conservation_file_number": self.instance.responsible.conservation_file_number,
 | 
			
		||||
            "fundings": self.instance.fundings.all(),
 | 
			
		||||
            "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)
 | 
			
		||||
            fundings = self.cleaned_data.get("fundings", None)
 | 
			
		||||
            handler = self.cleaned_data.get("handler", 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)
 | 
			
		||||
 | 
			
		||||
            # Create log entry
 | 
			
		||||
            action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=user,
 | 
			
		||||
                action=UserAction.EDITED,
 | 
			
		||||
            )
 | 
			
		||||
            # Process the geometry form
 | 
			
		||||
            geometry = geom_form.save(action)
 | 
			
		||||
 | 
			
		||||
            # Update responsible data
 | 
			
		||||
            self.instance.responsible.handler = handler
 | 
			
		||||
            self.instance.responsible.conservation_office = conservation_office
 | 
			
		||||
            self.instance.responsible.conservation_file_number = conservation_file_number
 | 
			
		||||
            self.instance.responsible.save()
 | 
			
		||||
 | 
			
		||||
            # Update main oject data
 | 
			
		||||
            self.instance.identifier = identifier
 | 
			
		||||
            self.instance.title = title
 | 
			
		||||
            self.instance.geometry = geometry
 | 
			
		||||
            self.instance.comment = comment
 | 
			
		||||
            self.instance.modified = action
 | 
			
		||||
            self.instance.save()
 | 
			
		||||
            self.instance.fundings.set(fundings)
 | 
			
		||||
 | 
			
		||||
            # Add the log entry to the main objects log list
 | 
			
		||||
            self.instance.log.add(action)
 | 
			
		||||
        return self.instance
 | 
			
		||||
@ -49,9 +49,9 @@ class Ema(AbstractCompensation):
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        if self.identifier is None or len(self.identifier) == 0:
 | 
			
		||||
            # Create new identifier
 | 
			
		||||
            new_id = self._generate_new_identifier()
 | 
			
		||||
            new_id = self.generate_new_identifier()
 | 
			
		||||
            while Ema.objects.filter(identifier=new_id).exists():
 | 
			
		||||
                new_id = self._generate_new_identifier()
 | 
			
		||||
                new_id = self.generate_new_identifier()
 | 
			
		||||
            self.identifier = new_id
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,5 +6,5 @@ Created on: 19.08.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
EMA_ACCOUNT_IDENTIFIER_LENGTH = 10
 | 
			
		||||
EMA_ACCOUNT_IDENTIFIER_LENGTH = 6
 | 
			
		||||
EMA_ACCOUNT_IDENTIFIER_TEMPLATE = "EMA-{}"
 | 
			
		||||
@ -24,7 +24,7 @@
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% if is_default_member %}
 | 
			
		||||
        <a href="{% url 'home' %}" class="mr-2">
 | 
			
		||||
        <a href="{% url 'ema:edit' obj.id %}" class="mr-2">
 | 
			
		||||
            <button class="btn btn-default" title="{% trans 'Edit' %}">
 | 
			
		||||
                {% fa5_icon 'edit' %}
 | 
			
		||||
            </button>
 | 
			
		||||
 | 
			
		||||
@ -43,7 +43,7 @@
 | 
			
		||||
                        <td class="align-middle">{{obj.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr {% if not obj.responsible.conservation_file_number %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
 | 
			
		||||
                        <th scope="row">{% trans 'Conversation office file number' %}</th>
 | 
			
		||||
                        <th scope="row">{% trans 'Conservation office file number' %}</th>
 | 
			
		||||
                        <td class="align-middle">{{obj.responsible.conservation_file_number|default_if_none:""}}</td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr {% if not obj.responsible.handler %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								ema/templates/ema/form/view.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								ema/templates/ema/form/view.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
{% extends 'base.html' %}
 | 
			
		||||
{% load i18n l10n %}
 | 
			
		||||
 | 
			
		||||
{% block body %}
 | 
			
		||||
    {% include 'form/collapsable/form.html' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -12,6 +12,7 @@ app_name = "ema"
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path("", index_view, name="index"),
 | 
			
		||||
    path("new/", new_view, name="new"),
 | 
			
		||||
    path("new/id", new_id_view, name="new-id"),
 | 
			
		||||
    path("<id>", open_view, name="open"),
 | 
			
		||||
    path('<id>/log', log_view, name='log'),
 | 
			
		||||
    path('<id>/edit', edit_view, name='edit'),
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										91
									
								
								ema/views.py
									
									
									
									
									
								
							
							
						
						
									
										91
									
								
								ema/views.py
									
									
									
									
									
								
							@ -1,12 +1,14 @@
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.db.models import Sum
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
from django.shortcuts import render, get_object_or_404
 | 
			
		||||
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 _
 | 
			
		||||
 | 
			
		||||
import compensation
 | 
			
		||||
from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
 | 
			
		||||
from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
 | 
			
		||||
from ema.forms import NewEmaForm, EditEmaForm
 | 
			
		||||
from ema.tables import EmaTable
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.decorators import conservation_office_group_required
 | 
			
		||||
@ -14,6 +16,7 @@ from ema.models import Ema, EmaDocument
 | 
			
		||||
from konova.forms import RemoveModalForm, NewDocumentForm, SimpleGeomForm, RecordModalForm
 | 
			
		||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 | 
			
		||||
from konova.utils.documents import get_document, remove_document
 | 
			
		||||
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID
 | 
			
		||||
from konova.utils.user_checks import in_group
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -47,7 +50,8 @@ def index_view(request: HttpRequest):
 | 
			
		||||
@login_required
 | 
			
		||||
@conservation_office_group_required
 | 
			
		||||
def new_view(request: HttpRequest):
 | 
			
		||||
    """ Renders the form for a new EMA
 | 
			
		||||
    """
 | 
			
		||||
    Renders a view for a new eco account creation
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
@ -55,12 +59,54 @@ def new_view(request: HttpRequest):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    template = "generic_index.html"
 | 
			
		||||
    context = {}
 | 
			
		||||
    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:open", id=ema.id)
 | 
			
		||||
        else:
 | 
			
		||||
            messages.error(request, FORM_INVALID)
 | 
			
		||||
    else:
 | 
			
		||||
        # For clarification: nothing in this case
 | 
			
		||||
        pass
 | 
			
		||||
    context = {
 | 
			
		||||
        "form": data_form,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
    return render(request, template, context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_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={
 | 
			
		||||
            "identifier": identifier
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def open_view(request: HttpRequest, id: str):
 | 
			
		||||
    """ Renders the detail view of an EMA
 | 
			
		||||
@ -133,7 +179,38 @@ def log_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def edit_view(request: HttpRequest, id: str):
 | 
			
		||||
    get_object_or_404(Ema, id=id)
 | 
			
		||||
    """
 | 
			
		||||
    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)
 | 
			
		||||
    # 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:open", id=ema.id)
 | 
			
		||||
        else:
 | 
			
		||||
            messages.error(request, FORM_INVALID)
 | 
			
		||||
    else:
 | 
			
		||||
        # For clarification: nothing in this case
 | 
			
		||||
        pass
 | 
			
		||||
    context = {
 | 
			
		||||
        "form": data_form,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
    return render(request, template, context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ from django.db.models import QuerySet, Q
 | 
			
		||||
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from intervention.forms import DummyFilterInput
 | 
			
		||||
from intervention.inputs import DummyFilterInput
 | 
			
		||||
from intervention.models import Intervention
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										359
									
								
								intervention/forms/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								intervention/forms/forms.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,359 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 02.12.20
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from dal import autocomplete
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.urls import reverse, reverse_lazy
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from codelist.models import KonovaCode
 | 
			
		||||
from codelist.settings import CODELIST_PROCESS_TYPE_ID, CODELIST_LAW_ID, \
 | 
			
		||||
    CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID
 | 
			
		||||
from intervention.inputs import GenerateInput
 | 
			
		||||
from intervention.models import Intervention, LegalData, ResponsibilityData
 | 
			
		||||
from konova.forms import BaseForm, SimpleGeomForm
 | 
			
		||||
from user.models import UserActionLogEntry, UserAction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewInterventionForm(BaseForm):
 | 
			
		||||
    identifier = forms.CharField(
 | 
			
		||||
        label=_("Identifier"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        help_text=_("Generated automatically"),
 | 
			
		||||
        widget=GenerateInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
                "url": reverse_lazy("intervention:new-id"),
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    title = forms.CharField(
 | 
			
		||||
        label=_("Title"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        help_text=_("An explanatory name"),
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        widget=forms.TextInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "placeholder": _("Construction XY; Location ABC"),
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    type = forms.ModelChoiceField(
 | 
			
		||||
        label=_("Process type"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        help_text=_(""),
 | 
			
		||||
        required=False,
 | 
			
		||||
        queryset=KonovaCode.objects.filter(
 | 
			
		||||
            is_archived=False,
 | 
			
		||||
            is_leaf=True,
 | 
			
		||||
            code_lists__in=[CODELIST_PROCESS_TYPE_ID],
 | 
			
		||||
        ),
 | 
			
		||||
        widget=autocomplete.ModelSelect2(
 | 
			
		||||
            url="codes-process-type-autocomplete",
 | 
			
		||||
            attrs={
 | 
			
		||||
                "data-placeholder": _("Click for selection"),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    laws = forms.ModelMultipleChoiceField(
 | 
			
		||||
        label=_("Law"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        help_text=_("Multiple selection possible"),
 | 
			
		||||
        required=False,
 | 
			
		||||
        queryset=KonovaCode.objects.filter(
 | 
			
		||||
            is_archived=False,
 | 
			
		||||
            is_leaf=True,
 | 
			
		||||
            code_lists__in=[CODELIST_LAW_ID],
 | 
			
		||||
        ),
 | 
			
		||||
        widget=autocomplete.ModelSelect2Multiple(
 | 
			
		||||
            url="codes-law-autocomplete",
 | 
			
		||||
            attrs={
 | 
			
		||||
                "data-placeholder": _("Click for selection"),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    registration_office = forms.ModelChoiceField(
 | 
			
		||||
        label=_("Registration office"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        required=False,
 | 
			
		||||
        queryset=KonovaCode.objects.filter(
 | 
			
		||||
            is_archived=False,
 | 
			
		||||
            is_leaf=True,
 | 
			
		||||
            code_lists__in=[CODELIST_REGISTRATION_OFFICE_ID],
 | 
			
		||||
        ),
 | 
			
		||||
        widget=autocomplete.ModelSelect2(
 | 
			
		||||
            url="codes-registration-office-autocomplete",
 | 
			
		||||
            attrs={
 | 
			
		||||
                "data-placeholder": _("Click for selection"),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    conservation_office = forms.ModelChoiceField(
 | 
			
		||||
        label=_("Conservation office"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        required=False,
 | 
			
		||||
        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"),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    registration_file_number = forms.CharField(
 | 
			
		||||
        label=_("Registration office file number"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        required=False,
 | 
			
		||||
        widget=forms.TextInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "placeholder": _("ZB-123/ABC.456"),
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    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 = forms.CharField(
 | 
			
		||||
        label=_("Intervention handler"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        required=False,
 | 
			
		||||
        help_text=_("Who performs the intervention"),
 | 
			
		||||
        widget=forms.TextInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "placeholder": _("Company Mustermann"),
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    registration_date = forms.DateField(
 | 
			
		||||
        label=_("Registration date"),
 | 
			
		||||
        label_suffix=_(""),
 | 
			
		||||
        required=False,
 | 
			
		||||
        widget=forms.DateInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "type": "date",
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            },
 | 
			
		||||
            format="%d.%m.%Y"
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    binding_date = forms.DateField(
 | 
			
		||||
        label=_("Binding on"),
 | 
			
		||||
        label_suffix=_(""),
 | 
			
		||||
        required=False,
 | 
			
		||||
        widget=forms.DateInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "type": "date",
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            },
 | 
			
		||||
            format="%d.%m.%Y"
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    comment = forms.CharField(
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        label=_("Comment"),
 | 
			
		||||
        required=False,
 | 
			
		||||
        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 intervention")
 | 
			
		||||
        self.action_url = reverse("intervention:new")
 | 
			
		||||
        self.cancel_redirect = reverse("intervention:index")
 | 
			
		||||
 | 
			
		||||
        tmp_intervention = Intervention()
 | 
			
		||||
        identifier = tmp_intervention.generate_new_identifier()
 | 
			
		||||
        self.initialize_form_field("identifier", identifier)
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
            _type = self.cleaned_data.get("type", None)
 | 
			
		||||
            laws = self.cleaned_data.get("laws", None)
 | 
			
		||||
            handler = self.cleaned_data.get("handler", None)
 | 
			
		||||
            registration_office = self.cleaned_data.get("registration_office", None)
 | 
			
		||||
            conservation_office = self.cleaned_data.get("conservation_office", None)
 | 
			
		||||
            conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
 | 
			
		||||
            registration_file_number = self.cleaned_data.get("registration_file_number", None)
 | 
			
		||||
            binding_date = self.cleaned_data.get("binding_date", None)
 | 
			
		||||
            registration_date = self.cleaned_data.get("registration_date", None)
 | 
			
		||||
            comment = self.cleaned_data.get("comment", None)
 | 
			
		||||
 | 
			
		||||
            # Create log entry
 | 
			
		||||
            action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=user,
 | 
			
		||||
                action=UserAction.CREATED,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Create legal data object (without M2M laws first)
 | 
			
		||||
            legal_data = LegalData.objects.create(
 | 
			
		||||
                registration_date=registration_date,
 | 
			
		||||
                binding_date=binding_date,
 | 
			
		||||
                process_type=_type,
 | 
			
		||||
            )
 | 
			
		||||
            # Then add the M2M laws to the object
 | 
			
		||||
            legal_data.laws.set(laws)
 | 
			
		||||
 | 
			
		||||
            # Create responsible data object
 | 
			
		||||
            responsibility_data = ResponsibilityData.objects.create(
 | 
			
		||||
                registration_office=registration_office,
 | 
			
		||||
                conservation_office=conservation_office,
 | 
			
		||||
                registration_file_number=registration_file_number,
 | 
			
		||||
                conservation_file_number=conservation_file_number,
 | 
			
		||||
                handler=handler,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Process the geometry form
 | 
			
		||||
            geometry = geom_form.save(action)
 | 
			
		||||
 | 
			
		||||
            # Finally create main object, holding the other objects
 | 
			
		||||
            intervention = Intervention.objects.create(
 | 
			
		||||
                identifier=identifier,
 | 
			
		||||
                title=title,
 | 
			
		||||
                responsible=responsibility_data,
 | 
			
		||||
                legal=legal_data,
 | 
			
		||||
                created=action,
 | 
			
		||||
                geometry=geometry,
 | 
			
		||||
                comment=comment,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Add the log entry to the main objects log list
 | 
			
		||||
            intervention.log.add(action)
 | 
			
		||||
 | 
			
		||||
            # Add the performing user as the first user having access to the data
 | 
			
		||||
            intervention.users.add(user)
 | 
			
		||||
        return intervention
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditInterventionForm(NewInterventionForm):
 | 
			
		||||
    """ Subclasses NewInterventionForm
 | 
			
		||||
 | 
			
		||||
    Simply adds initializing of a provided self.instance object into the form fields
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        if self.instance is not None:
 | 
			
		||||
            self.action_url = reverse("intervention:edit", args=(self.instance.id,))
 | 
			
		||||
        self.cancel_redirect = reverse("intervention:open", args=(self.instance.id,))
 | 
			
		||||
        self.form_title = _("Edit intervention")
 | 
			
		||||
        self.form_caption = ""
 | 
			
		||||
 | 
			
		||||
        reg_date = self.instance.legal.registration_date
 | 
			
		||||
        bind_date = self.instance.legal.binding_date
 | 
			
		||||
        if reg_date is not None:
 | 
			
		||||
            reg_date = reg_date.isoformat()
 | 
			
		||||
        if bind_date is not None:
 | 
			
		||||
            bind_date = bind_date.isoformat()
 | 
			
		||||
 | 
			
		||||
        # Initialize form data
 | 
			
		||||
        form_data = {
 | 
			
		||||
            "identifier": self.instance.identifier,
 | 
			
		||||
            "title": self.instance.title,
 | 
			
		||||
            "type": self.instance.legal.process_type,
 | 
			
		||||
            "laws": list(self.instance.legal.laws.values_list("id", flat=True)),
 | 
			
		||||
            "handler": self.instance.responsible.handler,
 | 
			
		||||
            "registration_office": self.instance.responsible.registration_office,
 | 
			
		||||
            "registration_file_number": self.instance.responsible.registration_file_number,
 | 
			
		||||
            "conservation_office": self.instance.responsible.conservation_office,
 | 
			
		||||
            "conservation_file_number": self.instance.responsible.conservation_file_number,
 | 
			
		||||
            "registration_date": reg_date,
 | 
			
		||||
            "binding_date": bind_date,
 | 
			
		||||
            "comment": self.instance.comment,
 | 
			
		||||
        }
 | 
			
		||||
        disabled_fields = []
 | 
			
		||||
        self.load_initial_data(
 | 
			
		||||
            form_data,
 | 
			
		||||
            disabled_fields
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def save(self, user: User, geom_form: SimpleGeomForm):
 | 
			
		||||
        """ Overwrite instance with new form data
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            user ():
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            identifier = self.cleaned_data.get("identifier", None)
 | 
			
		||||
            title = self.cleaned_data.get("title", None)
 | 
			
		||||
            process_type = self.cleaned_data.get("type", None)
 | 
			
		||||
            laws = self.cleaned_data.get("laws", None)
 | 
			
		||||
            handler = self.cleaned_data.get("handler", None)
 | 
			
		||||
            registration_office = self.cleaned_data.get("registration_office", None)
 | 
			
		||||
            registration_file_number = self.cleaned_data.get("registration_file_number", None)
 | 
			
		||||
            conservation_office = self.cleaned_data.get("conservation_office", None)
 | 
			
		||||
            conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
 | 
			
		||||
            registration_date = self.cleaned_data.get("registration_date", None)
 | 
			
		||||
            binding_date = self.cleaned_data.get("binding_date", None)
 | 
			
		||||
            comment = self.cleaned_data.get("comment", None)
 | 
			
		||||
 | 
			
		||||
            self.instance.legal.process_type = process_type
 | 
			
		||||
            self.instance.legal.registration_date = registration_date
 | 
			
		||||
            self.instance.legal.binding_date = binding_date
 | 
			
		||||
            self.instance.legal.laws.set(laws)
 | 
			
		||||
            self.instance.legal.save()
 | 
			
		||||
 | 
			
		||||
            self.instance.responsible.handler = handler
 | 
			
		||||
            self.instance.responsible.registration_office = registration_office
 | 
			
		||||
            self.instance.responsible.registration_file_number = registration_file_number
 | 
			
		||||
            self.instance.responsible.conservation_office = conservation_office
 | 
			
		||||
            self.instance.responsible.conservation_file_number = conservation_file_number
 | 
			
		||||
            self.instance.responsible.save()
 | 
			
		||||
 | 
			
		||||
            user_action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=user,
 | 
			
		||||
                timestamp=timezone.now(),
 | 
			
		||||
                action=UserAction.EDITED,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            geometry = geom_form.save(user_action)
 | 
			
		||||
            self.instance.geometry = geometry
 | 
			
		||||
            self.instance.geometry.save()
 | 
			
		||||
 | 
			
		||||
            self.instance.log.add(user_action)
 | 
			
		||||
 | 
			
		||||
            self.instance.identifier = identifier
 | 
			
		||||
            self.instance.title = title
 | 
			
		||||
            self.instance.comment = comment
 | 
			
		||||
            self.instance.modified = user_action
 | 
			
		||||
            self.instance.save()
 | 
			
		||||
 | 
			
		||||
        return self.instance
 | 
			
		||||
 | 
			
		||||
@ -2,244 +2,28 @@
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 02.12.20
 | 
			
		||||
Created on: 27.09.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from dal import autocomplete
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.contrib.gis import forms as gis_forms
 | 
			
		||||
from django.contrib.gis.geos import Polygon
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.utils.translation import  gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from compensation.models import EcoAccountDeduction, EcoAccount
 | 
			
		||||
from intervention.models import Intervention, Revocation, RevocationDocument
 | 
			
		||||
from konova.forms import BaseForm, BaseModalForm
 | 
			
		||||
from konova.settings import DEFAULT_LAT, DEFAULT_LON, DEFAULT_ZOOM, ZB_GROUP, ETS_GROUP
 | 
			
		||||
from compensation.models import EcoAccount, EcoAccountDeduction
 | 
			
		||||
from intervention.inputs import TextToClipboardInput
 | 
			
		||||
from intervention.models import Revocation, RevocationDocument, Intervention
 | 
			
		||||
from konova.forms import BaseModalForm
 | 
			
		||||
from konova.settings import ZB_GROUP, ETS_GROUP
 | 
			
		||||
from konova.utils.general import format_german_float
 | 
			
		||||
from konova.utils.messenger import Messenger
 | 
			
		||||
from konova.utils.user_checks import in_group
 | 
			
		||||
from organisation.models import Organisation
 | 
			
		||||
from user.models import UserActionLogEntry, UserAction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewInterventionForm(BaseForm):
 | 
			
		||||
    identifier = forms.CharField(
 | 
			
		||||
        label=_("Identifier"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        help_text=_("Generated automatically if none was given"),
 | 
			
		||||
        required=False,
 | 
			
		||||
    )
 | 
			
		||||
    title = forms.CharField(
 | 
			
		||||
        label=_("Title"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
    )
 | 
			
		||||
    type = forms.CharField(
 | 
			
		||||
        label=_("Type"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        help_text=_("Which intervention type is this"),
 | 
			
		||||
    )
 | 
			
		||||
    law = forms.CharField(
 | 
			
		||||
        label=_("Law"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        help_text=_("Based on which law"),
 | 
			
		||||
    )
 | 
			
		||||
    handler = forms.CharField(
 | 
			
		||||
        label=_("Intervention handler"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        help_text=_("Who performs the intervention"),
 | 
			
		||||
    )
 | 
			
		||||
    data_provider = forms.ModelChoiceField(
 | 
			
		||||
        label=_("Data provider"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        help_text=_("Who provides the data for the intervention"),
 | 
			
		||||
        queryset=Organisation.objects.all(),
 | 
			
		||||
        widget=autocomplete.ModelSelect2(
 | 
			
		||||
            url="other-orgs-autocomplete",
 | 
			
		||||
            attrs={
 | 
			
		||||
                "data-placeholder": _("Organization"),
 | 
			
		||||
                "data-minimum-input-length": 3,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    data_provider_detail = forms.CharField(
 | 
			
		||||
        label=_("Data provider details"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        help_text=_("Further details"),
 | 
			
		||||
        required=False,
 | 
			
		||||
    )
 | 
			
		||||
    geometry = gis_forms.MultiPolygonField(
 | 
			
		||||
        widget=gis_forms.OSMWidget(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "default_lat": DEFAULT_LAT,
 | 
			
		||||
                "default_lon": DEFAULT_LON,
 | 
			
		||||
                "default_zoom": DEFAULT_ZOOM,
 | 
			
		||||
                'map_width': 800,
 | 
			
		||||
                'map_height': 500
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        label=_("Map"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        help_text=_("Where does the intervention take place")
 | 
			
		||||
    )
 | 
			
		||||
    documents = forms.FileField(
 | 
			
		||||
        widget=forms.ClearableFileInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "multiple": True,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        label=_("Files"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        required=False,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.form_title = _("New intervention")
 | 
			
		||||
        self.action_url = reverse("intervention:new")
 | 
			
		||||
        self.cancel_redirect = reverse("intervention:index")
 | 
			
		||||
 | 
			
		||||
    def save(self, user: User):
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            identifier = self.cleaned_data.get("identifier", None)
 | 
			
		||||
            title = self.cleaned_data.get("title", None)
 | 
			
		||||
            _type = self.cleaned_data.get("type", None)
 | 
			
		||||
            law = self.cleaned_data.get("law", None)
 | 
			
		||||
            handler = self.cleaned_data.get("handler", None)
 | 
			
		||||
            data_provider = self.cleaned_data.get("data_provider", None)
 | 
			
		||||
            data_provider_detail = self.cleaned_data.get("data_provider_detail", None)
 | 
			
		||||
            geometry = self.cleaned_data.get("geometry", Polygon())
 | 
			
		||||
            documents = self.cleaned_data.get("documents", []) or []
 | 
			
		||||
 | 
			
		||||
            action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=user,
 | 
			
		||||
                action=UserAction.CREATED,
 | 
			
		||||
            )
 | 
			
		||||
            intervention = Intervention(
 | 
			
		||||
                identifier=identifier,
 | 
			
		||||
                title=title,
 | 
			
		||||
                type=_type,
 | 
			
		||||
                law=law,
 | 
			
		||||
                handler=handler,
 | 
			
		||||
                data_provider=data_provider,
 | 
			
		||||
                data_provider_detail=data_provider_detail,
 | 
			
		||||
                geometry=geometry,
 | 
			
		||||
                created=action,
 | 
			
		||||
            )
 | 
			
		||||
            intervention.save()
 | 
			
		||||
            intervention.log.add(action)
 | 
			
		||||
        return intervention
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditInterventionForm(NewInterventionForm):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        if self.instance is not None:
 | 
			
		||||
            self.action_url = reverse("intervention:edit", args=(self.instance.id,))
 | 
			
		||||
        self.cancel_redirect = reverse("intervention:index")
 | 
			
		||||
        self.form_title = _("Edit intervention")
 | 
			
		||||
        self.form_caption = ""
 | 
			
		||||
 | 
			
		||||
        # Initialize form data
 | 
			
		||||
        form_data = {
 | 
			
		||||
            "identifier": self.instance.identifier,
 | 
			
		||||
            "title": self.instance.title,
 | 
			
		||||
            "type": self.instance.type,
 | 
			
		||||
            "law": self.instance.law,
 | 
			
		||||
            "handler": self.instance.handler,
 | 
			
		||||
            "data_provider": self.instance.data_provider,
 | 
			
		||||
            "data_provider_detail": self.instance.data_provider_detail,
 | 
			
		||||
            "geometry": self.instance.geometry,
 | 
			
		||||
            "documents": self.instance.documents.all(),
 | 
			
		||||
        }
 | 
			
		||||
        disabled_fields = [
 | 
			
		||||
            "identifier",
 | 
			
		||||
        ]
 | 
			
		||||
        self.load_initial_data(
 | 
			
		||||
            form_data,
 | 
			
		||||
            disabled_fields,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def save(self, user: User):
 | 
			
		||||
        """ Overwrite instance with new form data
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            user ():
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            identifier = self.cleaned_data.get("identifier", None)
 | 
			
		||||
            title = self.cleaned_data.get("title", None)
 | 
			
		||||
            _type = self.cleaned_data.get("type", None)
 | 
			
		||||
            law = self.cleaned_data.get("law", None)
 | 
			
		||||
            handler = self.cleaned_data.get("handler", None)
 | 
			
		||||
            data_provider = self.cleaned_data.get("data_provider", None)
 | 
			
		||||
            data_provider_detail = self.cleaned_data.get("data_provider_detail", None)
 | 
			
		||||
            geometry = self.cleaned_data.get("geometry", Polygon())
 | 
			
		||||
            documents = self.cleaned_data.get("documents", []) or []
 | 
			
		||||
 | 
			
		||||
            self.instance.identifier = identifier
 | 
			
		||||
            self.instance.title = title
 | 
			
		||||
            self.instance.type = _type
 | 
			
		||||
            self.instance.law = law
 | 
			
		||||
            self.instance.handler = handler
 | 
			
		||||
            self.instance.data_provider = data_provider
 | 
			
		||||
            self.instance.data_provider_detail = data_provider_detail
 | 
			
		||||
            self.instance.geometry = geometry
 | 
			
		||||
            self.instance.save()
 | 
			
		||||
 | 
			
		||||
            user_action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=self.user,
 | 
			
		||||
                action=UserAction.EDITED
 | 
			
		||||
            )
 | 
			
		||||
            self.instance.log.add(user_action)
 | 
			
		||||
            self.instance.modified = user_action
 | 
			
		||||
            self.instance.save()
 | 
			
		||||
 | 
			
		||||
        return self.instance
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OpenInterventionForm(EditInterventionForm):
 | 
			
		||||
    """
 | 
			
		||||
    This form is not intended to be used as data-input form. It's used to simplify the rendering of intervention:open
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        # Resize map
 | 
			
		||||
        self.fields["geometry"].widget.attrs["map_width"] = 500
 | 
			
		||||
        self.fields["geometry"].widget.attrs["map_height"] = 300
 | 
			
		||||
 | 
			
		||||
        # Disable all form fields
 | 
			
		||||
        for field in self.fields:
 | 
			
		||||
            self.disable_form_field(field)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DummyFilterInput(forms.HiddenInput):
 | 
			
		||||
    """ A dummy input widget
 | 
			
		||||
 | 
			
		||||
    Does not render anything. Can be used to keep filter logic using django_filter without having a pre defined
 | 
			
		||||
    filter widget being rendered to the template.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    template_name = "konova/custom_widgets/dummy-filter-input.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TextToClipboardInput(forms.TextInput):
 | 
			
		||||
    template_name = "konova/custom_widgets/text-to-clipboard-input.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShareInterventionForm(BaseModalForm):
 | 
			
		||||
class ShareInterventionModalForm(BaseModalForm):
 | 
			
		||||
    url = forms.CharField(
 | 
			
		||||
        label=_("Share link"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
@ -247,7 +31,8 @@ class ShareInterventionForm(BaseModalForm):
 | 
			
		||||
        required=False,
 | 
			
		||||
        widget=TextToClipboardInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "readonly": True
 | 
			
		||||
                "readonly": True,
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
@ -316,7 +101,7 @@ class ShareInterventionForm(BaseModalForm):
 | 
			
		||||
        self.instance.users.set(accessing_users)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewRevocationForm(BaseModalForm):
 | 
			
		||||
class NewRevocationModalForm(BaseModalForm):
 | 
			
		||||
    date = forms.DateField(
 | 
			
		||||
        label=_("Date"),
 | 
			
		||||
        label_suffix=_(""),
 | 
			
		||||
@ -325,6 +110,7 @@ class NewRevocationForm(BaseModalForm):
 | 
			
		||||
            attrs={
 | 
			
		||||
                "type": "date",
 | 
			
		||||
                "data-provide": "datepicker",
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            },
 | 
			
		||||
            format="%d.%m.%Y"
 | 
			
		||||
        )
 | 
			
		||||
@ -336,7 +122,7 @@ class NewRevocationForm(BaseModalForm):
 | 
			
		||||
        help_text=_("Must be smaller than 15 Mb"),
 | 
			
		||||
        widget=forms.FileInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "class": "w-75"
 | 
			
		||||
                "class": "form-control-file"
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
@ -350,6 +136,7 @@ class NewRevocationForm(BaseModalForm):
 | 
			
		||||
            attrs={
 | 
			
		||||
                "cols": 30,
 | 
			
		||||
                "rows": 5,
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
@ -394,7 +181,7 @@ class NewRevocationForm(BaseModalForm):
 | 
			
		||||
        return revocation
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RunCheckForm(BaseModalForm):
 | 
			
		||||
class RunCheckModalForm(BaseModalForm):
 | 
			
		||||
    checked_intervention = forms.BooleanField(
 | 
			
		||||
        label=_("Checked intervention data"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
@ -458,7 +245,7 @@ class RunCheckForm(BaseModalForm):
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewDeductionForm(BaseModalForm):
 | 
			
		||||
class NewDeductionModalForm(BaseModalForm):
 | 
			
		||||
    """ Form for creating new deduction
 | 
			
		||||
 | 
			
		||||
    Can be used for Intervention view as well as for EcoAccount views.
 | 
			
		||||
@ -487,6 +274,12 @@ class NewDeductionForm(BaseModalForm):
 | 
			
		||||
        label=_("Surface"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        help_text=_("in m²"),
 | 
			
		||||
        widget=forms.NumberInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
                "placeholder": "0,00",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    intervention = forms.ModelChoiceField(
 | 
			
		||||
        label=_("Intervention"),
 | 
			
		||||
@ -508,9 +301,6 @@ class NewDeductionForm(BaseModalForm):
 | 
			
		||||
        self.form_caption = _("Enter the information for a new deduction from a chosen eco-account")
 | 
			
		||||
        self.is_intervention_initially = False
 | 
			
		||||
 | 
			
		||||
        # Add a placeholder for field 'surface' without having to define the whole widget above
 | 
			
		||||
        self.add_placeholder_for_field("surface", "0,00")
 | 
			
		||||
 | 
			
		||||
        # Check for Intervention or EcoAccount
 | 
			
		||||
        if isinstance(self.instance, Intervention):
 | 
			
		||||
            # Form has been called with a given intervention
 | 
			
		||||
							
								
								
									
										32
									
								
								intervention/inputs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								intervention/inputs.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
from django import forms
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DummyFilterInput(forms.HiddenInput):
 | 
			
		||||
    """ A dummy input widget
 | 
			
		||||
 | 
			
		||||
    Does not render anything. Can be used to keep filter logic using django_filter without having a pre defined
 | 
			
		||||
    filter widget being rendered to the template.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    template_name = "konova/widgets/empty-dummy-input.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TextToClipboardInput(forms.TextInput):
 | 
			
		||||
    template_name = "konova/widgets/text-to-clipboard-input.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GenerateInput(forms.TextInput):
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    Provides a form group with a button at the end, which generates new content for the input.
 | 
			
		||||
    The url used to fetch new content can be added using the attrs like
 | 
			
		||||
 | 
			
		||||
    widget=GenerateInput(
 | 
			
		||||
        attrs={
 | 
			
		||||
            "url": reverse_lazy("app_name:view_name")
 | 
			
		||||
            ...
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    template_name = "konova/widgets/generate-content-input.html"
 | 
			
		||||
@ -260,14 +260,41 @@ class Intervention(BaseObject):
 | 
			
		||||
            self.save()
 | 
			
		||||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        """ Custom save functionality
 | 
			
		||||
 | 
			
		||||
        Performs some pre-save checks:
 | 
			
		||||
            1. Checking for existing identifiers
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            *args ():
 | 
			
		||||
            **kwargs ():
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if self.identifier is None or len(self.identifier) == 0:
 | 
			
		||||
            # Create new identifier
 | 
			
		||||
            new_id = self._generate_new_identifier()
 | 
			
		||||
            while Intervention.objects.filter(identifier=new_id).exists():
 | 
			
		||||
                new_id = self._generate_new_identifier()
 | 
			
		||||
            self.identifier = new_id
 | 
			
		||||
            # No identifier given
 | 
			
		||||
            self.identifier = self.generate_new_identifier()
 | 
			
		||||
 | 
			
		||||
        # Before saving, make sure the set identifier is not used, yet
 | 
			
		||||
        while Intervention.objects.filter(identifier=self.identifier).exists():
 | 
			
		||||
            self.identifier = self.generate_new_identifier()
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def delete(self, using=None, keep_parents=False):
 | 
			
		||||
        to_delete = [
 | 
			
		||||
            self.legal,
 | 
			
		||||
            self.responsible,
 | 
			
		||||
            self.geometry,
 | 
			
		||||
            self.log.all()
 | 
			
		||||
        ]
 | 
			
		||||
        for entry in to_delete:
 | 
			
		||||
            try:
 | 
			
		||||
                entry.delete()
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                pass
 | 
			
		||||
        super().delete(using, keep_parents)
 | 
			
		||||
 | 
			
		||||
    def quality_check(self) -> list:
 | 
			
		||||
        """ Quality check
 | 
			
		||||
 | 
			
		||||
@ -298,7 +325,7 @@ class Intervention(BaseObject):
 | 
			
		||||
                ret_msgs.append(_("Registration office file number missing"))
 | 
			
		||||
 | 
			
		||||
            if not self.responsible.conservation_file_number or len(self.responsible.conservation_file_number) == 0:
 | 
			
		||||
                ret_msgs.append(_("Conversation office file number missing"))
 | 
			
		||||
                ret_msgs.append(_("Conservation office file number missing"))
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            # responsible data not found
 | 
			
		||||
            ret_msgs.append(_("Responsible data missing"))
 | 
			
		||||
 | 
			
		||||
@ -5,5 +5,5 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 30.11.20
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
INTERVENTION_IDENTIFIER_LENGTH = 10
 | 
			
		||||
INTERVENTION_IDENTIFIER_LENGTH = 6
 | 
			
		||||
INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}"
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
{% load i18n fontawesome_5 %}
 | 
			
		||||
 | 
			
		||||
{% if intervention.comment %}
 | 
			
		||||
<div class="w-100">
 | 
			
		||||
    <div class="card mt-3">
 | 
			
		||||
        <div class="card-header rlp-gd">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-sm-12 col-md-12 col-lg-12">
 | 
			
		||||
                    <h5 class="card-title">
 | 
			
		||||
                        {% fa5_icon 'info-circle' %}
 | 
			
		||||
                        {% trans 'Comment' %}
 | 
			
		||||
                    </h5>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
            <div class="card-text font-italic">
 | 
			
		||||
                {{intervention.comment}}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
@ -11,7 +11,7 @@
 | 
			
		||||
            <div class="col-sm-6">
 | 
			
		||||
                <div class="d-flex justify-content-end">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <a href="{% url 'compensation:new' %}" title="{% trans 'Add new compensation' %}">
 | 
			
		||||
                    <a href="{% url 'compensation:new' intervention.id %}" title="{% trans 'Add new compensation' %}">
 | 
			
		||||
                        <button class="btn btn-outline-default">
 | 
			
		||||
                            {% fa5_icon 'plus' %}
 | 
			
		||||
                            {% fa5_icon 'leaf' %}
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% if is_default_member %}
 | 
			
		||||
            <a href="{% url 'home' %}" class="mr-2">
 | 
			
		||||
            <a href="{% url 'intervention:edit' intervention.id %}" class="mr-2">
 | 
			
		||||
                <button class="btn btn-default" title="{% trans 'Edit' %}">
 | 
			
		||||
                    {% fa5_icon 'edit' %}
 | 
			
		||||
                </button>
 | 
			
		||||
 | 
			
		||||
@ -56,7 +56,7 @@
 | 
			
		||||
                        <td class="align-middle">{{intervention.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr {% if not intervention.responsible.conservation_file_number %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
 | 
			
		||||
                        <th scope="row">{% trans 'Conversation office file number' %}</th>
 | 
			
		||||
                        <th scope="row">{% trans 'Conservation office file number' %}</th>
 | 
			
		||||
                        <td class="align-middle">{{intervention.responsible.conservation_file_number|default_if_none:""}}</td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr {% if not intervention.responsible.handler %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
 | 
			
		||||
@ -123,7 +123,12 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-sm-12 col-md-12 col-lg-6">
 | 
			
		||||
            {% include 'map/geom_form.html' %}
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                {% include 'map/geom_form.html' %}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                {% include 'intervention/detail/includes/comment.html' %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								intervention/templates/intervention/form/view.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								intervention/templates/intervention/form/view.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
{% extends 'base.html' %}
 | 
			
		||||
{% load i18n l10n %}
 | 
			
		||||
 | 
			
		||||
{% block body %}
 | 
			
		||||
    {% include 'form/collapsable/form.html' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -9,12 +9,13 @@ from django.urls import path
 | 
			
		||||
 | 
			
		||||
from intervention.views import index_view, new_view, open_view, edit_view, remove_view, new_document_view, share_view, \
 | 
			
		||||
    create_share_view, remove_revocation_view, new_revocation_view, run_check_view, log_view, new_deduction_view, \
 | 
			
		||||
    record_view, remove_document_view, get_document_view, get_revocation_view
 | 
			
		||||
    record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view
 | 
			
		||||
 | 
			
		||||
app_name = "intervention"
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path("", index_view, name="index"),
 | 
			
		||||
    path('new/', new_view, name='new'),
 | 
			
		||||
    path('new/id', new_id_view, name='new-id'),
 | 
			
		||||
    path('<id>', open_view, name='open'),
 | 
			
		||||
    path('<id>/log', log_view, name='log'),
 | 
			
		||||
    path('<id>/edit', edit_view, name='edit'),
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,11 @@
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
from django.http import HttpRequest, JsonResponse
 | 
			
		||||
from django.shortcuts import render, get_object_or_404
 | 
			
		||||
 | 
			
		||||
from intervention.forms import NewInterventionForm, EditInterventionForm, ShareInterventionForm, NewRevocationForm, \
 | 
			
		||||
    RunCheckForm, NewDeductionForm
 | 
			
		||||
from intervention.forms.forms import NewInterventionForm, EditInterventionForm
 | 
			
		||||
from intervention.forms.modalForms import ShareInterventionModalForm, NewRevocationModalForm, \
 | 
			
		||||
    RunCheckModalForm, NewDeductionModalForm
 | 
			
		||||
from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
 | 
			
		||||
from intervention.tables import InterventionTable
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
@ -13,7 +13,7 @@ from konova.decorators import *
 | 
			
		||||
from konova.forms import SimpleGeomForm, NewDocumentForm, RemoveModalForm, RecordModalForm
 | 
			
		||||
from konova.sub_settings.django_settings import DEFAULT_DATE_FORMAT
 | 
			
		||||
from konova.utils.documents import remove_document, get_document
 | 
			
		||||
from konova.utils.message_templates import FORM_INVALID, INTERVENTION_INVALID
 | 
			
		||||
from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED
 | 
			
		||||
from konova.utils.user_checks import in_group
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -58,25 +58,54 @@ def new_view(request: HttpRequest):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    template = "konova/form.html"
 | 
			
		||||
    form = NewInterventionForm(request.POST or None)
 | 
			
		||||
    template = "intervention/form/view.html"
 | 
			
		||||
    data_form = NewInterventionForm(request.POST or None)
 | 
			
		||||
    geom_form = SimpleGeomForm(request.POST or None, read_only=False)
 | 
			
		||||
    if request.method == "POST":
 | 
			
		||||
        if form.is_valid():
 | 
			
		||||
            intervention = form.save(request.user)
 | 
			
		||||
            messages.success(request, _("Intervention {} added").format(intervention.title))
 | 
			
		||||
            return redirect("intervention:index")
 | 
			
		||||
        if data_form.is_valid() and geom_form.is_valid():
 | 
			
		||||
            generated_identifier = data_form.cleaned_data.get("identifier", None)
 | 
			
		||||
            intervention = data_form.save(request.user, geom_form)
 | 
			
		||||
            if generated_identifier != intervention.identifier:
 | 
			
		||||
                messages.info(
 | 
			
		||||
                    request,
 | 
			
		||||
                    IDENTIFIER_REPLACED.format(
 | 
			
		||||
                        generated_identifier,
 | 
			
		||||
                        intervention.identifier
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
            messages.success(request, _("Intervention {} added").format(intervention.identifier))
 | 
			
		||||
            return redirect("intervention:open", id=intervention.id)
 | 
			
		||||
        else:
 | 
			
		||||
            messages.error(request, _("Invalid input"))
 | 
			
		||||
            messages.error(request, FORM_INVALID)
 | 
			
		||||
    else:
 | 
			
		||||
        # For clarification: nothing in this case
 | 
			
		||||
        pass
 | 
			
		||||
    context = {
 | 
			
		||||
        "form": form,
 | 
			
		||||
        "form": data_form,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
    return render(request, template, context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def new_id_view(request: HttpRequest):
 | 
			
		||||
    """ JSON endpoint
 | 
			
		||||
 | 
			
		||||
    Provides fetching of free identifiers for e.g. AJAX calls
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    tmp_intervention = Intervention()
 | 
			
		||||
    identifier = tmp_intervention.generate_new_identifier()
 | 
			
		||||
    while Intervention.objects.filter(identifier=identifier).exists():
 | 
			
		||||
        identifier = tmp_intervention.generate_new_identifier()
 | 
			
		||||
    return JsonResponse(
 | 
			
		||||
        data={
 | 
			
		||||
            "identifier": identifier
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def new_document_view(request: HttpRequest, id: str):
 | 
			
		||||
    """ Renders a form for uploading new documents
 | 
			
		||||
@ -212,19 +241,26 @@ def edit_view(request: HttpRequest, id: str):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    template = "konova/form.html"
 | 
			
		||||
    template = "intervention/form/view.html"
 | 
			
		||||
    # Get object from db
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    # Create forms, initialize with values from db/from POST request
 | 
			
		||||
    data_form = EditInterventionForm(request.POST or None, instance=intervention)
 | 
			
		||||
    geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention)
 | 
			
		||||
    if request.method == "POST":
 | 
			
		||||
        form = EditInterventionForm(request.POST or None, instance=intervention)
 | 
			
		||||
        if form.is_valid():
 | 
			
		||||
            intervention = form.save(request.user)
 | 
			
		||||
            messages.success(request, _("{} edited").format(intervention))
 | 
			
		||||
            return redirect("intervention:index")
 | 
			
		||||
        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
 | 
			
		||||
            intervention = data_form.save(request.user, geom_form)
 | 
			
		||||
            messages.success(request, _("Intervention {} edited").format(intervention.identifier))
 | 
			
		||||
            return redirect("intervention:open", id=intervention.id)
 | 
			
		||||
        else:
 | 
			
		||||
            messages.error(request, _("Invalid input"))
 | 
			
		||||
    form = EditInterventionForm(instance=intervention)
 | 
			
		||||
            messages.error(request, FORM_INVALID)
 | 
			
		||||
    else:
 | 
			
		||||
        # For clarification: nothing in this case
 | 
			
		||||
        pass
 | 
			
		||||
    context = {
 | 
			
		||||
        "form": form,
 | 
			
		||||
        "form": data_form,
 | 
			
		||||
        "geom_form": geom_form,
 | 
			
		||||
    }
 | 
			
		||||
    context = BaseContext(request, context).context
 | 
			
		||||
    return render(request, template, context)
 | 
			
		||||
@ -324,7 +360,7 @@ def create_share_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    form = ShareInterventionForm(request.POST or None, instance=intervention, request=request)
 | 
			
		||||
    form = ShareInterventionModalForm(request.POST or None, instance=intervention, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Share settings updated")
 | 
			
		||||
@ -343,7 +379,7 @@ def run_check_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    form = RunCheckForm(request.POST or None, instance=intervention, user=request.user)
 | 
			
		||||
    form = RunCheckModalForm(request.POST or None, instance=intervention, user=request.user)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Check performed"),
 | 
			
		||||
@ -363,7 +399,7 @@ def new_revocation_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    form = NewRevocationForm(request.POST or None, request.FILES or None, instance=intervention, user=request.user)
 | 
			
		||||
    form = NewRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, user=request.user)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Revocation added")
 | 
			
		||||
@ -407,7 +443,7 @@ def new_deduction_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    form = NewDeductionForm(request.POST or None, instance=intervention, user=request.user)
 | 
			
		||||
    form = NewDeductionModalForm(request.POST or None, instance=intervention, user=request.user)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Deduction added")
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,8 @@ from django.db.models import Q
 | 
			
		||||
 | 
			
		||||
from codelist.models import KonovaCode
 | 
			
		||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, \
 | 
			
		||||
    CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_PROCESS_TYPE_ID
 | 
			
		||||
    CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_PROCESS_TYPE_ID, \
 | 
			
		||||
    CODELIST_COMPENSATION_FUNDING_ID
 | 
			
		||||
from compensation.models import EcoAccount
 | 
			
		||||
from intervention.models import Intervention
 | 
			
		||||
from organisation.models import Organisation
 | 
			
		||||
@ -45,12 +46,18 @@ class NonOfficialOrganisationAutocomplete(Select2QuerySetView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EcoAccountAutocomplete(Select2QuerySetView):
 | 
			
		||||
    """ Autocomplete for ecoAccount entries
 | 
			
		||||
 | 
			
		||||
    Only returns entries that are accessible for the requesting user and already are recorded
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        if self.request.user.is_anonymous:
 | 
			
		||||
            return EcoAccount.objects.none()
 | 
			
		||||
        qs = EcoAccount.objects.filter(
 | 
			
		||||
            deleted=None,
 | 
			
		||||
            recorded__isnull=False,
 | 
			
		||||
            users__in=[self.request.user],
 | 
			
		||||
        )
 | 
			
		||||
        if self.q:
 | 
			
		||||
            qs = qs.filter(
 | 
			
		||||
@ -63,6 +70,11 @@ class EcoAccountAutocomplete(Select2QuerySetView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InterventionAutocomplete(Select2QuerySetView):
 | 
			
		||||
    """ Autocomplete for intervention entries
 | 
			
		||||
 | 
			
		||||
    Only returns entries that are accessible for the requesting user
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        if self.request.user.is_anonymous:
 | 
			
		||||
            return Intervention.objects.none()
 | 
			
		||||
@ -104,10 +116,18 @@ class KonovaCodeAutocomplete(Select2QuerySetView):
 | 
			
		||||
                code_lists__in=[self.c]
 | 
			
		||||
            )
 | 
			
		||||
        if self.q:
 | 
			
		||||
            q_or = Q()
 | 
			
		||||
            q_or |= Q(long_name__icontains=self.q)
 | 
			
		||||
            q_or |= Q(short_name__icontains=self.q)
 | 
			
		||||
            qs = qs.filter(q_or)
 | 
			
		||||
            # 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)
 | 
			
		||||
                _filter.add(q_or, Q.AND)
 | 
			
		||||
            qs = qs.filter(_filter).distinct()
 | 
			
		||||
        return qs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -120,6 +140,15 @@ class CompensationActionCodeAutocomplete(KonovaCodeAutocomplete):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CompensationFundingCodeAutocomplete(KonovaCodeAutocomplete):
 | 
			
		||||
    """
 | 
			
		||||
    Due to limitations of the django dal package, we need to subclass for each code list
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.c = CODELIST_COMPENSATION_FUNDING_ID
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BiotopeCodeAutocomplete(KonovaCodeAutocomplete):
 | 
			
		||||
    """
 | 
			
		||||
    Due to limitations of the django dal package, we need to subclass for each code list
 | 
			
		||||
 | 
			
		||||
@ -9,11 +9,12 @@ Created on: 16.11.20
 | 
			
		||||
from functools import wraps
 | 
			
		||||
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.shortcuts import redirect
 | 
			
		||||
from django.shortcuts import redirect, get_object_or_404
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from konova.settings import DEFAULT_GROUP, ETS_GROUP, ZB_GROUP
 | 
			
		||||
from konova.utils.message_templates import MISSING_GROUP_PERMISSION, DATA_UNSHARED
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def staff_required(function):
 | 
			
		||||
@ -80,7 +81,7 @@ def default_group_required(function):
 | 
			
		||||
        if has_group:
 | 
			
		||||
            return function(request, *args, **kwargs)
 | 
			
		||||
        else:
 | 
			
		||||
            messages.info(request, _("You need to be part of another user group."))
 | 
			
		||||
            messages.info(request, MISSING_GROUP_PERMISSION)
 | 
			
		||||
            return redirect(request.META.get("HTTP_REFERER", reverse("home")))
 | 
			
		||||
    return wrap
 | 
			
		||||
 | 
			
		||||
@ -100,7 +101,7 @@ def registration_office_group_required(function):
 | 
			
		||||
        if has_group:
 | 
			
		||||
            return function(request, *args, **kwargs)
 | 
			
		||||
        else:
 | 
			
		||||
            messages.info(request, _("You need to be part of another user group."))
 | 
			
		||||
            messages.info(request, MISSING_GROUP_PERMISSION)
 | 
			
		||||
            return redirect(request.META.get("HTTP_REFERER", reverse("home")))
 | 
			
		||||
    return wrap
 | 
			
		||||
 | 
			
		||||
@ -120,6 +121,35 @@ def conservation_office_group_required(function):
 | 
			
		||||
        if has_group:
 | 
			
		||||
            return function(request, *args, **kwargs)
 | 
			
		||||
        else:
 | 
			
		||||
            messages.info(request, _("You need to be part of another user group."))
 | 
			
		||||
            messages.info(request, MISSING_GROUP_PERMISSION)
 | 
			
		||||
            return redirect(request.META.get("HTTP_REFERER", reverse("home")))
 | 
			
		||||
    return wrap
 | 
			
		||||
    return wrap
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def shared_access_required(obj_class, id_key):
 | 
			
		||||
    """ Checks whether the data is shared with the requesting user
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        obj_class (Model): The object/model class
 | 
			
		||||
        id_key (str): The name of the identifier attribute in **kwargs
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    def decorator(function):
 | 
			
		||||
        @wraps(function)
 | 
			
		||||
        def wrap(request, *args, **kwargs):
 | 
			
		||||
            user = request.user
 | 
			
		||||
            _id = kwargs.get(id_key, None)
 | 
			
		||||
            if _id is not None:
 | 
			
		||||
                obj = get_object_or_404(obj_class, id=_id)
 | 
			
		||||
                is_shared = obj.is_shared_with(user)
 | 
			
		||||
                if not is_shared:
 | 
			
		||||
                    messages.info(
 | 
			
		||||
                        request,
 | 
			
		||||
                        DATA_UNSHARED
 | 
			
		||||
                    )
 | 
			
		||||
                    return redirect("home")
 | 
			
		||||
            return function(request, *args, **kwargs)
 | 
			
		||||
        return wrap
 | 
			
		||||
    return decorator
 | 
			
		||||
@ -13,8 +13,8 @@ from bootstrap_modal_forms.utils import is_ajax
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.contrib.gis.forms import GeometryField, OSMWidget
 | 
			
		||||
from django.contrib.gis.geos import Polygon
 | 
			
		||||
from django.contrib.gis.forms import OSMWidget, MultiPolygonField
 | 
			
		||||
from django.contrib.gis.geos import Polygon, MultiPolygon
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.http import HttpRequest, HttpResponseRedirect
 | 
			
		||||
from django.shortcuts import render
 | 
			
		||||
@ -25,7 +25,8 @@ from compensation.models import EcoAccount, Compensation, EcoAccountDocument, Co
 | 
			
		||||
from ema.models import Ema, EmaDocument
 | 
			
		||||
from intervention.models import Intervention, Revocation, RevocationDocument, InterventionDocument
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.models import BaseObject
 | 
			
		||||
from konova.models import BaseObject, Geometry
 | 
			
		||||
from konova.settings import DEFAULT_SRID
 | 
			
		||||
from konova.utils.message_templates import FORM_INVALID
 | 
			
		||||
from user.models import UserActionLogEntry, UserAction
 | 
			
		||||
 | 
			
		||||
@ -75,7 +76,7 @@ class BaseForm(forms.Form):
 | 
			
		||||
 | 
			
		||||
    def add_placeholder_for_field(self, field: str, val):
 | 
			
		||||
        """
 | 
			
		||||
        Adds a placeholder to a field after initialization
 | 
			
		||||
        Adds a placeholder to a field after initialization without the need to redefine the form widget
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            field (str): Field name
 | 
			
		||||
@ -184,16 +185,10 @@ class BaseModalForm(BaseForm, BSModalForm):
 | 
			
		||||
    """
 | 
			
		||||
    is_modal_form = True
 | 
			
		||||
    render_submit = True
 | 
			
		||||
    full_width_fields = False
 | 
			
		||||
    template = "modal/modal_form.html"
 | 
			
		||||
 | 
			
		||||
    def __init__(self, full_width_fields: bool = True, *args, **kwargs):
 | 
			
		||||
        self.full_width_fields = full_width_fields
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        if self.full_width_fields:
 | 
			
		||||
            # Automatically add bootstrap w-100 class for maximum width of form fields in modals
 | 
			
		||||
            for key, val in self.fields.items():
 | 
			
		||||
                self.add_widget_html_class(key, "w-100")
 | 
			
		||||
 | 
			
		||||
    def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
 | 
			
		||||
        """ Generic processing of request
 | 
			
		||||
@ -243,32 +238,65 @@ class SimpleGeomForm(BaseForm):
 | 
			
		||||
    """ A geometry form for rendering geometry read-only using a widget
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    geom = GeometryField(
 | 
			
		||||
    geom = MultiPolygonField(
 | 
			
		||||
        srid=DEFAULT_SRID,
 | 
			
		||||
        label=_("Geometry"),
 | 
			
		||||
        help_text=_(""),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        required=False,
 | 
			
		||||
        disabled=True,
 | 
			
		||||
        disabled=False,
 | 
			
		||||
        widget=OSMWidget(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "map_width": 600,
 | 
			
		||||
                "map_height": 400,
 | 
			
		||||
                # default_zoom defines the nearest possible zoom level from which the JS automatically
 | 
			
		||||
                # zooms out if geometry requires a larger view port. So define a larger range for smaller geometries
 | 
			
		||||
                "default_zoom": 25,
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        read_only = kwargs.pop("read_only", True)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        # Initialize geometry
 | 
			
		||||
        try:
 | 
			
		||||
            geom = self.instance.geometry.geom
 | 
			
		||||
            if geom is None:
 | 
			
		||||
                raise AttributeError
 | 
			
		||||
            self.empty = geom.empty
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            # catches if no geometry has been added, yet. Replace with empty placeholder polygon.
 | 
			
		||||
            geom = Polygon.from_bbox([0, 0, 0, 0])
 | 
			
		||||
            # Zoom out to a very high level, so the user can see directly that there is no geometry for this entry
 | 
			
		||||
            # If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
 | 
			
		||||
            geom = None
 | 
			
		||||
            self.empty = True
 | 
			
		||||
            self.fields["geom"].widget.attrs["default_zoom"] = 1
 | 
			
		||||
 | 
			
		||||
        self.initialize_form_field("geom", geom)
 | 
			
		||||
        self.area = geom.area
 | 
			
		||||
        if read_only:
 | 
			
		||||
            self.fields["geom"].disabled = True
 | 
			
		||||
 | 
			
		||||
    def save(self, action: UserActionLogEntry):
 | 
			
		||||
        """ Saves the form's geometry
 | 
			
		||||
 | 
			
		||||
        Creates a new geometry entry if none is set, yet
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            action ():
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            geometry = self.instance.geometry
 | 
			
		||||
            geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID))
 | 
			
		||||
            geometry.modified = action
 | 
			
		||||
            geometry.save()
 | 
			
		||||
        except (AttributeError) as e:
 | 
			
		||||
            # No geometry or linked instance holding a geometry exist --> create a new one!
 | 
			
		||||
            geometry = Geometry.objects.create(
 | 
			
		||||
                geom=self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID)),
 | 
			
		||||
                created=action,
 | 
			
		||||
            )
 | 
			
		||||
        return geometry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoveModalForm(BaseModalForm):
 | 
			
		||||
@ -294,15 +322,7 @@ class RemoveModalForm(BaseModalForm):
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        if isinstance(self.instance, BaseObject):
 | 
			
		||||
            with transaction.atomic():
 | 
			
		||||
                action = UserActionLogEntry.objects.create(
 | 
			
		||||
                    user=self.user,
 | 
			
		||||
                    timestamp=timezone.now(),
 | 
			
		||||
                    action=UserAction.DELETED,
 | 
			
		||||
                )
 | 
			
		||||
                self.instance.deleted = action
 | 
			
		||||
                self.instance.log.add(action)
 | 
			
		||||
                self.instance.save()
 | 
			
		||||
            self.instance.mark_as_deleted(self.user)
 | 
			
		||||
        else:
 | 
			
		||||
            # If the class does not provide restorable delete functionality, we must delete the entry finally
 | 
			
		||||
            self.instance.delete()
 | 
			
		||||
@ -316,6 +336,11 @@ class NewDocumentForm(BaseModalForm):
 | 
			
		||||
        label=_("Title"),
 | 
			
		||||
        label_suffix=_(""),
 | 
			
		||||
        max_length=500,
 | 
			
		||||
        widget=forms.TextInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    creation_date = forms.DateField(
 | 
			
		||||
        label=_("Created on"),
 | 
			
		||||
@ -325,6 +350,7 @@ class NewDocumentForm(BaseModalForm):
 | 
			
		||||
            attrs={
 | 
			
		||||
                "type": "date",
 | 
			
		||||
                "data-provide": "datepicker",
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            },
 | 
			
		||||
            format="%d.%m.%Y"
 | 
			
		||||
        )
 | 
			
		||||
@ -335,7 +361,7 @@ class NewDocumentForm(BaseModalForm):
 | 
			
		||||
        help_text=_("Must be smaller than 15 Mb"),
 | 
			
		||||
        widget=forms.FileInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "class": "w-75"
 | 
			
		||||
                "class": "form-control-file",
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
@ -349,6 +375,7 @@ class NewDocumentForm(BaseModalForm):
 | 
			
		||||
            attrs={
 | 
			
		||||
                "cols": 30,
 | 
			
		||||
                "rows": 5,
 | 
			
		||||
                "class": "form-control",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ class Command(BaseCommand):
 | 
			
		||||
            len_ids = len(identifiers)
 | 
			
		||||
            while len_ids < max_iterations:
 | 
			
		||||
                tmp_intervention = Intervention()
 | 
			
		||||
                _id = tmp_intervention._generate_new_identifier()
 | 
			
		||||
                _id = tmp_intervention.generate_new_identifier()
 | 
			
		||||
                len_ids = len(identifiers)
 | 
			
		||||
                if _id not in identifiers:
 | 
			
		||||
                    if len_ids % (max_iterations/5) == 0:
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,8 @@ import os
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.utils.timezone import now
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.contrib.gis.db.models import MultiPolygonField
 | 
			
		||||
@ -61,8 +63,20 @@ class BaseResource(UuidModel):
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    def delete(self, using=None, keep_parents=False):
 | 
			
		||||
        if self.created:
 | 
			
		||||
        """ Base deleting of a resource
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            using ():
 | 
			
		||||
            keep_parents ():
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            self.created.delete()
 | 
			
		||||
        except (ObjectDoesNotExist, AttributeError) as e:
 | 
			
		||||
            # Object does not exist anymore - we can skip this
 | 
			
		||||
            pass
 | 
			
		||||
        super().delete()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -81,14 +95,13 @@ class BaseObject(BaseResource):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    def delete(self, *args, **kwargs):
 | 
			
		||||
        """ Custom delete functionality
 | 
			
		||||
    def mark_as_deleted(self, user: User):
 | 
			
		||||
        """ Mark an entry as deleted
 | 
			
		||||
 | 
			
		||||
        Does not delete from database but sets a timestamp for being deleted on and which user deleted the object
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            *args ():
 | 
			
		||||
            **kwargs ():
 | 
			
		||||
            user (User): The performing user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
@ -97,13 +110,14 @@ class BaseObject(BaseResource):
 | 
			
		||||
            # Nothing to do here
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        _user = kwargs.get("user", None)
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            action = UserActionLogEntry.objects.create(
 | 
			
		||||
                user=_user,
 | 
			
		||||
                action=UserAction.DELETED
 | 
			
		||||
                user=user,
 | 
			
		||||
                action=UserAction.DELETED,
 | 
			
		||||
                timestamp=timezone.now()
 | 
			
		||||
            )
 | 
			
		||||
            self.deleted = action
 | 
			
		||||
            self.log.add(action)
 | 
			
		||||
            self.save()
 | 
			
		||||
 | 
			
		||||
    def add_log_entry(self, action: UserAction, user: User, comment: str):
 | 
			
		||||
@ -139,7 +153,7 @@ class BaseObject(BaseResource):
 | 
			
		||||
        else:
 | 
			
		||||
            return User.objects.none()
 | 
			
		||||
 | 
			
		||||
    def _generate_new_identifier(self) -> str:
 | 
			
		||||
    def generate_new_identifier(self) -> str:
 | 
			
		||||
        """ Generates a new identifier for the intervention object
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
@ -177,7 +191,9 @@ class BaseObject(BaseResource):
 | 
			
		||||
        curr_year = str(_now.year)
 | 
			
		||||
        rand_str = generate_random_string(
 | 
			
		||||
            length=definitions[self.__class__]["length"],
 | 
			
		||||
            only_numbers=True,
 | 
			
		||||
            use_numbers=True,
 | 
			
		||||
            use_letters_lc=False,
 | 
			
		||||
            use_letters_uc=True,
 | 
			
		||||
        )
 | 
			
		||||
        _str = "{}{}-{}".format(curr_month, curr_year, rand_str)
 | 
			
		||||
        return definitions[self.__class__]["template"].format(_str)
 | 
			
		||||
 | 
			
		||||
@ -60,6 +60,14 @@ a {
 | 
			
		||||
    color: var(--rlp-red);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-control:focus{
 | 
			
		||||
    outline: none;
 | 
			
		||||
    border-color: var(--rlp-red);
 | 
			
		||||
    box-shadow: 0 0 3px var(--rlp-red);
 | 
			
		||||
    -moz-box-shadow: 0 0 3px var(--rlp-red);
 | 
			
		||||
    -webkit-box-shadow: 0 0 3px var(--rlp-red);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.body-content{
 | 
			
		||||
    margin: 1rem 0rem 0 0rem;
 | 
			
		||||
}
 | 
			
		||||
@ -133,6 +141,13 @@ a {
 | 
			
		||||
    height: 8rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
Overwrites bootstrap .btn:focus box shadow color
 | 
			
		||||
*/
 | 
			
		||||
.btn:focus{
 | 
			
		||||
    box-shadow: 0 0 5px .2rem var(--rlp-gray-light);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-default{
 | 
			
		||||
    color: white;
 | 
			
		||||
    background-color: var(--rlp-red);
 | 
			
		||||
@ -171,13 +186,6 @@ a {
 | 
			
		||||
    background-color: var(--rlp-gray-light);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input:focus, textarea:focus, select:focus{
 | 
			
		||||
    border-color: var(--rlp-red) !important;
 | 
			
		||||
    box-shadow: 0 0 3px var(--rlp-red) !important;
 | 
			
		||||
    -moz-box-shadow: 0 0 3px var(--rlp-red) !important;
 | 
			
		||||
    -webkit-box-shadow: 0 0 3px var(--rlp-red) !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.check-star{
 | 
			
		||||
    color: goldenrod;
 | 
			
		||||
}
 | 
			
		||||
@ -215,4 +223,7 @@ No other approach worked to get the autocomplete fields to full width of parent
 | 
			
		||||
*/
 | 
			
		||||
.select2-container{
 | 
			
		||||
    width: 100% !important;
 | 
			
		||||
}
 | 
			
		||||
.select2-results__option--highlighted{
 | 
			
		||||
    background-color: var(--rlp-red) !important;
 | 
			
		||||
}
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<form action="{{ form.action_url }}" method="post">
 | 
			
		||||
    {% csrf_token %}
 | 
			
		||||
    {{ form.as_p }}
 | 
			
		||||
</form>
 | 
			
		||||
@ -3,6 +3,6 @@
 | 
			
		||||
 | 
			
		||||
{% block body %}
 | 
			
		||||
<div class="column">
 | 
			
		||||
    {% include 'form/generic_table_form.html' %}
 | 
			
		||||
    {% include 'form/table/generic_table_form.html' %}
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										22
									
								
								konova/templates/konova/widgets/generate-content-input.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								konova/templates/konova/widgets/generate-content-input.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
{% load i18n fontawesome_5 %}
 | 
			
		||||
 | 
			
		||||
<div class="input-group w-100" title="{{ widget.value|stringformat:'s' }}">
 | 
			
		||||
    <input id="gen-id-input" aria-describedby="gen-id-btn" type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
 | 
			
		||||
    <div class="input-group-append" onclick="fetchNewIdentifier()">
 | 
			
		||||
        <span id="gen-id-btn" class="btn btn-default" value="{% trans 'Generate new' %}" title="{% trans 'Generate new' %}">{% fa5_icon 'dice' %}</span>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
<script>
 | 
			
		||||
    function fetchNewIdentifier() {
 | 
			
		||||
        fetch("{{ widget.attrs.url }}")
 | 
			
		||||
            .then(function(response){
 | 
			
		||||
                return response.json();
 | 
			
		||||
            })
 | 
			
		||||
            .then(function(data){
 | 
			
		||||
                document.getElementById("gen-id-input").value = data["identifier"];
 | 
			
		||||
            })
 | 
			
		||||
            .catch(function(error){
 | 
			
		||||
                console.log(error);
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
@ -19,7 +19,8 @@ from django.urls import path, include
 | 
			
		||||
 | 
			
		||||
from konova.autocompletes import OrganisationAutocomplete, NonOfficialOrganisationAutocomplete, EcoAccountAutocomplete, \
 | 
			
		||||
    InterventionAutocomplete, CompensationActionCodeAutocomplete, BiotopeCodeAutocomplete, LawCodeAutocomplete, \
 | 
			
		||||
    RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete
 | 
			
		||||
    RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete, ProcessTypeCodeAutocomplete, \
 | 
			
		||||
    CompensationFundingCodeAutocomplete
 | 
			
		||||
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
 | 
			
		||||
from konova.sso.sso import KonovaSSOClient
 | 
			
		||||
from konova.views import logout_view, home_view, remove_deadline_view
 | 
			
		||||
@ -46,9 +47,11 @@ urlpatterns = [
 | 
			
		||||
    path("atcmplt/orgs/other", NonOfficialOrganisationAutocomplete.as_view(), name="other-orgs-autocomplete"),
 | 
			
		||||
    path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"),
 | 
			
		||||
    path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="interventions-autocomplete"),
 | 
			
		||||
    path("atcmplt/codes/compensation-action", CompensationActionCodeAutocomplete.as_view(), name="codes-compensation-action-autocomplete"),
 | 
			
		||||
    path("atcmplt/codes/comp/action", CompensationActionCodeAutocomplete.as_view(), name="codes-compensation-action-autocomplete"),
 | 
			
		||||
    path("atcmplt/codes/comp/funding", CompensationFundingCodeAutocomplete.as_view(), name="codes-compensation-funding-autocomplete"),
 | 
			
		||||
    path("atcmplt/codes/biotope", BiotopeCodeAutocomplete.as_view(), name="codes-biotope-autocomplete"),
 | 
			
		||||
    path("atcmplt/codes/law", LawCodeAutocomplete.as_view(), name="codes-law-autocomplete"),
 | 
			
		||||
    path("atcmplt/codes/prc-type", ProcessTypeCodeAutocomplete.as_view(), name="codes-process-type-autocomplete"),
 | 
			
		||||
    path("atcmplt/codes/reg-off", RegistrationOfficeCodeAutocomplete.as_view(), name="codes-registration-office-autocomplete"),
 | 
			
		||||
    path("atcmplt/codes/cons-off", ConservationOfficeCodeAutocomplete.as_view(), name="codes-conservation-office-autocomplete"),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -9,14 +9,18 @@ import random
 | 
			
		||||
import string
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_random_string(length: int, only_numbers: bool = False) -> str:
 | 
			
		||||
def generate_random_string(length: int, use_numbers: bool = False, use_letters_lc: bool = False, use_letters_uc: bool = False) -> str:
 | 
			
		||||
    """
 | 
			
		||||
    Generates a random string of variable length
 | 
			
		||||
    """
 | 
			
		||||
    if only_numbers:
 | 
			
		||||
        elements = string.digits
 | 
			
		||||
    else:
 | 
			
		||||
        elements = string.ascii_letters
 | 
			
		||||
    elements = []
 | 
			
		||||
    if use_numbers:
 | 
			
		||||
        elements.append(string.digits)
 | 
			
		||||
    if use_letters_lc:
 | 
			
		||||
        elements.append(string.ascii_lowercase)
 | 
			
		||||
    if use_letters_uc:
 | 
			
		||||
        elements.append(string.ascii_uppercase)
 | 
			
		||||
 | 
			
		||||
    elements = "".join(elements)
 | 
			
		||||
    ret_val = "".join(random.choice(elements) for i in range(length))
 | 
			
		||||
    return ret_val
 | 
			
		||||
 | 
			
		||||
@ -10,3 +10,6 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
FORM_INVALID = _("There was an error on this form.")
 | 
			
		||||
INTERVENTION_INVALID = _("There are errors in this intervention.")
 | 
			
		||||
IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since another entry has been added in the meanwhile, which uses this identifier")
 | 
			
		||||
DATA_UNSHARED = _("This data is not shared with you")
 | 
			
		||||
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -13,8 +13,8 @@ django-simple-sso==1.1.0
 | 
			
		||||
django-tables2==2.3.4
 | 
			
		||||
idna==2.10
 | 
			
		||||
importlib-metadata==2.1.1
 | 
			
		||||
pkg-resources==0.0.0
 | 
			
		||||
psycopg2==2.8.6
 | 
			
		||||
itsdangerous
 | 
			
		||||
psycopg2-binary
 | 
			
		||||
pytz==2020.4
 | 
			
		||||
requests==2.25.0
 | 
			
		||||
six==1.15.0
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										65
									
								
								templates/form/collapsable/form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								templates/form/collapsable/form.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
			
		||||
{% load i18n l10n fontawesome_5 %}
 | 
			
		||||
<form method="post" action="{{ form.action_url }}" {% for attr_key, attr_val in form.form_attrs.items %} {{attr_key}}="{{attr_val}}"{% endfor %}>
 | 
			
		||||
    {% csrf_token %}
 | 
			
		||||
    <h2>{{form.form_title}}</h2>
 | 
			
		||||
    <div id="help" class="col">
 | 
			
		||||
        <div class="row rlp-gd-outline p-2">
 | 
			
		||||
            <div class="col-lg-1 rlp-r-inv">
 | 
			
		||||
                <span class="d-flex justify-content-center align-items-center h-100">
 | 
			
		||||
                    {% fa5_icon 'question-circle' 'far' %}
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col-lg-11">
 | 
			
		||||
                <small>
 | 
			
		||||
                    {% blocktrans %}
 | 
			
		||||
                    First enter the most basic data. Of course you can change everything later.
 | 
			
		||||
                    All further data, like documents or further details, can be added in the detail view after saving
 | 
			
		||||
                    your new entry.
 | 
			
		||||
                    {% endblocktrans %}
 | 
			
		||||
                    <br>
 | 
			
		||||
                    {% trans 'Open the input topic with a simple click.' %}
 | 
			
		||||
                </small>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="mt-3">
 | 
			
		||||
        <div class="card">
 | 
			
		||||
            <div id="dataCardHeader" class="card-header cursor-pointer rlp-r" data-toggle="collapse" data-target="#dataCard" aria-expanded="true" aria-controls="dataCard">
 | 
			
		||||
                <h5>
 | 
			
		||||
                    {% fa5_icon 'list' %}
 | 
			
		||||
                    {% trans 'General data' %}
 | 
			
		||||
                </h5>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div id="dataCard" class="collapse" aria-labelledby="dataCardHeader">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% include 'form/table/generic_table_form_body.html' %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="">
 | 
			
		||||
        <div class="card">
 | 
			
		||||
            <div id="geometryCardHeader" class="card-header cursor-pointer rlp-r" data-toggle="collapse" data-target="#geometryCard" aria-expanded="true" aria-controls="geometryCard">
 | 
			
		||||
                <h5>
 | 
			
		||||
                    {% fa5_icon 'map-marked-alt' %}
 | 
			
		||||
                    {% trans 'Geometry' %}
 | 
			
		||||
                </h5>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div id="geometryCard" class="collapse show" aria-labelledby="geometryCardHeader">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% include 'map/geom_form.html' %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-6">
 | 
			
		||||
            <a href="{{ form.cancel_redirect }}">
 | 
			
		||||
                <button class="btn btn-default" type="button" title="{% trans 'Cancel' %}">{% trans 'Cancel' %}</button>
 | 
			
		||||
            </a>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-6 d-flex justify-content-end">
 | 
			
		||||
            <button class="btn btn-default" type="submit" title="{% trans 'Save' %}">{% trans 'Save' %}</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</form>
 | 
			
		||||
@ -16,14 +16,14 @@
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    <form method="post" action="{{ form.action_url }}" {% for attr_key, attr_val in form.form_attrs.items %} {{attr_key}}="{{attr_val}}"{% endfor %}>
 | 
			
		||||
        {% csrf_token %}
 | 
			
		||||
        {% include 'form/generic_table_form_body.html' %}
 | 
			
		||||
        {% include 'form/table/generic_table_form_body.html' %}
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-md">
 | 
			
		||||
            <div class="col-6">
 | 
			
		||||
                <a href="{{ form.cancel_redirect }}">
 | 
			
		||||
                    <button class="btn btn-default" type="button" title="{% trans 'Cancel' %}">{% trans 'Cancel' %}</button>
 | 
			
		||||
                </a>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col-md d-flex justify-content-end">
 | 
			
		||||
            <div class="col-6 d-flex justify-content-end">
 | 
			
		||||
                <button class="btn btn-default" type="submit" title="{% trans 'Save' %}">{% trans 'Save' %}</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
@ -6,6 +6,7 @@
 | 
			
		||||
        <tr title="{{ field.help_text }}" class="{% if field.errors %}alert-danger{% endif %}">
 | 
			
		||||
            <th scope="row" class="col-sm-3">
 | 
			
		||||
                <label for="id_{{ field.name }}">{{ field.label }}<span class="label-required">{% if field.field.required %}*{% endif %}</span></label>
 | 
			
		||||
                <br>
 | 
			
		||||
                <small>{{ field.help_text }}</small>
 | 
			
		||||
            </th>
 | 
			
		||||
            <td class="col-sm-9">
 | 
			
		||||
@ -4,9 +4,10 @@
 | 
			
		||||
    Encapsules the rendering and initializing of a geometry view component, e.g. used in the detail views.
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% if geom_form.area == 0 %}
 | 
			
		||||
    <div class="alert alert-info">{% trans 'No geometry added, yet.' %}</div>
 | 
			
		||||
{% if geom_form.empty %}
 | 
			
		||||
    <div class="w-100">
 | 
			
		||||
        <div class="alert alert-info">{% trans 'No geometry added, yet.' %}</div>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{{geom_form.media}}
 | 
			
		||||
{{geom_form.geom}}
 | 
			
		||||
@ -18,7 +18,7 @@
 | 
			
		||||
        <article>
 | 
			
		||||
            {{ form.form_caption }}
 | 
			
		||||
        </article>
 | 
			
		||||
        {% include 'form/generic_table_form_body.html' %}
 | 
			
		||||
        {% include 'form/table/generic_table_form_body.html' %}
 | 
			
		||||
    </div>
 | 
			
		||||
    {% if form.render_submit %}
 | 
			
		||||
    <div class="modal-footer">
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
{% extends 'base.html' %}
 | 
			
		||||
 | 
			
		||||
{% block body %}
 | 
			
		||||
    {% include 'form/generic_table_form.html' %}
 | 
			
		||||
    {% include 'form/table/generic_table_form.html' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user