# 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
This commit is contained in:
mpeltriaux 2025-10-20 16:13:58 +02:00
parent ed5d571704
commit 97fbe02742
10 changed files with 127 additions and 138 deletions

View File

@ -11,10 +11,11 @@ from django.http import Http404
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
_ECO_ACCOUNT_DETAIl_URL_NAME = "compensation:acc:detail"
class NewEcoAccountDeductionView(LoginRequiredMixin, AbstractNewDeductionView): class NewEcoAccountDeductionView(LoginRequiredMixin, AbstractNewDeductionView):
_MODEL = EcoAccount _MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:detail" _REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME
def _custom_check(self, obj): def _custom_check(self, obj):
# New deductions can only be created if the eco account has been recorded # 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): class EditEcoAccountDeductionView(LoginRequiredMixin, AbstractEditDeductionView):
_MODEL = EcoAccount _MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:detail" _REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME
class RemoveEcoAccountDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView): class RemoveEcoAccountDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView):
_MODEL = EcoAccount _MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:detail" _REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME

View File

@ -172,7 +172,8 @@ class EditEcoAccountDeductionModalForm(NewEcoAccountDeductionModalForm):
deduction = None deduction = None
def __init__(self, *args, **kwargs): 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) super().__init__(*args, **kwargs)
self.form_title = _("Edit Deduction") self.form_title = _("Edit Deduction")
form_data = { 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. 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): def __init__(self, *args, **kwargs):
deduction = kwargs.pop("deduction", None) deduction_id = kwargs.pop("deduction_id", None)
self.deduction = deduction deduction = EcoAccountDeduction.objects.get(id=deduction_id)
self._DEDUCTION_OBJ = deduction
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):
with transaction.atomic(): with transaction.atomic():
self.deduction.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED) self._DEDUCTION_OBJ.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
self.deduction.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED) self._DEDUCTION_OBJ.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
self.deduction.delete() self._DEDUCTION_OBJ.delete()
def check_for_recorded_instance(self): def check_for_recorded_instance(self):
if self.deduction.intervention.is_recorded: if self._DEDUCTION_OBJ.intervention.is_recorded:
self.block_form() self.block_form()

View File

@ -8,7 +8,7 @@ Created on: 30.11.20
from django.urls import path from django.urls import path
from intervention.autocomplete.intervention import InterventionAutocomplete 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.compensation import remove_compensation_view
from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \ from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \
RemoveInterventionDeductionView RemoveInterventionDeductionView
@ -36,7 +36,7 @@ urlpatterns = [
path('<id>/remove', remove_view, name='remove'), path('<id>/remove', remove_view, name='remove'),
path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'), path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'),
path('<id>/share', InterventionShareFormView.as_view(), name='share-form'), path('<id>/share', InterventionShareFormView.as_view(), name='share-form'),
path('<id>/check', check_view, name='check'), path('<id>/check', InterventionCheckView.as_view(), name='check'),
path('<id>/record', InterventionRecordView.as_view(), name='record'), path('<id>/record', InterventionRecordView.as_view(), name='record'),
path('<id>/report', InterventionReportView.as_view(), name='report'), path('<id>/report', InterventionReportView.as_view(), name='report'),
path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'), path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'),

View File

