Intervention forms

* refactors intervention/forms and ../modalForms into individual files in separated packages
    * forms.py has been renamed into intervention.py, now can be found as intervention/forms/intervention.py
    * modalForms.py has been split into individual files living in modals package, can be found as intervention/forms/modals/...
pull/200/head
mpeltriaux 2 years ago
parent 1367fd2b5f
commit de8d79983d

@ -50,7 +50,7 @@ class KonovaCode(models.Model):
def __str__(self, with_parent: bool = True):
ret_val = ""
if self.parent and with_parent:
if self.parent and self.parent.long_name and with_parent:
ret_val += self.parent.long_name + " > "
ret_val += self.long_name
if self.short_name and self.short_name != self.long_name:

@ -23,8 +23,9 @@ from compensation.forms.modals.state import NewCompensationStateModalForm, Remov
EditCompensationStateModalForm
from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
from compensation.tables import EcoAccountTable
from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm, \
from intervention.forms.modals.deduction import RemoveEcoAccountDeductionModalForm, NewEcoAccountDeductionModalForm, \
EditEcoAccountDeductionModalForm
from intervention.forms.modals.share import ShareModalForm
from konova.contexts import BaseContext
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
shared_access_required
@ -707,7 +708,7 @@ def new_deduction_view(request: HttpRequest, id: str):
acc = get_object_or_404(EcoAccount, id=id)
if not acc.recorded:
raise Http404()
form = NewDeductionModalForm(request.POST or None, instance=acc, request=request)
form = NewEcoAccountDeductionModalForm(request.POST or None, instance=acc, request=request)
return form.process_request(
request,
msg_success=DEDUCTION_ADDED,

@ -14,7 +14,7 @@ from compensation.forms.modals.state import NewCompensationStateModalForm, Remov
from compensation.models import CompensationAction, CompensationState
from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentModalForm
from ema.tables import EmaTable
from intervention.forms.modalForms import ShareModalForm
from intervention.forms.modals.share import ShareModalForm
from konova.contexts import BaseContext
from konova.decorators import conservation_office_group_required, shared_access_required
from ema.models import Ema, EmaDocument

@ -1,574 +0,0 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 27.09.21
"""
from dal import autocomplete
from django.core.exceptions import ObjectDoesNotExist
from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \
REVOCATION_EDITED, ENTRY_REMOVE_MISSING_PERMISSION
from user.models import User, Team
from user.models import UserActionLogEntry
from django.db import transaction
from django import forms
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount, EcoAccountDeduction
from intervention.inputs import TextToClipboardInput
from intervention.models import Intervention, InterventionDocument, RevocationDocument
from konova.forms.modals import BaseModalForm
from konova.forms.modals import NewDocumentModalForm, RemoveModalForm
from konova.utils.general import format_german_float
from konova.utils.user_checks import is_default_group_only
class ShareModalForm(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,
"class": "form-control",
}
)
)
teams = forms.ModelMultipleChoiceField(
label=_("Add team to share with"),
label_suffix="",
help_text=_("Multiple selection possible - You can only select teams which do not already have access."),
required=False,
queryset=Team.objects.all(),
widget=autocomplete.ModelSelect2Multiple(
url="share-team-autocomplete",
attrs={
"data-placeholder": _("Click for selection"),
"data-minimum-input-length": 3,
},
),
)
users = forms.ModelMultipleChoiceField(
label=_("Add user to share with"),
label_suffix="",
help_text=_("Multiple selection possible - You can only select users which do not already have access. Enter the full username."),
required=False,
queryset=User.objects.all(),
widget=autocomplete.ModelSelect2Multiple(
url="share-user-autocomplete",
attrs={
"data-placeholder": _("Click for selection"),
"data-minimum-input-length": 3,
},
),
)
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 _user_team_valid(self):
""" Checks whether users and teams have been removed by the user and if the user is allowed to do so or not
Returns:
"""
users = self.cleaned_data.get("users", User.objects.none())
teams = self.cleaned_data.get("teams", Team.objects.none())
_is_valid = True
if is_default_group_only(self.user):
shared_users = self.instance.shared_users
shared_teams = self.instance.shared_teams
shared_users_are_removed = not set(shared_users).issubset(users)
shared_teams_are_removed = not set(shared_teams).issubset(teams)
if shared_users_are_removed:
self.add_error(
"users",
ENTRY_REMOVE_MISSING_PERMISSION
)
_is_valid = False
if shared_teams_are_removed:
self.add_error(
"teams",
ENTRY_REMOVE_MISSING_PERMISSION
)
_is_valid = False
return _is_valid
def is_valid(self):
""" Extended validity check
Returns:
"""
super_valid = super().is_valid()
user_team_valid = self._user_team_valid()
_is_valid = super_valid and user_team_valid
return _is_valid
def _init_fields(self):
""" Wraps initializing of fields
Returns:
"""
# Initialize share_link field
share_link = self.instance.get_share_link()
self.share_link = self.request.build_absolute_uri(share_link)
self.initialize_form_field(
"url",
self.share_link
)
form_data = {
"teams": self.instance.teams.all(),
"users": self.instance.users.all(),
}
self.load_initial_data(form_data)
def save(self):
self.instance.update_shared_access(self)
class NewRevocationModalForm(BaseModalForm):
date = forms.DateField(
label=_("Date"),
label_suffix=_(""),
help_text=_("Date of revocation"),
widget=forms.DateInput(
attrs={
"type": "date",
"data-provide": "datepicker",
"class": "form-control",
},
format="%d.%m.%Y"
)
)
file = forms.FileField(
label=_("Document"),
label_suffix=_(""),
required=False,
help_text=_("Must be smaller than 15 Mb"),
widget=forms.FileInput(
attrs={
"class": "form-control-file"
}
)
)
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",
}
)
)
document_model = RevocationDocument
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):
revocation = self.instance.add_revocation(self)
self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_ADDED)
return revocation
class EditRevocationModalForm(NewRevocationModalForm):
revocation = None
def __init__(self, *args, **kwargs):
self.revocation = kwargs.pop("revocation", None)
super().__init__(*args, **kwargs)
self.form_title = _("Edit revocation")
try:
doc = self.revocation.document.file
except ObjectDoesNotExist:
doc = None
form_data = {
"date": str(self.revocation.date),
"file": doc,
"comment": self.revocation.comment,
}
self.load_initial_data(form_data)
def save(self):
revocation = self.instance.edit_revocation(self)
self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_EDITED)
return revocation
class RemoveRevocationModalForm(RemoveModalForm):
""" Removing modal form for Revocation
Can be used for anything, where removing shall be confirmed by the user a second time.
"""
revocation = None
def __init__(self, *args, **kwargs):
revocation = kwargs.pop("revocation", None)
self.revocation = revocation
super().__init__(*args, **kwargs)
def save(self):
self.instance.remove_revocation(self)
class CheckModalForm(BaseModalForm):
""" The modal form for running a check on interventions and their compensations
"""
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
)
valid = None
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)
self.valid = False
def _are_deductions_valid(self):
""" Performs validity checks on deductions and their eco-account
Returns:
"""
deductions = self.instance.deductions.all()
for deduction in deductions:
checker = deduction.account.quality_check()
for msg in checker.messages:
self.add_error(
"checked_comps",
f"{deduction.account.identifier}: {msg}"
)
return checker.valid
return True
def _are_comps_valid(self):
""" Performs validity checks on all types of compensations
Types of compensations are
* regular Compensations
* deductions from EcoAccounts
Returns:
"""
comps = self.instance.compensations.filter(
deleted=None,
)
comps_valid = True
for comp in comps:
checker = comp.quality_check()
for msg in checker.messages:
self.add_error(
"checked_comps",
f"{comp.identifier}: {msg}"
)
comps_valid = checker.valid
deductions_valid = self._are_deductions_valid()
return deductions_valid and comps_valid
def is_valid(self) -> bool:
""" Perform a validity check based on quality_check() logic
Returns:
result (bool)
"""
super_valid = super().is_valid()
# Perform check
checker = self.instance.quality_check()
for msg in checker.messages:
self.add_error(
"checked_intervention",
msg
)
all_comps_valid = self._are_comps_valid()
intervention_valid = checker.valid
return super_valid and intervention_valid and all_comps_valid
def save(self):
""" Saving logic
Returns:
"""
with transaction.atomic():
self.instance.set_checked(self.user)
class NewDeductionModalForm(BaseModalForm):
""" Form for creating new deduction
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 deductions"),
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²"),
widget=forms.NumberInput(
attrs={
"class": "form-control",
"placeholder": "0,00",
}
)
)
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 Deduction")
self.form_caption = _("Enter the information for a new deduction from a chosen eco-account")
# 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")
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 _get_available_surface(self, acc):
""" Calculates how much available surface is left on the account
Args:
acc (EcoAccount):
Returns:
"""
# Calculate valid surface
deductable_surface = acc.deductable_surface
sum_surface_deductions = acc.get_deductions_surface()
rest_surface = deductable_surface - sum_surface_deductions
return rest_surface
def is_valid(self):
""" Custom validity check
Makes sure the deduction can not contain more surface than the account still provides
Returns:
is_valid (bool)
"""
super_result = super().is_valid()
acc = self.cleaned_data["account"]
intervention = self.cleaned_data["intervention"]
objects_valid = True
if not acc.recorded:
self.add_error(
"account",
_("Eco-account {} is not recorded yet. You can only deduct from recorded accounts.").format(acc.identifier)
)
objects_valid = False
if intervention.is_recorded:
self.add_error(
"intervention",
_("Intervention {} is currently recorded. To change any data on it, the entry must be unrecorded.").format(intervention.identifier)
)
objects_valid = False
rest_surface = self._get_available_surface(acc)
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 deduction of {} m². There are only {} m² left").format(
acc.identifier,
format_german_float(form_surface),
format_german_float(rest_surface),
),
)
return is_valid_surface and objects_valid and super_result
def __create_deduction(self):
""" Creates the deduction
Returns:
"""
with transaction.atomic():
user_action_create = UserActionLogEntry.get_created_action(self.user)
deduction = EcoAccountDeduction.objects.create(
intervention=self.cleaned_data["intervention"],
account=self.cleaned_data["account"],
surface=self.cleaned_data["surface"],
created=user_action_create,
)
return deduction
def save(self):
deduction = self.__create_deduction()
self.cleaned_data["intervention"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED)
self.cleaned_data["account"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED)
return deduction
class EditEcoAccountDeductionModalForm(NewDeductionModalForm):
deduction = None
def __init__(self, *args, **kwargs):
self.deduction = kwargs.pop("deduction", None)
super().__init__(*args, **kwargs)
self.form_title = _("Edit Deduction")
form_data = {
"account": self.deduction.account,
"intervention": self.deduction.intervention,
"surface": self.deduction.surface,
}
self.load_initial_data(form_data)
def _get_available_surface(self, acc):
rest_surface = super()._get_available_surface(acc)
# Increase available surface by the currently deducted surface, so we can 'deduct' the same amount again or
# increase the surface only a little, which will still be valid.
# Example: 200 m² left, 500 m² deducted. Entering 700 m² would fail if we would not add the 500 m² to the available
# surface again.
rest_surface += self.deduction.surface
return rest_surface
def save(self):
deduction = self.deduction
form_account = self.cleaned_data.get("account", None)
form_intervention = self.cleaned_data.get("intervention", None)
old_account = deduction.account
old_intervention = deduction.intervention
old_surface = deduction.surface
# If account or intervention has been changed, we put that change in the logs just as if the deduction has
# been removed for this entry. Act as if the deduction is newly created for the new entries
if old_account != form_account:
old_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
form_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
else:
old_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
if old_intervention != form_intervention:
old_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
form_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
else:
old_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
deduction.account = form_account
deduction.intervention = self.cleaned_data.get("intervention", None)
deduction.surface = self.cleaned_data.get("surface", None)
deduction.save()
data_changes = {
"surface": {
"old": old_surface,
"new": deduction.surface,
},
"intervention": {
"old": old_intervention.identifier,
"new": deduction.intervention.identifier,
},
"account": {
"old": old_account.identifier,
"new": deduction.account.identifier,
}
}
old_account.send_notification_mail_on_deduction_change(data_changes)
return deduction
class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
""" Removing modal form for EcoAccountDeduction
Can be used for anything, where removing shall be confirmed by the user a second time.
"""
deduction = None
def __init__(self, *args, **kwargs):
deduction = kwargs.pop("deduction", None)
self.deduction = deduction
super().__init__(*args, **kwargs)
def save(self):
with transaction.atomic():
self.deduction.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
self.deduction.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
self.deduction.delete()
class NewInterventionDocumentModalForm(NewDocumentModalForm):
document_model = InterventionDocument

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

