You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
konova/intervention/forms.py

561 lines
19 KiB
Python

3 years ago
"""
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.contrib.gis import forms as gis_forms
from django.contrib.gis.geos import Polygon
from django.db import transaction
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccountWithdraw, EcoAccount
from intervention.models import Intervention, Revocation
from konova.forms import BaseForm, BaseModalForm
3 years ago
from konova.models import Document
from konova.settings import DEFAULT_LAT, DEFAULT_LON, DEFAULT_ZOOM, ZB_GROUP, ETS_GROUP
from konova.utils.user_checks import in_group
3 years ago
from organisation.models import Organisation
from user.models import UserActionLogEntry, UserAction
3 years ago
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,
)
3 years ago
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,
3 years ago
)
intervention.save()
intervention.log.add(action)
3 years ago
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)
3 years ago
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):
url = forms.CharField(
label=_("Share link"),
label_suffix="",
help_text=_("Send this link to users who you want to have writing access on the data"),
required=False,
widget=TextToClipboardInput(
attrs={
"readonly": True
}
)
)
users = forms.MultipleChoiceField(
label=_("Shared with"),
label_suffix="",
required=True,
help_text=_("Remove check to remove access for this user"),
widget=forms.CheckboxSelectMultiple(
attrs={
"class": "list-unstyled",
}
),
choices=[]
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Share")
self.form_caption = _("Share settings for {}").format(self.instance.identifier)
self.template = "modal/modal_form.html"
# Make sure an access_token is set
if self.instance.access_token is None:
self.instance.generate_access_token()
self._init_fields()
def _init_fields(self):
""" Wraps initializing of fields
Returns:
"""
# Initialize share_link field
self.share_link = self.request.build_absolute_uri(
reverse("intervention:share", args=(self.instance.id, self.instance.access_token,))
)
self.initialize_form_field(
"url",
self.share_link
)
# Initialize users field
# Remove field if user is not in registration or conservation group
if not in_group(self.request.user, ZB_GROUP) and not in_group(self.request.user, ETS_GROUP):
del self.fields["users"]
else:
users = self.instance.users.all()
choices = []
for n in users:
choices.append(
(n.id, n.username)
)
self.fields["users"].choices = choices
u_ids = list(users.values_list("id", flat=True))
self.initialize_form_field(
"users",
u_ids
)
def save(self):
accessing_users = User.objects.filter(
id__in=self.cleaned_data["users"]
)
self.instance.users.set(accessing_users)
class NewRevocationForm(BaseModalForm):
date = forms.DateField(
label=_("Date"),
label_suffix=_(""),
help_text=_("Date of revocation"),
widget=forms.DateInput(
attrs={
"type": "date",
"data-provide": "datepicker",
},
format="%d.%m.%Y"
)
)
file = forms.FileField(
label=_("Document"),
label_suffix=_(""),
help_text=_("Must be smaller than 15 Mb"),
widget=forms.FileInput(
attrs={
"class": "w-75"
}
)
)
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,
}
)
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Add revocation")
self.form_caption = ""
self.form_attrs = {
"enctype": "multipart/form-data", # important for file upload
}
def save(self):
with transaction.atomic():
created_action = UserActionLogEntry.objects.create(
user=self.user,
action=UserAction.CREATED
)
edited_action = UserActionLogEntry.objects.create(
user=self.user,
action=UserAction.EDITED
)
document = Document.objects.create(
title="revocation_of_{}".format(self.instance.identifier),
date_of_creation=self.cleaned_data["date"],
comment=self.cleaned_data["comment"],
file=self.cleaned_data["file"],
)
revocation = Revocation.objects.create(
date=self.cleaned_data["date"],
comment=self.cleaned_data["comment"],
document=document,
created=created_action,
)
self.instance.log.add(edited_action)
self.instance.legal.revocation = revocation
self.instance.legal.save()
return revocation
class RunCheckForm(BaseModalForm):
checked_intervention = forms.BooleanField(
label=_("Checked intervention data"),
label_suffix="",
widget=forms.CheckboxInput(),
required=True,
)
checked_comps = forms.BooleanField(
label=_("Checked compensations data and payments"),
label_suffix="",
widget=forms.CheckboxInput(),
required=True
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Run check")
self.form_caption = _("I, {} {}, confirm that all necessary control steps have been performed by myself.").format(self.user.first_name, self.user.last_name)
def is_valid(self):
super_result = super().is_valid()
# Perform check
msgs = self.instance.quality_check()
for msg in msgs:
self.add_error(
"checked_intervention",
msg
)
return super_result and (len(msgs) == 0)
def save(self):
with transaction.atomic():
user_action = UserActionLogEntry.objects.create(
user=self.user,
action=UserAction.CHECKED
)
# Replace old checked
if self.instance.checked:
self.instance.checked.delete()
self.instance.checked = user_action
self.instance.log.add(user_action)
self.instance.save()
class NewWithdrawForm(BaseModalForm):
""" Form for creating new withdraws
Can be used for Intervention view as well as for EcoAccount views.
Parameter 'instance' can be an intervention, as well as an ecoAccount.
An instance check handles both workflows properly.
"""
account = forms.ModelChoiceField(
label=_("Eco-account"),
label_suffix="",
help_text=_("Only recorded accounts can be selected for withdraws"),
queryset=EcoAccount.objects.filter(deleted=None),
widget=autocomplete.ModelSelect2(
url="accounts-autocomplete",
attrs={
"data-placeholder": _("Eco-account"),
"data-minimum-input-length": 3,
"readonly": True,
}
),
)
surface = forms.DecimalField(
min_value=0.00,
decimal_places=2,
label=_("Surface"),
label_suffix="",
help_text=_("in m²"),
)
intervention = forms.ModelChoiceField(
label=_("Intervention"),
label_suffix="",
help_text=_("Only shared interventions can be selected"),
queryset=Intervention.objects.filter(deleted=None),
widget=autocomplete.ModelSelect2(
url="interventions-autocomplete",
attrs={
"data-placeholder": _("Intervention"),
"data-minimum-input-length": 3,
}
),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("New Withdraw")
self.form_caption = _("Enter the information for a new withdraw 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.fields["surface"].widget.attrs["placeholder"] = "0,00"
# Check for Intervention or EcoAccount
if isinstance(self.instance, Intervention):
# Form has been called with a given intervention
self.initialize_form_field("intervention", self.instance)
self.disable_form_field("intervention")
self.is_intervention_initially = True
elif isinstance(self.instance, EcoAccount):
# Form has been called with a given account --> make it initial in the form and read-only
self.initialize_form_field("account", self.instance)
self.disable_form_field("account")
else:
raise NotImplementedError
def is_valid(self):
""" Custom validity check
Makes sure the withdraw can not contain more surface than the account still provides
Returns:
is_valid (bool)
"""
super_result = super().is_valid()
if self.is_intervention_initially:
acc = self.cleaned_data["account"]
else:
acc = self.instance
if not acc.recorded:
self.add_error(
"account",
_("Eco-account {} is not recorded yet. You can only withdraw from recorded accounts.").format(acc.identifier)
)
return False
# Calculate valid surface
sum_surface = acc.get_surface()
sum_surface_withdraws = acc.get_surface_withdraws()
rest_surface = sum_surface - sum_surface_withdraws
form_surface = float(self.cleaned_data["surface"])
is_valid_surface = form_surface < rest_surface
if not is_valid_surface:
self.add_error(
"surface",
_("The account {} has not enough surface for a withdraw of {} m². There are only {} m² left").format(acc.identifier, form_surface, rest_surface),
)
return is_valid_surface and super_result
def save(self):
with transaction.atomic():
# Create log entry
user_action_edit = UserActionLogEntry.objects.create(
user=self.user,
action=UserAction.EDITED
)
user_action_create = UserActionLogEntry.objects.create(
user=self.user,
action=UserAction.CREATED
)
self.instance.log.add(user_action_edit)
# Create withdraw depending on Intervention or EcoAccount as the initial instance
if self.is_intervention_initially:
withdraw = EcoAccountWithdraw.objects.create(
intervention=self.instance,
account=self.cleaned_data["account"],
surface=self.cleaned_data["surface"],
created=user_action_create,
)
else:
withdraw = EcoAccountWithdraw.objects.create(
intervention=self.cleaned_data["intervention"],
account=self.instance,
surface=self.cleaned_data["surface"],
created=user_action_create,
)
return withdraw