@ -5,35 +5,26 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from intervention.forms.modals.check import CheckModalForm from intervention.forms.modals.check import CheckModalForm
from intervention.models import Intervention 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.utils.message_templates import INTERVENTION_INVALID
from konova.views.base import BaseModalFormView
@login_required class InterventionCheckView(LoginRequiredMixin, BaseModalFormView):
@registration_office_group_required _MODEL_CLS = Intervention
@shared_access_required(Intervention, "id") _FORM_CLS = CheckModalForm
def check_view(request: HttpRequest, id: str): _MSG_SUCCESS = _("Check performed")
""" Renders check form for an intervention _MSG_ERROR = INTERVENTION_INVALID
_REDIRECT_URL = "intervention:detail"
Args: def _user_has_permission(self, user):
request (HttpRequest): The incoming request return user.is_zb_user()
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 _get_redirect_url(self, *args, **kwargs):
redirect_url = super()._get_redirect_url(*args, **kwargs)
redirect_url += "#related_data"
return redirect_url

View File

@ -8,19 +8,24 @@ Created on: 19.08.22
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from intervention.models import Intervention 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 from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
_INTERVENTION_DETAIL_URL_NAME = "intervention:detail"
class NewInterventionDeductionView(LoginRequiredMixin, AbstractNewDeductionView): class NewInterventionDeductionView(LoginRequiredMixin, AbstractNewDeductionView):
_MODEL = Intervention _MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:detail" _MSG_SUCCESS = DEDUCTION_ADDED
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
class EditInterventionDeductionView(LoginRequiredMixin, AbstractEditDeductionView): class EditInterventionDeductionView(LoginRequiredMixin, AbstractEditDeductionView):
_MODEL = Intervention _MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:detail" _MSG_SUCCESS = DEDUCTION_EDITED
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
class RemoveInterventionDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView): class RemoveInterventionDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView):
_MODEL = Intervention _MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:detail" _MSG_SUCCESS = DEDUCTION_REMOVED
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME

View File

@ -290,6 +290,6 @@ Overwrites netgis.css attributes
background: var(--rlp-red) !important; background: var(--rlp-red) !important;
} }
.modal{ .netgis-menu{
z-index: 100000; z-index: 100 !important;
} }

View File

@ -41,8 +41,7 @@ def check_user_is_in_any_group(request: HttpRequest):
) )
return request return request
def check_id_is_valid_uuid(**kwargs: dict): def check_id_is_valid_uuid(uuid: str):
uuid = kwargs.get("uuid", None) or kwargs.get("id", None)
if uuid: if uuid:
try: try:
# Check whether the id is a proper uuid or something that would break a db fetch # Check whether the id is a proper uuid or something that would break a db fetch

View File

@ -5,9 +5,10 @@ Created on: 15.10.25
""" """
from abc import abstractmethod from abc import abstractmethod
from bootstrap_modal_forms.mixins import is_ajax
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin 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.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse from django.urls import reverse
from django.views import View from django.views import View
@ -16,9 +17,9 @@ from django.utils.translation import gettext_lazy as _
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.forms import BaseForm, SimpleGeomForm from konova.forms import BaseForm, SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER 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, \ 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): class BaseView(View):
@ -65,14 +66,64 @@ class BaseView(View):
""" """
return False 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): class BaseModalFormView(BaseView):
_TEMPLATE = "modal/modal_form.html" _TEMPLATE = "modal/modal_form.html"
_MODEL_CLS = None
_FORM_CLS = None
_TAB_TITLE = None _TAB_TITLE = None
_MSG_SUCCESS = None
_MSG_ERROR = None
class Meta: class Meta:
abstract = True 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): class BaseIndexView(BaseView):
""" Base class for index views """ Base class for index views

View File

@ -6,20 +6,21 @@ Created on: 22.08.22
""" """
from django.core.exceptions import ObjectDoesNotExist 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 django.urls import reverse
from intervention.forms.modals.deduction import NewEcoAccountDeductionModalForm, EditEcoAccountDeductionModalForm, \ from intervention.forms.modals.deduction import NewEcoAccountDeductionModalForm, EditEcoAccountDeductionModalForm, \
RemoveEcoAccountDeductionModalForm 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 from konova.views.base import BaseModalFormView
class AbstractDeductionView(BaseModalFormView): class AbstractDeductionView(BaseModalFormView):
_MODEL = None
_REDIRECT_URL = 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): def _custom_check(self, obj):
""" """
Can be used by inheriting classes to provide custom checks before further processing Can be used by inheriting classes to provide custom checks before further processing
@ -50,7 +51,7 @@ class AbstractDeductionView(BaseModalFormView):
""" """
ret_val: bool = False ret_val: bool = False
try: try:
obj = self._MODEL.objects.get( obj = self._MODEL_CLS.objects.get(
id=kwargs.get("id") id=kwargs.get("id")
) )
ret_val = obj.is_shared_with(user) ret_val = obj.is_shared_with(user)
@ -58,96 +59,35 @@ class AbstractDeductionView(BaseModalFormView):
ret_val = False ret_val = False
return ret_val 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): class AbstractNewDeductionView(AbstractDeductionView):
_FORM_CLS = NewEcoAccountDeductionModalForm
class Meta: class Meta:
abstract = True 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): 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: class Meta:
abstract = True 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): 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: class Meta:
abstract = True 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)

View File

@ -26,7 +26,7 @@ class BaseDetailView(LoginRequiredMixin, BaseView):
abstract = True abstract = True
def dispatch(self, request, *args, **kwargs): 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) return super().dispatch(request, *args, **kwargs)
def _user_has_shared_access(self, user, **kwargs): def _user_has_shared_access(self, user, **kwargs):