@ -0,0 +1,107 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 18.08.22
"""
from django import forms
from django.db import transaction
from django.utils.translation import gettext_lazy as _
from konova.forms.modals import BaseModalForm
class CheckModalForm(BaseModalForm):
""" The modal form for running a check on interventions and their compensations
"""
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
)
valid = None
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)
self.valid = False
def _are_deductions_valid(self):
""" Performs validity checks on deductions and their eco-account
Returns:
"""
deductions = self.instance.deductions.all()
for deduction in deductions:
checker = deduction.account.quality_check()
for msg in checker.messages:
self.add_error(
"checked_comps",
f"{deduction.account.identifier}: {msg}"
)
return checker.valid
return True
def _are_comps_valid(self):
""" Performs validity checks on all types of compensations
Types of compensations are
* regular Compensations
* deductions from EcoAccounts
Returns:
"""
comps = self.instance.compensations.filter(
deleted=None,
)
comps_valid = True
for comp in comps:
checker = comp.quality_check()
for msg in checker.messages:
self.add_error(
"checked_comps",
f"{comp.identifier}: {msg}"
)
comps_valid = checker.valid
deductions_valid = self._are_deductions_valid()
return deductions_valid and comps_valid
def is_valid(self) -> bool:
""" Perform a validity check based on quality_check() logic
Returns:
result (bool)
"""
super_valid = super().is_valid()
# Perform check
checker = self.instance.quality_check()
for msg in checker.messages:
self.add_error(
"checked_intervention",
msg
)
all_comps_valid = self._are_comps_valid()
intervention_valid = checker.valid
return super_valid and intervention_valid and all_comps_valid
def save(self):
""" Saving logic
Returns:
"""
with transaction.atomic():
self.instance.set_checked(self.user)

@ -0,0 +1,252 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 18.08.22
"""
from dal import autocomplete
from django import forms
from django.db import transaction
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount, EcoAccountDeduction
from intervention.models import Intervention
from konova.forms.modals import BaseModalForm, RemoveModalForm
from konova.utils.general import format_german_float
from konova.utils.message_templates import DEDUCTION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED
from user.models import UserActionLogEntry
class NewEcoAccountDeductionModalForm(BaseModalForm):
""" Form for creating new deduction
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 deductions"),
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²"),
widget=forms.NumberInput(
attrs={
"class": "form-control",
"placeholder": "0,00",
}
)
)
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 Deduction")
self.form_caption = _("Enter the information for a new deduction from a chosen eco-account")
# 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")
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 _get_available_surface(self, acc):
""" Calculates how much available surface is left on the account
Args:
acc (EcoAccount):
Returns:
"""
# Calculate valid surface
deductable_surface = acc.deductable_surface
sum_surface_deductions = acc.get_deductions_surface()
rest_surface = deductable_surface - sum_surface_deductions
return rest_surface
def is_valid(self):
""" Custom validity check
Makes sure the deduction can not contain more surface than the account still provides
Returns:
is_valid (bool)
"""
super_result = super().is_valid()
acc = self.cleaned_data["account"]
intervention = self.cleaned_data["intervention"]
objects_valid = True
if not acc.recorded:
self.add_error(
"account",
_("Eco-account {} is not recorded yet. You can only deduct from recorded accounts.").format(acc.identifier)
)
objects_valid = False
if intervention.is_recorded:
self.add_error(
"intervention",
_("Intervention {} is currently recorded. To change any data on it, the entry must be unrecorded.").format(intervention.identifier)
)
objects_valid = False
rest_surface = self._get_available_surface(acc)
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 deduction of {} m². There are only {} m² left").format(
acc.identifier,
format_german_float(form_surface),
format_german_float(rest_surface),
),
)
return is_valid_surface and objects_valid and super_result
def __create_deduction(self):
""" Creates the deduction
Returns:
"""
with transaction.atomic():
user_action_create = UserActionLogEntry.get_created_action(self.user)
deduction = EcoAccountDeduction.objects.create(
intervention=self.cleaned_data["intervention"],
account=self.cleaned_data["account"],
surface=self.cleaned_data["surface"],
created=user_action_create,
)
return deduction
def save(self):
deduction = self.__create_deduction()
self.cleaned_data["intervention"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED)
self.cleaned_data["account"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED)
return deduction
class EditEcoAccountDeductionModalForm(NewEcoAccountDeductionModalForm):
deduction = None
def __init__(self, *args, **kwargs):
self.deduction = kwargs.pop("deduction", None)
super().__init__(*args, **kwargs)
self.form_title = _("Edit Deduction")
form_data = {
"account": self.deduction.account,
"intervention": self.deduction.intervention,
"surface": self.deduction.surface,
}
self.load_initial_data(form_data)
def _get_available_surface(self, acc):
rest_surface = super()._get_available_surface(acc)
# Increase available surface by the currently deducted surface, so we can 'deduct' the same amount again or
# increase the surface only a little, which will still be valid.
# Example: 200 m² left, 500 m² deducted. Entering 700 m² would fail if we would not add the 500 m² to the available
# surface again.
rest_surface += self.deduction.surface
return rest_surface
def save(self):
deduction = self.deduction
form_account = self.cleaned_data.get("account", None)
form_intervention = self.cleaned_data.get("intervention", None)
old_account = deduction.account
old_intervention = deduction.intervention
old_surface = deduction.surface
# If account or intervention has been changed, we put that change in the logs just as if the deduction has
# been removed for this entry. Act as if the deduction is newly created for the new entries
if old_account != form_account:
old_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
form_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
else:
old_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
if old_intervention != form_intervention:
old_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
form_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
else:
old_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
deduction.account = form_account
deduction.intervention = self.cleaned_data.get("intervention", None)
deduction.surface = self.cleaned_data.get("surface", None)
deduction.save()
data_changes = {
"surface": {
"old": old_surface,
"new": deduction.surface,
},
"intervention": {
"old": old_intervention.identifier,
"new": deduction.intervention.identifier,
},
"account": {
"old": old_account.identifier,
"new": deduction.account.identifier,
}
}
old_account.send_notification_mail_on_deduction_change(data_changes)
return deduction
class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
""" Removing modal form for EcoAccountDeduction
Can be used for anything, where removing shall be confirmed by the user a second time.
"""
deduction = None
def __init__(self, *args, **kwargs):
deduction = kwargs.pop("deduction", None)
self.deduction = deduction
super().__init__(*args, **kwargs)
def save(self):
with transaction.atomic():
self.deduction.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
self.deduction.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
self.deduction.delete()

@ -0,0 +1,13 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 18.08.22
"""
from intervention.models import InterventionDocument
from konova.forms.modals import NewDocumentModalForm
class NewInterventionDocumentModalForm(NewDocumentModalForm):
document_model = InterventionDocument

