# (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
This commit is contained in:
mpeltriaux 2025-12-15 13:02:11 +01:00
parent 6aad76866f
commit 0b84d418db
7 changed files with 261 additions and 150 deletions

View File

@ -15,10 +15,10 @@
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
{% fa5_icon 'bell' %}
</button>
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-form' obj.id %}">
{% fa5_icon 'share-alt' %}
</button>
{% if is_ets_member %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-form' obj.id %}">
{% fa5_icon 'share-alt' %}
</button>
{% if obj.recorded %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Unrecord' %}" data-form-url="{% url 'ema:record' obj.id %}">
{% fa5_icon 'bookmark' 'far' %}
@ -28,19 +28,21 @@
{% fa5_icon 'bookmark' %}
</button>
{% endif %}
<a href="{% url 'ema:edit' obj.id %}" class="mr-2">
<button class="btn btn-default" title="{% trans 'Edit' %}">
{% fa5_icon 'edit' %}
</button>
</a>
{% endif %}
{% if is_default_member %}
<a href="{% url 'ema:edit' obj.id %}" class="mr-2">
<button class="btn btn-default" title="{% trans 'Edit' %}">
{% fa5_icon 'edit' %}
<button class="btn btn-default btn-modal mr-2" data-form-url="{% url 'ema:log' obj.id %}" title="{% trans 'Show log' %}">
{% fa5_icon 'history' %}
</button>
{% endif %}
{% if is_ets_member %}
<button class="btn btn-default btn-modal" data-form-url="{% url 'ema:remove' obj.id %}" title="{% trans 'Delete' %}">
{% fa5_icon 'trash' %}
</button>
</a>
<button class="btn btn-default btn-modal mr-2" data-form-url="{% url 'ema:log' obj.id %}" title="{% trans 'Show log' %}">
{% fa5_icon 'history' %}
</button>
<button class="btn btn-default btn-modal" data-form-url="{% url 'ema:remove' obj.id %}" title="{% trans 'Delete' %}">
{% fa5_icon 'trash' %}
</button>
{% endif %}
{% endif %}
</div>

View File

@ -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)

View File

@ -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("<id>", DetailEmaView.as_view(), name="detail"),
path('<id>/log', EmaLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/edit', EditEmaView.as_view(), name='edit'),
path('<id>/remove', RemoveEmaView.as_view(), name='remove'),
path('<id>/record', EmaRecordView.as_view(), name='record'),
path('<id>/report', EmaPublicReportView.as_view(), name='report'),

View File

@ -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)

View File

@ -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)

View File

@ -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('<id>', DetailInterventionView.as_view(), name='detail'),
path('<id>/log', InterventionLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/edit', EditInterventionView.as_view(), name='edit'),
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'),

View File

@ -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)