# Remove View refactoring

* refactors remove view for kom, eiv, oek and ema
* introduces BaseRemoveModalFormView
* moves html blocking logic from BaseModalForm into BaseModalFormView
This commit is contained in:
mpeltriaux 2025-10-21 20:28:43 +02:00
parent fe2ac3d97d
commit 837c1de938
17 changed files with 117 additions and 157 deletions

View File

@ -106,7 +106,7 @@ class NewCompensationStateModalForm(BaseModalForm):
"""
redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
template = self.template
template = self._TEMPLATE
if request.method == "POST":
if self.is_valid():
# Modal forms send one POST for checking on data validity. This can be used to return possible errors

View File

@ -18,8 +18,8 @@ from compensation.views.compensation.action import NewCompensationActionView, Ed
from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \
RemoveCompensationStateView
from compensation.views.compensation.compensation import \
remove_view, CompensationIndexView, CompensationIdentifierGeneratorView, CompensationDetailView, \
NewCompensationFormView, EditCompensationFormView
CompensationIndexView, CompensationIdentifierGeneratorView, CompensationDetailView, \
NewCompensationFormView, EditCompensationFormView, RemoveCompensationView
from compensation.views.compensation.log import CompensationLogView
urlpatterns = [
@ -31,7 +31,7 @@ urlpatterns = [
path('<id>', CompensationDetailView.as_view(), name='detail'),
path('<id>/log', CompensationLogView.as_view(), name='log'),
path('<id>/edit', EditCompensationFormView.as_view(), name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/remove', RemoveCompensationView.as_view(), name='remove'),
path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'),
path('<id>/state/<state_id>/edit', EditCompensationStateView.as_view(), name='state-edit'),

View File

@ -8,9 +8,8 @@ Created on: 24.08.21
from django.urls import path
from compensation.autocomplete.eco_account import EcoAccountAutocomplete
from compensation.views.eco_account.eco_account import remove_view, \
EcoAccountIndexView, EcoAccountIdentifierGeneratorView, EcoAccountDetailView, NewEcoAccountFormView, \
EditEcoAccountFormView
from compensation.views.eco_account.eco_account import EcoAccountIndexView, EcoAccountIdentifierGeneratorView, \
EcoAccountDetailView, NewEcoAccountFormView, EditEcoAccountFormView, RemoveEcoAccountView
from compensation.views.eco_account.log import EcoAccountLogView
from compensation.views.eco_account.record import EcoAccountRecordView
from compensation.views.eco_account.report import EcoAccountReportView
@ -37,7 +36,7 @@ urlpatterns = [
path('<id>/record', EcoAccountRecordView.as_view(), name='record'),
path('<id>/report', EcoAccountReportView.as_view(), name='report'),
path('<id>/edit', EditEcoAccountFormView.as_view(), name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/remove', RemoveEcoAccountView.as_view(), name='remove'),
path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'),
path('<id>/state/new', NewEcoAccountStateView.as_view(), name='new-state'),

View File

@ -25,6 +25,7 @@ from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE, DATA_C
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class CompensationIndexView(LoginRequiredMixin, BaseIndexView):
@ -164,25 +165,10 @@ class CompensationDetailView(BaseDetailView):
return context
@login_required_modal
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
form = RemoveModalForm(request.POST or None, instance=comp, request=request)
return form.process_request(
request=request,
msg_success=COMPENSATION_REMOVED_TEMPLATE.format(comp.identifier),
redirect_url=reverse("compensation:index"),
)
class RemoveCompensationView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = Compensation
_FORM_CLS = RemoveModalForm
_REDIRECT_URL = "compensation:index"
def _user_has_permission(self, user):
return user.is_default_user()

View File

@ -22,6 +22,10 @@ class NewEcoAccountDeductionView(LoginRequiredMixin, AbstractNewDeductionView):
if not obj.recorded:
raise Http404()
def _check_for_recorded_instance(self, obj):
# Deductions can be created on recorded as well as on non-recorded entries
return None
class EditEcoAccountDeductionView(LoginRequiredMixin, AbstractEditDeductionView):
_MODEL_CLS = EcoAccount

View File

@ -26,6 +26,7 @@ from konova.utils.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECO
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class EcoAccountIndexView(LoginRequiredMixin, BaseIndexView):
@ -254,34 +255,10 @@ class EcoAccountDetailView(BaseDetailView):
return context
@login_required_modal
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the eco account
Args:
request (HttpRequest): The incoming request
id (str): The account's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
# If the eco account has already been recorded OR there are already deductions, it can not be deleted by a regular
# default group user
if acc.recorded is not None or acc.deductions.exists():
user = request.user
if not user.in_group(ETS_GROUP):
messages.info(request, CANCEL_ACC_RECORDED_OR_DEDUCTED)
return redirect("compensation:acc:detail", id=id)
form = RemoveEcoAccountModalForm(request.POST or None, instance=acc, request=request)
return form.process_request(
request=request,
msg_success=_("Eco-account removed"),
redirect_url=reverse("compensation:acc:index"),
)
class RemoveEcoAccountView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = EcoAccount
_FORM_CLS = RemoveEcoAccountModalForm
_REDIRECT_URL = "compensation:acc:index"
def _user_has_permission(self, user):
return user.is_default_user()

View File

@ -10,8 +10,8 @@ from django.urls import path
from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActionView
from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView
from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView
from ema.views.ema import remove_view, EmaIndexView, \
EmaIdentifierGeneratorView, EmaDetailView, EditEmaFormView, NewEmaFormView
from ema.views.ema import EmaIndexView, EmaIdentifierGeneratorView, EmaDetailView, EditEmaFormView, NewEmaFormView, \
RemoveEmaView
from ema.views.log import EmaLogView
from ema.views.record import EmaRecordView
from ema.views.report import EmaReportView
@ -27,7 +27,7 @@ urlpatterns = [
path("<id>", EmaDetailView.as_view(), name="detail"),
path('<id>/log', EmaLogView.as_view(), name='log'),
path('<id>/edit', EditEmaFormView.as_view(), name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/remove', RemoveEmaView.as_view(), name='remove'),
path('<id>/record', EmaRecordView.as_view(), name='record'),
path('<id>/report', EmaReportView.as_view(), name='report'),
path('<id>/resub', EmaResubmissionView.as_view(), name='resubmission-create'),

View File

@ -5,21 +5,17 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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.urls import reverse
from django.utils.translation import gettext_lazy as _
from ema.forms import NewEmaForm, EditEmaForm
from ema.models import Ema
from ema.tables import EmaTable
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.forms.modals import RemoveModalForm
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class EmaIndexView(LoginRequiredMixin, BaseIndexView):
@ -112,26 +108,9 @@ class EmaDetailView(BaseDetailView):
}
return context
class RemoveEmaView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = Ema
_REDIRECT_URL = "ema:index"
@login_required_modal
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = RemoveModalForm(request.POST or None, instance=ema, request=request)
return form.process_request(
request=request,
msg_success=_("EMA removed"),
redirect_url=reverse("ema:index"),
)
def _user_has_permission(self, user):
return user.is_ets_user()

View File

@ -14,9 +14,8 @@ from intervention.views.deduction import NewInterventionDeductionView, EditInter
RemoveInterventionDeductionView
from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \
RemoveInterventionDocumentView, EditInterventionDocumentView
from intervention.views.intervention import remove_view, \
InterventionIndexView, InterventionIdentifierGeneratorView, InterventionDetailView, NewInterventionFormView, \
EditInterventionFormView
from intervention.views.intervention import InterventionIndexView, InterventionIdentifierGeneratorView, \
InterventionDetailView, NewInterventionFormView, EditInterventionFormView, RemoveInterventionView
from intervention.views.log import InterventionLogView
from intervention.views.record import InterventionRecordView
from intervention.views.report import InterventionReportView
@ -33,7 +32,7 @@ urlpatterns = [
path('<id>', InterventionDetailView.as_view(), name='detail'),
path('<id>/log', InterventionLogView.as_view(), name='log'),
path('<id>/edit', EditInterventionFormView.as_view(), name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/remove', RemoveInterventionView.as_view(), name='remove'),
path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'),
path('<id>/share', InterventionShareFormView.as_view(), name='share-form'),
path('<id>/check', InterventionCheckView.as_view(), name='check'),

View File

@ -10,22 +10,21 @@ 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, render, redirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from intervention.forms.intervention import EditInterventionForm, NewInterventionForm
from intervention.models import Intervention
from intervention.tables import InterventionTable
from konova.contexts import BaseContext
from konova.decorators import default_group_required, shared_access_required, login_required_modal
from konova.decorators import default_group_required, shared_access_required
from konova.forms import SimpleGeomForm
from konova.forms.modals import RemoveModalForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, RECORDED_BLOCKS_EDIT, \
CHECK_STATE_RESET, FORM_INVALID, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class InterventionIndexView(LoginRequiredMixin, BaseIndexView):
@ -181,26 +180,6 @@ def edit_view(request: HttpRequest, id: str):
context = BaseContext(request, context).context
return render(request, template, context)
@login_required_modal
@login_required
@default_group_required
@shared_access_required(Intervention, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a remove view for this intervention
Args:
request (HttpRequest): The incoming request
id (str): The uuid id as string
Returns:
"""
obj = Intervention.objects.get(id=id)
identifier = obj.identifier
form = RemoveModalForm(request.POST or None, instance=obj, request=request)
return form.process_request(
request,
_("{} removed").format(identifier),
redirect_url=reverse("intervention:index")
)
class RemoveInterventionView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:index"