@ -0,0 +1,110 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 18.08.22
"""
from django import forms
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import gettext_lazy as _
from intervention.models import RevocationDocument
from konova.forms.modals import BaseModalForm, RemoveModalForm
from konova.utils.message_templates import REVOCATION_ADDED, REVOCATION_EDITED
class NewRevocationModalForm(BaseModalForm):
date = forms.DateField(
label=_("Date"),
label_suffix=_(""),
help_text=_("Date of revocation"),
widget=forms.DateInput(
attrs={
"type": "date",
"data-provide": "datepicker",
"class": "form-control",
},
format="%d.%m.%Y"
)
)
file = forms.FileField(
label=_("Document"),
label_suffix=_(""),
required=False,
help_text=_("Must be smaller than 15 Mb"),
widget=forms.FileInput(
attrs={
"class": "form-control-file"
}
)
)
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",
}
)
)
document_model = RevocationDocument
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):
revocation = self.instance.add_revocation(self)
self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_ADDED)
return revocation
class EditRevocationModalForm(NewRevocationModalForm):
revocation = None
def __init__(self, *args, **kwargs):
self.revocation = kwargs.pop("revocation", None)
super().__init__(*args, **kwargs)
self.form_title = _("Edit revocation")
try:
doc = self.revocation.document.file
except ObjectDoesNotExist:
doc = None
form_data = {
"date": str(self.revocation.date),
"file": doc,
"comment": self.revocation.comment,
}
self.load_initial_data(form_data)
def save(self):
revocation = self.instance.edit_revocation(self)
self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_EDITED)
return revocation
class RemoveRevocationModalForm(RemoveModalForm):
""" Removing modal form for Revocation
Can be used for anything, where removing shall be confirmed by the user a second time.
"""
revocation = None
def __init__(self, *args, **kwargs):
revocation = kwargs.pop("revocation", None)
self.revocation = revocation
super().__init__(*args, **kwargs)
def save(self):
self.instance.remove_revocation(self)

@ -0,0 +1,136 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 18.08.22
"""
from dal import autocomplete
from django import forms
from django.utils.translation import gettext_lazy as _
from intervention.inputs import TextToClipboardInput
from konova.forms.modals import BaseModalForm
from konova.utils.message_templates import ENTRY_REMOVE_MISSING_PERMISSION
from konova.utils.user_checks import is_default_group_only
from user.models import Team, User
class ShareModalForm(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,
"class": "form-control",
}
)
)
teams = forms.ModelMultipleChoiceField(
label=_("Add team to share with"),
label_suffix="",
help_text=_("Multiple selection possible - You can only select teams which do not already have access."),
required=False,
queryset=Team.objects.all(),
widget=autocomplete.ModelSelect2Multiple(
url="share-team-autocomplete",
attrs={
"data-placeholder": _("Click for selection"),
"data-minimum-input-length": 3,
},
),
)
users = forms.ModelMultipleChoiceField(
label=_("Add user to share with"),
label_suffix="",
help_text=_("Multiple selection possible - You can only select users which do not already have access. Enter the full username."),
required=False,
queryset=User.objects.all(),
widget=autocomplete.ModelSelect2Multiple(
url="share-user-autocomplete",
attrs={
"data-placeholder": _("Click for selection"),
"data-minimum-input-length": 3,
},
),
)
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 _user_team_valid(self):
""" Checks whether users and teams have been removed by the user and if the user is allowed to do so or not
Returns:
"""
users = self.cleaned_data.get("users", User.objects.none())
teams = self.cleaned_data.get("teams", Team.objects.none())
_is_valid = True
if is_default_group_only(self.user):
shared_users = self.instance.shared_users
shared_teams = self.instance.shared_teams
shared_users_are_removed = not set(shared_users).issubset(users)
shared_teams_are_removed = not set(shared_teams).issubset(teams)
if shared_users_are_removed:
self.add_error(
"users",
ENTRY_REMOVE_MISSING_PERMISSION
)
_is_valid = False
if shared_teams_are_removed:
self.add_error(
"teams",
ENTRY_REMOVE_MISSING_PERMISSION
)
_is_valid = False
return _is_valid
def is_valid(self):
""" Extended validity check
Returns:
"""
super_valid = super().is_valid()
user_team_valid = self._user_team_valid()
_is_valid = super_valid and user_team_valid
return _is_valid
def _init_fields(self):
""" Wraps initializing of fields
Returns:
"""
# Initialize share_link field
share_link = self.instance.get_share_link()
self.share_link = self.request.build_absolute_uri(share_link)
self.initialize_form_field(
"url",
self.share_link
)
form_data = {
"teams": self.instance.teams.all(),
"users": self.instance.users.all(),
}
self.load_initial_data(form_data)
def save(self):
self.instance.update_shared_access(self)

