2021-09-27 11:45:13 +02:00
"""
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 . contrib . auth . models import User
from django . db import transaction
from django import forms
from django . urls import reverse
from django . utils . translation import gettext_lazy as _
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 user . models import UserActionLogEntry , UserAction
class ShareInterventionModalForm ( 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 = {
2021-09-27 13:57:56 +02:00
" readonly " : True ,
" class " : " form-control " ,
2021-09-27 11:45:13 +02:00
}
)
)
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 NewRevocationModalForm ( BaseModalForm ) :
date = forms . DateField (
label = _ ( " Date " ) ,
label_suffix = _ ( " " ) ,
help_text = _ ( " Date of revocation " ) ,
widget = forms . DateInput (
attrs = {
" type " : " date " ,
" data-provide " : " datepicker " ,
2021-09-27 13:57:56 +02:00
" class " : " form-control " ,
2021-09-27 11:45:13 +02:00
} ,
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 = {
2021-09-27 13:57:56 +02:00
" class " : " form-control-file "
2021-09-27 11:45:13 +02:00
}
)
)
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 ,
2021-09-27 13:57:56 +02:00
" class " : " form-control " ,
2021-09-27 11:45:13 +02:00
}
)
)
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
)
revocation = Revocation . objects . create (
date = self . cleaned_data [ " date " ] ,
comment = self . cleaned_data [ " comment " ] ,
created = created_action ,
)
self . instance . modified = edited_action
self . instance . save ( )
self . instance . log . add ( edited_action )
self . instance . legal . revocation = revocation
self . instance . legal . save ( )
if self . cleaned_data [ " file " ] :
RevocationDocument . 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 " ] ,
instance = revocation
)
return revocation
class RunCheckModalForm ( 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 ) - > bool :
""" Perform a validity check based on quality_check() logic
Returns :
result ( bool )
"""
super_result = super ( ) . is_valid ( )
# Perform check
2021-10-25 13:06:54 +02:00
checker = self . instance . quality_check ( )
for msg in checker . messages :
2021-09-27 11:45:13 +02:00
self . add_error (
" checked_intervention " ,
msg
)
2021-10-25 13:44:54 +02:00
comps = self . instance . compensations . all ( )
for comp in comps :
checker = comp . quality_check ( )
for msg in checker . messages :
self . add_error (
" checked_comps " ,
f " { comp . identifier } : { msg } "
)
2021-10-25 13:06:54 +02:00
return super_result and checker . valid
2021-09-27 11:45:13 +02:00
def save ( self ) :
""" Saving logic
Returns :
"""
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 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² " ) ,
2021-09-27 13:57:56 +02:00
widget = forms . NumberInput (
attrs = {
" class " : " form-control " ,
2021-10-06 13:10:10 +02:00
" placeholder " : " 0,00 " ,
2021-09-27 13:57:56 +02:00
}
)
2021-09-27 11:45:13 +02: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 " )
self . is_intervention_initially = False
# 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 deduction 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 deduct from recorded accounts. " ) . format ( acc . identifier )
)
return False
# Calculate valid surface
2021-10-25 13:44:54 +02:00
sum_surface = acc . get_surface_after_states ( )
2021-09-27 11:45:13 +02:00
sum_surface_deductions = acc . get_deductions_surface ( )
rest_surface = sum_surface - sum_surface_deductions
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 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 )
self . instance . modified = user_action_edit
self . instance . save ( )
# Create deductions depending on Intervention or EcoAccount as the initial instance
if self . is_intervention_initially :
deduction = EcoAccountDeduction . objects . create (
intervention = self . instance ,
account = self . cleaned_data [ " account " ] ,
surface = self . cleaned_data [ " surface " ] ,
created = user_action_create ,
)
else :
deduction = EcoAccountDeduction . objects . create (
intervention = self . cleaned_data [ " intervention " ] ,
account = self . instance ,
surface = self . cleaned_data [ " surface " ] ,
created = user_action_create ,
)
return deduction