View File

@ -10,14 +10,11 @@ from abc import abstractmethod
from django import forms
from django.utils.translation import gettext_lazy as _
from konova.models import BaseObject
class BaseForm(forms.Form):
"""
Basic form for that holds attributes needed in all other forms
"""
template = None
action_url = None
action_btn_label = _("Save")
form_title = None
@ -43,7 +40,6 @@ class BaseForm(forms.Form):
self.has_required_fields = True
break
self.check_for_recorded_instance()
self.__check_valid_label_input_ratio()
@abstractmethod
@ -137,34 +133,3 @@ class BaseForm(forms.Form):
set_class = self.fields[field].widget.attrs.get("class", "")
set_class = set_class.replace(cls, "")
self.fields[field].widget.attrs["class"] = set_class
def check_for_recorded_instance(self):
""" Checks if the instance is recorded and runs some special logic if yes
If the instance is recorded, the form shall not display any possibility to
edit any data. Instead, the users should get some information about why they can not edit anything.
There are situations where the form should be rendered regularly,
e.g deduction forms for (recorded) eco accounts.
Returns:
"""
is_none = self.instance is None
is_other_data_type = not isinstance(self.instance, BaseObject)
if is_none or is_other_data_type:
# Do nothing
return
if self.instance.is_recorded:
self.block_form()
def block_form(self):
"""
Overwrites template, providing no actions
Returns:
"""
self.template = "form/recorded_no_edit.html"

