2021-07-01 13:36:07 +02:00
"""
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 _
2021-08-10 10:42:04 +02:00
from compensation . models import EcoAccountWithdraw , EcoAccount
2021-08-04 13:32:35 +02:00
from intervention . models import Intervention , Revocation
2021-07-30 13:30:42 +02:00
from konova . forms import BaseForm , BaseModalForm
2021-07-01 13:36:07 +02:00
from konova . models import Document
2021-08-02 16:23:29 +02:00
from konova . settings import DEFAULT_LAT , DEFAULT_LON , DEFAULT_ZOOM , ZB_GROUP , ETS_GROUP
from konova . utils . user_checks import in_group
2021-07-01 13:36:07 +02:00
from organisation . models import Organisation
2021-08-03 17:22:41 +02:00
from user . models import UserActionLogEntry , UserAction
2021-07-01 13:36:07 +02:00
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 [ ]
2021-08-02 11:52:20 +02:00
action = UserActionLogEntry . objects . create (
user = user ,
2021-08-03 17:22:41 +02:00
action = UserAction . CREATED ,
2021-08-02 11:52:20 +02:00
)
2021-07-01 13:36:07 +02:00
intervention = Intervention (
identifier = identifier ,
title = title ,
type = _type ,
law = law ,
handler = handler ,
data_provider = data_provider ,
data_provider_detail = data_provider_detail ,
geometry = geometry ,
2021-08-02 11:52:20 +02:00
created = action ,
2021-07-01 13:36:07 +02:00
)
intervention . save ( )
2021-08-05 12:54:28 +02:00
intervention . log . add ( action )
2021-07-01 13:36:07 +02:00
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 ( )
2021-08-05 12:54:28 +02:00
user_action = UserActionLogEntry . objects . create (
user = self . user ,
action = UserAction . EDITED
)
self . instance . log . add ( user_action )
2021-07-01 13:36:07 +02:00
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 )
2021-07-28 09:53:14 +02:00
2021-07-30 14:34:36 +02:00
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 "
2021-07-30 13:30:42 +02:00
class ShareInterventionForm ( BaseModalForm ) :
url = forms . CharField (
label = _ ( " Share link " ) ,
label_suffix = " " ,
2021-07-30 15:23:46 +02:00
help_text = _ ( " Send this link to users who you want to have writing access on the data " ) ,
2021-07-30 13:30:42 +02:00
required = False ,
2021-07-30 14:34:36 +02:00
widget = TextToClipboardInput (
2021-07-30 13:30:42 +02:00
attrs = {
2021-07-30 14:34:36 +02:00
" readonly " : True
2021-07-30 13:30:42 +02:00
}
)
)
2021-07-30 15:23:46 +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 = [ ]
)
2021-07-30 13:30:42 +02:00
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
self . form_title = _ ( " Share " )
2021-07-30 15:23:46 +02:00
self . form_caption = _ ( " Share settings for {} " ) . format ( self . instance . identifier )
2021-07-30 13:30:42 +02:00
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 ( )
2021-07-30 15:23:46 +02:00
self . _init_fields ( )
def _init_fields ( self ) :
""" Wraps initializing of fields
Returns :
"""
# Initialize share_link field
2021-07-30 13:30:42 +02:00
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
)
2021-07-30 15:23:46 +02:00
# Initialize users field
2021-08-02 16:23:29 +02:00
# 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
2021-07-30 15:23:46 +02:00
)
def save ( self ) :
accessing_users = User . objects . filter (
id__in = self . cleaned_data [ " users " ]
)
self . instance . users . set ( accessing_users )
2021-08-04 13:32:35 +02:00
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 ( ) :
2021-08-05 12:54:28 +02:00
created_action = UserActionLogEntry . objects . create (
2021-08-04 13:32:35 +02:00
user = self . user ,
action = UserAction . CREATED
)
2021-08-05 12:54:28 +02:00
edited_action = UserActionLogEntry . objects . create (
user = self . user ,
action = UserAction . EDITED
)
2021-08-04 13:32:35 +02:00
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 ,
2021-08-05 12:54:28 +02:00
created = created_action ,
2021-08-04 13:32:35 +02:00
)
2021-08-05 12:54:28 +02:00
self . instance . log . add ( edited_action )
2021-08-04 13:32:35 +02:00
self . instance . legal . revocation = revocation
self . instance . legal . save ( )
return revocation
2021-08-04 15:19:06 +02:00
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
2021-08-10 17:19:42 +02:00
msgs = self . instance . quality_check ( )
for msg in msgs :
self . add_error (
" checked_intervention " ,
msg
)
return super_result and ( len ( msgs ) == 0 )
2021-08-04 15:19:06 +02:00
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
2021-08-05 12:54:28 +02:00
self . instance . log . add ( user_action )
self . instance . save ( )
2021-08-10 10:42:04 +02:00
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 " ) ,
2021-08-10 14:15:42 +02:00
queryset = EcoAccount . objects . filter ( deleted = None ) ,
2021-08-10 10:42:04 +02:00
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
2021-08-11 14:17:43 +02:00
self . add_placeholder_for_field ( " surface " , " 0,00 " )
2021-08-10 10:42:04 +02: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
2021-08-10 14:15:42 +02:00
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
2021-08-10 10:42:04 +02:00
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