konova/konova/forms.py

305 lines
9.4 KiB
Python
Raw Normal View History

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
from bootstrap_modal_forms.forms import BSModalForm
2021-07-01 13:36:07 +02:00
from django import forms
from django.contrib import messages
from django.contrib.auth.models import User
from django.contrib.gis.forms import GeometryField, OSMWidget
from django.contrib.gis.geos import Polygon
from django.db import transaction
from django.http import HttpRequest
from django.shortcuts import redirect, render
from django.utils import timezone
2021-07-01 13:36:07 +02:00
from django.utils.translation import gettext_lazy as _
from konova.contexts import BaseContext
from konova.models import Document
from konova.utils.message_templates import FORM_INVALID
from user.models import UserActionLogEntry, UserAction
2021-07-01 13:36:07 +02:00
class BaseForm(forms.Form):
"""
Basic form for that holds attributes needed in all other forms
"""
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
form_attrs = {} # Holds additional attributes, that can be used in the template
2021-07-01 13:36:07 +02:00
def __init__(self, *args, **kwargs):
self.instance = kwargs.pop("instance", None)
self.user = kwargs.pop("user", None)
2021-07-01 13:36:07 +02:00
super().__init__(*args, **kwargs)
@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
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)
def save(self, user: User):
""" 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():
with transaction.atomic():
self.object_to_remove.is_active = False
action = UserActionLogEntry.objects.create(
user=user,
timestamp=timezone.now(),
action=UserAction.DELETED
)
self.object_to_remove.deleted = action
self.object_to_remove.save()
2021-07-01 13:36:07 +02:00
return self.object_to_remove
class BaseModalForm(BaseForm, BSModalForm):
""" A specialzed form class for modal form handling
"""
is_modal_form = True
render_submit = True
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():
self.save()
messages.success(
request,
msg_success
)
return redirect(redirect_url)
else:
messages.info(
request,
msg_error
)
return redirect(redirect_url)
elif request.method == "GET":
context = {
"form": self,
}
context = BaseContext(request, context).context
return render(request, template, context)
else:
raise NotImplementedError
class SimpleGeomForm(BaseForm):
""" A geometry form for rendering geometry read-only using a widget
"""
geom = GeometryField(
required=False,
disabled=True,
widget=OSMWidget(
attrs={
"map_width": 600,
"map_height": 400,
}
)
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 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
self.initialize_form_field("geom", geom)
self.area = geom.area
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):
self.template = "modal/modal_form.html"
super().__init__(*args, **kwargs)
self.form_title = _("Remove")
self.form_caption = _("Are you sure?")
def save(self):
if hasattr(self.instance, "deleted"):
action = UserActionLogEntry.objects.create(
user=self.user,
timestamp=timezone.now(),
action=UserAction.DELETED,
)
self.instance.deleted = action
self.instance.save()
else:
# If the class does not provide restorable delete functionality, we must delete the entry finally
self.instance.delete()
class NewDocumentForm(BaseModalForm):
""" Modal form for new documents
"""
title = forms.CharField(
label=_("Title"),
label_suffix=_(""),
max_length=500,
)
creation_date = forms.DateField(
label=_("Created on"),
label_suffix=_(""),
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"),
widget=forms.FileInput(
attrs={
"class": "w-75"
}
)
)
comment = forms.CharField(
required=False,
label=_("Comment"),
label_suffix=_(""),
help_text=_("Additional comment on this file"),
widget=forms.Textarea(
attrs={
"cols": 30,
"rows": 5,
}
)
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Add new document")
self.form_caption = _("")
self.template = "modal/modal_form.html"
self.form_attrs = {
"enctype": "multipart/form-data",
}
def save(self):
with transaction.atomic():
action = UserActionLogEntry.objects.create(
user=self.user,
action=UserAction.CREATED,
)
doc = Document.objects.create(
created=action,
title=self.cleaned_data["title"],
comment=self.cleaned_data["comment"],
document=self.cleaned_data["file"],
date_of_creation=self.cleaned_data["creation_date"],
)
self.instance.documents.add(doc)
return doc