View File

@ -23,7 +23,7 @@ class BaseModalForm(BaseForm, BSModalForm):
"""
is_modal_form = True
render_submit = True
template = "modal/modal_form.html"
_TEMPLATE = "modal/modal_form.html"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -43,7 +43,7 @@ class BaseModalForm(BaseForm, BSModalForm):
"""
redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
template = self.template
template = self._TEMPLATE
if request.method == "POST":
if self.is_valid():
if not is_ajax(request.META):

View File

@ -20,6 +20,9 @@ ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office us
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
CHECK_STATE_RESET = _("Status of Checked reset")
# REMOVED
GENERIC_REMOVED_TEMPLATE = _("{} removed")
# RECORDING
RECORDED_BLOCKS_EDIT = _("Entry is recorded. To edit data, the entry first needs to be unrecorded.")
ENTRY_RECORDED = _("{} recorded")

View File

@ -16,8 +16,9 @@ from django.utils.translation import gettext_lazy as _
from konova.contexts import BaseContext
from konova.forms import BaseForm, SimpleGeomForm
from konova.models import BaseObject
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.general import check_user_is_in_any_group, check_id_is_valid_uuid
from konova.utils.general import check_user_is_in_any_group
from konova.utils.message_templates import MISSING_GROUP_PERMISSION, DATA_UNSHARED, IDENTIFIER_REPLACED, \
GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE, RECORDED_BLOCKS_EDIT, FORM_INVALID
@ -89,6 +90,7 @@ class BaseModalFormView(BaseView):
def get(self, request: HttpRequest, id: str, *args, **kwargs):
obj = self._MODEL_CLS.objects.get(id=id)
self._check_for_recorded_instance(obj)
form = self._FORM_CLS(
request.POST or None,
request.FILES or None,
@ -104,6 +106,7 @@ class BaseModalFormView(BaseView):
def post(self, request: HttpRequest, id: str, *args, **kwargs):
obj = self._MODEL_CLS.objects.get(id=id)
self._check_for_recorded_instance(obj)
form = self._FORM_CLS(
request.POST or None,
request.FILES or None,
@ -140,6 +143,36 @@ class BaseModalFormView(BaseView):
def _get_msg_success(self, *args, **kwargs):
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 yes
If the instance is recorded, the view should provide some information about why the user can not edit anything.
There are situations where the form should be rendered regularly,
e.g deduction forms for (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:
self._block_form()
def _block_form(self):
"""
Overwrites template, providing no actions
Returns:
"""
self._TEMPLATE = "form/recorded_no_edit.html"
class BaseIndexView(BaseView):
""" Base class for index views

View File

@ -26,3 +26,7 @@ class AbstractRecordView(BaseModalFormView):
return ENTRY_RECORDED.format(obj.identifier)
else:
return ENTRY_UNRECORDED.format(obj.identifier)
def _check_for_recorded_instance(self, obj):
# Do not block record view if instance might be recorded
return None

28
konova/views/remove.py Normal file
View File

@ -0,0 +1,28 @@
"""
Author: Michel Peltriaux
Created on: 21.10.25
"""
from django.urls import reverse
from konova.forms.modals import RemoveModalForm
from konova.utils.message_templates import GENERIC_REMOVED_TEMPLATE
from konova.views.base import BaseModalFormView
class BaseRemoveModalFormView(BaseModalFormView):
_MODEL_CLS = None
_FORM_CLS = RemoveModalForm
_MSG_SUCCESS = GENERIC_REMOVED_TEMPLATE
_REDIRECT_URL = None
def _user_has_permission(self, user):
return user.is_default_user()
def _get_redirect_url(self, *args, **kwargs):
return reverse(self._REDIRECT_URL)
def _get_msg_success(self, *args, **kwargs):
obj = kwargs.get("obj", None)
assert obj is not None
return self._MSG_SUCCESS.format(obj.identifier)

View File

@ -22,3 +22,7 @@ class AbstractResubmissionView(LoginRequiredMixin, BaseModalFormView):
def _user_has_permission(self, user):
return user.is_default_user()
def _check_for_recorded_instance(self, obj):
# Resubmissions are allowed despite an entry being recorded
return None