Compare commits

...

5 Commits

Author SHA1 Message Date
a86d86b731 # NewEcoAccount EditEcoAccount view
* refactors new and edit eco account views from function to class based
* removes info message if checked intervention is altered and loses the current checked state
* updates comments/documentation
* removes code duplicates
* fixes display error where modal form was hidden behind menu bar of map client
* fixes bug where compensation could not be created directly from intervention
2025-10-20 09:49:18 +02:00
73178b3fd2 # NewCompensation EditCompensation view
* refactors new and edit compensation views from function to class based
* adds checked property to compensation to return parent-intervention's checked info
* fixes bug where compensation could be added to recorded intervention
* updates translations
2025-10-19 14:06:14 +02:00
278a951e92 # NewEma EditEma views
* refactors views for new ema and edit ema from function to class based
* moves shared access check to base edit form view to be checked for every inheriting class
* fixes bug where private variables changed on singleton objects
* updates translations
2025-10-19 13:10:22 +02:00
9e4a78ec60 # EditIntervention view
* refactors edit intervention view from function to class
2025-10-19 12:50:35 +02:00
d03b714fb5 # NewIntervention view
* introduces BaseFormView and BaseNewSpatialLocatedObjectFormView
* refactors new intervention view from function to class
2025-10-19 12:37:13 +02:00
14 changed files with 409 additions and 389 deletions

View File

