mpeltriaux
eb975cd3c5
* changes trigger for sending data to EGON: on each new payment, edited payment or deleted payment action, the data will be sent to EGON instead only once on "recording"
531 lines
18 KiB
Python
531 lines
18 KiB
Python
"""
|
|
Author: Michel Peltriaux
|
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
|
Created on: 04.10.21
|
|
|
|
"""
|
|
from bootstrap_modal_forms.utils import is_ajax
|
|
from dal import autocomplete
|
|
from django import forms
|
|
from django.contrib import messages
|
|
from django.http import HttpRequest, HttpResponseRedirect
|
|
from django.shortcuts import render
|
|
from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _
|
|
|
|
from codelist.models import KonovaCode
|
|
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \
|
|
CODELIST_COMPENSATION_ACTION_DETAIL_ID
|
|
from compensation.models import CompensationDocument, EcoAccountDocument
|
|
from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple
|
|
from konova.contexts import BaseContext
|
|
from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
|
|
from konova.models import DeadlineType
|
|
from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, \
|
|
ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
|
|
|
|
|
|
class NewPaymentForm(BaseModalForm):
|
|
""" Form handling payment related input
|
|
|
|
"""
|
|
amount = forms.DecimalField(
|
|
min_value=0.00,
|
|
decimal_places=2,
|
|
label=_con("money", "Amount"), # contextual translation
|
|
label_suffix=_(""),
|
|
help_text=_("in Euro"),
|
|
widget=forms.NumberInput(
|
|
attrs={
|
|
"class": "form-control",
|
|
"placeholder": "0,00",
|
|
}
|
|
)
|
|
)
|
|
due = forms.DateField(
|
|
label=_("Due on"),
|
|
label_suffix=_(""),
|
|
required=False,
|
|
help_text=_("Due on which date"),
|
|
widget=forms.DateInput(
|
|
attrs={
|
|
"type": "date",
|
|
"data-provide": "datepicker",
|
|
"class": "form-control",
|
|
},
|
|
format="%d.%m.%Y"
|
|
)
|
|
)
|
|
comment = forms.CharField(
|
|
max_length=200,
|
|
required=False,
|
|
label=_("Comment"),
|
|
label_suffix=_(""),
|
|
help_text=_("Additional comment, maximum {} letters").format(200),
|
|
widget=forms.Textarea(
|
|
attrs={
|
|
"rows": 5,
|
|
"class": "form-control"
|
|
}
|
|
)
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.intervention = self.instance
|
|
self.form_title = _("Payment")
|
|
self.form_caption = _("Add a payment for intervention '{}'").format(self.intervention.title)
|
|
|
|
def is_valid(self):
|
|
"""
|
|
Checks on form validity.
|
|
|
|
For this form we need to make sure that a date or a comment is set.
|
|
If both are missing, the user needs to enter at least an explanation why
|
|
there is no date to be entered.
|
|
|
|
Returns:
|
|
is_valid (bool): True if valid, False otherwise
|
|
"""
|
|
super_valid = super().is_valid()
|
|
date = self.cleaned_data["due"]
|
|
comment = self.cleaned_data["comment"] or None
|
|
if not date and not comment:
|
|
# At least one needs to be set!
|
|
self.add_error(
|
|
"comment",
|
|
_("If there is no date you can enter, please explain why.")
|
|
)
|
|
return False
|
|
return super_valid
|
|
|
|
def save(self):
|
|
pay = self.instance.add_payment(self)
|
|
return pay
|
|
|
|
|
|
class EditPaymentModalForm(NewPaymentForm):
|
|
""" Form handling edit for Payment
|
|
|
|
"""
|
|
payment = None
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.payment = kwargs.pop("payment", None)
|
|
super().__init__(*args, **kwargs)
|
|
self.form_title = _("Edit payment")
|
|
form_date = {
|
|
"amount": self.payment.amount,
|
|
"due": str(self.payment.due_on),
|
|
"comment": self.payment.comment,
|
|
}
|
|
self.load_initial_data(form_date, disabled_fields=[])
|
|
|
|
def save(self):
|
|
payment = self.payment
|
|
payment.amount = self.cleaned_data.get("amount", None)
|
|
payment.due_on = self.cleaned_data.get("due", None)
|
|
payment.comment = self.cleaned_data.get("comment", None)
|
|
payment.save()
|
|
self.instance.mark_as_edited(self.user, self.request, edit_comment=PAYMENT_EDITED)
|
|
self.instance.send_data_to_egon()
|
|
return payment
|
|
|
|
|
|
class RemovePaymentModalForm(RemoveModalForm):
|
|
""" Removing modal form for Payment
|
|
|
|
Can be used for anything, where removing shall be confirmed by the user a second time.
|
|
|
|
"""
|
|
payment = None
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
payment = kwargs.pop("payment", None)
|
|
self.payment = payment
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def save(self):
|
|
self.instance.remove_payment(self)
|
|
|
|
|
|
class NewStateModalForm(BaseModalForm):
|
|
""" Form handling state related input
|
|
|
|
Compensation states refer to 'before' and 'after' states of a compensated surface. Basically it means:
|
|
What has been on this area before changes/compensations have been applied and what will be the result ('after')?
|
|
|
|
"""
|
|
biotope_type = forms.ModelChoiceField(
|
|
label=_("Biotope Type"),
|
|
label_suffix="",
|
|
required=True,
|
|
help_text=_("Select the biotope type"),
|
|
queryset=KonovaCode.objects.filter(
|
|
is_archived=False,
|
|
is_leaf=True,
|
|
code_lists__in=[CODELIST_BIOTOPES_ID],
|
|
),
|
|
widget=autocomplete.ModelSelect2(
|
|
url="codes-biotope-autocomplete",
|
|
attrs={
|
|
"data-placeholder": _("Biotope Type"),
|
|
}
|
|
),
|
|
)
|
|
biotope_extra = forms.ModelMultipleChoiceField(
|
|
label=_("Biotope additional type"),
|
|
label_suffix="",
|
|
required=False,
|
|
help_text=_("Select an additional biotope type"),
|
|
queryset=KonovaCode.objects.filter(
|
|
is_archived=False,
|
|
is_leaf=True,
|
|
code_lists__in=[CODELIST_BIOTOPES_EXTRA_CODES_ID],
|
|
),
|
|
widget=autocomplete.ModelSelect2Multiple(
|
|
url="codes-biotope-extra-type-autocomplete",
|
|
attrs={
|
|
"data-placeholder": _("Biotope additional type"),
|
|
}
|
|
),
|
|
)
|
|
surface = forms.DecimalField(
|
|
min_value=0.00,
|
|
decimal_places=2,
|
|
label=_("Surface"),
|
|
label_suffix="",
|
|
required=True,
|
|
help_text=_("in m²"),
|
|
widget=forms.NumberInput(
|
|
attrs={
|
|
"class": "form-control",
|
|
"placeholder": "0,00"
|
|
}
|
|
)
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.form_title = _("New state")
|
|
self.form_caption = _("Insert data for the new state")
|
|
|
|
def save(self, is_before_state: bool = False):
|
|
state = self.instance.add_state(self, is_before_state)
|
|
self.instance.mark_as_edited(self.user, self.request, ADDED_COMPENSATION_STATE)
|
|
return state
|
|
|
|
def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
|
|
""" Generic processing of request
|
|
|
|
Wraps the request processing logic, so we don't need the same code everywhere a RemoveModalForm is being used
|
|
|
|
+++
|
|
The generic method from super class can not be used, since we need to do some request parameter check in here.
|
|
+++
|
|
|
|
Args:
|
|
request (HttpRequest): The incoming request
|
|
msg_success (str): The message in case of successful removing
|
|
msg_error (str): The message in case of an error
|
|
|
|
Returns:
|
|
|
|
"""
|
|
redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
|
|
template = self.template
|
|
if request.method == "POST":
|
|
if self.is_valid():
|
|
# Modal forms send one POST for checking on data validity. This can be used to return possible errors
|
|
# on the form. A second POST (if no errors occured) is sent afterwards and needs to process the
|
|
# saving/commiting of the data to the database. is_ajax() performs this check. The first request is
|
|
# an ajax call, the second is a regular form POST.
|
|
if not is_ajax(request.META):
|
|
is_before_state = bool(request.GET.get("before", False))
|
|
self.save(is_before_state=is_before_state)
|
|
messages.success(
|
|
request,
|
|
msg_success
|
|
)
|
|
return HttpResponseRedirect(redirect_url)
|
|
else:
|
|
context = {
|
|
"form": self,
|
|
}
|
|
context = BaseContext(request, context).context
|
|
return render(request, template, context)
|
|
elif request.method == "GET":
|
|
context = {
|
|
"form": self,
|
|
}
|
|
context = BaseContext(request, context).context
|
|
return render(request, template, context)
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
|
|
class EditCompensationStateModalForm(NewStateModalForm):
|
|
state = None
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.state = kwargs.pop("state", None)
|
|
super().__init__(*args, **kwargs)
|
|
self.form_title = _("Edit state")
|
|
form_data = {
|
|
"biotope_type": self.state.biotope_type,
|
|
"biotope_extra": self.state.biotope_type_details.all(),
|
|
"surface": self.state.surface,
|
|
}
|
|
self.load_initial_data(form_data)
|
|
|
|
def save(self, is_before_state: bool = False):
|
|
state = self.state
|
|
state.biotope_type = self.cleaned_data.get("biotope_type", None)
|
|
state.biotope_type_details.set(self.cleaned_data.get("biotope_extra", []))
|
|
state.surface = self.cleaned_data.get("surface", None)
|
|
state.save()
|
|
self.instance.mark_as_edited(self.user, self.request, edit_comment=COMPENSATION_STATE_EDITED)
|
|
return state
|
|
|
|
|
|
class RemoveCompensationStateModalForm(RemoveModalForm):
|
|
""" Removing modal form for CompensationState
|
|
|
|
Can be used for anything, where removing shall be confirmed by the user a second time.
|
|
|
|
"""
|
|
state = None
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
state = kwargs.pop("state", None)
|
|
self.state = state
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def save(self):
|
|
self.instance.remove_state(self)
|
|
|
|
|
|
class RemoveCompensationActionModalForm(RemoveModalForm):
|
|
""" Removing modal form for CompensationAction
|
|
|
|
Can be used for anything, where removing shall be confirmed by the user a second time.
|
|
|
|
"""
|
|
action = None
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
action = kwargs.pop("action", None)
|
|
self.action = action
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def save(self):
|
|
self.instance.remove_action(self)
|
|
|
|
|
|
class NewDeadlineModalForm(BaseModalForm):
|
|
""" Form handling deadline related input
|
|
|
|
"""
|
|
type = forms.ChoiceField(
|
|
label=_("Deadline Type"),
|
|
label_suffix="",
|
|
required=True,
|
|
help_text=_("Select the deadline type"),
|
|
choices=DeadlineType.choices,
|
|
widget=forms.Select(
|
|
attrs={
|
|
"class": "form-control"
|
|
}
|
|
)
|
|
)
|
|
date = forms.DateField(
|
|
label=_("Date"),
|
|
label_suffix="",
|
|
required=True,
|
|
help_text=_("Select date"),
|
|
widget=forms.DateInput(
|
|
attrs={
|
|
"type": "date",
|
|
"data-provide": "datepicker",
|
|
"class": "form-control",
|
|
},
|
|
format="%d.%m.%Y"
|
|
)
|
|
)
|
|
comment = forms.CharField(
|
|
required=False,
|
|
max_length=200,
|
|
label=_("Comment"),
|
|
label_suffix=_(""),
|
|
help_text=_("Additional comment, maximum {} letters").format(200),
|
|
widget=forms.Textarea(
|
|
attrs={
|
|
"cols": 30,
|
|
"rows": 5,
|
|
"class": "form-control",
|
|
}
|
|
)
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.form_title = _("New deadline")
|
|
self.form_caption = _("Insert data for the new deadline")
|
|
|
|
def save(self):
|
|
deadline = self.instance.add_deadline(self)
|
|
return deadline
|
|
|
|
|
|
class EditDeadlineModalForm(NewDeadlineModalForm):
|
|
deadline = None
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.deadline = kwargs.pop("deadline", None)
|
|
super().__init__(*args, **kwargs)
|
|
self.form_title = _("Edit deadline")
|
|
form_data = {
|
|
"type": self.deadline.type,
|
|
"date": str(self.deadline.date),
|
|
"comment": self.deadline.comment,
|
|
}
|
|
self.load_initial_data(form_data)
|
|
|
|
def save(self):
|
|
deadline = self.deadline
|
|
deadline.type = self.cleaned_data.get("type", None)
|
|
deadline.date = self.cleaned_data.get("date", None)
|
|
deadline.comment = self.cleaned_data.get("comment", None)
|
|
deadline.save()
|
|
self.instance.mark_as_edited(self.user, self.request, edit_comment=DEADLINE_EDITED)
|
|
return deadline
|
|
|
|
|
|
class NewActionModalForm(BaseModalForm):
|
|
""" Form handling action related input
|
|
|
|
Compensation actions are the actions performed on the area, which shall be compensated. Actions will change the
|
|
surface of the area, the biotopes, and have an environmental impact. With actions the before-after states can change
|
|
(not in the process logic in Konova, but in the real world).
|
|
|
|
"""
|
|
from compensation.models import UnitChoices
|
|
action_type = forms.MultipleChoiceField(
|
|
label=_("Action Type"),
|
|
label_suffix="",
|
|
required=True,
|
|
help_text=_("An action can consist of multiple different action types. All the selected action types are expected to be performed according to the amount and unit below on this form."),
|
|
choices=[],
|
|
widget=CompensationActionTreeCheckboxSelectMultiple(),
|
|
)
|
|
action_type_details = forms.ModelMultipleChoiceField(
|
|
label=_("Action Type detail"),
|
|
label_suffix="",
|
|
required=False,
|
|
help_text=_("Select the action type detail"),
|
|
queryset=KonovaCode.objects.filter(
|
|
is_archived=False,
|
|
is_leaf=True,
|
|
code_lists__in=[CODELIST_COMPENSATION_ACTION_DETAIL_ID],
|
|
),
|
|
widget=autocomplete.ModelSelect2Multiple(
|
|
url="codes-compensation-action-detail-autocomplete",
|
|
attrs={
|
|
"data-placeholder": _("Action Type detail"),
|
|
}
|
|
),
|
|
)
|
|
unit = forms.ChoiceField(
|
|
label=_("Unit"),
|
|
label_suffix="",
|
|
required=True,
|
|
help_text=_("Select the unit"),
|
|
choices=UnitChoices.choices,
|
|
widget=forms.Select(
|
|
attrs={
|
|
"class": "form-control"
|
|
}
|
|
)
|
|
)
|
|
amount = forms.DecimalField(
|
|
label=_("Amount"),
|
|
label_suffix="",
|
|
required=True,
|
|
help_text=_("Insert the amount"),
|
|
decimal_places=2,
|
|
min_value=0.00,
|
|
widget=forms.NumberInput(
|
|
attrs={
|
|
"class": "form-control",
|
|
"placeholder": "0,00",
|
|
}
|
|
)
|
|
)
|
|
comment = forms.CharField(
|
|
required=False,
|
|
label=_("Comment"),
|
|
label_suffix=_(""),
|
|
help_text=_("Additional comment"),
|
|
widget=forms.Textarea(
|
|
attrs={
|
|
"rows": 5,
|
|
"class": "form-control",
|
|
}
|
|
)
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.form_title = _("New action")
|
|
self.form_caption = _("Insert data for the new action")
|
|
choices =KonovaCode.objects.filter(
|
|
code_lists__in=[CODELIST_COMPENSATION_ACTION_ID],
|
|
is_archived=False,
|
|
is_leaf=True,
|
|
).values_list("id", flat=True)
|
|
choices = [
|
|
(choice, choice)
|
|
for choice in choices
|
|
]
|
|
self.fields["action_type"].choices = choices
|
|
|
|
def save(self):
|
|
action = self.instance.add_action(self)
|
|
self.instance.mark_as_edited(self.user, self.request, ADDED_COMPENSATION_ACTION)
|
|
return action
|
|
|
|
|
|
class EditCompensationActionModalForm(NewActionModalForm):
|
|
action = None
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.action = kwargs.pop("action", None)
|
|
super().__init__(*args, **kwargs)
|
|
self.form_title = _("Edit action")
|
|
form_data = {
|
|
"action_type": list(self.action.action_type.values_list("id", flat=True)),
|
|
"action_type_details": self.action.action_type_details.all(),
|
|
"amount": self.action.amount,
|
|
"unit": self.action.unit,
|
|
"comment": self.action.comment,
|
|
}
|
|
self.load_initial_data(form_data)
|
|
|
|
def save(self):
|
|
action = self.action
|
|
action.action_type.set(self.cleaned_data.get("action_type", []))
|
|
action.action_type_details.set(self.cleaned_data.get("action_type_details", []))
|
|
action.amount = self.cleaned_data.get("amount", None)
|
|
action.unit = self.cleaned_data.get("unit", None)
|
|
action.comment = self.cleaned_data.get("comment", None)
|
|
action.save()
|
|
self.instance.mark_as_edited(self.user, self.request, edit_comment=COMPENSATION_ACTION_EDITED)
|
|
return action
|
|
|
|
|
|
class NewCompensationDocumentModalForm(NewDocumentModalForm):
|
|
document_model = CompensationDocument
|
|
|
|
|
|
class NewEcoAccountDocumentModalForm(NewDocumentModalForm):
|
|
document_model = EcoAccountDocument |