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 : 16.11 .20
"""
from abc import abstractmethod
2021-07-26 10:23:09 +02:00
from bootstrap_modal_forms . forms import BSModalForm
2021-08-17 15:27:49 +02:00
from bootstrap_modal_forms . utils import is_ajax
2021-07-01 13:36:07 +02:00
from django import forms
2021-08-02 10:53:34 +02:00
from django . contrib import messages
2021-07-21 14:17:18 +02:00
from django . contrib . auth . models import User
2021-07-22 16:06:13 +02:00
from django . contrib . gis . forms import GeometryField , OSMWidget
from django . contrib . gis . geos import Polygon
2021-07-26 15:16:16 +02:00
from django . db import transaction
2021-08-17 15:27:49 +02:00
from django . http import HttpRequest , HttpResponseRedirect
from django . shortcuts import render
2021-07-21 14:17:18 +02:00
from django . utils import timezone
2021-07-01 13:36:07 +02:00
from django . utils . translation import gettext_lazy as _
2021-08-10 17:19:42 +02:00
from compensation . models import EcoAccount
2021-08-19 13:44:06 +02:00
from ema . models import Ema
2021-08-10 17:19:42 +02:00
from intervention . models import Intervention
2021-08-02 10:53:34 +02:00
from konova . contexts import BaseContext
2021-08-05 12:54:28 +02:00
from konova . models import Document , BaseObject
2021-08-02 10:53:34 +02:00
from konova . utils . message_templates import FORM_INVALID
2021-08-03 17:22:41 +02:00
from user . models import UserActionLogEntry , UserAction
2021-07-26 15:16:16 +02:00
2021-07-01 13:36:07 +02:00
class BaseForm ( forms . Form ) :
"""
Basic form for that holds attributes needed in all other forms
"""
2021-07-26 15:16:16 +02:00
template = None
2021-07-01 13:36:07 +02:00
action_url = None
form_title = None
cancel_redirect = None
form_caption = None
instance = None # The data holding model object
2021-07-26 15:16:16 +02:00
form_attrs = { } # Holds additional attributes, that can be used in the template
2021-08-04 08:41:21 +02:00
has_required_fields = False # Automatically set. Triggers hint rendering in templates
2021-07-01 13:36:07 +02:00
def __init__ ( self , * args , * * kwargs ) :
self . instance = kwargs . pop ( " instance " , None )
2021-07-26 11:29:05 +02:00
self . user = kwargs . pop ( " user " , None )
2021-07-01 13:36:07 +02:00
super ( ) . __init__ ( * args , * * kwargs )
2021-08-04 08:41:21 +02:00
# Check for required fields
for _field_name , _field_val in self . fields . items ( ) :
if _field_val . required :
self . has_required_fields = True
break
2021-07-01 13:36:07 +02:00
@abstractmethod
def save ( self ) :
# To be implemented in subclasses!
pass
def disable_form_field ( self , field : str ) :
"""
Disables a form field for user editing
"""
self . fields [ field ] . widget . attrs [ " readonly " ] = True
self . fields [ field ] . disabled = True
self . fields [ field ] . widget . attrs [ " title " ] = _ ( " Not editable " )
def initialize_form_field ( self , field : str , val ) :
"""
Initializes a form field with a value
"""
self . fields [ field ] . initial = val
2021-08-11 14:17:43 +02:00
def add_placeholder_for_field ( self , field : str , val ) :
"""
Adds a placeholder to a field after initialization
Args :
field ( str ) : Field name
val ( str ) : Placeholder
Returns :
"""
self . fields [ field ] . widget . attrs [ " placeholder " ] = val
2021-07-01 13:36:07 +02:00
def load_initial_data ( self , form_data : dict , disabled_fields : list ) :
""" Initializes form data from instance
Inserts instance data into form and disables form fields
Returns :
"""
if self . instance is None :
return
for k , v in form_data . items ( ) :
self . initialize_form_field ( k , v )
for field in disabled_fields :
self . disable_form_field ( field )
class RemoveForm ( BaseForm ) :
check = forms . BooleanField (
label = _ ( " Confirm " ) ,
label_suffix = _ ( " " ) ,
required = True ,
)
def __init__ ( self , * args , * * kwargs ) :
self . object_to_remove = kwargs . pop ( " object_to_remove " , None )
self . remove_post_url = kwargs . pop ( " remove_post_url " , " " )
self . cancel_url = kwargs . pop ( " cancel_url " , " " )
super ( ) . __init__ ( * args , * * kwargs )
self . form_title = _ ( " Remove " )
if self . object_to_remove is not None :
self . form_caption = _ ( " You are about to remove {} {} " ) . format ( self . object_to_remove . __class__ . __name__ , self . object_to_remove )
self . action_url = self . remove_post_url
self . cancel_redirect = self . cancel_url
def is_checked ( self ) - > bool :
return self . cleaned_data . get ( " check " , False )
2021-07-21 14:17:18 +02:00
def save ( self , user : User ) :
2021-08-02 11:52:20 +02:00
""" Perform generic removing by running the form typical ' save() ' method
Args :
user ( User ) : The performing user
Returns :
"""
2021-07-01 13:36:07 +02:00
if self . object_to_remove is not None and self . is_checked ( ) :
2021-08-02 11:52:20 +02:00
with transaction . atomic ( ) :
self . object_to_remove . is_active = False
action = UserActionLogEntry . objects . create (
user = user ,
timestamp = timezone . now ( ) ,
2021-08-03 17:22:41 +02:00
action = UserAction . DELETED
2021-08-02 11:52:20 +02:00
)
self . object_to_remove . deleted = action
self . object_to_remove . save ( )
2021-07-01 13:36:07 +02:00
return self . object_to_remove
2021-07-22 13:19:14 +02:00
2021-07-26 10:23:09 +02:00
class BaseModalForm ( BaseForm , BSModalForm ) :
""" A specialzed form class for modal form handling
"""
is_modal_form = True
2021-07-30 13:30:42 +02:00
render_submit = True
2021-08-03 13:13:01 +02:00
template = " modal/modal_form.html "
def process_request ( self , request : HttpRequest , msg_success : str = _ ( " Object removed " ) , msg_error : str = FORM_INVALID , redirect_url : str = None ) :
""" Generic processing of request
Wraps the request processing logic , so we don ' t need the same code everywhere a RemoveModalForm is being used
Args :
request ( HttpRequest ) : The incoming request
msg_success ( str ) : The message in case of successful removing
msg_error ( str ) : The message in case of an error
Returns :
"""
redirect_url = redirect_url if redirect_url is not None else request . META . get ( " HTTP_REFERER " , " home " )
template = self . template
if request . method == " POST " :
if self . is_valid ( ) :
2021-08-17 15:27:49 +02:00
if not is_ajax ( request . META ) :
2021-08-19 09:06:35 +02:00
# Modal forms send one POST for checking on data validity. This can be used to return possible errors
# on the form. A second POST (if no errors occured) is sent afterwards and needs to process the
# saving/commiting of the data to the database. is_ajax() performs this check. The first request is
# an ajax call, the second is a regular form POST.
2021-08-17 15:27:49 +02:00
self . save ( )
messages . success (
2021-08-04 11:56:56 +02:00
request ,
2021-08-17 15:27:49 +02:00
msg_success
2021-08-04 11:56:56 +02:00
)
2021-08-17 15:27:49 +02:00
return HttpResponseRedirect ( redirect_url )
else :
context = {
" form " : self ,
}
context = BaseContext ( request , context ) . context
return render ( request , template , context )
2021-08-03 13:13:01 +02:00
elif request . method == " GET " :
context = {
" form " : self ,
}
context = BaseContext ( request , context ) . context
return render ( request , template , context )
else :
raise NotImplementedError
2021-07-26 10:23:09 +02:00
2021-07-22 13:19:14 +02:00
class SimpleGeomForm ( BaseForm ) :
""" A geometry form for rendering geometry read-only using a widget
"""
geom = GeometryField (
2021-07-22 16:06:13 +02:00
required = False ,
disabled = True ,
2021-07-22 13:19:14 +02:00
widget = OSMWidget (
attrs = {
" map_width " : 600 ,
" map_height " : 400 ,
}
)
)
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
2021-07-22 16:06:13 +02:00
# Initialize geometry
try :
geom = self . instance . geometry . geom
if geom is None :
raise AttributeError
except AttributeError :
# catches if no geometry has been added, yet. Replace with empty placeholder polygon.
geom = Polygon . from_bbox ( [ 0 , 0 , 0 , 0 ] )
# Zoom out to a very high level, so the user can see directly that there is no geometry for this entry
self . fields [ " geom " ] . widget . attrs [ " default_zoom " ] = 1
2021-07-22 13:19:14 +02:00
self . initialize_form_field ( " geom " , geom )
2021-07-22 16:06:13 +02:00
self . area = geom . area
2021-07-23 18:27:53 +02:00
2021-07-26 11:29:05 +02:00
class RemoveModalForm ( BaseModalForm ) :
""" Generic removing modal form
Can be used for anything , where removing shall be confirmed by the user a second time .
"""
confirm = forms . BooleanField (
label = _ ( " Confirm " ) ,
label_suffix = _ ( " " ) ,
widget = forms . CheckboxInput ( ) ,
required = True ,
)
def __init__ ( self , * args , * * kwargs ) :
2021-07-26 15:16:16 +02:00
self . template = " modal/modal_form.html "
2021-07-26 11:29:05 +02:00
super ( ) . __init__ ( * args , * * kwargs )
self . form_title = _ ( " Remove " )
self . form_caption = _ ( " Are you sure? " )
def save ( self ) :
2021-08-05 12:54:28 +02:00
if isinstance ( self . instance , BaseObject ) :
with transaction . atomic ( ) :
action = UserActionLogEntry . objects . create (
user = self . user ,
timestamp = timezone . now ( ) ,
action = UserAction . DELETED ,
)
self . instance . deleted = action
self . instance . log . add ( action )
self . instance . save ( )
2021-07-26 11:29:05 +02:00
else :
# If the class does not provide restorable delete functionality, we must delete the entry finally
self . instance . delete ( )
2021-07-26 15:16:16 +02:00
class NewDocumentForm ( BaseModalForm ) :
""" Modal form for new documents
"""
title = forms . CharField (
2021-07-26 15:34:30 +02:00
label = _ ( " Title " ) ,
label_suffix = _ ( " " ) ,
2021-07-26 15:16:16 +02:00
max_length = 500 ,
)
creation_date = forms . DateField (
label = _ ( " Created on " ) ,
2021-07-23 18:27:53 +02:00
label_suffix = _ ( " " ) ,
2021-07-26 15:16:16 +02:00
help_text = _ ( " When has this file been created? Important for photos. " ) ,
widget = forms . DateInput (
attrs = {
" type " : " date " ,
" data-provide " : " datepicker " ,
} ,
format = " %d . % m. % Y "
)
)
file = forms . FileField (
label = _ ( " File " ) ,
label_suffix = _ ( " " ) ,
help_text = _ ( " Must be smaller than 15 Mb " ) ,
2021-07-26 15:34:30 +02:00
widget = forms . FileInput (
attrs = {
" class " : " w-75 "
}
)
2021-07-26 15:16:16 +02:00
)
comment = forms . CharField (
required = False ,
2021-08-04 11:56:56 +02:00
max_length = 200 ,
2021-07-26 15:16:16 +02:00
label = _ ( " Comment " ) ,
label_suffix = _ ( " " ) ,
2021-08-04 11:56:56 +02:00
help_text = _ ( " Additional comment, maximum {} letters " ) . format ( 200 ) ,
2021-07-26 15:34:30 +02:00
widget = forms . Textarea (
attrs = {
" cols " : 30 ,
" rows " : 5 ,
}
)
2021-07-23 18:27:53 +02:00
)
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
2021-07-26 15:16:16 +02:00
self . form_title = _ ( " Add new document " )
self . form_caption = _ ( " " )
self . template = " modal/modal_form.html "
self . form_attrs = {
2021-08-04 13:32:35 +02:00
" enctype " : " multipart/form-data " , # important for file upload
2021-07-26 15:16:16 +02:00
}
def save ( self ) :
with transaction . atomic ( ) :
2021-08-02 11:52:20 +02:00
action = UserActionLogEntry . objects . create (
user = self . user ,
2021-08-03 17:22:41 +02:00
action = UserAction . CREATED ,
2021-08-02 11:52:20 +02:00
)
2021-07-26 15:16:16 +02:00
doc = Document . objects . create (
2021-08-02 11:52:20 +02:00
created = action ,
2021-07-26 15:16:16 +02:00
title = self . cleaned_data [ " title " ] ,
comment = self . cleaned_data [ " comment " ] ,
2021-08-04 15:19:06 +02:00
file = self . cleaned_data [ " file " ] ,
2021-07-26 15:16:16 +02:00
date_of_creation = self . cleaned_data [ " creation_date " ] ,
)
2021-07-26 15:34:30 +02:00
self . instance . documents . add ( doc )
2021-08-05 12:54:28 +02:00
edited_action = UserActionLogEntry . objects . create (
user = self . user ,
action = UserAction . EDITED ,
comment = _ ( " Added document " ) ,
)
self . instance . log . add ( edited_action )
2021-08-19 13:02:31 +02:00
self . instance . modified = edited_action
self . instance . save ( )
2021-08-05 12:54:28 +02:00
return doc
2021-08-10 17:19:42 +02:00
class RecordForm ( BaseModalForm ) :
""" Modal form for recording data
"""
confirm = forms . BooleanField (
label = _ ( " Confirm record " ) ,
label_suffix = " " ,
widget = forms . CheckboxInput ( ) ,
required = True ,
)
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
self . form_title = _ ( " Record data " )
self . form_caption = _ ( " I, {} {} , confirm that all necessary control steps have been performed by myself. " ) . format ( self . user . first_name , self . user . last_name )
if self . instance . recorded :
# unrecord!
self . fields [ " confirm " ] . label = _ ( " Confirm unrecord " )
self . form_title = _ ( " Unrecord data " )
self . form_caption = _ ( " I, {} {} , confirm that this data must be unrecorded. " ) . format ( self . user . first_name , self . user . last_name )
implemented_cls_logic = {
Intervention ,
2021-08-19 13:44:06 +02:00
EcoAccount ,
Ema ,
2021-08-10 17:19:42 +02:00
}
instance_name = self . instance . __class__
if instance_name not in implemented_cls_logic :
raise NotImplementedError
def is_valid ( self ) :
""" Checks for instance ' s validity and data quality
Returns :
"""
super_val = super ( ) . is_valid ( )
2021-08-11 14:17:43 +02:00
msgs = self . instance . quality_check ( ) or [ ]
2021-08-10 17:19:42 +02:00
for msg in msgs :
self . add_error (
" confirm " ,
msg
)
return super_val and ( len ( msgs ) == 0 )
def save ( self ) :
with transaction . atomic ( ) :
if self . cleaned_data [ " confirm " ] :
if self . instance . recorded :
# unrecord!
unrecord_action = UserActionLogEntry . objects . create (
user = self . user ,
action = UserAction . UNRECORDED
)
# Do not delete the old .recorded attribute, since it shall stay in the .log list!
self . instance . recorded = None
self . instance . log . add ( unrecord_action )
else :
record_action = UserActionLogEntry . objects . create (
user = self . user ,
action = UserAction . RECORDED
)
self . instance . recorded = record_action
self . instance . log . add ( record_action )
self . instance . save ( )
return self . instance