""" 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 from konova.models import Document from konova.settings import DEFAULT_LAT, DEFAULT_LON, DEFAULT_ZOOM, ZB_GROUP, ETS_GROUP 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) 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() # Send message to the SSO server messenger = Messenger( self.instance.users.all(), type="INFO", ) messenger.send_object_checked( self.instance.identifier, self.user, ) 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.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 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