konova/konova/views/modal.py
mpeltriaux e49eed21da # Renaming
* renames certain classes to match their content
* splits larger files into smaller ones
2025-12-12 14:13:24 +01:00

184 lines
6.3 KiB
Python

"""
Author: Michel Peltriaux
Created on: 12.12.25
"""
from bootstrap_modal_forms.mixins import is_ajax
from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpRequest, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from konova.contexts import BaseContext
from konova.models import BaseObject
from konova.views.base import AbstractBaseView
class AbstractModalFormView(AbstractBaseView):
""" Abstract base view providing logic to perform most modal form based view renderings
"""
_TEMPLATE: str = "modal/modal_form.html"
_MODEL_CLS = None
_FORM_CLS = None
_MSG_SUCCESS = None
class Meta:
abstract = True
def _user_has_shared_access(self, user, **kwargs):
""" Checks whether the user has shared access to this object.
For objects inheriting from BaseObject class the method 'is_shared_with()' is a handy
wrapper for checking shared access. For any other circumstances this method should be overwritten
to provide custom shared-access-checking logic.
If no shared-access-check is needed, this method can be overwritten with a simple True returning.
Args:
user (User): The performing user
**kwargs ():
Returns:
has_shared_access (bool): Whether the user has shared access
"""
obj = get_object_or_404(self._MODEL_CLS, id=kwargs.get("id"))
return obj.is_shared_with(user)
def get(self, request: HttpRequest, *args, **kwargs):
""" GET endpoint for rendering a view holding a modal form
Args:
request (HttpRequest): The incoming request
*args ():
**kwargs ():
Returns:
"""
# If there is an id provided as mapped parameter from the URL -> take it ...
_id = kwargs.pop("id", None)
try:
# ... and try to resolve it into an object
obj = self._MODEL_CLS.objects.get(id=_id)
self._check_for_recorded_instance(obj)
except ObjectDoesNotExist:
# ... If there is none, maybe we are currently processing
# the creation of a new object (therefore no id yet), so let's continue
obj = None
form = self._FORM_CLS(
request.POST or None,
request.FILES or None,
instance=obj,
request=request,
**kwargs
)
context = {
"form": form,
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
def post(self, request: HttpRequest, *args, **kwargs):
""" POST endpoint for processing form contents of a view
Args:
request (HttpRequest): The incoming request
*args ():
**kwargs ():
Returns:
"""
# If there is an id provided as mapped parameter from the URL take it ...
_id = kwargs.pop("id", None)
try:
# ... and try to resolve it into a record
obj = self._MODEL_CLS.objects.get(id=_id)
self._check_for_recorded_instance(obj)
except ObjectDoesNotExist:
# ... If there is none, maybe we are currently processing
# the creation of a new object (therefore no id yet), so let's continue
obj = None
form = self._FORM_CLS(
request.POST or None,
request.FILES or None,
instance=obj,
request=request,
**kwargs
)
# Get now the redirect url and take specifics of the obj into account for that.
# We do not do this after saving the form to avoid side effects due to possibly changed data
redirect_url = self._get_redirect_url(obj=obj)
if form.is_valid():
# Modal forms send one POST for checking on data validity. This is used to evaluate possible errors
# on the form. The second POST (if no errors have been found) is the 'proper' one,
# which we want to process by saving/commiting of the data to the database.
if not is_ajax(request.META):
# Get now the success message and take specifics of the obj into account for that
msg_success = self._get_msg_success(obj=obj, *args, **kwargs)
form.save()
messages.success(
request,
msg_success
)
return HttpResponseRedirect(redirect_url)
else:
context = {
"form": form,
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
def _get_redirect_url(self, *args, **kwargs):
""" Getter to construct a more specific, data dependant redirect URL (if needed)
Args:
*args ():
**kwargs ():
Returns:
url (str): Reversed redirect url
"""
obj = kwargs.get("obj", None)
if obj:
return reverse(self._REDIRECT_URL, args=(obj.id,))
else:
return reverse(self._REDIRECT_URL)
def _get_msg_success(self, *args, **kwargs):
""" Getter to construct a more specific, data dependant success message
Args:
*args ():
**kwargs ():
Returns:
"""
return self._MSG_SUCCESS
def _check_for_recorded_instance(self, obj):
""" Checks if the object on this view is recorded and runs some special logic if so
If the instance is recorded, the view should provide some information about why the user can not edit anything.
This behaviour is only intended to mask any form for instances based on the BaseObject class.
There are situations where the form should be rendered regularly, despite the instance being recorded,
e.g. for rendering deduction form contents on (recorded) eco accounts.
Returns:
"""
is_none = obj is None
is_other_data_type = not isinstance(obj, BaseObject)
if is_none or is_other_data_type:
# Do nothing
return
if obj.is_recorded:
# Replace default template with a blocking one
self._TEMPLATE = "form/recorded_no_edit.html"