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
2022-02-10 10:21:18 +01:00
from django . db . models . fields . files import FieldFile
2022-01-12 12:56:22 +01:00
from user . models import User
2021-09-23 15:05:17 +02:00
from django . contrib . gis . forms import OSMWidget , MultiPolygonField
2021-11-17 14:33:05 +01:00
from django . contrib . gis . geos import MultiPolygon
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-01 13:36:07 +02:00
from django . utils . translation import gettext_lazy as _
2021-08-02 10:53:34 +02:00
from konova . contexts import BaseContext
2022-02-10 10:21:18 +01:00
from konova . models import BaseObject , Geometry , RecordableObjectMixin , AbstractDocument
2021-09-23 15:05:17 +02:00
from konova . settings import DEFAULT_SRID
2022-01-06 12:08:38 +01:00
from konova . tasks import celery_update_parcels
2022-02-10 10:21:18 +01:00
from konova . utils . message_templates import FORM_INVALID , FILE_TYPE_UNSUPPORTED , FILE_SIZE_TOO_LARGE , DOCUMENT_EDITED
2021-11-16 13:15:15 +01:00
from user . models import UserActionLogEntry
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
2021-10-20 13:41:32 +02:00
action_btn_label = _ ( " Save " )
2021-07-01 13:36:07 +02:00
form_title = None
cancel_redirect = None
form_caption = None
instance = None # The data holding model object
2021-11-17 14:33:05 +01:00
request = None
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-10-20 13:41:32 +02:00
show_cancel_btn = True
2021-07-01 13:36:07 +02:00
def __init__ ( self , * args , * * kwargs ) :
self . instance = kwargs . pop ( " instance " , None )
super ( ) . __init__ ( * args , * * kwargs )
2021-11-17 14:33:05 +01:00
if self . request is not None :
self . user = self . request . user
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 ) :
"""
2021-10-06 13:10:10 +02:00
Adds a placeholder to a field after initialization without the need to redefine the form widget
2021-08-11 14:17:43 +02:00
Args :
field ( str ) : Field name
val ( str ) : Placeholder
Returns :
"""
self . fields [ field ] . widget . attrs [ " placeholder " ] = val
2022-02-09 10:29:34 +01:00
def load_initial_data ( self , form_data : dict , disabled_fields : list = None ) :
2021-07-01 13:36:07 +02:00
""" 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 )
2022-02-09 10:29:34 +01:00
if disabled_fields :
for field in disabled_fields :
self . disable_form_field ( field )
2021-07-01 13:36:07 +02:00
2021-09-20 14:13:59 +02:00
def add_widget_html_class ( self , field : str , cls : str ) :
""" Adds a HTML class string to the widget of a field
Args :
field ( str ) : The field ' s name
cls ( str ) : The new class string
Returns :
"""
set_class = self . fields [ field ] . widget . attrs . get ( " class " , " " )
if cls in set_class :
return
else :
set_class + = " " + cls
self . fields [ field ] . widget . attrs [ " class " ] = set_class
def remove_widget_html_class ( self , field : str , cls : str ) :
""" Removes a HTML class string from the widget of a field
Args :
field ( str ) : The field ' s name
cls ( str ) : The new class string
Returns :
"""
set_class = self . fields [ field ] . widget . attrs . get ( " class " , " " )
set_class = set_class . replace ( cls , " " )
self . fields [ field ] . widget . attrs [ " class " ] = set_class
2021-07-01 13:36:07 +02:00
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
2021-11-16 13:15:15 +01:00
action = UserActionLogEntry . get_deleted_action ( user )
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 "
2021-09-23 15:05:17 +02:00
def __init__ ( self , * args , * * kwargs ) :
2021-08-24 14:50:51 +02:00
super ( ) . __init__ ( * args , * * kwargs )
2021-10-20 13:41:32 +02:00
self . action_btn_label = _ ( " Continue " )
2021-08-24 14:50:51 +02:00
2021-08-03 13:13:01 +02:00
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
"""
2021-09-23 15:05:17 +02:00
geom = MultiPolygonField (
srid = DEFAULT_SRID ,
label = _ ( " Geometry " ) ,
help_text = _ ( " " ) ,
label_suffix = " " ,
2021-07-22 16:06:13 +02:00
required = False ,
2021-09-23 15:05:17 +02:00
disabled = False ,
2021-07-22 13:19:14 +02:00
widget = OSMWidget (
attrs = {
" map_width " : 600 ,
" map_height " : 400 ,
2021-09-23 15:05:17 +02:00
# default_zoom defines the nearest possible zoom level from which the JS automatically
# zooms out if geometry requires a larger view port. So define a larger range for smaller geometries
" default_zoom " : 25 ,
2021-07-22 13:19:14 +02:00
}
)
)
def __init__ ( self , * args , * * kwargs ) :
2021-09-23 15:05:17 +02:00
read_only = kwargs . pop ( " read_only " , True )
2021-07-22 13:19:14 +02:00
super ( ) . __init__ ( * args , * * kwargs )
2021-07-22 16:06:13 +02:00
# Initialize geometry
try :
geom = self . instance . geometry . geom
2021-09-29 14:49:17 +02:00
self . empty = geom . empty
2021-07-22 16:06:13 +02:00
except AttributeError :
2021-09-29 14:49:17 +02:00
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
geom = None
self . empty = True
2021-07-22 16:06:13 +02:00
self . fields [ " geom " ] . widget . attrs [ " default_zoom " ] = 1
2021-09-29 14:49:17 +02:00
2021-09-27 11:12:40 +02:00
self . initialize_form_field ( " geom " , geom )
2021-09-23 15:05:17 +02:00
if read_only :
self . fields [ " geom " ] . disabled = True
def save ( self , action : UserActionLogEntry ) :
2021-10-04 16:06:20 +02:00
""" Saves the form ' s geometry
Creates a new geometry entry if none is set , yet
Args :
action ( ) :
Returns :
"""
2021-10-05 16:35:24 +02:00
try :
2021-12-15 15:10:35 +01:00
if self . instance is None or self . instance . geometry is None :
raise LookupError
2021-10-04 16:06:20 +02:00
geometry = self . instance . geometry
geometry . geom = self . cleaned_data . get ( " geom " , MultiPolygon ( srid = DEFAULT_SRID ) )
geometry . modified = action
2022-01-06 12:08:38 +01:00
2021-10-04 16:06:20 +02:00
geometry . save ( )
2021-12-15 15:10:35 +01:00
except LookupError :
2021-10-05 16:35:24 +02:00
# No geometry or linked instance holding a geometry exist --> create a new one!
geometry = Geometry . objects . create (
geom = self . cleaned_data . get ( " geom " , MultiPolygon ( srid = DEFAULT_SRID ) ) ,
created = action ,
)
2022-01-06 12:08:38 +01:00
# Start the parcel update procedure in a background process
celery_update_parcels . delay ( geometry . id )
2021-09-23 15:05:17 +02:00
return geometry
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? " )
2021-08-24 14:50:51 +02:00
# Disable automatic w-100 setting for this type of modal form. Looks kinda strange
self . fields [ " confirm " ] . widget . attrs [ " class " ] = " "
2021-07-26 11:29:05 +02:00
def save ( self ) :
2021-08-05 12:54:28 +02:00
if isinstance ( self . instance , BaseObject ) :
2021-09-23 15:05:17 +02:00
self . instance . mark_as_deleted ( self . user )
2021-07-26 11:29:05 +02:00
else :
# If the class does not provide restorable delete functionality, we must delete the entry finally
2022-02-08 13:16:20 +01:00
self . instance . delete ( )
2021-07-26 11:29:05 +02:00
2022-02-08 13:31:40 +01:00
class RemoveDeadlineModalForm ( RemoveModalForm ) :
2022-02-07 09:56:37 +01:00
""" Removing modal form for deadlines
Can be used for anything , where removing shall be confirmed by the user a second time .
"""
deadline = None
def __init__ ( self , * args , * * kwargs ) :
deadline = kwargs . pop ( " deadline " , None )
self . deadline = deadline
super ( ) . __init__ ( * args , * * kwargs )
def save ( self ) :
self . instance . remove_deadline ( self )
2022-02-10 10:21:18 +01:00
class NewDocumentModalForm ( BaseModalForm ) :
2021-07-26 15:16:16 +02:00
""" 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 ,
2021-09-27 13:57:56 +02:00
widget = forms . TextInput (
attrs = {
" class " : " form-control " ,
}
)
2021-07-26 15:16:16 +02:00
)
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 " ,
2021-09-27 13:57:56 +02:00
" class " : " form-control " ,
2021-07-26 15:16:16 +02:00
} ,
format = " %d . % m. % Y "
)
)
file = forms . FileField (
label = _ ( " File " ) ,
label_suffix = _ ( " " ) ,
2021-12-09 12:39:04 +01:00
help_text = _ ( " Allowed formats: pdf, jpg, png. Max size 15 MB. " ) ,
2021-07-26 15:34:30 +02:00
widget = forms . FileInput (
attrs = {
2021-09-27 13:57:56 +02:00
" class " : " form-control-file " ,
2021-07-26 15:34:30 +02:00
}
2021-09-01 16:24:49 +02:00
) ,
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-09-27 13:57:56 +02:00
" class " : " form-control " ,
2021-07-26 15:34:30 +02:00
}
)
2021-07-23 18:27:53 +02:00
)
2021-11-15 17:09:17 +01:00
document_model = None
class Meta :
abstract = True
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
}
2021-11-15 17:09:17 +01:00
if not self . document_model :
2021-09-01 16:24:49 +02:00
raise NotImplementedError ( " Unsupported document type for {} " . format ( self . instance . __class__ ) )
2021-07-26 15:16:16 +02:00
2021-12-09 12:39:04 +01:00
def is_valid ( self ) :
super_valid = super ( ) . is_valid ( )
_file = self . cleaned_data . get ( " file " , None )
2022-02-10 10:21:18 +01:00
if _file is None or isinstance ( _file , FieldFile ) :
# FieldFile declares that no new file has been uploaded and we do not need to check on the file again
return super_valid
2021-12-09 12:39:04 +01:00
mime_type_valid = self . document_model . is_mime_type_valid ( _file )
if not mime_type_valid :
self . add_error (
" file " ,
2022-02-09 16:02:28 +01:00
FILE_TYPE_UNSUPPORTED
2021-12-09 12:39:04 +01:00
)
file_size_valid = self . document_model . is_file_size_valid ( _file )
if not file_size_valid :
self . add_error (
" file " ,
2022-02-09 16:02:28 +01:00
FILE_SIZE_TOO_LARGE
2021-12-09 12:39:04 +01:00
)
file_valid = mime_type_valid and file_size_valid
return super_valid and file_valid
2021-07-26 15:16:16 +02:00
def save ( self ) :
with transaction . atomic ( ) :
2021-11-16 13:15:15 +01:00
action = UserActionLogEntry . get_created_action ( self . user )
edited_action = UserActionLogEntry . get_edited_action ( self . user , _ ( " Added document " ) )
2021-11-15 17:09:17 +01:00
doc = self . document_model . 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-09-01 16:24:49 +02:00
instance = self . instance ,
2021-07-26 15:16:16 +02:00
)
2021-08-05 12:54:28 +02:00
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
2022-02-10 10:21:18 +01:00
class EditDocumentModalForm ( NewDocumentModalForm ) :
document = None
document_model = AbstractDocument
def __init__ ( self , * args , * * kwargs ) :
self . document = kwargs . pop ( " document " , None )
super ( ) . __init__ ( * args , * * kwargs )
form_data = {
" title " : self . document . title ,
" comment " : self . document . comment ,
" creation_date " : str ( self . document . date_of_creation ) ,
" file " : self . document . file ,
}
self . load_initial_data ( form_data )
def save ( self ) :
with transaction . atomic ( ) :
document = self . document
file = self . cleaned_data . get ( " file " , None )
document . title = self . cleaned_data . get ( " title " , None )
document . comment = self . cleaned_data . get ( " comment " , None )
document . date_of_creation = self . cleaned_data . get ( " creation_date " , None )
if not isinstance ( file , FieldFile ) :
document . replace_file ( file )
document . save ( )
self . instance . mark_as_edited ( self . user , self . request , edit_comment = DOCUMENT_EDITED )
return document
2021-08-26 15:45:24 +02:00
class RecordModalForm ( BaseModalForm ) :
2021-08-10 17:19:42 +02:00
""" 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 )
2021-08-26 15:45:24 +02:00
# Disable automatic w-100 setting for this type of modal form. Looks kinda strange
self . fields [ " confirm " ] . widget . attrs [ " class " ] = " "
2021-08-10 17:19:42 +02:00
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 )
2021-11-16 08:29:18 +01:00
if not isinstance ( self . instance , RecordableObjectMixin ) :
2021-08-10 17:19:42 +02:00
raise NotImplementedError
def is_valid ( self ) :
""" Checks for instance ' s validity and data quality
Returns :
"""
2021-11-15 17:09:17 +01:00
from intervention . models import Intervention
2021-08-10 17:19:42 +02:00
super_val = super ( ) . is_valid ( )
2021-10-25 14:36:58 +02:00
if self . instance . recorded :
# If user wants to unrecord an already recorded dataset, we do not need to perform custom checks
return super_val
checker = self . instance . quality_check ( )
for msg in checker . messages :
2021-08-10 17:19:42 +02:00
self . add_error (
" confirm " ,
msg
)
2021-10-26 07:56:26 +02:00
valid = checker . valid
2021-10-26 07:47:53 +02:00
# Special case: Intervention
# Add direct checks for related compensations
if isinstance ( self . instance , Intervention ) :
2021-10-26 07:56:26 +02:00
comps_valid = self . _are_compensations_valid ( )
valid = valid and comps_valid
return super_val and valid
2021-08-10 17:19:42 +02:00
2022-01-07 15:41:40 +01:00
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 (
" confirm " ,
f " { deduction . account . identifier } : { msg } "
)
return checker . valid
return True
2021-10-26 07:47:53 +02:00
def _are_compensations_valid ( self ) :
""" Runs a special case for intervention-compensations validity
Returns :
"""
2022-02-04 15:59:53 +01:00
comps = self . instance . compensations . filter (
deleted = None ,
)
2021-10-26 07:56:26 +02:00
comps_valid = True
2021-10-26 07:47:53 +02:00
for comp in comps :
checker = comp . quality_check ( )
2021-10-26 07:56:26 +02:00
comps_valid = comps_valid and checker . valid
2021-10-26 07:47:53 +02:00
for msg in checker . messages :
self . add_error (
" confirm " ,
f " { comp . identifier } : { msg } "
)
2022-01-07 15:41:40 +01:00
deductions_valid = self . _are_deductions_valid ( )
return comps_valid and deductions_valid
2021-10-26 07:47:53 +02:00
2021-08-10 17:19:42 +02:00
def save ( self ) :
with transaction . atomic ( ) :
if self . cleaned_data [ " confirm " ] :
2021-11-17 12:09:49 +01:00
if self . instance . recorded :
self . instance . set_unrecorded ( self . user )
else :
self . instance . set_recorded ( self . user )
2021-08-10 17:19:42 +02:00
return self . instance