@ -4,10 +4,14 @@ from django.utils.translation import gettext_lazy as _
from django.http import HttpRequest, JsonResponse, Http404
from django.shortcuts import render
from intervention.forms.forms import NewInterventionForm, EditInterventionForm
from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \
CheckModalForm, NewDeductionModalForm, NewInterventionDocumentModalForm, RemoveEcoAccountDeductionModalForm, \
RemoveRevocationModalForm, EditEcoAccountDeductionModalForm, EditRevocationModalForm
from intervention.forms.intervention import NewInterventionForm, EditInterventionForm
from intervention.forms.modals.check import CheckModalForm
from intervention.forms.modals.deduction import NewEcoAccountDeductionModalForm, RemoveEcoAccountDeductionModalForm, \
EditEcoAccountDeductionModalForm
from intervention.forms.modals.document import NewInterventionDocumentModalForm
from intervention.forms.modals.revocation import EditRevocationModalForm, RemoveRevocationModalForm, \
NewRevocationModalForm
from intervention.forms.modals.share import ShareModalForm
from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
from intervention.tables import InterventionTable
from konova.contexts import BaseContext
@ -583,7 +587,7 @@ def new_deduction_view(request: HttpRequest, id: str):
"""
intervention = get_object_or_404(Intervention, id=id)
form = NewDeductionModalForm(request.POST or None, instance=intervention, request=request)
form = NewEcoAccountDeductionModalForm(request.POST or None, instance=intervention, request=request)
return form.process_request(
request,
msg_success=DEDUCTION_ADDED,

@ -134,7 +134,7 @@ class BaseForm(forms.Form):
Returns:
"""
from intervention.forms.modalForms import NewDeductionModalForm, EditEcoAccountDeductionModalForm, \
from intervention.forms.modals.deduction import NewEcoAccountDeductionModalForm, EditEcoAccountDeductionModalForm, \
RemoveEcoAccountDeductionModalForm
from konova.forms.modals.resubmission_form import ResubmissionModalForm
is_none = self.instance is None
@ -142,7 +142,7 @@ class BaseForm(forms.Form):
is_deduction_form_from_account = isinstance(
self,
(
NewDeductionModalForm,
NewEcoAccountDeductionModalForm,
ResubmissionModalForm,
EditEcoAccountDeductionModalForm,
RemoveEcoAccountDeductionModalForm,

Loading…
Cancel
Save