From d03b714fb5280dab2b74adc048847c08ad9a8e4f Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Sun, 19 Oct 2025 12:37:13 +0200 Subject: [PATCH] # NewIntervention view * introduces BaseFormView and BaseNewSpatialLocatedObjectFormView * refactors new intervention view from function to class --- intervention/urls.py | 6 +- intervention/views/intervention.py | 71 +++-------------- konova/forms/base_form.py | 2 + konova/views/base.py | 119 ++++++++++++++++++++++++++++- 4 files changed, 132 insertions(+), 66 deletions(-) diff --git a/intervention/urls.py b/intervention/urls.py index 3a21df5f..4b701dca 100644 --- a/intervention/urls.py +++ b/intervention/urls.py @@ -14,8 +14,8 @@ from intervention.views.deduction import NewInterventionDeductionView, EditInter RemoveInterventionDeductionView from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \ RemoveInterventionDocumentView, EditInterventionDocumentView -from intervention.views.intervention import new_view, edit_view, remove_view, \ - InterventionIndexView, InterventionIdentifierGeneratorView, InterventionDetailView +from intervention.views.intervention import edit_view, remove_view, \ + InterventionIndexView, InterventionIdentifierGeneratorView, InterventionDetailView, NewInterventionFormView from intervention.views.log import InterventionLogView from intervention.views.record import InterventionRecordView from intervention.views.report import InterventionReportView @@ -27,7 +27,7 @@ from intervention.views.share import InterventionShareFormView, InterventionShar app_name = "intervention" urlpatterns = [ path("", InterventionIndexView.as_view(), name="index"), - path('new/', new_view, name='new'), + path('new/', NewInterventionFormView.as_view(), name='new'), path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'), path('', InterventionDetailView.as_view(), name='detail'), path('/log', InterventionLogView.as_view(), name='log'), diff --git a/intervention/views/intervention.py b/intervention/views/intervention.py index e9d297f7..f45d55a0 100644 --- a/intervention/views/intervention.py +++ b/intervention/views/intervention.py @@ -8,7 +8,7 @@ Created on: 19.08.22 from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin -from django.http import JsonResponse, HttpRequest +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 _ @@ -17,16 +17,13 @@ from intervention.forms.intervention import EditInterventionForm, NewInterventio 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, any_group_check, login_required_modal, \ - uuid_required +from konova.decorators import default_group_required, shared_access_required, login_required_modal from konova.forms import SimpleGeomForm from konova.forms.modals import RemoveModalForm -from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP 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, IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, \ - GEOMETRIES_IGNORED_TEMPLATE -from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView + CHECK_STATE_RESET, FORM_INVALID, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE +from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView from konova.views.detail import BaseDetailView @@ -45,60 +42,12 @@ class InterventionIndexView(LoginRequiredMixin, BaseIndexView): return qs -@login_required -@default_group_required -def new_view(request: HttpRequest): - """ - Renders a view for a new intervention creation - - Args: - request (HttpRequest): The incoming request - - Returns: - - """ - template = "intervention/form/view.html" - data_form = NewInterventionForm(request.POST or None) - geom_form = SimpleGeomForm(request.POST or None, read_only=False) - if request.method == "POST": - if data_form.is_valid() and geom_form.is_valid(): - generated_identifier = data_form.cleaned_data.get("identifier", None) - intervention = data_form.save(request.user, geom_form) - if generated_identifier != intervention.identifier: - messages.info( - request, - IDENTIFIER_REPLACED.format( - generated_identifier, - intervention.identifier - ) - ) - messages.success(request, _("Intervention {} added").format(intervention.identifier)) - if geom_form.has_geometry_simplified(): - messages.info( - request, - GEOMETRY_SIMPLIFIED - ) - - num_ignored_geometries = geom_form.get_num_geometries_ignored() - if num_ignored_geometries > 0: - messages.info( - request, - GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries) - ) - - return redirect("intervention:detail", id=intervention.id) - else: - messages.error(request, FORM_INVALID, extra_tags="danger",) - else: - # For clarification: nothing in this case - pass - context = { - "form": data_form, - "geom_form": geom_form, - TAB_TITLE_IDENTIFIER: _("New intervention"), - } - context = BaseContext(request, context).context - return render(request, template, context) +class NewInterventionFormView(BaseNewSpatialLocatedObjectFormView): + _MODEL_CLS = Intervention + _FORM_CLS = NewInterventionForm + _TEMPLATE = "intervention/form/view.html" + _REDIRECT_URL = "intervention:detail" + _TAB_TITLE = _("New intervention") class InterventionIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView): diff --git a/konova/forms/base_form.py b/konova/forms/base_form.py index 98eaa41c..337f1a75 100644 --- a/konova/forms/base_form.py +++ b/konova/forms/base_form.py @@ -25,6 +25,7 @@ class BaseForm(forms.Form): cancel_redirect = None form_caption = None instance = None # The data holding model object + user = None # The performing user request = None form_attrs = {} # Holds additional attributes, that can be used in the template has_required_fields = False # Automatically set. Triggers hint rendering in templates @@ -33,6 +34,7 @@ class BaseForm(forms.Form): def __init__(self, *args, **kwargs): self.instance = kwargs.pop("instance", None) + self.user = kwargs.pop("user", None) super().__init__(*args, **kwargs) if self.request is not None: self.user = self.request.user diff --git a/konova/views/base.py b/konova/views/base.py index 74a0bdd5..545c400b 100644 --- a/konova/views/base.py +++ b/konova/views/base.py @@ -6,15 +6,19 @@ Created on: 15.10.25 from abc import abstractmethod from django.contrib import messages +from django.contrib.auth.mixins import LoginRequiredMixin from django.http import HttpRequest, JsonResponse -from django.shortcuts import render, redirect +from django.shortcuts import render, redirect, get_object_or_404 from django.urls import reverse from django.views import View +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.message_templates import MISSING_GROUP_PERMISSION, DATA_UNSHARED +from konova.utils.message_templates import MISSING_GROUP_PERMISSION, DATA_UNSHARED, IDENTIFIER_REPLACED, \ + GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE class BaseView(View): @@ -139,3 +143,114 @@ class BaseIdentifierGeneratorView(BaseView): def _user_has_shared_access(self, user, **kwargs): # No specific constraints for shared access return True + + +class BaseFormView(BaseView): + _MODEL_CLS = None + _FORM_CLS = None + + class Meta: + abstract = True + + def _get_specific_context_data(self, **kwargs): + return {} + + +class BaseNewSpatialLocatedObjectFormView(LoginRequiredMixin, BaseFormView): + _GEOMETRY_FORM_CLS = SimpleGeomForm + + def _user_has_permission(self, user): + # User has to have default privilege to call this endpoint + return user.is_default_user() + + def _user_has_shared_access(self, user, **kwargs): + # There is no shared access control since nothing exists yet + return True + + def get(self, request: HttpRequest): + form: BaseForm = self._FORM_CLS(None, user=request.user) + geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(None, user=request.user, read_only=False) + + context = self._get_specific_context_data() + context = BaseContext(request, additional_context=context).context + context.update( + { + "form": form, + "geom_form": geom_form, + TAB_TITLE_IDENTIFIER: self._TAB_TITLE, + } + ) + return render(request, self._TEMPLATE, context) + + def post(self, request: HttpRequest): + form: BaseForm = self._FORM_CLS(request.POST or None, user=request.user) + geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(request.POST or None, user=request.user, read_only=False) + + if form.is_valid() and geom_form.is_valid(): + obj = form.save(request.user, geom_form) + self._REDIRECT_URL = reverse(self._REDIRECT_URL, args=(obj.id,)) + + generated_identifier = form.cleaned_data.get("identifier", None) + + if generated_identifier != obj.identifier: + messages.info( + request, + IDENTIFIER_REPLACED.format( + generated_identifier, + obj.identifier + ) + ) + messages.success(request, _("{} added").format(obj.identifier)) + if geom_form.has_geometry_simplified(): + messages.info( + request, + GEOMETRY_SIMPLIFIED + ) + + num_ignored_geometries = geom_form.get_num_geometries_ignored() + if num_ignored_geometries > 0: + messages.info( + request, + GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries) + ) + + return redirect(self._REDIRECT_URL) + else: + context = self._get_specific_context_data() + + context = BaseContext(request, additional_context=context).context + return render(request, self._TEMPLATE, context) + + +class BaseEditSpatialLocatedObjectFormView(LoginRequiredMixin, BaseFormView): + def get(self, request: HttpRequest, id: str): + obj = get_object_or_404( + self._MODEL_CLS, + id=id + ) + form: BaseForm = self._FORM_CLS(None, instance=obj, user=request.user) + context = self._get_specific_context_data() + context = BaseContext(request, additional_context=context).context + context.update( + { + "form": form, + TAB_TITLE_IDENTIFIER: self._TAB_TITLE, + } + ) + return render(request, self._TEMPLATE, context) + + def post(self, request: HttpRequest, id: str): + obj = get_object_or_404( + self._MODEL_CLS, + id=id + ) + form: BaseForm = self._FORM_CLS(request.POST or None, instance=obj, user=request.user) + + if form.is_valid(): + obj = form.save() + context = self._get_specific_context_data(obj=obj) + else: + context = self._get_specific_context_data() + + context = BaseContext(request, additional_context=context).context + return render(request, self._TEMPLATE, context)