From 97fbe0274210d644386c4ecf2d2acebbfecd04f3 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Mon, 20 Oct 2025 16:13:58 +0200 Subject: [PATCH] # BaseModalFormView refactoring * extends BaseModalFormView to hold general logic for processing GET and POST requests for BaseModalForm endpoints * refactors uuid check to use a specific parameter instead of kwargs * fixes css bug where modal form input elements would not be visible * refactors check view for intervention from function to class * refactors DeductionViews to inherit from extended BaseModalFormView --- compensation/views/eco_account/deduction.py | 13 +-- intervention/forms/modals/deduction.py | 18 ++-- intervention/urls.py | 4 +- intervention/views/check.py | 37 +++---- intervention/views/deduction.py | 17 +-- konova/static/css/konova.css | 4 +- konova/utils/general.py | 3 +- konova/views/base.py | 57 +++++++++- konova/views/deduction.py | 110 +++++--------------- konova/views/detail.py | 2 +- 10 files changed, 127 insertions(+), 138 deletions(-) diff --git a/compensation/views/eco_account/deduction.py b/compensation/views/eco_account/deduction.py index 80040a20..01e84f3f 100644 --- a/compensation/views/eco_account/deduction.py +++ b/compensation/views/eco_account/deduction.py @@ -11,10 +11,11 @@ from django.http import Http404 from compensation.models import EcoAccount from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView +_ECO_ACCOUNT_DETAIl_URL_NAME = "compensation:acc:detail" class NewEcoAccountDeductionView(LoginRequiredMixin, AbstractNewDeductionView): - _MODEL = EcoAccount - _REDIRECT_URL = "compensation:acc:detail" + _MODEL_CLS = EcoAccount + _REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME def _custom_check(self, obj): # New deductions can only be created if the eco account has been recorded @@ -23,10 +24,10 @@ class NewEcoAccountDeductionView(LoginRequiredMixin, AbstractNewDeductionView): class EditEcoAccountDeductionView(LoginRequiredMixin, AbstractEditDeductionView): - _MODEL = EcoAccount - _REDIRECT_URL = "compensation:acc:detail" + _MODEL_CLS = EcoAccount + _REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME class RemoveEcoAccountDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView): - _MODEL = EcoAccount - _REDIRECT_URL = "compensation:acc:detail" + _MODEL_CLS = EcoAccount + _REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME diff --git a/intervention/forms/modals/deduction.py b/intervention/forms/modals/deduction.py index 8e12a442..c3fe245d 100644 --- a/intervention/forms/modals/deduction.py +++ b/intervention/forms/modals/deduction.py @@ -172,7 +172,8 @@ class EditEcoAccountDeductionModalForm(NewEcoAccountDeductionModalForm): deduction = None def __init__(self, *args, **kwargs): - self.deduction = kwargs.pop("deduction", None) + deduction_id = kwargs.pop("deduction_id", None) + self.deduction = EcoAccountDeduction.objects.get(id=deduction_id) super().__init__(*args, **kwargs) self.form_title = _("Edit Deduction") form_data = { @@ -252,19 +253,20 @@ class RemoveEcoAccountDeductionModalForm(RemoveModalForm): Can be used for anything, where removing shall be confirmed by the user a second time. """ - deduction = None + _DEDUCTION_OBJ = None def __init__(self, *args, **kwargs): - deduction = kwargs.pop("deduction", None) - self.deduction = deduction + deduction_id = kwargs.pop("deduction_id", None) + deduction = EcoAccountDeduction.objects.get(id=deduction_id) + self._DEDUCTION_OBJ = deduction super().__init__(*args, **kwargs) def save(self): with transaction.atomic(): - self.deduction.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED) - self.deduction.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED) - self.deduction.delete() + self._DEDUCTION_OBJ.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED) + self._DEDUCTION_OBJ.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED) + self._DEDUCTION_OBJ.delete() def check_for_recorded_instance(self): - if self.deduction.intervention.is_recorded: + if self._DEDUCTION_OBJ.intervention.is_recorded: self.block_form() diff --git a/intervention/urls.py b/intervention/urls.py index dae2d47d..e179d000 100644 --- a/intervention/urls.py +++ b/intervention/urls.py @@ -8,7 +8,7 @@ Created on: 30.11.20 from django.urls import path from intervention.autocomplete.intervention import InterventionAutocomplete -from intervention.views.check import check_view +from intervention.views.check import InterventionCheckView from intervention.views.compensation import remove_compensation_view from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \ RemoveInterventionDeductionView @@ -36,7 +36,7 @@ urlpatterns = [ path('/remove', remove_view, name='remove'), path('/share/', InterventionShareByTokenView.as_view(), name='share-token'), path('/share', InterventionShareFormView.as_view(), name='share-form'), - path('/check', check_view, name='check'), + path('/check', InterventionCheckView.as_view(), name='check'), path('/record', InterventionRecordView.as_view(), name='record'), path('/report', InterventionReportView.as_view(), name='report'), path('/resub', InterventionResubmissionView.as_view(), name='resubmission-create'), diff --git a/intervention/views/check.py b/intervention/views/check.py index 1fae75bb..3ed0cdfb 100644 --- a/intervention/views/check.py +++ b/intervention/views/check.py @@ -5,35 +5,26 @@ Contact: ksp-servicestelle@sgdnord.rlp.de Created on: 19.08.22 """ -from django.contrib.auth.decorators import login_required -from django.http import HttpRequest -from django.shortcuts import get_object_or_404 +from django.contrib.auth.mixins import LoginRequiredMixin from django.utils.translation import gettext_lazy as _ from intervention.forms.modals.check import CheckModalForm from intervention.models import Intervention -from konova.decorators import registration_office_group_required, shared_access_required from konova.utils.message_templates import INTERVENTION_INVALID +from konova.views.base import BaseModalFormView -@login_required -@registration_office_group_required -@shared_access_required(Intervention, "id") -def check_view(request: HttpRequest, id: str): - """ Renders check form for an intervention +class InterventionCheckView(LoginRequiredMixin, BaseModalFormView): + _MODEL_CLS = Intervention + _FORM_CLS = CheckModalForm + _MSG_SUCCESS = _("Check performed") + _MSG_ERROR = INTERVENTION_INVALID + _REDIRECT_URL = "intervention:detail" - Args: - request (HttpRequest): The incoming request - id (str): Intervention's id - - Returns: - - """ - intervention = get_object_or_404(Intervention, id=id) - form = CheckModalForm(request.POST or None, instance=intervention, request=request) - return form.process_request( - request, - msg_success=_("Check performed"), - msg_error=INTERVENTION_INVALID - ) + def _user_has_permission(self, user): + return user.is_zb_user() + def _get_redirect_url(self, *args, **kwargs): + redirect_url = super()._get_redirect_url(*args, **kwargs) + redirect_url += "#related_data" + return redirect_url diff --git a/intervention/views/deduction.py b/intervention/views/deduction.py index 122c1dba..8aed81bf 100644 --- a/intervention/views/deduction.py +++ b/intervention/views/deduction.py @@ -8,19 +8,24 @@ Created on: 19.08.22 from django.contrib.auth.mixins import LoginRequiredMixin from intervention.models import Intervention +from konova.utils.message_templates import DEDUCTION_ADDED, DEDUCTION_EDITED, DEDUCTION_REMOVED from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView +_INTERVENTION_DETAIL_URL_NAME = "intervention:detail" class NewInterventionDeductionView(LoginRequiredMixin, AbstractNewDeductionView): - _MODEL = Intervention - _REDIRECT_URL = "intervention:detail" + _MODEL_CLS = Intervention + _MSG_SUCCESS = DEDUCTION_ADDED + _REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME class EditInterventionDeductionView(LoginRequiredMixin, AbstractEditDeductionView): - _MODEL = Intervention - _REDIRECT_URL = "intervention:detail" + _MODEL_CLS = Intervention + _MSG_SUCCESS = DEDUCTION_EDITED + _REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME class RemoveInterventionDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView): - _MODEL = Intervention - _REDIRECT_URL = "intervention:detail" + _MODEL_CLS = Intervention + _MSG_SUCCESS = DEDUCTION_REMOVED + _REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME diff --git a/konova/static/css/konova.css b/konova/static/css/konova.css index 1067d230..8eb5f447 100644 --- a/konova/static/css/konova.css +++ b/konova/static/css/konova.css @@ -290,6 +290,6 @@ Overwrites netgis.css attributes background: var(--rlp-red) !important; } -.modal{ - z-index: 100000; +.netgis-menu{ + z-index: 100 !important; } \ No newline at end of file diff --git a/konova/utils/general.py b/konova/utils/general.py index 666d4811..dd7dc1ea 100644 --- a/konova/utils/general.py +++ b/konova/utils/general.py @@ -41,8 +41,7 @@ def check_user_is_in_any_group(request: HttpRequest): ) return request -def check_id_is_valid_uuid(**kwargs: dict): - uuid = kwargs.get("uuid", None) or kwargs.get("id", None) +def check_id_is_valid_uuid(uuid: str): if uuid: try: # Check whether the id is a proper uuid or something that would break a db fetch diff --git a/konova/views/base.py b/konova/views/base.py index eadcc524..5cc4fefe 100644 --- a/konova/views/base.py +++ b/konova/views/base.py @@ -5,9 +5,10 @@ Created on: 15.10.25 """ from abc import abstractmethod +from bootstrap_modal_forms.mixins import is_ajax from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin -from django.http import HttpRequest, JsonResponse +from django.http import HttpRequest, JsonResponse, HttpResponseRedirect from django.shortcuts import render, redirect, get_object_or_404 from django.urls import reverse from django.views import View @@ -16,9 +17,9 @@ from django.utils.translation import gettext_lazy as _ from konova.contexts import BaseContext from konova.forms import BaseForm, SimpleGeomForm from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER -from konova.utils.general import check_user_is_in_any_group +from konova.utils.general import check_user_is_in_any_group, check_id_is_valid_uuid from konova.utils.message_templates import MISSING_GROUP_PERMISSION, DATA_UNSHARED, IDENTIFIER_REPLACED, \ - GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE, RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID + GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE, RECORDED_BLOCKS_EDIT, FORM_INVALID class BaseView(View): @@ -65,14 +66,64 @@ class BaseView(View): """ return False + def _get_redirect_url(self, *args, **kwargs): + return self._REDIRECT_URL + + def _get_redirect_url_error(self, *args, **kwargs): + return self._REDIRECT_URL_ERROR class BaseModalFormView(BaseView): _TEMPLATE = "modal/modal_form.html" + _MODEL_CLS = None + _FORM_CLS = None _TAB_TITLE = None + _MSG_SUCCESS = None + _MSG_ERROR = None class Meta: abstract = True + def _user_has_shared_access(self, user, **kwargs): + obj = get_object_or_404(self._MODEL_CLS, id=kwargs.get("id")) + return obj.is_shared_with(user) + + def get(self, request: HttpRequest, id: str, *args, **kwargs): + obj = self._MODEL_CLS.objects.get(id=id) + form = self._FORM_CLS(request.POST 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, id: str, *args, **kwargs): + obj = self._MODEL_CLS.objects.get(id=id) + form = self._FORM_CLS(request.POST or None, instance=obj, request=request, **kwargs) + redirect_url = self._get_redirect_url(obj=obj) + if form.is_valid(): + if not is_ajax(request.META): + # 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. + form.save() + messages.success( + request, + self._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): + obj = kwargs.get("obj", None) + assert obj is not None + return reverse(self._REDIRECT_URL, args=(obj.id,)) + class BaseIndexView(BaseView): """ Base class for index views diff --git a/konova/views/deduction.py b/konova/views/deduction.py index 539ab108..b7b9968c 100644 --- a/konova/views/deduction.py +++ b/konova/views/deduction.py @@ -6,20 +6,21 @@ Created on: 22.08.22 """ from django.core.exceptions import ObjectDoesNotExist -from django.http import Http404, HttpRequest -from django.shortcuts import get_object_or_404 from django.urls import reverse from intervention.forms.modals.deduction import NewEcoAccountDeductionModalForm, EditEcoAccountDeductionModalForm, \ RemoveEcoAccountDeductionModalForm -from konova.utils.message_templates import DEDUCTION_ADDED, DEDUCTION_EDITED, DEDUCTION_REMOVED, DEDUCTION_UNKNOWN +from konova.utils.general import check_id_is_valid_uuid from konova.views.base import BaseModalFormView class AbstractDeductionView(BaseModalFormView): - _MODEL = None _REDIRECT_URL = None + def dispatch(self, request, *args, **kwargs): + check_id_is_valid_uuid(kwargs.get("id")) + return super().dispatch(request, *args, **kwargs) + def _custom_check(self, obj): """ Can be used by inheriting classes to provide custom checks before further processing @@ -50,7 +51,7 @@ class AbstractDeductionView(BaseModalFormView): """ ret_val: bool = False try: - obj = self._MODEL.objects.get( + obj = self._MODEL_CLS.objects.get( id=kwargs.get("id") ) ret_val = obj.is_shared_with(user) @@ -58,96 +59,35 @@ class AbstractDeductionView(BaseModalFormView): ret_val = False return ret_val + def _get_redirect_url(self, *args, **kwargs): + obj = kwargs.get("obj", None) + assert obj is not None + return reverse(self._REDIRECT_URL, args=(obj.id,)) + "#related_data" + class AbstractNewDeductionView(AbstractDeductionView): + _FORM_CLS = NewEcoAccountDeductionModalForm + class Meta: abstract = True - def get(self, request, id: str): - """ Renders a modal form view for creating deductions - - Args: - request (HttpRequest): The incoming request - id (str): The obj's id which shall benefit from this deduction - - Returns: - - """ - obj = get_object_or_404(self._MODEL, id=id) - self._custom_check(obj) - form = NewEcoAccountDeductionModalForm(request.POST or None, instance=obj, request=request) - return form.process_request( - request, - msg_success=DEDUCTION_ADDED, - redirect_url=reverse(self._REDIRECT_URL, args=(id,)) + "#related_data", - ) - - def post(self, request, id: str): - return self.get(request, id) - class AbstractEditDeductionView(AbstractDeductionView): + _FORM_CLS = EditEcoAccountDeductionModalForm + + def dispatch(self, request, *args, **kwargs): + check_id_is_valid_uuid(kwargs.get("deduction_id")) + return super().dispatch(request, *args, **kwargs) + class Meta: abstract = True - def get(self, request, id: str, deduction_id: str): - """ Renders a modal view for editing deductions - - Args: - request (HttpRequest): The incoming request - id (str): The object's id - deduction_id (str): The deduction's id - - Returns: - - """ - obj = get_object_or_404(self._MODEL, id=id) - self._custom_check(obj) - try: - eco_deduction = obj.deductions.get(id=deduction_id) - except ObjectDoesNotExist: - raise Http404(DEDUCTION_UNKNOWN) - - form = EditEcoAccountDeductionModalForm(request.POST or None, instance=obj, deduction=eco_deduction, - request=request) - return form.process_request( - request=request, - msg_success=DEDUCTION_EDITED, - redirect_url=reverse(self._REDIRECT_URL, args=(id,)) + "#related_data" - ) - - def post(self, request, id: str, deduction_id: str): - return self.get(request, id, deduction_id) - - class AbstractRemoveDeductionView(AbstractDeductionView): + _FORM_CLS = RemoveEcoAccountDeductionModalForm + + def dispatch(self, request, *args, **kwargs): + check_id_is_valid_uuid(kwargs.get("deduction_id")) + return super().dispatch(request, *args, **kwargs) + class Meta: abstract = True - - def get(self, request, id: str, deduction_id: str): - """ Renders a modal view for removing deductions - - Args: - request (HttpRequest): The incoming request - id (str): The object's id - deduction_id (str): The deduction's id - - Returns: - - """ - obj = get_object_or_404(self._MODEL, id=id) - self._custom_check(obj) - try: - eco_deduction = obj.deductions.get(id=deduction_id) - except ObjectDoesNotExist: - raise Http404(DEDUCTION_UNKNOWN) - form = RemoveEcoAccountDeductionModalForm(request.POST or None, instance=obj, deduction=eco_deduction, - request=request) - return form.process_request( - request=request, - msg_success=DEDUCTION_REMOVED, - redirect_url=reverse(self._REDIRECT_URL, args=(id,)) + "#related_data" - ) - - def post(self, request, id: str, deduction_id: str): - return self.get(request, id, deduction_id) diff --git a/konova/views/detail.py b/konova/views/detail.py index 6322ab9b..72b4ad17 100644 --- a/konova/views/detail.py +++ b/konova/views/detail.py @@ -26,7 +26,7 @@ class BaseDetailView(LoginRequiredMixin, BaseView): abstract = True def dispatch(self, request, *args, **kwargs): - check_id_is_valid_uuid(**kwargs) + check_id_is_valid_uuid(kwargs.get('id')) return super().dispatch(request, *args, **kwargs) def _user_has_shared_access(self, user, **kwargs):