From 0b84d418dbf7d12e6d5276b51cba98342607016a Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Mon, 15 Dec 2025 13:02:11 +0100 Subject: [PATCH] # (EMA/EIV) Edit and New view * refactors 'new' view methods into classes for eiv and ema * refactors 'edit' view methods into classes for eiv and ema * reorganizes permissions on non-conservation-office users on ema entries * users can now open the log view properly if they have shared access * ema actions that require conservation office permission are now hidden on the frontend for non-conservation-office users --- .../ema/detail/includes/controls.html | 28 +-- ema/tests/test_views.py | 2 +- ema/urls.py | 6 +- ema/views/ema.py | 186 ++++++++++++------ ema/views/log.py | 1 - intervention/urls.py | 8 +- intervention/views/intervention.py | 180 +++++++++++------ 7 files changed, 261 insertions(+), 150 deletions(-) diff --git a/ema/templates/ema/detail/includes/controls.html b/ema/templates/ema/detail/includes/controls.html index 96c7bbb3..672cdb22 100644 --- a/ema/templates/ema/detail/includes/controls.html +++ b/ema/templates/ema/detail/includes/controls.html @@ -15,10 +15,10 @@ - {% if is_ets_member %} + {% if obj.recorded %} {% endif %} + + + {% endif %} {% if is_default_member %} - - + {% endif %} + {% if is_ets_member %} + - - - {% endif %} {% endif %} \ No newline at end of file diff --git a/ema/tests/test_views.py b/ema/tests/test_views.py index 627753ac..e58ff42d 100644 --- a/ema/tests/test_views.py +++ b/ema/tests/test_views.py @@ -118,6 +118,7 @@ class EmaViewTestCase(CompensationViewTestCase): self.index_url, self.detail_url, self.report_url, + self.log_url, ] fail_urls = [ self.new_url, @@ -133,7 +134,6 @@ class EmaViewTestCase(CompensationViewTestCase): self.action_remove_url, self.action_new_url, self.new_doc_url, - self.log_url, self.remove_url, ] self.assert_url_fail(client, fail_urls) diff --git a/ema/urls.py b/ema/urls.py index d3a928d9..a36cbe5c 100644 --- a/ema/urls.py +++ b/ema/urls.py @@ -11,7 +11,7 @@ from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActio from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView from ema.views.detail import DetailEmaView from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView -from ema.views.ema import new_view, edit_view, IndexEmaView, EmaIdentifierGeneratorView +from ema.views.ema import IndexEmaView, EmaIdentifierGeneratorView, EditEmaView, NewEmaView from ema.views.log import EmaLogView from ema.views.record import EmaRecordView from ema.views.remove import RemoveEmaView @@ -23,11 +23,11 @@ from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateVie app_name = "ema" urlpatterns = [ path("", IndexEmaView.as_view(), name="index"), - path("new/", new_view, name="new"), + path("new/", NewEmaView.as_view(), name="new"), path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"), path("", DetailEmaView.as_view(), name="detail"), path('/log', EmaLogView.as_view(), name='log'), - path('/edit', edit_view, name='edit'), + path('/edit', EditEmaView.as_view(), name='edit'), path('/remove', RemoveEmaView.as_view(), name='remove'), path('/record', EmaRecordView.as_view(), name='record'), path('/report', EmaPublicReportView.as_view(), name='report'), diff --git a/ema/views/ema.py b/ema/views/ema.py index 0d949727..c9ee4889 100644 --- a/ema/views/ema.py +++ b/ema/views/ema.py @@ -7,19 +7,19 @@ Created on: 19.08.22 """ from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.http import HttpRequest, JsonResponse, HttpResponse +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, redirect, render -from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ +from django.views.generic.base import View from ema.forms import NewEmaForm, EditEmaForm from ema.models import Ema from ema.tables import EmaTable from konova.contexts import BaseContext -from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal +from konova.decorators import shared_access_required, conservation_office_group_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 RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \ GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE @@ -54,23 +54,49 @@ class IndexEmaView(AbstractIndexView): context = BaseContext(request, context).context return render(request, self._TEMPLATE, context) +class NewEmaView(LoginRequiredMixin, View): + _TEMPLATE = "ema/form/view.html" -@login_required -@conservation_office_group_required -def new_view(request: HttpRequest): - """ - Renders a view for a new eco account creation + @method_decorator(conservation_office_group_required) + def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: + """ GET endpoint - Args: - request (HttpRequest): The incoming request + Renders form for new EMA - Returns: + Args: + request (HttpRequest): The incoming request + *args (): + **kwargs (): - """ - template = "ema/form/view.html" - data_form = NewEmaForm(request.POST or None) - geom_form = SimpleGeomForm(request.POST or None, read_only=False) - if request.method == "POST": + Returns: + + """ + data_form = NewEmaForm(request.POST or None) + geom_form = SimpleGeomForm(request.POST or None, read_only=False) + context = { + "form": data_form, + "geom_form": geom_form, + TAB_TITLE_IDENTIFIER: _("New EMA"), + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) + + @method_decorator(conservation_office_group_required) + def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: + """ POST endpoint + + Processes submitted form + + Args: + request (HttpRequest): The incoming request + *args (): + **kwargs (): + + Returns: + + """ + data_form = NewEmaForm(request.POST or None) + geom_form = SimpleGeomForm(request.POST or None, read_only=False) if data_form.is_valid() and geom_form.is_valid(): generated_identifier = data_form.cleaned_data.get("identifier", None) ema = data_form.save(request.user, geom_form) @@ -94,21 +120,16 @@ def new_view(request: HttpRequest): request, GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries) ) - return redirect("ema:detail", id=ema.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 EMA"), - } - context = BaseContext(request, context).context - return render(request, template, context) - + context = { + "form": data_form, + "geom_form": geom_form, + TAB_TITLE_IDENTIFIER: _("New EMA"), + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) class EmaIdentifierGeneratorView(AbstractIdentifierGeneratorView): _MODEL = Ema @@ -117,33 +138,73 @@ class EmaIdentifierGeneratorView(AbstractIdentifierGeneratorView): def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: return super().get(request, *args, **kwargs) -@login_required -@conservation_office_group_required -@shared_access_required(Ema, "id") -def edit_view(request: HttpRequest, id: str): - """ - Renders a view for editing compensations +class EditEmaView(LoginRequiredMixin, View): + _TEMPLATE = "compensation/form/view.html" - Args: - request (HttpRequest): The incoming request + @method_decorator(conservation_office_group_required) + @method_decorator(shared_access_required(Ema, "id")) + def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: + """ GET endpoint - Returns: + Renders form - """ - template = "compensation/form/view.html" - # Get object from db - ema = get_object_or_404(Ema, id=id) - if ema.is_recorded: - messages.info( - request, - RECORDED_BLOCKS_EDIT - ) - return redirect("ema:detail", id=id) + Args: + request (HttpRequest): The incoming request + id (str): The ema identifier + *args (): + **kwargs (): - # Create forms, initialize with values from db/from POST request - data_form = EditEmaForm(request.POST or None, instance=ema) - geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema) - if request.method == "POST": + Returns: + + """ + # Get object from db + ema = get_object_or_404(Ema, id=id) + if ema.is_recorded: + messages.info( + request, + RECORDED_BLOCKS_EDIT + ) + return redirect("ema:detail", id=id) + + # Create forms, initialize with values from db/from POST request + data_form = EditEmaForm(instance=ema) + geom_form = SimpleGeomForm(read_only=False, instance=ema) + context = { + "form": data_form, + "geom_form": geom_form, + TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier), + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) + + @method_decorator(conservation_office_group_required) + @method_decorator(shared_access_required(Ema, "id")) + def post(self, request: HttpRequest, id:str, *args, **kwargs) -> HttpResponse: + """ POST endpoint + + Process submitted forms + + Args: + request (HttpRequest): The incoming request + id (str): The id of the ema + *args (): + **kwargs (): + + Returns: + + """ + # Get object from db + ema = get_object_or_404(Ema, id=id) + if ema.is_recorded: + messages.info( + request, + RECORDED_BLOCKS_EDIT + ) + return redirect("ema:detail", id=id) + + # Create forms, initialize with values from db/from POST request + data_form = EditEmaForm(request.POST or None, instance=ema) + geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema) if data_form.is_valid() and geom_form.is_valid(): # The data form takes the geom form for processing, as well as the performing user ema = data_form.save(request.user, geom_form) @@ -153,24 +214,19 @@ def edit_view(request: HttpRequest, id: str): 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("ema:detail", id=ema.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: _("Edit {}").format(ema.identifier), - } - context = BaseContext(request, context).context - return render(request, template, context) + messages.error(request, FORM_INVALID, extra_tags="danger", ) + context = { + "form": data_form, + "geom_form": geom_form, + TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier), + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) diff --git a/ema/views/log.py b/ema/views/log.py index 82162ba4..b468cc15 100644 --- a/ema/views/log.py +++ b/ema/views/log.py @@ -18,7 +18,6 @@ class EmaLogView(AbstractLogView): @method_decorator(login_required_modal) @method_decorator(login_required) - @method_decorator(conservation_office_group_required) @method_decorator(shared_access_required(Ema, "id")) def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) diff --git a/intervention/urls.py b/intervention/urls.py index 6a3855b5..6676f777 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, \ - IndexInterventionView, InterventionIdentifierGeneratorView +from intervention.views.intervention import IndexInterventionView, InterventionIdentifierGeneratorView, \ + NewInterventionView, EditInterventionView from intervention.views.remove import RemoveInterventionView from intervention.views.detail import DetailInterventionView from intervention.views.log import InterventionLogView @@ -29,11 +29,11 @@ from intervention.views.share import InterventionShareFormView, InterventionShar app_name = "intervention" urlpatterns = [ path("", IndexInterventionView.as_view(), name="index"), - path('new/', new_view, name='new'), + path('new/', NewInterventionView.as_view(), name='new'), path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'), path('', DetailInterventionView.as_view(), name='detail'), path('/log', InterventionLogView.as_view(), name='log'), - path('/edit', edit_view, name='edit'), + path('/edit', EditInterventionView.as_view(), name='edit'), path('/remove', RemoveInterventionView.as_view(), name='remove'), path('/share/', InterventionShareByTokenView.as_view(), name='share-token'), path('/share', InterventionShareFormView.as_view(), name='share-form'), diff --git a/intervention/views/intervention.py b/intervention/views/intervention.py index 7bc3fabb..f8808914 100644 --- a/intervention/views/intervention.py +++ b/intervention/views/intervention.py @@ -7,9 +7,12 @@ 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 HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, render, redirect +from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ +from django.views import View from intervention.forms.intervention import EditInterventionForm, NewInterventionForm from intervention.models import Intervention @@ -56,22 +59,46 @@ class IndexInterventionView(AbstractIndexView): return render(request, self._TEMPLATE, context) -@login_required -@default_group_required -def new_view(request: HttpRequest): - """ - Renders a view for a new intervention creation +class NewInterventionView(LoginRequiredMixin, View): + _TEMPLATE = "intervention/form/view.html" - Args: - request (HttpRequest): The incoming request + @method_decorator(default_group_required) + def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: - Returns: + """ + Renders a view for a new intervention creation + + Args: + request (HttpRequest): The incoming request + + Returns: + + """ + data_form = NewInterventionForm() + geom_form = SimpleGeomForm(read_only=False) + context = { + "form": data_form, + "geom_form": geom_form, + TAB_TITLE_IDENTIFIER: _("New intervention"), + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) + + @method_decorator(default_group_required) + def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: + + """ + Renders a view for a new intervention creation + + Args: + request (HttpRequest): The incoming request + + Returns: + + """ + data_form = NewInterventionForm(request.POST or None) + geom_form = SimpleGeomForm(request.POST or None, read_only=False) - """ - 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) @@ -83,6 +110,7 @@ def new_view(request: HttpRequest): intervention.identifier ) ) + messages.success(request, _("Intervention {} added").format(intervention.identifier)) if geom_form.has_geometry_simplified(): messages.info( @@ -96,52 +124,86 @@ def new_view(request: HttpRequest): 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) + messages.error(request, FORM_INVALID, extra_tags="danger", ) + context = { + "form": data_form, + "geom_form": geom_form, + TAB_TITLE_IDENTIFIER: _("New intervention"), + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) + class InterventionIdentifierGeneratorView(AbstractIdentifierGeneratorView): _MODEL = Intervention -@login_required -@default_group_required -@shared_access_required(Intervention, "id") -def edit_view(request: HttpRequest, id: str): - """ - Renders a view for editing interventions +class EditInterventionView(LoginRequiredMixin, View): + _TEMPLATE = "intervention/form/view.html" - Args: - request (HttpRequest): The incoming request + @method_decorator(default_group_required) + @method_decorator(shared_access_required(Intervention, "id")) + def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: + """ + Renders a view for editing interventions - Returns: + Args: + request (HttpRequest): The incoming request + id (str): The intervention identifier - """ - template = "intervention/form/view.html" - # Get object from db - intervention = get_object_or_404(Intervention, id=id) - if intervention.is_recorded: - messages.info( - request, - RECORDED_BLOCKS_EDIT - ) - return redirect("intervention:detail", id=id) + Returns: + HttpResponse: The rendered view + """ - # Create forms, initialize with values from db/from POST request - data_form = EditInterventionForm(request.POST or None, instance=intervention) - geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention) - if request.method == "POST": + # Get object from db + intervention = get_object_or_404(Intervention, id=id) + if intervention.is_recorded: + messages.info( + request, + RECORDED_BLOCKS_EDIT + ) + return redirect("intervention:detail", id=id) + + # Create forms, initialize with values from db/from POST request + data_form = EditInterventionForm(request.POST or None, instance=intervention) + geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention) + context = { + "form": data_form, + "geom_form": geom_form, + TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier), + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) + + + @method_decorator(default_group_required) + @method_decorator(shared_access_required(Intervention, "id")) + def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: + """ + Process saved form content + + Args: + request (HttpRequest): The incoming request + id (str): The intervention id + + Returns: + HttpResponse: + """ + # Get object from db + intervention = get_object_or_404(Intervention, id=id) + if intervention.is_recorded: + messages.info( + request, + RECORDED_BLOCKS_EDIT + ) + return redirect("intervention:detail", id=id) + + # Create forms, initialize with values from db/from POST request + data_form = EditInterventionForm(request.POST or None, instance=intervention) + geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention) if data_form.is_valid() and geom_form.is_valid(): # The data form takes the geom form for processing, as well as the performing user # Save the current state of recorded|checked to inform the user in case of a status reset due to editing @@ -155,25 +217,17 @@ def edit_view(request: HttpRequest, id: str): 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: _("Edit {}").format(intervention.identifier), - } - context = BaseContext(request, context).context - return render(request, template, context) - + context = { + "form": data_form, + "geom_form": geom_form, + TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier), + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context)