@ -168,6 +168,17 @@ class NewCompensationForm(AbstractCompensationForm,
comp.log.add(action)
return comp, action
def is_valid(self):
valid = super().is_valid()
intervention = self.cleaned_data.get("intervention", None)
valid &= not intervention.is_recorded
if not valid:
self.add_error(
"intervention",
_("This intervention is currently recorded. You cannot add further compensations as long as it is recorded.")
)
return valid
def save(self, user: User, geom_form: SimpleGeomForm):
with transaction.atomic():
comp, action = self.__create_comp(user)

View File

@ -17,19 +17,20 @@ from compensation.views.compensation.action import NewCompensationActionView, Ed
RemoveCompensationActionView
from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \
RemoveCompensationStateView
from compensation.views.compensation.compensation import new_view, edit_view, \
remove_view, CompensationIndexView, CompensationIdentifierGeneratorView, CompensationDetailView
from compensation.views.compensation.compensation import \
remove_view, CompensationIndexView, CompensationIdentifierGeneratorView, CompensationDetailView, \
NewCompensationFormView, EditCompensationFormView
from compensation.views.compensation.log import CompensationLogView
urlpatterns = [
# Main compensation
path("", CompensationIndexView.as_view(), name="index"),
path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'),
path('new/<intervention_id>', new_view, name='new'),
path('new', new_view, name='new'),
path('new/<intervention_id>', NewCompensationFormView.as_view(), name='new'),
path('new', NewCompensationFormView.as_view(), name='new'),
path('<id>', CompensationDetailView.as_view(), name='detail'),
path('<id>/log', CompensationLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/edit', EditCompensationFormView.as_view(), name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'),

View File

@ -8,8 +8,9 @@ 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 new_view, edit_view, remove_view, \
EcoAccountIndexView, EcoAccountIdentifierGeneratorView, EcoAccountDetailView
from compensation.views.eco_account.eco_account import remove_view, \
EcoAccountIndexView, EcoAccountIdentifierGeneratorView, EcoAccountDetailView, NewEcoAccountFormView, \
EditEcoAccountFormView
from compensation.views.eco_account.log import EcoAccountLogView
from compensation.views.eco_account.record import EcoAccountRecordView
from compensation.views.eco_account.report import EcoAccountReportView
@ -29,13 +30,13 @@ from compensation.views.eco_account.deduction import NewEcoAccountDeductionView,
app_name = "acc"
urlpatterns = [
path("", EcoAccountIndexView.as_view(), name="index"),
path('new/', new_view, name='new'),
path('new/', NewEcoAccountFormView.as_view(), name='new'),
path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'),
path('<id>', EcoAccountDetailView.as_view(), name='detail'),
path('<id>/log', EcoAccountLogView.as_view(), name='log'),
path('<id>/record', EcoAccountRecordView.as_view(), name='record'),
path('<id>/report', EcoAccountReportView.as_view(), name='report'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/edit', EditEcoAccountFormView.as_view(), name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'),

View File

@ -18,15 +18,12 @@ from compensation.forms.compensation import EditCompensationForm, NewCompensatio
from compensation.models import Compensation
from compensation.tables.compensation import CompensationTable
from intervention.models import Intervention
from konova.contexts import BaseContext
from konova.decorators import shared_access_required, default_group_required, login_required_modal
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 COMPENSATION_REMOVED_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE, \
RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, IDENTIFIER_REPLACED, \
COMPENSATION_ADDED_TEMPLATE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView
RECORDED_BLOCKS_EDIT, PARAMS_INVALID
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
@ -44,74 +41,55 @@ class CompensationIndexView(LoginRequiredMixin, BaseIndexView):
return qs
@login_required
@default_group_required
@shared_access_required(Intervention, "intervention_id")
def new_view(request: HttpRequest, intervention_id: str = None):
"""
Renders a view for a new compensation creation
class NewCompensationFormView(BaseNewSpatialLocatedObjectFormView):
_FORM_CLS = NewCompensationForm
_MODEL_CLS = Compensation
_TEMPLATE = "compensation/form/view.html"
_TAB_TITLE = _("New Compensation")
_REDIRECT_URL = "compensation:detail"
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
if intervention_id is not None:
try:
intervention = Intervention.objects.get(id=intervention_id)
except ObjectDoesNotExist:
messages.error(request, PARAMS_INVALID)
return redirect("home")
if intervention.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("intervention:detail", id=intervention_id)
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
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)
comp = data_form.save(request.user, geom_form)
if generated_identifier != comp.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
comp.identifier
)
)
messages.success(request, COMPENSATION_ADDED_TEMPLATE.format(comp.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("compensation:detail", id=comp.id)
def _user_has_shared_access(self, user, **kwargs):
# On a new compensation make sure the intervention (if call came directly through an intervention's detail
# view) is shared with the user
intervention_id = kwargs.get("intervention_id", None)
if not intervention_id:
return True
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 compensation"),
}
context = BaseContext(request, context).context
return render(request, template, context)
intervention = get_object_or_404(Intervention, id=intervention_id)
return intervention.is_shared_with(user)
def _user_has_permission(self, user):
# User has to be an ets user
return user.is_default_user()
def dispatch(self, request, *args, **kwargs):
# Make sure there is an existing intervention based on the given id
# Compensations can not exist without an intervention
intervention_id = kwargs.get("intervention_id", None)
if intervention_id:
try:
intervention = Intervention.objects.get(id=intervention_id)
if intervention.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("intervention:detail", id=intervention_id)
except ObjectDoesNotExist:
messages.error(request, PARAMS_INVALID, extra_tags="danger")
return redirect("home")
return super().dispatch(request, *args, **kwargs)
class EditCompensationFormView(BaseEditSpatialLocatedObjectFormView):
_MODEL_CLS = Compensation
_FORM_CLS = EditCompensationForm
_TEMPLATE = "compensation/form/view.html"
_REDIRECT_URL = "compensation:detail"
def _user_has_permission(self, user):
# User has to be a default user
return user.is_default_user()
class CompensationIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView):
@ -119,71 +97,6 @@ class CompensationIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGene
_REDIRECT_URL = "compensation:index"
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
# Get object from db
comp = get_object_or_404(Compensation, id=id)
if comp.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("compensation:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditCompensationForm(request.POST or None, instance=comp)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
# Preserve state of intervention checked to determine whether the user must be informed or not
# about a change of the check state
intervention_is_checked = comp.intervention.checked is not None
# The data form takes the geom form for processing, as well as the performing user
comp = data_form.save(request.user, geom_form)
if intervention_is_checked:
messages.info(request, CHECK_STATE_RESET)
messages.success(request, _("Compensation {} edited").format(comp.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("compensation:detail", id=comp.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(comp.identifier),
}
context = BaseContext(request, context).context
return render(request, template, context)
class CompensationDetailView(BaseDetailView):
_MODEL_CLS = Compensation
_TEMPLATE = "compensation/detail/compensation/view.html"

View File

@ -23,7 +23,8 @@ from konova.settings import ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECORDED_BLOCKS_EDIT, FORM_INVALID, \
IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
@ -40,6 +41,29 @@ class EcoAccountIndexView(LoginRequiredMixin, BaseIndexView):
return qs
class NewEcoAccountFormView(BaseNewSpatialLocatedObjectFormView):
_FORM_CLS = NewEcoAccountForm
_MODEL_CLS = EcoAccount
_TEMPLATE = "compensation/form/view.html"
_TAB_TITLE = _("New Eco-Account")
_REDIRECT_URL = "compensation:acc:detail"
def _user_has_permission(self, user):
# User has to be a default user
return user.is_default_user()
class EditEcoAccountFormView(BaseEditSpatialLocatedObjectFormView):
_FORM_CLS = EditEcoAccountForm
_MODEL_CLS = EcoAccount
_TEMPLATE = "compensation/form/view.html"
_REDIRECT_URL = "compensation:acc:detail"
def _user_has_permission(self, user):
# User has to be a default user
return user.is_default_user()
@login_required
@default_group_required
def new_view(request: HttpRequest):

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 new_view, edit_view, remove_view, EmaIndexView, \
EmaIdentifierGeneratorView, EmaDetailView
from ema.views.ema import remove_view, EmaIndexView, \
EmaIdentifierGeneratorView, EmaDetailView, EditEmaFormView, NewEmaFormView
from ema.views.log import EmaLogView
from ema.views.record import EmaRecordView
from ema.views.report import EmaReportView
@ -22,11 +22,11 @@ from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateVie
app_name = "ema"
urlpatterns = [
path("", EmaIndexView.as_view(), name="index"),
path("new/", new_view, name="new"),
path("new/", NewEmaFormView.as_view(), name="new"),
path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"),
path("<id>", EmaDetailView.as_view(), name="detail"),
path('<id>/log', EmaLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/edit', EditEmaFormView.as_view(), name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/record', EmaRecordView.as_view(), name='record'),
path('<id>/report', EmaReportView.as_view(), name='report'),

View File

@ -5,25 +5,20 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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
from django.shortcuts import get_object_or_404, redirect, render
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.contexts import BaseContext
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
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
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
@ -40,59 +35,28 @@ class EmaIndexView(LoginRequiredMixin, BaseIndexView):
return qs
@login_required
@conservation_office_group_required
def new_view(request: HttpRequest):
"""
Renders a view for a new eco account creation
class NewEmaFormView(BaseNewSpatialLocatedObjectFormView):
_FORM_CLS = NewEmaForm
_MODEL_CLS = Ema
_TEMPLATE = "ema/form/view.html"
_TAB_TITLE = _("New EMA")
_REDIRECT_URL = "ema:detail"
Args:
request (HttpRequest): The incoming request
def _user_has_permission(self, user):
# User has to be an ets user
return user.is_ets_user()
Returns:
"""
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":
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)
if generated_identifier != ema.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
ema.identifier
)
)
messages.success(request, _("EMA {} added").format(ema.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)
)
class EditEmaFormView(BaseEditSpatialLocatedObjectFormView):
_MODEL_CLS = Ema
_FORM_CLS = EditEmaForm
_TEMPLATE = "ema/form/view.html"
_REDIRECT_URL = "ema:detail"
_TAB_TITLE = _("Edit {}")
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)
def _user_has_permission(self, user):
# User has to be an ets user
return user.is_ets_user()
class EmaIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView):
@ -149,65 +113,6 @@ class EmaDetailView(BaseDetailView):
return context
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args:
request (HttpRequest): The incoming request
Returns:
"""
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)
# 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":
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)
messages.success(request, _("EMA {} edited").format(ema.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("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)
@login_required_modal
@login_required
@conservation_office_group_required

View File

@ -14,8 +14,9 @@ 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 remove_view, \
InterventionIndexView, InterventionIdentifierGeneratorView, InterventionDetailView, NewInterventionFormView, \
EditInterventionFormView
from intervention.views.log import InterventionLogView
from intervention.views.record import InterventionRecordView
from intervention.views.report import InterventionReportView
@ -27,11 +28,11 @@ 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('<id>', InterventionDetailView.as_view(), name='detail'),
path('<id>/log', InterventionLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/edit', EditInterventionFormView.as_view(), name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'),
path('<id>/share', InterventionShareFormView.as_view(), name='share-form'),

View File

@ -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,14 @@ 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, \
BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
@ -45,60 +43,20 @@ class InterventionIndexView(LoginRequiredMixin, BaseIndexView):
return qs
@login_required
@default_group_required
def new_view(request: HttpRequest):
"""
Renders a view for a new intervention creation
class NewInterventionFormView(BaseNewSpatialLocatedObjectFormView):
_MODEL_CLS = Intervention
_FORM_CLS = NewInterventionForm
_TEMPLATE = "intervention/form/view.html"
_REDIRECT_URL = "intervention:detail"
_TAB_TITLE = _("New intervention")
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 EditInterventionFormView(BaseEditSpatialLocatedObjectFormView):
_MODEL_CLS = Intervention
_FORM_CLS = EditInterventionForm
_TEMPLATE = "intervention/form/view.html"
_REDIRECT_URL = "intervention:detail"
_TAB_TITLE = _("Edit {}")
class InterventionIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView):

View File

@ -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
@ -46,7 +48,7 @@ class BaseForm(forms.Form):
self.__check_valid_label_input_ratio()
@abstractmethod
def save(self):
def save(self, *arg, **kwargs):
# To be implemented in subclasses!
pass

View File

@ -288,4 +288,8 @@ Overwrites netgis.css attributes
Overwrites gradient used on default css of netgis map client
*/
background: var(--rlp-red) !important;
}
.modal{
z-index: 100000;
}

View File

@ -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, RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID
class BaseView(View):
@ -139,3 +143,183 @@ 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_additional_context(self, **kwargs):
"""
Args:
**kwargs ():
Returns:
"""
return {}
class BaseSpatialLocatedObjectFormView(LoginRequiredMixin, BaseFormView):
_GEOMETRY_FORM_CLS = SimpleGeomForm
class Meta:
abstract = True
class BaseNewSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView):
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, **kwargs):
form: BaseForm = self._FORM_CLS(None, **kwargs, user=request.user)
geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(None, user=request.user, read_only=False)
context = self._get_additional_context()
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, **kwargs):
form: BaseForm = self._FORM_CLS(request.POST or None, **kwargs, 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)
obj_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(obj_redirect_url)
else:
context = self._get_additional_context()
messages.error(request, FORM_INVALID, extra_tags="danger",)
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)
class BaseEditSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView):
_TAB_TITLE = _("Edit {}")
def get(self, request: HttpRequest, id: str):
obj = get_object_or_404(
self._MODEL_CLS,
id=id
)
obj_redirect_url = reverse(self._REDIRECT_URL, args=(obj.id,))
if obj.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect(obj_redirect_url)
form: BaseForm = self._FORM_CLS(None, instance=obj, user=request.user)
geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(None, instance=obj, read_only=False)
context = self._get_additional_context()
context = BaseContext(request, additional_context=context).context
context.update(
{
"form": form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: self._TAB_TITLE.format(obj.identifier),
}
)
return render(request, self._TEMPLATE, context)
def post(self, request: HttpRequest, id: str):
obj = get_object_or_404(
self._MODEL_CLS,
id=id
)
obj_redirect_url = reverse(self._REDIRECT_URL, args=(obj.id,))
form: BaseForm = self._FORM_CLS(request.POST or None, instance=obj, user=request.user)
geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(request.POST or None, instance=obj, read_only=False)
if form.is_valid() and geom_form.is_valid():
obj = form.save(request.user, geom_form)
messages.success(request, _("{} edited").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(obj_redirect_url)
else:
context = self._get_additional_context()
messages.error(request, FORM_INVALID, extra_tags="danger",)
context = BaseContext(request, additional_context=context).context
context.update(
{
"form": form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: self._TAB_TITLE.format(obj.identifier),
}
)
return render(request, self._TEMPLATE, context)
def _user_has_shared_access(self, user, **kwargs):
obj = get_object_or_404(self._MODEL_CLS, id=kwargs.get('id', None))
return obj.is_shared_with(user)
def _user_has_permission(self, user):
return user.is_default_user()

Binary file not shown.

View File

@ -45,7 +45,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-15 09:11+0200\n"
"POT-Creation-Date: 2025-10-19 13:56+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -448,11 +448,19 @@ msgid "Select the intervention for which this compensation compensates"
msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist"
#: compensation/forms/compensation.py:114
#: compensation/views/compensation/compensation.py:120
#: compensation/views/compensation/compensation.py:161
msgid "New compensation"
msgstr "Neue Kompensation"
#: compensation/forms/compensation.py:190
#: compensation/forms/compensation.py:179
msgid ""
"This intervention is currently recorded. You cannot add further "
"compensations as long as it is recorded."
msgstr ""
"Dieser Eingriff ist derzeit verzeichnet. "
"Sie können keine weiteren Kompensationen hinzufügen, so lange er verzeichnet ist."
#: compensation/forms/compensation.py:202
msgid "Edit compensation"
msgstr "Bearbeite Kompensation"
@ -475,7 +483,7 @@ msgid "When did the parties agree on this?"
msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?"
#: compensation/forms/eco_account.py:72
#: compensation/views/eco_account/eco_account.py:101
#: compensation/views/eco_account/eco_account.py:93
msgid "New Eco-Account"
msgstr "Neues Ökokonto"
@ -1288,44 +1296,45 @@ msgstr ""
msgid "Responsible data"
msgstr "Daten zu den verantwortlichen Stellen"
#: compensation/views/compensation/compensation.py:58
#: compensation/views/compensation/compensation.py:35
msgid "Compensations - Overview"
msgstr "Kompensationen - Übersicht"
#: compensation/views/compensation/compensation.py:181
#: compensation/views/compensation/compensation.py:52
#, fuzzy
#| msgid "New compensation"
msgid "New Compensation"
msgstr "Neue Kompensation"
#: compensation/views/compensation/compensation.py:208
#: konova/utils/message_templates.py:40
msgid "Compensation {} edited"
msgstr "Kompensation {} bearbeitet"
#: compensation/views/compensation/compensation.py:196
#: compensation/views/eco_account/eco_account.py:173 ema/views/ema.py:238
#: intervention/views/intervention.py:253
#: compensation/views/compensation/compensation.py:231
#: compensation/views/eco_account/eco_account.py:159 ema/views/ema.py:59
#: intervention/views/intervention.py:59 intervention/views/intervention.py:179
#: konova/views/base.py:239
msgid "Edit {}"
msgstr "Bearbeite {}"
#: compensation/views/compensation/report.py:35
#: compensation/views/eco_account/report.py:36 ema/views/report.py:35
#: intervention/views/report.py:35
msgid "Report {}"
msgstr "Bericht {}"
#: compensation/views/eco_account/eco_account.py:53
#: compensation/views/eco_account/eco_account.py:32
msgid "Eco-account - Overview"
msgstr "Ökokonten - Übersicht"
#: compensation/views/eco_account/eco_account.py:86
#: compensation/views/eco_account/eco_account.py:70
msgid "Eco-Account {} added"
msgstr "Ökokonto {} hinzugefügt"
#: compensation/views/eco_account/eco_account.py:158
#: compensation/views/eco_account/eco_account.py:136
msgid "Eco-Account {} edited"
msgstr "Ökokonto {} bearbeitet"
#: compensation/views/eco_account/eco_account.py:288
#: compensation/views/eco_account/eco_account.py:260
msgid "Eco-account removed"
msgstr "Ökokonto entfernt"
#: ema/forms.py:42 ema/tests/unit/test_forms.py:27 ema/views/ema.py:108
#: ema/forms.py:42 ema/tests/unit/test_forms.py:27 ema/views/ema.py:42
msgid "New EMA"
msgstr "Neue EMA hinzufügen"
@ -1353,19 +1362,11 @@ msgstr ""
msgid "Payment funded compensation"
msgstr "Ersatzzahlungsmaßnahme"
#: ema/views/ema.py:53
#: ema/views/ema.py:26
msgid "EMAs - Overview"
msgstr "EMAs - Übersicht"
#: ema/views/ema.py:86
msgid "EMA {} added"
msgstr "EMA {} hinzugefügt"
#: ema/views/ema.py:223
msgid "EMA {} edited"
msgstr "EMA {} bearbeitet"
#: ema/views/ema.py:262
#: ema/views/ema.py:138
msgid "EMA removed"
msgstr "EMA entfernt"
@ -1429,7 +1430,7 @@ msgstr "Datum Bestandskraft bzw. Rechtskraft"
#: intervention/forms/intervention.py:216
#: intervention/tests/unit/test_forms.py:36
#: intervention/views/intervention.py:105
#: intervention/views/intervention.py:51
msgid "New intervention"
msgstr "Neuer Eingriff"
@ -1665,19 +1666,15 @@ msgstr ""
msgid "Check performed"
msgstr "Prüfung durchgeführt"
#: intervention/views/intervention.py:57
#: intervention/views/intervention.py:33
msgid "Interventions - Overview"
msgstr "Eingriffe - Übersicht"
#: intervention/views/intervention.py:90
msgid "Intervention {} added"
msgstr "Eingriff {} hinzugefügt"
#: intervention/views/intervention.py:236
#: intervention/views/intervention.py:154
msgid "Intervention {} edited"
msgstr "Eingriff {} bearbeitet"
#: intervention/views/intervention.py:278
#: intervention/views/intervention.py:204
msgid "{} removed"
msgstr "{} entfernt"
@ -1689,7 +1686,7 @@ msgstr "Hierfür müssen Sie Mitarbeiter sein!"
msgid "You need to be administrator to perform this action!"
msgstr "Hierfür müssen Sie Administrator sein!"
#: konova/decorators.py:65
#: konova/decorators.py:65 konova/utils/general.py:40
msgid ""
"+++ Attention: You are not part of any group. You won't be able to create, "
"edit or do anything. Please contact an administrator. +++"
@ -1801,7 +1798,7 @@ msgstr "Sucht nach Einträgen, an denen diese Person gearbeitet hat"
msgid "Save"
msgstr "Speichern"
#: konova/forms/base_form.py:72
#: konova/forms/base_form.py:74
msgid "Not editable"
msgstr "Nicht editierbar"
@ -1810,7 +1807,7 @@ msgstr "Nicht editierbar"
msgid "Geometry"
msgstr "Geometrie"
#: konova/forms/geometry_form.py:100
#: konova/forms/geometry_form.py:101
msgid "Only surfaces allowed. Points or lines must be buffered."
msgstr ""
"Nur Flächen erlaubt. Punkte oder Linien müssen zu Flächen gepuffert werden."
@ -2268,8 +2265,9 @@ msgid ""
"too small to be valid). These parts have been removed. Please check the "
"stored geometry."
msgstr ""
"Die Geometrie enthielt {} invalide Bestandteile (z.B. unaussagekräftige Kleinstflächen)."
"Diese Bestandteile wurden automatisch entfernt. Bitte überprüfen Sie die angepasste Geometrie."
"Die Geometrie enthielt {} invalide Bestandteile (z.B. unaussagekräftige "
"Kleinstflächen).Diese Bestandteile wurden automatisch entfernt. Bitte "
"überprüfen Sie die angepasste Geometrie."
#: konova/utils/message_templates.py:89
msgid "This intervention has {} revocations"
@ -2310,7 +2308,15 @@ msgstr ""
"Dieses Datum ist unrealistisch. Geben Sie bitte das korrekte Datum ein "
"(>1950)."
#: konova/views/home.py:75 templates/navbars/navbar.html:16
#: konova/views/base.py:209
msgid "{} added"
msgstr "{} hinzugefügt"
#: konova/views/base.py:281
msgid "{} edited"
msgstr "{} bearbeitet"
#: konova/views/home.py:72 templates/navbars/navbar.html:16
msgid "Home"
msgstr "Home"
@ -2330,6 +2336,10 @@ msgstr "{} verzeichnet"
msgid "Errors found:"
msgstr "Fehler gefunden:"
#: konova/views/report.py:21
msgid "Report {}"
msgstr "Bericht {}"
#: konova/views/resubmission.py:39
msgid "Resubmission set"
msgstr "Wiedervorlage gesetzt"
@ -3056,7 +3066,7 @@ msgid "Manage teams"
msgstr ""
#: user/templates/user/index.html:53 user/templates/user/team/index.html:19
#: user/views/views.py:135
#: user/views/views.py:134
msgid "Teams"
msgstr ""
@ -3116,34 +3126,40 @@ msgstr "Läuft ab am"
msgid "User API token"
msgstr "API Nutzer Token"
#: user/views/views.py:33
#: user/views/views.py:31
msgid "User settings"
msgstr "Einstellungen"
#: user/views/views.py:59
msgid "Notifications edited"
msgstr "Benachrichtigungen bearbeitet"
#: user/views/views.py:71
#: user/views/views.py:44
msgid "User notifications"
msgstr "Benachrichtigungen"
#: user/views/views.py:147
#: user/views/views.py:64
msgid "Notifications edited"
msgstr "Benachrichtigungen bearbeitet"
#: user/views/views.py:152
msgid "New team added"
msgstr "Neues Team hinzugefügt"
#: user/views/views.py:162
#: user/views/views.py:167
msgid "Team edited"
msgstr "Team bearbeitet"
#: user/views/views.py:177
#: user/views/views.py:182
msgid "Team removed"
msgstr "Team gelöscht"
#: user/views/views.py:192
#: user/views/views.py:197
msgid "You are not a member of this team"
msgstr "Sie sind kein Mitglied dieses Teams"
#: user/views/views.py:199
#: user/views/views.py:204
msgid "Left Team"
msgstr "Team verlassen"
#~ msgid "EMA {} added"
#~ msgstr "EMA {} hinzugefügt"
#~ msgid "EMA {} edited"
#~ msgstr "EMA {} bearbeitet"