Compare commits

..

37 Commits

Author SHA1 Message Date
1b6eea2c9e # Refactoring eiv-kom remove view
* refactors removing compensation from intervention view
* drops unused view on api app
2025-11-08 13:05:14 +01:00
cf6f188ef3 # Refactoring APITokenView
* refactors API Token view
* updates tests
2025-11-05 10:37:27 +01:00
f122778232 # Refactoring team views
* refactors team views
* split views.py into users.py and teams.py in users app
* refactors method headers for _user_has_permission()
* adds method and class comments and documentation to base view classes
2025-11-05 10:12:49 +01:00
bc2e901ca9 # Refactoring payment view
* refactors views for adding, editing and removing payments
2025-11-04 09:09:05 +01:00
4a16727da1 # Refactoring revocation views
* refactors views for adding, editing and removing revocations
* refactors view for getting the document of a revocation
* updates tests
2025-11-04 08:58:47 +01:00
d85ebccec8 # Compensation State view refactoring
* refactors compensation state views for kom, ema, oek
* updates tests
* refactors before-after state toggling into initialization of NewCompensationStateModalForm
2025-10-21 21:05:45 +02:00
837c1de938 # Remove View refactoring
* refactors remove view for kom, eiv, oek and ema
* introduces BaseRemoveModalFormView
* moves html blocking logic from BaseModalForm into BaseModalFormView
2025-10-21 20:28:43 +02:00
fe2ac3d97d # Test update
* fixes bug for sharing via token where permission was too tight
2025-10-21 19:37:34 +02:00
554ade6794 # Share view refactoring
* refactors share views for eiv, oek, ema (kom does not have any)
2025-10-21 19:15:17 +02:00
3a9c4e13f6 # Resubmission view refactoring
* refactors resubmission view for eiv, kom, oek, ema
* removes unused attributes on BaseModalFormView
2025-10-21 17:03:12 +02:00
1fc1b533cd # Log view refactoring
* refactors log views to inherit from BaseView
2025-10-21 14:56:26 +02:00
1175fe3b37 # Parcel view refactoring
* refactors parcel view to inherit from BaseView
2025-10-21 14:06:11 +02:00
d2a57df080 # Document views refactoring
* refactors new, edit, get and delete views for eiv, kom, oek and ema
* introduces
2025-10-21 13:48:16 +02:00
d5accb2143 # Deadline tests refactored
* refactors tests for deadline views to check whether they work properly
2025-10-21 09:14:46 +02:00
6056a8882d # Deadline views refactored
* refactors AbstractDeadlineViews (new, edit, remove) to inherit from BaseModalFormView
* refactors KOM, OEK, EMA views for deadline views
2025-10-21 09:04:57 +02:00
c7a4c309bf # CompensationAction views refactored
* refactors AbstractCompensationActionViews (new, edit, remove) to inherit from BaseModalFormView
* refactors KOM, OEK, EMA views for compensation actions
* moves message template strings into message_templates.py
2025-10-21 08:48:30 +02:00
a7b23935a1 # RecordModalForm refactored
* refactors AbstractRecordModalForm
* refactors recording view for ema, intervention and eco account
2025-10-20 16:29:50 +02:00
97fbe02742 # BaseModalFormView refactoring
* extends BaseModalFormView to hold general logic for processing GET and POST requests for BaseModalForm endpoints
* refactors uuid check to use a specific parameter instead of kwargs
* fixes css bug where modal form input elements would not be visible
* refactors check view for intervention from function to class
* refactors DeductionViews to inherit from extended BaseModalFormView
2025-10-20 16:13:58 +02:00
ed5d571704 # Bugfix NewCompensationForm
* fixes bug where a form error would trigger a wrong error warning
2025-10-20 13:52:15 +02:00
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
a9b402862b # Detail View
* introduces BaseDetailView
* refactors detail views for EIV, KOM, OEK, EMA from function based to class based
* refactors already class based HomeView to inherit from new BaseView
2025-10-17 15:40:45 +02:00
61ec9c8c9b # ClientProxyView
* refactors login required from method decorator to mixin inheritance
2025-10-17 14:42:44 +02:00
f2baa054bf # Report view refactoring
* refactors function based report views into class based for EIV, OEK, EMA, KOM
* introduces BaseReportView for proper inheritance of shared logic
* refactors generating of qr codes into proper class
2025-10-17 11:07:16 +02:00
242730435e # Deduction views
* refactors deduction views on interventions and eco accounts from function to class based
* introduces basic checks on shared access and permission on BaseView on dispatching --> checks shall be overwritten on inheriting classes
2025-10-16 15:36:57 +02:00
afbdf221c3 # User view refactoring
* refactors majority of user views into class based views
* introduces BaseModalFormView and BaseView for even more generic usage
* renames url identifier user:index into user:detail for more clarity
2025-10-15 17:09:40 +02:00
be9f6f1b7e # Identifier Generator View EcoAccount refactoring
* refactors identifier generator view for ecoaccount
* simplifies base identifier generator view even further
2025-10-15 16:46:07 +02:00
80e8925a63 # Identifier Generator View Compensation refactoring
* refactors identifier generator view for compensation
2025-10-15 16:42:42 +02:00
c597e1934b # Identifier Generator View refactoring
* refactors identifier generator view for interventions
* simplifies same view for ema
2025-10-15 16:40:35 +02:00
a44d8658d4 # NewId Generator Ema refactoring
* introduces BaseNewIdentifierGeneratorView class
* refactors new identifier generator view for ema
2025-10-15 16:29:05 +02:00
bb71c0fcc8 # Index Ema refactoring
* refactors index view for ema
2025-10-15 16:14:03 +02:00
67acddf701 # Index EcoAccount refactoring
* refactors index view for eco account
2025-10-15 16:12:21 +02:00
21bb988d86 # Index Compensation refactoring
* refactors index view for compensations
2025-10-15 16:03:53 +02:00
1ceffccd40 # Index Intervention refactoring
* introduces BaseIndexView class
* refactors index view for interventions
2025-10-15 16:00:51 +02:00
134 changed files with 2968 additions and 4054 deletions

View File

@@ -10,6 +10,6 @@ from analysis.views import *
app_name = "analysis" app_name = "analysis"
urlpatterns = [ urlpatterns = [
path("reports/", ReportIndexView.as_view(), name="reports"), path("reports/", index_reports_view, name="reports"),
path("reports/<id>", ReportDetailView.as_view(), name="report-detail"), path("reports/<id>", detail_report_view, name="report-detail"),
] ]

View File

@@ -1,12 +1,8 @@
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views import View
from django.views.generic import DetailView
from analysis.forms import TimespanReportForm from analysis.forms import TimespanReportForm
from analysis.utils.excel.excel import TempExcelFile from analysis.utils.excel.excel import TempExcelFile
@@ -46,112 +42,57 @@ def index_reports_view(request: HttpRequest):
context = BaseContext(request, context).context context = BaseContext(request, context).context
return render(request, template, context) return render(request, template, context)
class ReportIndexView(LoginRequiredMixin, View):
@method_decorator(conservation_office_group_required)
def get(self, request: HttpRequest) -> HttpResponse:
""" @login_required
@conservation_office_group_required
def detail_report_view(request: HttpRequest, id: str):
""" Renders the detailed report for a conservation office
Args: Args:
request (HttpRequest): The incoming request request (HttpRequest): The incoming request
id (str): The conservation_office KonovaCode id
Returns: Returns:
""" """
template = "analysis/reports/index.html" # Try to resolve the requested office id
form = TimespanReportForm(None) cons_office = get_object_or_404(
context = { KonovaCode,
"form": form id=id
} )
context = BaseContext(request, context).context # Try to resolve the date parameters into Date objects -> redirect if this fails
return render(request, template, context) try:
df = request.GET.get("df", None)
@method_decorator(conservation_office_group_required) dt = request.GET.get("dt", None)
def post(self, request: HttpRequest) -> HttpResponse: date_from = timezone.make_aware(timezone.datetime.fromisoformat(df))
date_to = timezone.make_aware(timezone.datetime.fromisoformat(dt))
""" except ValueError:
messages.error(
Args: request,
request (HttpRequest): The incoming request PARAMS_INVALID,
extra_tags="danger",
Returns:
"""
template = "analysis/reports/index.html"
form = TimespanReportForm(request.POST or None)
if form.is_valid():
redirect_url = form.save()
return redirect(redirect_url)
else:
messages.error(
request,
FORM_INVALID,
extra_tags="danger",
)
context = {
"form": form
}
context = BaseContext(request, context).context
return render(request, template, context)
class ReportDetailView(LoginRequiredMixin, DetailView):
@method_decorator(conservation_office_group_required)
def get(self, request: HttpRequest, id: str):
""" Renders the detailed report for a conservation office
Args:
request (HttpRequest): The incoming request
id (str): The conservation_office KonovaCode id
Returns:
"""
# Try to resolve the requested office id
cons_office = get_object_or_404(
KonovaCode,
id=id
) )
# Try to resolve the date parameters into Date objects -> redirect if this fails return redirect("analysis:reports")
try:
df = request.GET.get("df", None)
dt = request.GET.get("dt", None)
date_from = timezone.make_aware(timezone.datetime.fromisoformat(df))
date_to = timezone.make_aware(timezone.datetime.fromisoformat(dt))
except ValueError:
messages.error(
request,
PARAMS_INVALID,
extra_tags="danger",
)
return redirect("analysis:reports")
# Check whether the html default rendering is requested or an alternative # Check whether the html default rendering is requested or an alternative
format_param = request.GET.get("format", "html") format_param = request.GET.get("format", "html")
report = TimespanReport(id, date_from, date_to) report = TimespanReport(id, date_from, date_to)
if format_param == "html": if format_param == "html":
return self.__handle_html_format(request, report, cons_office)
elif format_param == "excel":
return self.__handle_excel_format(report, cons_office, df, dt)
else:
raise NotImplementedError
def __handle_html_format(self, request, report: TimespanReport, office: KonovaCode):
template = "analysis/reports/detail.html" template = "analysis/reports/detail.html"
context = { context = {
"office": office, "office": cons_office,
"report": report, "report": report,
} }
context = BaseContext(request, context).context context = BaseContext(request, context).context
return render(request, template, context) return render(request, template, context)
elif format_param == "excel":
def __handle_excel_format(self, report: TimespanReport, office: KonovaCode, df: str, dt: str):
file = TempExcelFile(report.excel_template_path, report.excel_map) file = TempExcelFile(report.excel_template_path, report.excel_map)
response = HttpResponse( response = HttpResponse(
content=file.stream, content=file.stream,
content_type="application/ms-excel", content_type="application/ms-excel",
) )
response['Content-Disposition'] = f'attachment; filename={office.long_name}_{df}_{dt}.xlsx' response['Content-Disposition'] = f'attachment; filename={cons_office.long_name}_{df}_{dt}.xlsx'
return response return response
else:
raise NotImplementedError

View File

@@ -71,7 +71,7 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
# Expect this first request to fail, since user has no shared access on the intervention, we want to create # Expect this first request to fail, since user has no shared access on the intervention, we want to create
# a compensation for # a compensation for
response = self._run_create_request(url, post_body) response = self._run_create_request(url, post_body)
self.assertEqual(response.status_code, 400, msg=response.content) self.assertEqual(response.status_code, 500, msg=response.content)
content = json.loads(response.content) content = json.loads(response.content)
self.assertGreater(len(content.get("errors", [])), 0, msg=response.content) self.assertGreater(len(content.get("errors", [])), 0, msg=response.content)

View File

@@ -6,9 +6,7 @@ Created on: 21.01.22
""" """
import json import json
from json import JSONDecodeError
from django.core.exceptions import ObjectDoesNotExist
from django.http import JsonResponse, HttpRequest from django.http import JsonResponse, HttpRequest
from api.utils.serializer.v1.compensation import CompensationAPISerializerV1 from api.utils.serializer.v1.compensation import CompensationAPISerializerV1
@@ -68,12 +66,8 @@ class AbstractAPIViewV1(AbstractAPIView):
body = request.body.decode("utf-8") body = request.body.decode("utf-8")
body = json.loads(body) body = json.loads(body)
created_id = self.serializer.create_model_from_json(body, self.user) created_id = self.serializer.create_model_from_json(body, self.user)
except (JSONDecodeError, except Exception as e:
AssertionError, return self._return_error_response(e, 500)
ValueError,
PermissionError,
ObjectDoesNotExist) as e:
return self._return_error_response(e, 400)
return JsonResponse({"id": created_id}) return JsonResponse({"id": created_id})
def put(self, request: HttpRequest, id=None): def put(self, request: HttpRequest, id=None):

View File

@@ -81,7 +81,9 @@ class AbstractAPIView(View):
Returns: Returns:
""" """
content = [f"{error.__class__.__name__}: {str(error)}"] content = [error.__str__()]
if hasattr(error, "messages"):
content = error.messages
return JsonResponse( return JsonResponse(
{ {
"errors": content "errors": content

View File

View File

@@ -45,14 +45,6 @@ class AbstractCompensationAdmin(BaseObjectAdmin):
states = "\n".join(states) states = "\n".join(states)
return states return states
def get_actions(self, request):
DELETE_ACTION_IDENTIFIER = "delete_selected"
actions = super().get_actions(request)
if DELETE_ACTION_IDENTIFIER in actions:
del actions[DELETE_ACTION_IDENTIFIER]
return actions
class CompensationAdmin(AbstractCompensationAdmin): class CompensationAdmin(AbstractCompensationAdmin):
autocomplete_fields = [ autocomplete_fields = [

View File

@@ -168,6 +168,17 @@ class NewCompensationForm(AbstractCompensationForm,
comp.log.add(action) comp.log.add(action)
return comp, action return comp, action
def is_valid(self):
valid = super().is_valid()
intervention = self.cleaned_data.get("intervention", None)
if intervention.is_recorded:
valid &= False
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): def save(self, user: User, geom_form: SimpleGeomForm):
with transaction.atomic(): with transaction.atomic():
comp, action = self.__create_comp(user) comp, action = self.__create_comp(user)

View File

@@ -15,7 +15,6 @@ from compensation.models import EcoAccount
from intervention.models import Handler, Responsibility, Legal from intervention.models import Handler, Responsibility, Legal
from konova.forms import SimpleGeomForm from konova.forms import SimpleGeomForm
from konova.forms.modals import RemoveModalForm from konova.forms.modals import RemoveModalForm
from konova.settings import ETS_GROUP
from konova.utils import validators from konova.utils import validators
from user.models import User, UserActionLogEntry from user.models import User, UserActionLogEntry
@@ -247,13 +246,4 @@ class RemoveEcoAccountModalForm(RemoveModalForm):
"confirm", "confirm",
_("The account can not be removed, since there are still deductions.") _("The account can not be removed, since there are still deductions.")
) )
# If there are deductions but the performing user is not part of an ETS group, we assume this poor
# fella does not know what he/she does -> give a hint that they should contact someone in charge...
user_is_ets_user = self.user.in_group(ETS_GROUP)
if not user_is_ets_user:
self.add_error(
"confirm",
_("Please contact the responsible conservation office to find a solution!")
)
return super_valid and not has_deductions return super_valid and not has_deductions

View File

@@ -7,10 +7,12 @@ Created on: 18.08.22
""" """
from dal import autocomplete from dal import autocomplete
from django import forms from django import forms
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from codelist.models import KonovaCode from codelist.models import KonovaCode
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID
from compensation.models import CompensationAction
from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple
from konova.forms.modals import BaseModalForm, RemoveModalForm from konova.forms.modals import BaseModalForm, RemoveModalForm
from konova.utils.message_templates import COMPENSATION_ACTION_EDITED, ADDED_COMPENSATION_ACTION from konova.utils.message_templates import COMPENSATION_ACTION_EDITED, ADDED_COMPENSATION_ACTION
@@ -114,7 +116,8 @@ class EditCompensationActionModalForm(NewCompensationActionModalForm):
action = None action = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.action = kwargs.pop("action", None) action_id = kwargs.pop("action_id", None)
self.action = get_object_or_404(CompensationAction, id=action_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit action") self.form_title = _("Edit action")
form_data = { form_data = {
@@ -147,8 +150,8 @@ class RemoveCompensationActionModalForm(RemoveModalForm):
action = None action = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
action = kwargs.pop("action", None) action_id = kwargs.pop("action_id", None)
self.action = action self.action = get_object_or_404(CompensationAction, id=action_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):

View File

@@ -6,10 +6,11 @@ Created on: 18.08.22
""" """
from django import forms from django import forms
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from konova.forms.modals import BaseModalForm from konova.forms.modals import BaseModalForm
from konova.models import DeadlineType from konova.models import DeadlineType, Deadline
from konova.utils import validators from konova.utils import validators
from konova.utils.message_templates import DEADLINE_EDITED from konova.utils.message_templates import DEADLINE_EDITED
@@ -90,7 +91,8 @@ class EditDeadlineModalForm(NewDeadlineModalForm):
deadline = None deadline = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.deadline = kwargs.pop("deadline", None) deadline_id = kwargs.pop("deadline_id", None)
self.deadline = get_object_or_404(Deadline, id=deadline_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit deadline") self.form_title = _("Edit deadline")
form_data = { form_data = {

View File

@@ -6,12 +6,27 @@ Created on: 18.08.22
""" """
from compensation.models import CompensationDocument, EcoAccountDocument from compensation.models import CompensationDocument, EcoAccountDocument
from konova.forms.modals import NewDocumentModalForm from konova.forms.modals import NewDocumentModalForm, EditDocumentModalForm, RemoveDocumentModalForm
class NewCompensationDocumentModalForm(NewDocumentModalForm): class NewCompensationDocumentModalForm(NewDocumentModalForm):
document_model = CompensationDocument _DOCUMENT_CLS = CompensationDocument
class EditCompensationDocumentModalForm(EditDocumentModalForm):
_DOCUMENT_CLS = CompensationDocument
class RemoveCompensationDocumentModalForm(RemoveDocumentModalForm):
_DOCUMENT_CLS = CompensationDocument
class NewEcoAccountDocumentModalForm(NewDocumentModalForm): class NewEcoAccountDocumentModalForm(NewDocumentModalForm):
document_model = EcoAccountDocument _DOCUMENT_CLS = EcoAccountDocument
class EditEcoAccountDocumentModalForm(EditDocumentModalForm):
_DOCUMENT_CLS = EcoAccountDocument
class RemoveEcoAccountDocumentModalForm(RemoveDocumentModalForm):
_DOCUMENT_CLS = EcoAccountDocument

View File

@@ -6,8 +6,10 @@ Created on: 18.08.22
""" """
from django import forms from django import forms
from django.shortcuts import get_object_or_404
from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _ from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _
from compensation.models import Payment
from konova.forms.modals import RemoveModalForm, BaseModalForm from konova.forms.modals import RemoveModalForm, BaseModalForm
from konova.utils import validators from konova.utils import validators
from konova.utils.message_templates import PAYMENT_EDITED from konova.utils.message_templates import PAYMENT_EDITED
@@ -103,7 +105,8 @@ class EditPaymentModalForm(NewPaymentForm):
payment = None payment = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.payment = kwargs.pop("payment", None) payment_id = kwargs.pop("payment_id", None)
self.payment = get_object_or_404(Payment, id=payment_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit payment") self.form_title = _("Edit payment")
form_date = { form_date = {
@@ -133,8 +136,8 @@ class RemovePaymentModalForm(RemoveModalForm):
payment = None payment = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
payment = kwargs.pop("payment", None) payment_id = kwargs.pop("payment_id", None)
self.payment = payment self.payment = get_object_or_404(Payment, id=payment_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):

View File

@@ -0,0 +1,15 @@
"""
Author: Michel Peltriaux
Created on: 21.10.25
"""
from compensation.models import Compensation, EcoAccount
from konova.forms.modals import ResubmissionModalForm
class CompensationResubmissionModalForm(ResubmissionModalForm):
_MODEL_CLS = Compensation
class EcoAccountResubmissionModalForm(ResubmissionModalForm):
_MODEL_CLS = EcoAccount

View File

@@ -5,21 +5,17 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 18.08.22 Created on: 18.08.22
""" """
from bootstrap_modal_forms.mixins import is_ajax
from dal import autocomplete from dal import autocomplete
from django import forms from django import forms
from django.contrib import messages
from django.http import HttpResponseRedirect, HttpRequest
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from codelist.models import KonovaCode from codelist.models import KonovaCode
from codelist.settings import CODELIST_BIOTOPES_ID, \ from codelist.settings import CODELIST_BIOTOPES_ID, \
CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID
from compensation.models import CompensationState
from intervention.inputs import CompensationStateTreeRadioSelect from intervention.inputs import CompensationStateTreeRadioSelect
from konova.contexts import BaseContext
from konova.forms.modals import RemoveModalForm, BaseModalForm from konova.forms.modals import RemoveModalForm, BaseModalForm
from konova.utils.message_templates import COMPENSATION_STATE_EDITED, FORM_INVALID, ADDED_COMPENSATION_STATE from konova.utils.message_templates import COMPENSATION_STATE_EDITED, ADDED_COMPENSATION_STATE
class NewCompensationStateModalForm(BaseModalForm): class NewCompensationStateModalForm(BaseModalForm):
@@ -68,10 +64,13 @@ class NewCompensationStateModalForm(BaseModalForm):
) )
) )
_is_before_state: bool = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("New state") self.form_title = _("New state")
self.form_caption = _("Insert data for the new state") self.form_caption = _("Insert data for the new state")
self._is_before_state = bool(self.request.GET.get("before", False))
choices = KonovaCode.objects.filter( choices = KonovaCode.objects.filter(
code_lists__in=[CODELIST_BIOTOPES_ID], code_lists__in=[CODELIST_BIOTOPES_ID],
is_archived=False, is_archived=False,
@@ -83,65 +82,19 @@ class NewCompensationStateModalForm(BaseModalForm):
] ]
self.fields["biotope_type"].choices = choices self.fields["biotope_type"].choices = choices
def save(self, is_before_state: bool = False): def save(self):
state = self.instance.add_state(self, is_before_state) state = self.instance.add_state(self, self._is_before_state)
self.instance.mark_as_edited(self.user, self.request, ADDED_COMPENSATION_STATE) self.instance.mark_as_edited(self.user, self.request, ADDED_COMPENSATION_STATE)
return state return state
def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
""" Generic processing of request
Wraps the request processing logic, so we don't need the same code everywhere a RemoveModalForm is being used
+++
The generic method from super class can not be used, since we need to do some request parameter check in here.
+++
Args:
request (HttpRequest): The incoming request
msg_success (str): The message in case of successful removing
msg_error (str): The message in case of an error
Returns:
"""
redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
template = self.template
if request.method == "POST":
if self.is_valid():
# Modal forms send one POST for checking on data validity. This can be used to return possible errors
# on the form. A second POST (if no errors occured) is sent afterwards and needs to process the
# saving/commiting of the data to the database. is_ajax() performs this check. The first request is
# an ajax call, the second is a regular form POST.
if not is_ajax(request.META):
is_before_state = bool(request.GET.get("before", False))
self.save(is_before_state=is_before_state)
messages.success(
request,
msg_success
)
return HttpResponseRedirect(redirect_url)
else:
context = {
"form": self,
}
context = BaseContext(request, context).context
return render(request, template, context)
elif request.method == "GET":
context = {
"form": self,
}
context = BaseContext(request, context).context
return render(request, template, context)
else:
raise NotImplementedError
class EditCompensationStateModalForm(NewCompensationStateModalForm): class EditCompensationStateModalForm(NewCompensationStateModalForm):
state = None state = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.state = kwargs.pop("state", None) state_id = kwargs.pop("state_id", None)
self.state = CompensationState.objects.get(id=state_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit state") self.form_title = _("Edit state")
biotope_type_id = self.state.biotope_type.id if self.state.biotope_type else None biotope_type_id = self.state.biotope_type.id if self.state.biotope_type else None
@@ -172,8 +125,8 @@ class RemoveCompensationStateModalForm(RemoveModalForm):
state = None state = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
state = kwargs.pop("state", None) state_id = kwargs.pop("state_id", None)
self.state = state self.state = CompensationState.objects.get(id=state_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):

View File

@@ -53,7 +53,7 @@
</td> </td>
<td class="align-middle"> <td class="align-middle">
{% if deduction.intervention.recorded %} {% if deduction.intervention.recorded %}
<em title="{% trans 'Recorded on' %} {{deduction.intervention.recorded.timestamp}} {% trans 'by' %} {{deduction.intervention.recorded.user}}" class='fas fa-bookmark registered-bookmark'></em> <em title="{% trans 'Recorded on' %} {{obj.recorded.timestamp}} {% trans 'by' %} {{obj.recorded.user}}" class='fas fa-bookmark registered-bookmark'></em>
{% else %} {% else %}
<em title="{% trans 'Not recorded yet' %}" class='far fa-bookmark'></em> <em title="{% trans 'Not recorded yet' %}" class='far fa-bookmark'></em>
{% endif %} {% endif %}

View File

@@ -80,7 +80,11 @@ class EditCompensationActionModalFormTestCase(NewCompensationActionModalFormTest
self.compensation.actions.add(self.comp_action) self.compensation.actions.add(self.comp_action)
def test_init(self): def test_init(self):
form = EditCompensationActionModalForm(request=self.request, instance=self.compensation, action=self.comp_action) form = EditCompensationActionModalForm(
request=self.request,
instance=self.compensation,
action_id=self.comp_action.id
)
self.assertEqual(form.form_title, str(_("Edit action"))) self.assertEqual(form.form_title, str(_("Edit action")))
self.assertEqual(len(form.fields["action_type"].initial), self.comp_action.action_type.count()) self.assertEqual(len(form.fields["action_type"].initial), self.comp_action.action_type.count())
self.assertEqual(len(form.fields["action_type_details"].initial), self.comp_action.action_type_details.count()) self.assertEqual(len(form.fields["action_type_details"].initial), self.comp_action.action_type_details.count())
@@ -101,7 +105,7 @@ class EditCompensationActionModalFormTestCase(NewCompensationActionModalFormTest
"comment": comment, "comment": comment,
} }
form = EditCompensationActionModalForm(data, request=self.request, instance=self.compensation, action=self.comp_action) form = EditCompensationActionModalForm(data, request=self.request, instance=self.compensation, action_id=self.comp_action.id)
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
action = form.save() action = form.save()
@@ -126,7 +130,7 @@ class RemoveCompensationActionModalFormTestCase(EditCompensationActionModalFormT
def test_init(self): def test_init(self):
self.assertIn(self.comp_action, self.compensation.actions.all()) self.assertIn(self.comp_action, self.compensation.actions.all())
form = RemoveCompensationActionModalForm(request=self.request, instance=self.compensation, action=self.comp_action) form = RemoveCompensationActionModalForm(request=self.request, instance=self.compensation, action_id=self.comp_action.id)
self.assertEqual(form.action, self.comp_action) self.assertEqual(form.action, self.comp_action)
def test_save(self): def test_save(self):
@@ -137,7 +141,7 @@ class RemoveCompensationActionModalFormTestCase(EditCompensationActionModalFormT
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
action=self.comp_action action_id=self.comp_action.id
) )
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
self.assertIn(self.comp_action, self.compensation.actions.all()) self.assertIn(self.comp_action, self.compensation.actions.all())
@@ -186,12 +190,20 @@ class NewCompensationStateModalFormTestCase(BaseTestCase):
self.assertEqual(self.compensation.before_states.count(), 0) self.assertEqual(self.compensation.before_states.count(), 0)
self.assertEqual(self.compensation.after_states.count(), 0) self.assertEqual(self.compensation.after_states.count(), 0)
form = NewCompensationStateModalForm(data, request=self.request, instance=self.compensation) self.request.GET._mutable = True
self.request.GET.update(
{
"before": True,
}
)
self.request.GET._mutable = False
form = NewCompensationStateModalForm(
data,
request=self.request,
instance=self.compensation,
)
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
state = form.save()
is_before_state = True
state = form.save(is_before_state)
self.assertEqual(self.compensation.before_states.count(), 1) self.assertEqual(self.compensation.before_states.count(), 1)
self.assertEqual(self.compensation.after_states.count(), 0) self.assertEqual(self.compensation.after_states.count(), 0)
@@ -205,8 +217,16 @@ class NewCompensationStateModalFormTestCase(BaseTestCase):
self.assertEqual(last_log.action, UserAction.EDITED) self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.comment, ADDED_COMPENSATION_STATE) self.assertEqual(last_log.comment, ADDED_COMPENSATION_STATE)
is_before_state = False self.request.GET._mutable = True
state = form.save(is_before_state) del self.request.GET["before"]
self.request.GET._mutable = False
form = NewCompensationStateModalForm(
data,
request=self.request,
instance=self.compensation,
)
self.assertTrue(form.is_valid(), msg=form.errors)
state = form.save()
self.assertEqual(self.compensation.before_states.count(), 1) self.assertEqual(self.compensation.before_states.count(), 1)
self.assertEqual(self.compensation.after_states.count(), 1) self.assertEqual(self.compensation.after_states.count(), 1)
@@ -230,7 +250,11 @@ class EditCompensationStateModalFormTestCase(NewCompensationStateModalFormTestCa
self.compensation.after_states.add(self.comp_state) self.compensation.after_states.add(self.comp_state)
def test_init(self): def test_init(self):
form = EditCompensationStateModalForm(request=self.request, instance=self.compensation, state=self.comp_state) form = EditCompensationStateModalForm(
request=self.request,
instance=self.compensation,
state_id=self.comp_state.id
)
self.assertEqual(form.state, self.comp_state) self.assertEqual(form.state, self.comp_state)
self.assertEqual(form.form_title, str(_("Edit state"))) self.assertEqual(form.form_title, str(_("Edit state")))
@@ -261,7 +285,7 @@ class EditCompensationStateModalFormTestCase(NewCompensationStateModalFormTestCa
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
state=self.comp_state state_id=self.comp_state.id
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
@@ -282,7 +306,11 @@ class RemoveCompensationStateModalFormTestCase(EditCompensationStateModalFormTes
super().setUp() super().setUp()
def test_init(self): def test_init(self):
form = RemoveCompensationStateModalForm(request=self.request, instance=self.compensation, state=self.comp_state) form = RemoveCompensationStateModalForm(
request=self.request,
instance=self.compensation,
state_id=self.comp_state.id
)
self.assertEqual(form.state, self.comp_state) self.assertEqual(form.state, self.comp_state)
@@ -294,7 +322,7 @@ class RemoveCompensationStateModalFormTestCase(EditCompensationStateModalFormTes
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
state=self.comp_state state_id=self.comp_state.id
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)

View File

@@ -36,7 +36,7 @@ class AbstractCompensationModelTestCase(BaseTestCase):
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
deadline=self.finished_deadline, deadline_id=self.finished_deadline.id,
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
self.assertIn(self.finished_deadline, self.compensation.deadlines.all()) self.assertIn(self.finished_deadline, self.compensation.deadlines.all())

View File

@@ -7,31 +7,30 @@ Created on: 24.08.21
""" """
from django.urls import path from django.urls import path
from compensation.views.compensation.detail import DetailCompensationView
from compensation.views.compensation.document import EditCompensationDocumentView, NewCompensationDocumentView, \ from compensation.views.compensation.document import EditCompensationDocumentView, NewCompensationDocumentView, \
GetCompensationDocumentView, RemoveCompensationDocumentView GetCompensationDocumentView, RemoveCompensationDocumentView
from compensation.views.compensation.remove import RemoveCompensationView
from compensation.views.compensation.resubmission import CompensationResubmissionView from compensation.views.compensation.resubmission import CompensationResubmissionView
from compensation.views.compensation.report import CompensationPublicReportView from compensation.views.compensation.report import CompensationReportView
from compensation.views.compensation.deadline import NewCompensationDeadlineView, EditCompensationDeadlineView, \ from compensation.views.compensation.deadline import NewCompensationDeadlineView, EditCompensationDeadlineView, \
RemoveCompensationDeadlineView RemoveCompensationDeadlineView
from compensation.views.compensation.action import NewCompensationActionView, EditCompensationActionView, \ from compensation.views.compensation.action import NewCompensationActionView, EditCompensationActionView, \
RemoveCompensationActionView RemoveCompensationActionView
from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \ from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \
RemoveCompensationStateView RemoveCompensationStateView
from compensation.views.compensation.compensation import IndexCompensationView, CompensationIdentifierGeneratorView, \ from compensation.views.compensation.compensation import \
EditCompensationView, NewCompensationView CompensationIndexView, CompensationIdentifierGeneratorView, CompensationDetailView, \
NewCompensationFormView, EditCompensationFormView, RemoveCompensationView
from compensation.views.compensation.log import CompensationLogView from compensation.views.compensation.log import CompensationLogView
urlpatterns = [ urlpatterns = [
# Main compensation # Main compensation
path("", IndexCompensationView.as_view(), name="index"), path("", CompensationIndexView.as_view(), name="index"),
path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'), path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'),
path('new/<intervention_id>', NewCompensationView.as_view(), name='new'), path('new/<intervention_id>', NewCompensationFormView.as_view(), name='new'),
path('new', NewCompensationView.as_view(), name='new'), path('new', NewCompensationFormView.as_view(), name='new'),
path('<id>', DetailCompensationView.as_view(), name='detail'), path('<id>', CompensationDetailView.as_view(), name='detail'),
path('<id>/log', CompensationLogView.as_view(), name='log'), path('<id>/log', CompensationLogView.as_view(), name='log'),
path('<id>/edit', EditCompensationView.as_view(), name='edit'), path('<id>/edit', EditCompensationFormView.as_view(), name='edit'),
path('<id>/remove', RemoveCompensationView.as_view(), name='remove'), path('<id>/remove', RemoveCompensationView.as_view(), name='remove'),
path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'), path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'),
@@ -45,7 +44,7 @@ urlpatterns = [
path('<id>/deadline/new', NewCompensationDeadlineView.as_view(), name="new-deadline"), path('<id>/deadline/new', NewCompensationDeadlineView.as_view(), name="new-deadline"),
path('<id>/deadline/<deadline_id>/edit', EditCompensationDeadlineView.as_view(), name='deadline-edit'), path('<id>/deadline/<deadline_id>/edit', EditCompensationDeadlineView.as_view(), name='deadline-edit'),
path('<id>/deadline/<deadline_id>/remove', RemoveCompensationDeadlineView.as_view(), name='deadline-remove'), path('<id>/deadline/<deadline_id>/remove', RemoveCompensationDeadlineView.as_view(), name='deadline-remove'),
path('<id>/report', CompensationPublicReportView.as_view(), name='report'), path('<id>/report', CompensationReportView.as_view(), name='report'),
path('<id>/resub', CompensationResubmissionView.as_view(), name='resubmission-create'), path('<id>/resub', CompensationResubmissionView.as_view(), name='resubmission-create'),
# Documents # Documents

View File

@@ -8,13 +8,11 @@ Created on: 24.08.21
from django.urls import path from django.urls import path
from compensation.autocomplete.eco_account import EcoAccountAutocomplete from compensation.autocomplete.eco_account import EcoAccountAutocomplete
from compensation.views.eco_account.detail import DetailEcoAccountView from compensation.views.eco_account.eco_account import EcoAccountIndexView, EcoAccountIdentifierGeneratorView, \
from compensation.views.eco_account.eco_account import IndexEcoAccountView, EcoAccountIdentifierGeneratorView, \ EcoAccountDetailView, NewEcoAccountFormView, EditEcoAccountFormView, RemoveEcoAccountView
NewEcoAccountView, EditEcoAccountView
from compensation.views.eco_account.log import EcoAccountLogView from compensation.views.eco_account.log import EcoAccountLogView
from compensation.views.eco_account.record import EcoAccountRecordView from compensation.views.eco_account.record import EcoAccountRecordView
from compensation.views.eco_account.remove import RemoveEcoAccountView from compensation.views.eco_account.report import EcoAccountReportView
from compensation.views.eco_account.report import EcoAccountPublicReportView
from compensation.views.eco_account.resubmission import EcoAccountResubmissionView from compensation.views.eco_account.resubmission import EcoAccountResubmissionView
from compensation.views.eco_account.state import NewEcoAccountStateView, EditEcoAccountStateView, \ from compensation.views.eco_account.state import NewEcoAccountStateView, EditEcoAccountStateView, \
RemoveEcoAccountStateView RemoveEcoAccountStateView
@@ -30,14 +28,14 @@ from compensation.views.eco_account.deduction import NewEcoAccountDeductionView,
app_name = "acc" app_name = "acc"
urlpatterns = [ urlpatterns = [
path("", IndexEcoAccountView.as_view(), name="index"), path("", EcoAccountIndexView.as_view(), name="index"),
path('new/', NewEcoAccountView.as_view(), name='new'), path('new/', NewEcoAccountFormView.as_view(), name='new'),
path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'), path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'),
path('<id>', DetailEcoAccountView.as_view(), name='detail'), path('<id>', EcoAccountDetailView.as_view(), name='detail'),
path('<id>/log', EcoAccountLogView.as_view(), name='log'), path('<id>/log', EcoAccountLogView.as_view(), name='log'),
path('<id>/record', EcoAccountRecordView.as_view(), name='record'), path('<id>/record', EcoAccountRecordView.as_view(), name='record'),
path('<id>/report', EcoAccountPublicReportView.as_view(), name='report'), path('<id>/report', EcoAccountReportView.as_view(), name='report'),
path('<id>/edit', EditEcoAccountView.as_view(), name='edit'), path('<id>/edit', EditEcoAccountFormView.as_view(), name='edit'),
path('<id>/remove', RemoveEcoAccountView.as_view(), name='remove'), path('<id>/remove', RemoveEcoAccountView.as_view(), name='remove'),
path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'), path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'),

View File

@@ -6,7 +6,7 @@ Created on: 24.08.21
""" """
from django.urls import path from django.urls import path
from compensation.views.payment import * from compensation.views.payment import NewPaymentView, RemovePaymentView, EditPaymentView
app_name = "pay" app_name = "pay"
urlpatterns = [ urlpatterns = [

View File

@@ -5,53 +5,23 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.decorators import method_decorator
from compensation.forms.modals.compensation_action import RemoveCompensationActionModalForm, \ from compensation.models import Compensation
EditCompensationActionModalForm, NewCompensationActionModalForm
from compensation.models import Compensation, CompensationAction
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.utils.message_templates import COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_EDITED, \
COMPENSATION_ACTION_ADDED
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \ from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView AbstractRemoveCompensationActionView
_COMPENSATION_DETAIL_URL_NAME = "compensation:detail"
class NewCompensationActionView(AbstractNewCompensationActionView): class NewCompensationActionView(AbstractNewCompensationActionView):
model = Compensation _MODEL_CLS = Compensation
redirect_url = "compensation:detail" _REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationActionView(AbstractEditCompensationActionView): class EditCompensationActionView(AbstractEditCompensationActionView):
model = Compensation _MODEL_CLS = Compensation
redirect_url = "compensation:detail" _REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationActionView(AbstractRemoveCompensationActionView): class RemoveCompensationActionView(AbstractRemoveCompensationActionView):
model = Compensation _MODEL_CLS = Compensation
redirect_url = "compensation:detail" _REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -6,260 +6,169 @@ Created on: 19.08.22
""" """
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpRequest, JsonResponse
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.utils.decorators import method_decorator from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from compensation.forms.compensation import EditCompensationForm, NewCompensationForm from compensation.forms.compensation import EditCompensationForm, NewCompensationForm
from compensation.models import Compensation from compensation.models import Compensation
from compensation.tables.compensation import CompensationTable from compensation.tables.compensation import CompensationTable
from intervention.models import Intervention 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.decorators import shared_access_required, default_group_required from konova.forms.modals import RemoveModalForm
from konova.forms import SimpleGeomForm from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE, \
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER RECORDED_BLOCKS_EDIT, PARAMS_INVALID
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, \ from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
IDENTIFIER_REPLACED, COMPENSATION_ADDED_TEMPLATE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE BaseEditSpatialLocatedObjectFormView
from konova.views.identifier import AbstractIdentifierGeneratorView from konova.views.detail import BaseDetailView
from konova.views.index import AbstractIndexView from konova.views.remove import BaseRemoveModalFormView
class IndexCompensationView(AbstractIndexView): class CompensationIndexView(LoginRequiredMixin, BaseIndexView):
def get(self, request, *args, **kwargs) -> HttpResponse: _TAB_TITLE = _("Compensations - Overview")
""" _INDEX_TABLE_CLS = CompensationTable
Renders the index view for compensation
Args: def _get_queryset(self):
request (HttpRequest): The incoming request qs = Compensation.objects.filter(
Returns:
A rendered view
"""
compensations = Compensation.objects.filter(
deleted=None, # only show those which are not deleted individually deleted=None, # only show those which are not deleted individually
intervention__deleted=None, # and don't show the ones whose intervention has been deleted intervention__deleted=None, # and don't show the ones whose intervention has been deleted
).order_by( ).order_by(
"-modified__timestamp" "-modified__timestamp"
) )
table = CompensationTable( return qs
request=request,
queryset=compensations
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("Compensations - Overview"),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
class NewCompensationView(LoginRequiredMixin, View): class NewCompensationFormView(BaseNewSpatialLocatedObjectFormView):
_FORM_CLS = NewCompensationForm
_MODEL_CLS = Compensation
_TEMPLATE = "compensation/form/view.html" _TEMPLATE = "compensation/form/view.html"
_TAB_TITLE = _("New Compensation")
_REDIRECT_URL = "compensation:detail"
@method_decorator(default_group_required) def _user_has_shared_access(self, user, **kwargs):
@method_decorator(shared_access_required(Intervention, "intervention_id")) # On a new compensation make sure the intervention (if call came directly through an intervention's detail
def get(self, request: HttpRequest, intervention_id: str = None, *args, **kwargs) -> HttpResponse: # view) is shared with the user
""" intervention_id = kwargs.get("intervention_id", None)
Renders a view for new compensation if not intervention_id:
return True
A compensation creation may be called directly from the parent-intervention object. If so - we may take else:
the intervention's id and directly link the compensation to it.
Args:
request (HttpRequest): The incoming request
intervention_id (str): The intervention identifier
Returns:
"""
if intervention_id:
# If the parent-intervention is recorded, we are not allowed to change anything on it's data.
# Not even adding new child elements like compensations!
intervention = get_object_or_404(Intervention, id=intervention_id) intervention = get_object_or_404(Intervention, id=intervention_id)
recording_state_blocks_actions = intervention.is_recorded return intervention.is_shared_with(user)
if recording_state_blocks_actions:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("intervention:detail", id=intervention_id)
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id) def _user_has_permission(self, user, **kwargs):
geom_form = SimpleGeomForm(request.POST or None, read_only=False) # User has to be an ets user
return user.is_default_user()
context = { def dispatch(self, request, *args, **kwargs):
"form": data_form, # Make sure there is an existing intervention based on the given id
"geom_form": geom_form, # Compensations can not exist without an intervention
TAB_TITLE_IDENTIFIER: _("New compensation"), intervention_id = kwargs.get("intervention_id", None)
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "intervention_id"))
def post(self, request: HttpRequest, intervention_id: str = None, *args, **kwargs) -> HttpResponse:
"""
Renders a view for a new compensation creation
Args:
request (HttpRequest): The incoming request
Returns:
"""
if intervention_id: if intervention_id:
# If the parent-intervention is recorded, we are not allowed to change anything on it's data. try:
# Not even adding new child elements like compensations! intervention = Intervention.objects.get(id=intervention_id)
intervention = get_object_or_404(Intervention, id=intervention_id) if intervention.is_recorded:
recording_state_blocks_actions = intervention.is_recorded messages.info(
if recording_state_blocks_actions: request,
messages.info( RECORDED_BLOCKS_EDIT
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 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
) )
) return redirect("intervention:detail", id=intervention_id)
messages.success(request, COMPENSATION_ADDED_TEMPLATE.format(comp.identifier)) except ObjectDoesNotExist:
if geom_form.has_geometry_simplified(): messages.error(request, PARAMS_INVALID, extra_tags="danger")
messages.info( return redirect("home")
request, return super().dispatch(request, *args, **kwargs)
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", )
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New compensation"),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
class CompensationIdentifierGeneratorView(AbstractIdentifierGeneratorView): class EditCompensationFormView(BaseEditSpatialLocatedObjectFormView):
_MODEL = Compensation _MODEL_CLS = Compensation
_FORM_CLS = EditCompensationForm
class EditCompensationView(LoginRequiredMixin, View):
_TEMPLATE = "compensation/form/view.html" _TEMPLATE = "compensation/form/view.html"
_REDIRECT_URL = "compensation:detail"
@method_decorator(default_group_required) def _user_has_permission(self, user, **kwargs):
@method_decorator(shared_access_required(Compensation, "id")) # User has to be a default user
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: return user.is_default_user()
"""
Renders a view for editing compensations class CompensationIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView):
_MODEL_CLS = Compensation
_REDIRECT_URL = "compensation:index"
class CompensationDetailView(BaseDetailView):
_MODEL_CLS = Compensation
_TEMPLATE = "compensation/detail/compensation/view.html"
def _get_object(self, id: str):
""" Returns the compensation
Args: Args:
request (HttpRequest): The incoming request id (str): The compensation's id
Returns:
obj (Compensation): The compensation
"""
comp = get_object_or_404(
Compensation.objects.select_related(
"modified",
"created",
"geometry"
),
id=id,
deleted=None,
intervention__deleted=None,
)
return comp
def _get_detail_context(self, obj: Compensation):
""" Generate object specific detail context for view
Args:
obj (): The record
Returns: Returns:
""" """
# Get object from db # Order states according to surface
comp = get_object_or_404(Compensation, id=id) before_states = obj.before_states.all().prefetch_related("biotope_type").order_by("-surface")
if comp.is_recorded: after_states = obj.after_states.all().prefetch_related("biotope_type").order_by("-surface")
messages.info( actions = obj.actions.all().prefetch_related("action_type")
request,
RECORDED_BLOCKS_EDIT
)
return redirect("compensation:detail", id=id)
# Create forms, initialize with values from db/from POST request # Precalculate logical errors between before- and after-states
data_form = EditCompensationForm(request.POST or None, instance=comp) # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp) sum_before_states = obj.get_surface_before_states()
sum_after_states = obj.get_surface_after_states()
diff_states = abs(sum_before_states - sum_after_states)
last_checked = obj.intervention.get_last_checked_action()
last_checked_tooltip = ""
if last_checked:
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(
last_checked.get_timestamp_str_formatted(),
last_checked.user
)
context = { context = {
"form": data_form, "last_checked": last_checked,
"geom_form": geom_form, "last_checked_tooltip": last_checked_tooltip,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier), "actions": actions,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"has_finished_deadlines": obj.get_finished_deadlines().exists(),
} }
context = BaseContext(request, context).context return context
return render(request, self._TEMPLATE, context)
@method_decorator(default_group_required) class RemoveCompensationView(LoginRequiredMixin, BaseRemoveModalFormView):
@method_decorator(shared_access_required(Compensation, "id")) _MODEL_CLS = Compensation
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: _FORM_CLS = RemoveModalForm
_REDIRECT_URL = "compensation:index"
""" def _user_has_permission(self, user, **kwargs):
Renders a view for editing compensations return user.is_default_user()
Args:
request (HttpRequest): The incoming request
Returns:
"""
# 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 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", )
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)

View File

@@ -5,45 +5,21 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import Compensation from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.deadline import AbstractRemoveDeadlineView, AbstractEditDeadlineView, AbstractNewDeadlineView from konova.views.deadline import AbstractRemoveDeadlineView, AbstractEditDeadlineView, AbstractNewDeadlineView
_COMPENSATION_DETAIL_URL_NAME = "compensation:detail"
class NewCompensationDeadlineView(AbstractNewDeadlineView): class NewCompensationDeadlineView(AbstractNewDeadlineView):
model = Compensation _MODEL_CLS = Compensation
redirect_url = "compensation:detail" _REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationDeadlineView(AbstractEditDeadlineView): class EditCompensationDeadlineView(AbstractEditDeadlineView):
model = Compensation _MODEL_CLS = Compensation
redirect_url = "compensation:detail" _REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationDeadlineView(AbstractRemoveDeadlineView): class RemoveCompensationDeadlineView(AbstractRemoveDeadlineView):
model = Compensation _MODEL_CLS = Compensation
redirect_url = "compensation:detail" _REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -1,97 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.contrib import messages
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render, get_object_or_404
from compensation.models import Compensation
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.settings import ETS_GROUP, ZB_GROUP, DEFAULT_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE, DATA_CHECKED_PREVIOUSLY_TEMPLATE
from konova.views.detail import AbstractDetailView
class DetailCompensationView(AbstractDetailView):
_TEMPLATE = "compensation/detail/compensation/view.html"
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" Renders a detail view for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
comp = get_object_or_404(
Compensation.objects.select_related(
"modified",
"created",
"geometry"
),
id=id,
deleted=None,
intervention__deleted=None,
)
geom_form = SimpleGeomForm(instance=comp)
parcels = comp.get_underlying_parcels()
_user = request.user
is_data_shared = comp.intervention.is_shared_with(_user)
# Order states according to surface
before_states = comp.before_states.all().prefetch_related("biotope_type").order_by("-surface")
after_states = comp.after_states.all().prefetch_related("biotope_type").order_by("-surface")
actions = comp.actions.all().prefetch_related("action_type")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = comp.get_surface_before_states()
sum_after_states = comp.get_surface_after_states()
diff_states = abs(sum_before_states - sum_after_states)
request = comp.set_status_messages(request)
last_checked = comp.intervention.get_last_checked_action()
last_checked_tooltip = ""
if last_checked:
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(
last_checked.get_timestamp_str_formatted(),
last_checked.user
)
requesting_user_is_only_shared_user = comp.is_only_shared_with(_user)
if requesting_user_is_only_shared_user:
messages.info(
request,
DO_NOT_FORGET_TO_SHARE
)
context = {
"obj": comp,
"last_checked": last_checked,
"last_checked_tooltip": last_checked_tooltip,
"geom_form": geom_form,
"parcels": parcels,
"is_entry_shared": is_data_shared,
"actions": actions,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"is_default_member": _user.in_group(DEFAULT_GROUP),
"is_zb_member": _user.in_group(ZB_GROUP),
"is_ets_member": _user.in_group(ETS_GROUP),
"LANIS_LINK": comp.get_LANIS_link(),
TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}",
"has_finished_deadlines": comp.get_finished_deadlines().exists(),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)

View File

@@ -5,62 +5,33 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from compensation.forms.modals.document import NewCompensationDocumentModalForm, EditCompensationDocumentModalForm, \
from django.utils.decorators import method_decorator RemoveCompensationDocumentModalForm
from compensation.forms.modals.document import NewCompensationDocumentModalForm
from compensation.models import Compensation, CompensationDocument from compensation.models import Compensation, CompensationDocument
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.forms.modals import EditDocumentModalForm
from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \ from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \
AbstractEditDocumentView AbstractEditDocumentView
class NewCompensationDocumentView(AbstractNewDocumentView): class NewCompensationDocumentView(AbstractNewDocumentView):
model = Compensation _MODEL_CLS = Compensation
form = NewCompensationDocumentModalForm _FORM_CLS = NewCompensationDocumentModalForm
redirect_url = "compensation:detail" _REDIRECT_URL = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class GetCompensationDocumentView(AbstractGetDocumentView): class GetCompensationDocumentView(AbstractGetDocumentView):
model = Compensation _MODEL_CLS = Compensation
document_model = CompensationDocument _DOCUMENT_CLS = CompensationDocument
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationDocumentView(AbstractRemoveDocumentView): class RemoveCompensationDocumentView(AbstractRemoveDocumentView):
model = Compensation _MODEL_CLS = Compensation
document_model = CompensationDocument _DOCUMENT_CLS = CompensationDocument
_FORM_CLS = RemoveCompensationDocumentModalForm
@method_decorator(login_required_modal) _REDIRECT_URL = "compensation:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationDocumentView(AbstractEditDocumentView): class EditCompensationDocumentView(AbstractEditDocumentView):
model = Compensation _MODEL_CLS = Compensation
document_model = CompensationDocument _DOCUMENT_CLS = CompensationDocument
form = EditDocumentModalForm _FORM_CLS = EditCompensationDocumentModalForm
redirect_url = "compensation:detail" _REDIRECT_URL = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -5,20 +5,11 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
from compensation.models import Compensation from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.log import AbstractLogView from konova.views.log import AbstractLogView
class CompensationLogView(AbstractLogView): class CompensationLogView(LoginRequiredMixin, AbstractLogView):
model = Compensation _MODEL_CLS = Compensation
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -1,20 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.http import HttpRequest, HttpResponse
from django.utils.decorators import method_decorator
from compensation.models import Compensation
from konova.decorators import shared_access_required
from konova.views.remove import AbstractRemoveView
class RemoveCompensationView(AbstractRemoveView):
_MODEL = Compensation
_REDIRECT_URL = "compensation:index"
@method_decorator(shared_access_required(Compensation, "id"))
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
return super().get(request, *args, **kwargs)

View File

@@ -5,81 +5,48 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.models import Compensation from compensation.models import Compensation
from konova.contexts import BaseContext from konova.sub_settings.django_settings import BASE_URL
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.qrcode import QrCode from konova.utils.qrcode import QrCode
from konova.views.report import AbstractPublicReportView from konova.views.report import BaseReportView
class CompensationPublicReportView(AbstractPublicReportView): class BaseCompensationReportView(BaseReportView):
def _get_compensation_report_context(self, obj):
# Order states by surface
before_states = obj.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = obj.after_states.all().order_by("-surface").prefetch_related("biotope_type")
actions = obj.actions.all().prefetch_related("action_type")
return {
"before_states": before_states,
"after_states": after_states,
"actions": actions,
}
class CompensationReportView(BaseCompensationReportView):
_MODEL = Compensation
_TEMPLATE = "compensation/report/compensation/report.html" _TEMPLATE = "compensation/report/compensation/report.html"
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: def _get_report_context(self, obj):
""" Renders the public report view report_url = BASE_URL + reverse("compensation:report", args=(obj.id,))
qrcode_report = QrCode(report_url, 10)
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
Args: report_context = {
request (HttpRequest): The incoming request
id (str): The id of the intervention
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
tab_title = _("Report {}").format(comp.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not comp.is_ready_for_publish():
template = "report/unavailable.html"
context = {
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=comp
)
parcels = comp.get_underlying_parcels()
qrcode = QrCode(
content=request.build_absolute_uri(reverse("compensation:report", args=(id,))),
size=10
)
qrcode_lanis = QrCode(
content=comp.get_LANIS_link(),
size=7
)
# Order states by surface
before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type")
actions = comp.actions.all().prefetch_related("action_type")
context = {
"obj": comp,
"qrcode": { "qrcode": {
"img": qrcode.get_img(), "img": qrcode_report.get_img(),
"url": qrcode.get_content(), "url": qrcode_report.get_content(),
}, },
"qrcode_lanis": { "qrcode_lanis": {
"img": qrcode_lanis.get_img(), "img": qrcode_lanis.get_img(),
"url": qrcode_lanis.get_content(), "url": qrcode_lanis.get_content(),
}, },
"is_entry_shared": False, # disables action buttons during rendering "is_entry_shared": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
"geom_form": geom_form,
"parcels": parcels,
"actions": actions,
"tables_scrollable": False, "tables_scrollable": False,
TAB_TITLE_IDENTIFIER: tab_title,
} }
context = BaseContext(request, context).context report_context.update(self._get_compensation_report_context(obj))
return render(request, self._TEMPLATE, context) return report_context

View File

@@ -5,22 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from compensation.forms.modals.resubmission import CompensationResubmissionModalForm
from django.utils.decorators import method_decorator
from compensation.models import Compensation from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView from konova.views.resubmission import AbstractResubmissionView
class CompensationResubmissionView(AbstractResubmissionView): class CompensationResubmissionView(AbstractResubmissionView):
model = Compensation _MODEL_CLS = Compensation
redirect_url_base = "compensation:detail" _FORM_CLS = CompensationResubmissionModalForm
form_action_url_base = "compensation:resubmission-create" _REDIRECT_URL = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -5,46 +5,21 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import Compensation from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \ from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \
AbstractRemoveCompensationStateView AbstractRemoveCompensationStateView
class NewCompensationStateView(AbstractNewCompensationStateView): class NewCompensationStateView(AbstractNewCompensationStateView):
model = Compensation _MODEL_CLS = Compensation
redirect_url = "compensation:detail" _REDIRECT_URL = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationStateView(AbstractEditCompensationStateView): class EditCompensationStateView(AbstractEditCompensationStateView):
model = Compensation _MODEL_CLS = Compensation
redirect_url = "compensation:detail" _REDIRECT_URL = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationStateView(AbstractRemoveCompensationStateView): class RemoveCompensationStateView(AbstractRemoveCompensationStateView):
model = Compensation _MODEL_CLS = Compensation
redirect_url = "compensation:detail" _REDIRECT_URL = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -5,46 +5,22 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \ from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView AbstractRemoveCompensationActionView
_ECO_ACCOUNT_DETAIL_URL_NAME = "compensation:acc:detail"
class NewEcoAccountActionView(AbstractNewCompensationActionView): class NewEcoAccountActionView(AbstractNewCompensationActionView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url = "compensation:acc:detail" _REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountActionView(AbstractEditCompensationActionView): class EditEcoAccountActionView(AbstractEditCompensationActionView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url = "compensation:acc:detail" _REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountActionView(AbstractRemoveCompensationActionView): class RemoveEcoAccountActionView(AbstractRemoveCompensationActionView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url = "compensation:acc:detail" _REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -5,45 +5,22 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.deadline import AbstractNewDeadlineView, AbstractEditDeadlineView, AbstractRemoveDeadlineView from konova.views.deadline import AbstractNewDeadlineView, AbstractEditDeadlineView, AbstractRemoveDeadlineView
_ECO_ACCOUNT_DETAIL_URL_NAME = "compensation:acc:detail"
class NewEcoAccountDeadlineView(AbstractNewDeadlineView): class NewEcoAccountDeadlineView(AbstractNewDeadlineView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url = "compensation:acc:detail" _REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountDeadlineView(AbstractEditDeadlineView): class EditEcoAccountDeadlineView(AbstractEditDeadlineView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url = "compensation:acc:detail" _REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountDeadlineView(AbstractRemoveDeadlineView): class RemoveEcoAccountDeadlineView(AbstractRemoveDeadlineView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url = "compensation:acc:detail" _REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -5,54 +5,33 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404 from django.http import Http404
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import default_group_required, login_required_modal
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
_ECO_ACCOUNT_DETAIl_URL_NAME = "compensation:acc:detail"
class NewEcoAccountDeductionView(AbstractNewDeductionView): class NewEcoAccountDeductionView(LoginRequiredMixin, AbstractNewDeductionView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url = "compensation:acc:detail" _REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def _custom_check(self, obj): def _custom_check(self, obj):
# New deductions can only be created if the eco account has been recorded
if not obj.recorded: if not obj.recorded:
raise Http404() raise Http404()
def _check_for_recorded_instance(self, obj):
class EditEcoAccountDeductionView(AbstractEditDeductionView): # Deductions can be created on recorded as well as on non-recorded entries
def _custom_check(self, obj): return None
pass
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountDeductionView(AbstractRemoveDeductionView): class EditEcoAccountDeductionView(LoginRequiredMixin, AbstractEditDeductionView):
def _custom_check(self, obj): _MODEL_CLS = EcoAccount
pass _REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView):
_MODEL_CLS = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME

View File

@@ -1,97 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.contrib import messages
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render, get_object_or_404
from compensation.models import EcoAccount
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.settings import ETS_GROUP, ZB_GROUP, DEFAULT_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE
from konova.views.detail import AbstractDetailView
class DetailEcoAccountView(AbstractDetailView):
_TEMPLATE = "compensation/detail/eco_account/view.html"
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" Renders a detail view for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
acc = get_object_or_404(
EcoAccount.objects.prefetch_related(
"deadlines",
).select_related(
'geometry',
'responsible',
),
id=id,
deleted=None,
)
geom_form = SimpleGeomForm(instance=acc)
parcels = acc.get_underlying_parcels()
_user = request.user
is_data_shared = acc.is_shared_with(_user)
# Order states according to surface
before_states = acc.before_states.order_by("-surface")
after_states = acc.after_states.order_by("-surface")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = acc.get_surface_before_states()
sum_after_states = acc.get_surface_after_states()
diff_states = abs(sum_before_states - sum_after_states)
# Calculate rest of available surface for deductions
available_total = acc.deductable_rest
available_relative = acc.get_deductable_rest_relative()
# Prefetch related data to decrease the amount of db connections
deductions = acc.deductions.filter(
intervention__deleted=None,
)
actions = acc.actions.all()
request = acc.set_status_messages(request)
requesting_user_is_only_shared_user = acc.is_only_shared_with(_user)
if requesting_user_is_only_shared_user:
messages.info(
request,
DO_NOT_FORGET_TO_SHARE
)
context = {
"obj": acc,
"geom_form": geom_form,
"parcels": parcels,
"is_entry_shared": is_data_shared,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"available": available_relative,
"available_total": available_total,
"is_default_member": _user.in_group(DEFAULT_GROUP),
"is_zb_member": _user.in_group(ZB_GROUP),
"is_ets_member": _user.in_group(ETS_GROUP),
"LANIS_LINK": acc.get_LANIS_link(),
"deductions": deductions,
"actions": actions,
TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}",
"has_finished_deadlines": acc.get_finished_deadlines().exists(),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)

View File

@@ -5,65 +5,33 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from compensation.forms.modals.document import NewEcoAccountDocumentModalForm, RemoveEcoAccountDocumentModalForm, \
from django.http import HttpRequest EditEcoAccountDocumentModalForm
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.decorators import method_decorator
from compensation.forms.modals.document import NewEcoAccountDocumentModalForm
from compensation.models import EcoAccount, EcoAccountDocument from compensation.models import EcoAccount, EcoAccountDocument
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.forms.modals import EditDocumentModalForm
from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \ from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \
AbstractEditDocumentView AbstractEditDocumentView
class NewEcoAccountDocumentView(AbstractNewDocumentView): class NewEcoAccountDocumentView(AbstractNewDocumentView):
model = EcoAccount _MODEL_CLS = EcoAccount
form = NewEcoAccountDocumentModalForm _FORM_CLS = NewEcoAccountDocumentModalForm
redirect_url = "compensation:acc:detail" _REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class GetEcoAccountDocumentView(AbstractGetDocumentView): class GetEcoAccountDocumentView(AbstractGetDocumentView):
model = EcoAccount _MODEL_CLS = EcoAccount
document_model = EcoAccountDocument _DOCUMENT_CLS = EcoAccountDocument
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountDocumentView(AbstractRemoveDocumentView): class RemoveEcoAccountDocumentView(AbstractRemoveDocumentView):
model = EcoAccount _MODEL_CLS = EcoAccount
document_model = EcoAccountDocument _DOCUMENT_CLS = EcoAccountDocument
_FORM_CLS = RemoveEcoAccountDocumentModalForm
@method_decorator(login_required_modal) _REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountDocumentView(AbstractEditDocumentView): class EditEcoAccountDocumentView(AbstractEditDocumentView):
model = EcoAccount _MODEL_CLS = EcoAccount
document_model = EcoAccountDocument _DOCUMENT_CLS = EcoAccountDocument
form = EditDocumentModalForm _FORM_CLS = EditEcoAccountDocumentModalForm
redirect_url = "compensation:acc:detail" _REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -6,94 +6,81 @@ Created on: 19.08.22
""" """
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm, RemoveEcoAccountModalForm
from compensation.models import EcoAccount from compensation.models import EcoAccount
from compensation.tables.eco_account import EcoAccountTable from compensation.tables.eco_account import EcoAccountTable
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import shared_access_required, default_group_required from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.forms import SimpleGeomForm from konova.forms import SimpleGeomForm
from konova.settings import ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, FORM_INVALID, \ from konova.utils.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECORDED_BLOCKS_EDIT, FORM_INVALID, \
IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
from konova.views.identifier import AbstractIdentifierGeneratorView from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
from konova.views.index import AbstractIndexView BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class IndexEcoAccountView(AbstractIndexView): class EcoAccountIndexView(LoginRequiredMixin, BaseIndexView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: _INDEX_TABLE_CLS = EcoAccountTable
""" _TAB_TITLE = _("Eco-account - Overview")
Renders the index view for eco accounts
Args: def _get_queryset(self):
request (HttpRequest): The incoming request qs = EcoAccount.objects.filter(
Returns:
A rendered view
"""
eco_accounts = EcoAccount.objects.filter(
deleted=None, deleted=None,
).order_by( ).order_by(
"-modified__timestamp" "-modified__timestamp"
) )
table = EcoAccountTable( return qs
request=request,
queryset=eco_accounts
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("Eco-account - Overview"),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
class NewEcoAccountView(LoginRequiredMixin, View): class NewEcoAccountFormView(BaseNewSpatialLocatedObjectFormView):
_FORM_CLS = NewEcoAccountForm
_MODEL_CLS = EcoAccount
_TEMPLATE = "compensation/form/view.html" _TEMPLATE = "compensation/form/view.html"
_TAB_TITLE = _("New Eco-Account")
_REDIRECT_URL = "compensation:acc:detail"
@method_decorator(default_group_required) def _user_has_permission(self, user, **kwargs):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: # User has to be a default user
""" return user.is_default_user()
Renders a view for a new eco account creation
Args:
request (HttpRequest): The incoming request
Returns: 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, **kwargs):
data_form = NewEcoAccountForm(request.POST or None) # User has to be a default user
geom_form = SimpleGeomForm(request.POST or None, read_only=False) return user.is_default_user()
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context) @login_required
@default_group_required
def new_view(request: HttpRequest):
"""
Renders a view for a new eco account creation
@method_decorator(default_group_required) Args:
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: request (HttpRequest): The incoming request
""" Returns:
Renders a view for a new eco account creation
Args: """
request (HttpRequest): The incoming request template = "compensation/form/view.html"
data_form = NewEcoAccountForm(request.POST or None)
Returns: geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
"""
data_form = NewEcoAccountForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if data_form.is_valid() and geom_form.is_valid(): if data_form.is_valid() and geom_form.is_valid():
generated_identifier = data_form.cleaned_data.get("identifier", None) generated_identifier = data_form.cleaned_data.get("identifier", None)
acc = data_form.save(request.user, geom_form) acc = data_form.save(request.user, geom_form)
@@ -111,92 +98,61 @@ class NewEcoAccountView(LoginRequiredMixin, View):
request, request,
GEOMETRY_SIMPLIFIED GEOMETRY_SIMPLIFIED
) )
num_ignored_geometries = geom_form.get_num_geometries_ignored() num_ignored_geometries = geom_form.get_num_geometries_ignored()
if num_ignored_geometries > 0: if num_ignored_geometries > 0:
messages.info( messages.info(
request, request,
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries) GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
) )
return redirect("compensation:acc:detail", id=acc.id) return redirect("compensation:acc:detail", id=acc.id)
else: else:
messages.error(request, FORM_INVALID, extra_tags="danger", ) messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
context = { # For clarification: nothing in this case
"form": data_form, pass
"geom_form": geom_form, context = {
TAB_TITLE_IDENTIFIER: _("New Eco-Account"), "form": data_form,
} "geom_form": geom_form,
context = BaseContext(request, context).context TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
}
return render(request, self._TEMPLATE, context) context = BaseContext(request, context).context
return render(request, template, context)
class EcoAccountIdentifierGeneratorView(AbstractIdentifierGeneratorView):
_MODEL = EcoAccount
class EditEcoAccountView(LoginRequiredMixin, View): class EcoAccountIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView):
_TEMPLATE = "compensation/form/view.html" _MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:index"
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" @login_required
Renders a view for editing compensations @default_group_required
@shared_access_required(EcoAccount, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args: Args:
request (HttpRequest): The incoming request request (HttpRequest): The incoming request
Returns: Returns:
""" """
# Get object from db template = "compensation/form/view.html"
acc = get_object_or_404(EcoAccount, id=id) # Get object from db
if acc.is_recorded: acc = get_object_or_404(EcoAccount, id=id)
messages.info( if acc.is_recorded:
request, messages.info(
RECORDED_BLOCKS_EDIT request,
) RECORDED_BLOCKS_EDIT
return redirect("compensation:acc:detail", id=id) )
return redirect("compensation:acc:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
"""
Renders a view for editing compensations
Args:
request (HttpRequest): The incoming request
Returns:
"""
# Get object from db
acc = get_object_or_404(EcoAccount, id=id)
if acc.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("compensation:acc:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
# Create forms, initialize with values from db/from POST request
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
if request.method == "POST":
data_form_valid = data_form.is_valid() data_form_valid = data_form.is_valid()
geom_form_valid = geom_form.is_valid() geom_form_valid = geom_form.is_valid()
if data_form_valid and geom_form_valid: if data_form_valid and geom_form_valid:
@@ -208,21 +164,101 @@ class EditEcoAccountView(LoginRequiredMixin, View):
request, request,
GEOMETRY_SIMPLIFIED GEOMETRY_SIMPLIFIED
) )
num_ignored_geometries = geom_form.get_num_geometries_ignored() num_ignored_geometries = geom_form.get_num_geometries_ignored()
if num_ignored_geometries > 0: if num_ignored_geometries > 0:
messages.info( messages.info(
request, request,
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries) GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
) )
return redirect("compensation:acc:detail", id=acc.id) return redirect("compensation:acc:detail", id=acc.id)
else: else:
messages.error(request, FORM_INVALID, extra_tags="danger", ) 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(acc.identifier),
}
context = BaseContext(request, context).context
return render(request, template, context)
class EcoAccountDetailView(BaseDetailView):
_MODEL_CLS = EcoAccount
_TEMPLATE = "compensation/detail/eco_account/view.html"
def _get_object(self, id: str):
""" Fetch object for detail view
Args:
id (str): The record's id'
Returns:
"""
acc = get_object_or_404(
EcoAccount.objects.prefetch_related(
"deadlines",
).select_related(
'geometry',
'responsible',
),
id=id,
deleted=None,
)
return acc
def _get_detail_context(self, obj: EcoAccount):
""" Generate object specific detail context for view
Args:
obj (): The record
Returns:
"""
# Order states according to surface
before_states = obj.before_states.order_by("-surface")
after_states = obj.after_states.order_by("-surface")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = obj.get_surface_before_states()
sum_after_states = obj.get_surface_after_states()
diff_states = abs(sum_before_states - sum_after_states)
# Calculate rest of available surface for deductions
available_total = obj.deductable_rest
available_relative = obj.get_deductable_rest_relative()
# Prefetch related data to decrease the amount of db connections
deductions = obj.deductions.filter(
intervention__deleted=None,
)
actions = obj.actions.all()
context = { context = {
"form": data_form, "before_states": before_states,
"geom_form": geom_form, "after_states": after_states,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier), "sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"available": available_relative,
"available_total": available_total,
"deductions": deductions,
"actions": actions,
"has_finished_deadlines": obj.get_finished_deadlines().exists(),
} }
context = BaseContext(request, context).context return context
return render(request, self._TEMPLATE, context)
class RemoveEcoAccountView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = EcoAccount
_FORM_CLS = RemoveEcoAccountModalForm
_REDIRECT_URL = "compensation:acc:index"
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()

View File

@@ -5,20 +5,11 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.log import AbstractLogView from konova.views.log import AbstractLogView
class EcoAccountLogView(AbstractLogView): class EcoAccountLogView(LoginRequiredMixin, AbstractLogView):
model = EcoAccount _MODEL_CLS = EcoAccount
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -5,20 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.record import AbstractRecordView from konova.views.record import AbstractRecordView
class EcoAccountRecordView(AbstractRecordView): class EcoAccountRecordView(LoginRequiredMixin, AbstractRecordView):
model = EcoAccount _MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -1,22 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.http import HttpRequest, HttpResponse
from django.utils.decorators import method_decorator
from compensation.forms.eco_account import RemoveEcoAccountModalForm
from compensation.models import EcoAccount
from konova.decorators import shared_access_required
from konova.views.remove import AbstractRemoveView
class RemoveEcoAccountView(AbstractRemoveView):
_MODEL = EcoAccount
_REDIRECT_URL = "compensation:acc:index"
_FORM = RemoveEcoAccountModalForm
@method_decorator(shared_access_required(EcoAccount, "id"))
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
return super().get(request, *args, **kwargs)

View File

@@ -5,88 +5,41 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.contexts import BaseContext from compensation.views.compensation.report import BaseCompensationReportView
from konova.forms import SimpleGeomForm from konova.sub_settings.django_settings import BASE_URL
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.qrcode import QrCode from konova.utils.qrcode import QrCode
from konova.views.report import AbstractPublicReportView
class EcoAccountPublicReportView(AbstractPublicReportView): class EcoAccountReportView(BaseCompensationReportView):
_MODEL = EcoAccount
_TEMPLATE = "compensation/report/eco_account/report.html" _TEMPLATE = "compensation/report/eco_account/report.html"
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: def _get_report_context(self, obj):
""" Renders the public report view report_url = BASE_URL + reverse("compensation:acc:report", args=(obj.id,))
qrcode_report = QrCode(report_url, 10)
Args: qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
request (HttpRequest): The incoming request
id (str): The id of the intervention
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
tab_title = _("Report {}").format(acc.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not acc.is_ready_for_publish():
template = "report/unavailable.html"
context = {
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=acc
)
parcels = acc.get_underlying_parcels()
qrcode = QrCode(
content=request.build_absolute_uri(reverse("compensation:acc:report", args=(id,))),
size=10
)
qrcode_lanis = QrCode(
content=acc.get_LANIS_link(),
size=7
)
# Order states by surface
before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent")
after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent")
actions = acc.actions.all().prefetch_related("action_type__parent")
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier) # Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier)
deductions = acc.deductions.all() \ deductions = obj.deductions.all() \
.distinct("intervention") \ .distinct("intervention") \
.select_related("intervention") \ .select_related("intervention") \
.values_list("intervention__id", "intervention__identifier", "intervention__title", named=True) .values_list("intervention__id", "intervention__identifier", "intervention__title", named=True)
context = { report_context = {
"obj": acc,
"qrcode": { "qrcode": {
"img": qrcode.get_img(), "img": qrcode_report.get_img(),
"url": qrcode.get_content(), "url": qrcode_report.get_content(),
}, },
"qrcode_lanis": { "qrcode_lanis": {
"img": qrcode_lanis.get_img(), "img": qrcode_lanis.get_img(),
"url": qrcode_lanis.get_content(), "url": qrcode_lanis.get_content(),
}, },
"is_entry_shared": False, # disables action buttons during rendering "is_entry_shared": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
"geom_form": geom_form,
"parcels": parcels,
"actions": actions,
"deductions": deductions, "deductions": deductions,
"tables_scrollable": False, "tables_scrollable": False,
TAB_TITLE_IDENTIFIER: tab_title,
} }
context = BaseContext(request, context).context report_context.update(self._get_compensation_report_context(obj))
return render(request, self._TEMPLATE, context) return report_context

View File

@@ -5,22 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from compensation.forms.modals.resubmission import EcoAccountResubmissionModalForm
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView from konova.views.resubmission import AbstractResubmissionView
class EcoAccountResubmissionView(AbstractResubmissionView): class EcoAccountResubmissionView(AbstractResubmissionView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url_base = "compensation:acc:detail" _FORM_CLS = EcoAccountResubmissionModalForm
form_action_url_base = "compensation:acc:resubmission-create" _REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -5,29 +5,15 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.share import AbstractShareByTokenView, AbstractShareFormView from konova.views.share import AbstractShareByTokenView, AbstractShareFormView
class EcoAccountShareByTokenView(AbstractShareByTokenView): class EcoAccountShareByTokenView(AbstractShareByTokenView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url = "compensation:acc:detail" _REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EcoAccountShareFormView(AbstractShareFormView): class EcoAccountShareFormView(AbstractShareFormView):
model = EcoAccount _MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -5,46 +5,21 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \ from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \
AbstractRemoveCompensationStateView AbstractRemoveCompensationStateView
class NewEcoAccountStateView(AbstractNewCompensationStateView): class NewEcoAccountStateView(AbstractNewCompensationStateView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url = "compensation:acc:detail" _REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountStateView(AbstractEditCompensationStateView): class EditEcoAccountStateView(AbstractEditCompensationStateView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url = "compensation:acc:detail" _REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountStateView(AbstractRemoveCompensationStateView): class RemoveEcoAccountStateView(AbstractRemoveCompensationStateView):
model = EcoAccount _MODEL_CLS = EcoAccount
redirect_url = "compensation:acc:detail" _REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -6,110 +6,37 @@ Created on: 09.08.21
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views import View
from compensation.forms.modals.payment import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm from compensation.forms.modals.payment import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
from compensation.models import Payment
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import default_group_required, shared_access_required
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED
from konova.views.base import BaseModalFormView
class NewPaymentView(LoginRequiredMixin, View): class BasePaymentView(LoginRequiredMixin, BaseModalFormView):
_MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:detail"
def __process_request(self, request: HttpRequest, id: str): class Meta:
""" Renders a modal view for adding new payments abstract = True
Args: def _get_redirect_url(self, *args, **kwargs):
request (HttpRequest): The incoming request url = super()._get_redirect_url(*args, **kwargs)
id (str): The intervention's id for which a new payment shall be added return f"{url}#related_data"
Returns: def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
"""
intervention = get_object_or_404(Intervention, id=id)
form = NewPaymentForm(request.POST or None, instance=intervention, request=request)
return form.process_request(
request,
msg_success=PAYMENT_ADDED,
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def get(self, request: HttpRequest, id: str):
return self.__process_request(request, id=id)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def post(self, request: HttpRequest, id: str):
return self.__process_request(request, id=id)
class RemovePaymentView(LoginRequiredMixin, View): class NewPaymentView(BasePaymentView):
_FORM_CLS = NewPaymentForm
def __process_request(self, request: HttpRequest, id: str, payment_id: str): _MSG_SUCCESS = PAYMENT_ADDED
""" Renders a modal view for removing payments
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id
payment_id (str): The payment's id
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
payment = get_object_or_404(Payment, id=payment_id)
form = RemovePaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
return form.process_request(
request=request,
msg_success=PAYMENT_REMOVED,
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def get(self, request: HttpRequest, id: str, payment_id: str):
return self.__process_request(request, id=id, payment_id=payment_id)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def post(self, request: HttpRequest, id: str, payment_id: str):
return self.__process_request(request, id=id, payment_id=payment_id)
class EditPaymentView(LoginRequiredMixin, View): class EditPaymentView(BasePaymentView):
def __process_request(self, request: HttpRequest, id: str, payment_id: str): _MSG_SUCCESS = PAYMENT_EDITED
""" Renders a modal view for editing payments _FORM_CLS = EditPaymentModalForm
Args: class RemovePaymentView(BasePaymentView):
request (HttpRequest): The incoming request _MSG_SUCCESS = PAYMENT_REMOVED
id (str): The intervention's id _FORM_CLS = RemovePaymentModalForm
payment_id (str): The payment's id
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
payment = get_object_or_404(Payment, id=payment_id)
form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
return form.process_request(
request=request,
msg_success=PAYMENT_EDITED,
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def get(self, request: HttpRequest, id: str, payment_id: str):
return self.__process_request(request, id=id, payment_id=payment_id)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def post(self, request: HttpRequest, id: str, payment_id: str):
return self.__process_request(request, id=id, payment_id=payment_id)

View File

@@ -15,7 +15,8 @@ from compensation.forms.compensation import AbstractCompensationForm
from ema.models import Ema, EmaDocument from ema.models import Ema, EmaDocument
from intervention.models import Responsibility, Handler from intervention.models import Responsibility, Handler
from konova.forms import SimpleGeomForm from konova.forms import SimpleGeomForm
from konova.forms.modals import NewDocumentModalForm from konova.forms.modals import NewDocumentModalForm, EditDocumentModalForm, RemoveDocumentModalForm, \
ResubmissionModalForm
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
@@ -170,4 +171,13 @@ class EditEmaForm(NewEmaForm):
class NewEmaDocumentModalForm(NewDocumentModalForm): class NewEmaDocumentModalForm(NewDocumentModalForm):
document_model = EmaDocument _DOCUMENT_CLS = EmaDocument
class EditEmaDocumentModalForm(EditDocumentModalForm):
_DOCUMENT_CLS = EmaDocument
class RemoveEmaDocumentModalForm(RemoveDocumentModalForm):
_DOCUMENT_CLS = EmaDocument
class EmaResubmissionModalForm(ResubmissionModalForm):
_MODEL_CLS = Ema

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 %}"> <button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
{% fa5_icon 'bell' %} {% fa5_icon 'bell' %}
</button> </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 %} {% 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 %} {% if obj.recorded %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Unrecord' %}" data-form-url="{% url 'ema:record' obj.id %}"> <button class="btn btn-default btn-modal mr-2" title="{% trans 'Unrecord' %}" data-form-url="{% url 'ema:record' obj.id %}">
{% fa5_icon 'bookmark' 'far' %} {% fa5_icon 'bookmark' 'far' %}
@@ -28,21 +28,19 @@
{% fa5_icon 'bookmark' %} {% fa5_icon 'bookmark' %}
</button> </button>
{% endif %} {% 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 %} {% endif %}
{% if is_default_member %} {% if is_default_member %}
<button class="btn btn-default btn-modal mr-2" data-form-url="{% url 'ema:log' obj.id %}" title="{% trans 'Show log' %}"> <a href="{% url 'ema:edit' obj.id %}" class="mr-2">
{% fa5_icon 'history' %} <button class="btn btn-default" title="{% trans 'Edit' %}">
</button> {% fa5_icon 'edit' %}
{% 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> </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 %}
{% endif %} {% endif %}
</div> </div>

View File

@@ -118,7 +118,6 @@ class EmaViewTestCase(CompensationViewTestCase):
self.index_url, self.index_url,
self.detail_url, self.detail_url,
self.report_url, self.report_url,
self.log_url,
] ]
fail_urls = [ fail_urls = [
self.new_url, self.new_url,
@@ -134,6 +133,7 @@ class EmaViewTestCase(CompensationViewTestCase):
self.action_remove_url, self.action_remove_url,
self.action_new_url, self.action_new_url,
self.new_doc_url, self.new_doc_url,
self.log_url,
self.remove_url, self.remove_url,
] ]
self.assert_url_fail(client, fail_urls) self.assert_url_fail(client, fail_urls)

View File

@@ -9,28 +9,27 @@ from django.urls import path
from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActionView from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActionView
from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView 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.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView
from ema.views.ema import IndexEmaView, EmaIdentifierGeneratorView, EditEmaView, NewEmaView from ema.views.ema import EmaIndexView, EmaIdentifierGeneratorView, EmaDetailView, EditEmaFormView, NewEmaFormView, \
RemoveEmaView
from ema.views.log import EmaLogView from ema.views.log import EmaLogView
from ema.views.record import EmaRecordView from ema.views.record import EmaRecordView
from ema.views.remove import RemoveEmaView from ema.views.report import EmaReportView
from ema.views.report import EmaPublicReportView
from ema.views.resubmission import EmaResubmissionView from ema.views.resubmission import EmaResubmissionView
from ema.views.share import EmaShareFormView, EmaShareByTokenView from ema.views.share import EmaShareFormView, EmaShareByTokenView
from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateView from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateView
app_name = "ema" app_name = "ema"
urlpatterns = [ urlpatterns = [
path("", IndexEmaView.as_view(), name="index"), path("", EmaIndexView.as_view(), name="index"),
path("new/", NewEmaView.as_view(), name="new"), path("new/", NewEmaFormView.as_view(), name="new"),
path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"), path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"),
path("<id>", DetailEmaView.as_view(), name="detail"), path("<id>", EmaDetailView.as_view(), name="detail"),
path('<id>/log', EmaLogView.as_view(), name='log'), path('<id>/log', EmaLogView.as_view(), name='log'),
path('<id>/edit', EditEmaView.as_view(), name='edit'), path('<id>/edit', EditEmaFormView.as_view(), name='edit'),
path('<id>/remove', RemoveEmaView.as_view(), name='remove'), path('<id>/remove', RemoveEmaView.as_view(), name='remove'),
path('<id>/record', EmaRecordView.as_view(), name='record'), path('<id>/record', EmaRecordView.as_view(), name='record'),
path('<id>/report', EmaPublicReportView.as_view(), name='report'), path('<id>/report', EmaReportView.as_view(), name='report'),
path('<id>/resub', EmaResubmissionView.as_view(), name='resubmission-create'), path('<id>/resub', EmaResubmissionView.as_view(), name='resubmission-create'),
path('<id>/state/new', NewEmaStateView.as_view(), name='new-state'), path('<id>/state/new', NewEmaStateView.as_view(), name='new-state'),

View File

@@ -5,46 +5,31 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \ from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView AbstractRemoveCompensationActionView
_EMA_ACCOUNT_DETAIL_URL_NAME = "ema:detail"
class NewEmaActionView(AbstractNewCompensationActionView): class NewEmaActionView(AbstractNewCompensationActionView):
model = Ema _MODEL_CLS = Ema
redirect_url = "ema:detail" _REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME
@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)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class EditEmaActionView(AbstractEditCompensationActionView): class EditEmaActionView(AbstractEditCompensationActionView):
model = Ema _MODEL_CLS = Ema
redirect_url = "ema:detail" _REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME
@method_decorator(login_required_modal) def _user_has_permission(self, user, **kwargs):
@method_decorator(login_required) return user.is_ets_user()
@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)
class RemoveEmaActionView(AbstractRemoveCompensationActionView): class RemoveEmaActionView(AbstractRemoveCompensationActionView):
model = Ema _MODEL_CLS = Ema
redirect_url = "ema:detail" _REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME
@method_decorator(login_required_modal) def _user_has_permission(self, user, **kwargs):
@method_decorator(login_required) return user.is_ets_user()
@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

@@ -5,46 +5,30 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.deadline import AbstractNewDeadlineView, AbstractRemoveDeadlineView, AbstractEditDeadlineView from konova.views.deadline import AbstractNewDeadlineView, AbstractRemoveDeadlineView, AbstractEditDeadlineView
_EMA_DETAIL_URL_NAME = "ema:detail"
class NewEmaDeadlineView(AbstractNewDeadlineView): class NewEmaDeadlineView(AbstractNewDeadlineView):
model = Ema _MODEL_CLS = Ema
redirect_url = "ema:detail" _REDIRECT_URL = _EMA_DETAIL_URL_NAME
@method_decorator(login_required_modal) def _user_has_permission(self, user, **kwargs):
@method_decorator(login_required) return user.is_ets_user()
@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)
class EditEmaDeadlineView(AbstractEditDeadlineView): class EditEmaDeadlineView(AbstractEditDeadlineView):
model = Ema _MODEL_CLS = Ema
redirect_url = "ema:detail" _REDIRECT_URL = _EMA_DETAIL_URL_NAME
@method_decorator(login_required_modal) def _user_has_permission(self, user, **kwargs):
@method_decorator(login_required) return user.is_ets_user()
@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)
class RemoveEmaDeadlineView(AbstractRemoveDeadlineView): class RemoveEmaDeadlineView(AbstractRemoveDeadlineView):
model = Ema _MODEL_CLS = Ema
redirect_url = "ema:detail" _REDIRECT_URL = _EMA_DETAIL_URL_NAME
@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)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()

View File

@@ -1,76 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.contrib import messages
from django.http import HttpResponse, HttpRequest
from django.shortcuts import get_object_or_404, render
from ema.models import Ema
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
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 DO_NOT_FORGET_TO_SHARE
from konova.views.detail import AbstractDetailView
class DetailEmaView(AbstractDetailView):
_TEMPLATE = "ema/detail/view.html"
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" Renders the detail view of an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA id
Returns:
"""
ema = get_object_or_404(Ema, id=id, deleted=None)
geom_form = SimpleGeomForm(instance=ema)
parcels = ema.get_underlying_parcels()
_user = request.user
is_entry_shared = ema.is_shared_with(_user)
# Order states according to surface
before_states = ema.before_states.all().order_by("-surface")
after_states = ema.after_states.all().order_by("-surface")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = ema.get_surface_before_states()
sum_after_states = ema.get_surface_after_states()
diff_states = abs(sum_before_states - sum_after_states)
ema.set_status_messages(request)
requesting_user_is_only_shared_user = ema.is_only_shared_with(_user)
if requesting_user_is_only_shared_user:
messages.info(
request,
DO_NOT_FORGET_TO_SHARE
)
context = {
"obj": ema,
"geom_form": geom_form,
"parcels": parcels,
"is_entry_shared": is_entry_shared,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"is_default_member": _user.in_group(DEFAULT_GROUP),
"is_zb_member": _user.in_group(ZB_GROUP),
"is_ets_member": _user.in_group(ETS_GROUP),
"LANIS_LINK": ema.get_LANIS_link(),
TAB_TITLE_IDENTIFIER: f"{ema.identifier} - {ema.title}",
"has_finished_deadlines": ema.get_finished_deadlines().exists(),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)

View File

@@ -5,62 +5,41 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from ema.forms import NewEmaDocumentModalForm, RemoveEmaDocumentModalForm, EditEmaDocumentModalForm
from django.utils.decorators import method_decorator
from ema.forms import NewEmaDocumentModalForm
from ema.models import Ema, EmaDocument from ema.models import Ema, EmaDocument
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.forms.modals import EditDocumentModalForm
from konova.views.document import AbstractEditDocumentView, AbstractRemoveDocumentView, AbstractGetDocumentView, \ from konova.views.document import AbstractEditDocumentView, AbstractRemoveDocumentView, AbstractGetDocumentView, \
AbstractNewDocumentView AbstractNewDocumentView
class NewEmaDocumentView(AbstractNewDocumentView): class NewEmaDocumentView(AbstractNewDocumentView):
model = Ema _MODEL_CLS = Ema
form = NewEmaDocumentModalForm _FORM_CLS = NewEmaDocumentModalForm
redirect_url = "ema:detail" _REDIRECT_URL = "ema:detail"
@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)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class GetEmaDocumentView(AbstractGetDocumentView): class GetEmaDocumentView(AbstractGetDocumentView):
model = Ema _MODEL_CLS = Ema
document_model = EmaDocument _DOCUMENT_CLS = EmaDocument
@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)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class RemoveEmaDocumentView(AbstractRemoveDocumentView): class RemoveEmaDocumentView(AbstractRemoveDocumentView):
model = Ema _MODEL_CLS = Ema
document_model = EmaDocument _DOCUMENT_CLS = EmaDocument
_FORM_CLS = RemoveEmaDocumentModalForm
@method_decorator(login_required_modal) _REDIRECT_URL = "ema:detail"
@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)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class EditEmaDocumentView(AbstractEditDocumentView): class EditEmaDocumentView(AbstractEditDocumentView):
model = Ema _MODEL_CLS = Ema
document_model = EmaDocument _FORM_CLS = EditEmaDocumentModalForm
form = EditDocumentModalForm _DOCUMENT_CLS = EmaDocument
redirect_url = "ema:detail" _REDIRECT_URL = "ema:detail"
@method_decorator(login_required_modal) def _user_has_permission(self, user, **kwargs):
@method_decorator(login_required) return user.is_ets_user()
@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

@@ -5,228 +5,112 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 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.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse from django.shortcuts import get_object_or_404
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic.base import View
from ema.forms import NewEmaForm, EditEmaForm from ema.forms import NewEmaForm, EditEmaForm
from ema.models import Ema from ema.models import Ema
from ema.tables import EmaTable from ema.tables import EmaTable
from konova.contexts import BaseContext from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
from konova.decorators import shared_access_required, conservation_office_group_required BaseEditSpatialLocatedObjectFormView
from konova.forms import SimpleGeomForm from konova.views.detail import BaseDetailView
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER from konova.views.remove import BaseRemoveModalFormView
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \
GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
from konova.views.identifier import AbstractIdentifierGeneratorView
from konova.views.index import AbstractIndexView
class IndexEmaView(AbstractIndexView): class EmaIndexView(LoginRequiredMixin, BaseIndexView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: _TAB_TITLE = _("EMAs - Overview")
""" Renders the index view for EMAs _INDEX_TABLE_CLS = EmaTable
Args: def _get_queryset(self):
request (HttpRequest): The incoming request qs = Ema.objects.filter(
Returns:
"""
emas = Ema.objects.filter(
deleted=None, deleted=None,
).order_by( ).order_by(
"-modified__timestamp" "-modified__timestamp"
) )
return qs
table = EmaTable(
request,
queryset=emas
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("EMAs - Overview"),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
class NewEmaView(LoginRequiredMixin, View): class NewEmaFormView(BaseNewSpatialLocatedObjectFormView):
_FORM_CLS = NewEmaForm
_MODEL_CLS = Ema
_TEMPLATE = "ema/form/view.html" _TEMPLATE = "ema/form/view.html"
_TAB_TITLE = _("New EMA")
_REDIRECT_URL = "ema:detail"
@method_decorator(conservation_office_group_required) def _user_has_permission(self, user, **kwargs):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: # User has to be an ets user
""" GET endpoint return user.is_ets_user()
Renders form for new EMA
class EditEmaFormView(BaseEditSpatialLocatedObjectFormView):
_MODEL_CLS = Ema
_FORM_CLS = EditEmaForm
_TEMPLATE = "ema/form/view.html"
_REDIRECT_URL = "ema:detail"
_TAB_TITLE = _("Edit {}")
def _user_has_permission(self, user, **kwargs):
# User has to be an ets user
return user.is_ets_user()
class EmaIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView):
_MODEL_CLS = Ema
_REDIRECT_URL = "ema:index"
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class EmaDetailView(BaseDetailView):
_MODEL_CLS = Ema
_TEMPLATE = "ema/detail/view.html"
def _get_object(self, id: str):
""" Fetch object for detail view
Args: Args:
request (HttpRequest): The incoming request id (str): The record's id'
*args ():
**kwargs ():
Returns: Returns:
""" """
data_form = NewEmaForm(request.POST or None) ema = get_object_or_404(Ema, id=id, deleted=None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False) return ema
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 _get_detail_context(self, obj: Ema):
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """ Generate object specific detail context for view
""" POST endpoint
Processes submitted form
Args: Args:
request (HttpRequest): The incoming request obj (): The record
*args ():
**kwargs ():
Returns: Returns:
""" """
data_form = NewEmaForm(request.POST or None) # Order states according to surface
geom_form = SimpleGeomForm(request.POST or None, read_only=False) before_states = obj.before_states.all().order_by("-surface")
if data_form.is_valid() and geom_form.is_valid(): after_states = obj.after_states.all().order_by("-surface")
generated_identifier = data_form.cleaned_data.get("identifier", None)
ema = data_form.save(request.user, geom_form) # Precalculate logical errors between before- and after-states
if generated_identifier != ema.identifier: # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
messages.info( sum_before_states = obj.get_surface_before_states()
request, sum_after_states = obj.get_surface_after_states()
IDENTIFIER_REPLACED.format( diff_states = abs(sum_before_states - sum_after_states)
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)
)
return redirect("ema:detail", id=ema.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
context = { context = {
"form": data_form, "before_states": before_states,
"geom_form": geom_form, "after_states": after_states,
TAB_TITLE_IDENTIFIER: _("New EMA"), "sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"has_finished_deadlines": obj.get_finished_deadlines().exists(),
} }
context = BaseContext(request, context).context return context
return render(request, self._TEMPLATE, context)
class EmaIdentifierGeneratorView(AbstractIdentifierGeneratorView): class RemoveEmaView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL = Ema _MODEL_CLS = Ema
_REDIRECT_URL = "ema:index"
@method_decorator(conservation_office_group_required) def _user_has_permission(self, user, **kwargs):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: return user.is_ets_user()
return super().get(request, *args, **kwargs)
class EditEmaView(LoginRequiredMixin, View):
_TEMPLATE = "compensation/form/view.html"
@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
Renders form
Args:
request (HttpRequest): The incoming request
id (str): The ema identifier
*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(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)
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", )
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

@@ -5,19 +5,14 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.log import AbstractLogView from konova.views.log import AbstractLogView
class EmaLogView(AbstractLogView): class EmaLogView(LoginRequiredMixin, AbstractLogView):
model = Ema _MODEL_CLS = Ema
@method_decorator(login_required_modal) def _user_has_permission(self, user, **kwargs):
@method_decorator(login_required) return user.is_ets_user()
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -5,20 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.record import AbstractRecordView from konova.views.record import AbstractRecordView
class EmaRecordView(AbstractRecordView): class EmaRecordView(LoginRequiredMixin, AbstractRecordView):
model = Ema _MODEL_CLS = Ema
_REDIRECT_URL = "ema:detail"
@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

@@ -1,21 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.http import HttpRequest, HttpResponse
from django.utils.decorators import method_decorator
from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required
from konova.views.remove import AbstractRemoveView
class RemoveEmaView(AbstractRemoveView):
_MODEL = Ema
_REDIRECT_URL = "ema:index"
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
return super().get(request, *args, **kwargs)

View File

@@ -5,81 +5,36 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.views.compensation.report import BaseCompensationReportView
from ema.models import Ema from ema.models import Ema
from konova.contexts import BaseContext from konova.sub_settings.django_settings import BASE_URL
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.qrcode import QrCode from konova.utils.qrcode import QrCode
from konova.views.report import AbstractPublicReportView
class EmaPublicReportView(AbstractPublicReportView): class EmaReportView(BaseCompensationReportView):
_TEMPLATE = "ema/report/report.html" _TEMPLATE = "ema/report/report.html"
_MODEL = Ema
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: def _get_report_context(self, obj):
""" Renders the public report view report_url = BASE_URL + reverse("ema:report", args=(obj.id,))
qrcode_report = QrCode(report_url, 10)
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
Args: generic_compensation_report_context = self._get_compensation_report_context(obj)
request (HttpRequest): The incoming request
id (str): The id of the intervention
Returns: report_context = {
"""
ema = get_object_or_404(Ema, id=id)
tab_title = _("Report {}").format(ema.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not ema.is_ready_for_publish():
template = "report/unavailable.html"
context = {
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=ema,
)
parcels = ema.get_underlying_parcels()
qrcode = QrCode(
content=request.build_absolute_uri(reverse("ema:report", args=(id,))),
size=10
)
qrcode_lanis = QrCode(
content=ema.get_LANIS_link(),
size=7
)
# Order states by surface
before_states = ema.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = ema.after_states.all().order_by("-surface").prefetch_related("biotope_type")
actions = ema.actions.all().prefetch_related("action_type")
context = {
"obj": ema,
"qrcode": { "qrcode": {
"img": qrcode.get_img(), "img": qrcode_report.get_img(),
"url": qrcode.get_content(), "url": qrcode_report.get_content(),
}, },
"qrcode_lanis": { "qrcode_lanis": {
"img": qrcode_lanis.get_img(), "img": qrcode_lanis.get_img(),
"url": qrcode_lanis.get_content(), "url": qrcode_lanis.get_content(),
}, },
"is_entry_shared": False, # disables action buttons during rendering "is_entry_shared": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
"geom_form": geom_form,
"parcels": parcels,
"actions": actions,
"tables_scrollable": False, "tables_scrollable": False,
TAB_TITLE_IDENTIFIER: tab_title,
} }
context = BaseContext(request, context).context report_context.update(generic_compensation_report_context)
return render(request, self._TEMPLATE, context) return report_context

View File

@@ -5,22 +5,16 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from ema.forms import EmaResubmissionModalForm
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView from konova.views.resubmission import AbstractResubmissionView
class EmaResubmissionView(AbstractResubmissionView): class EmaResubmissionView(AbstractResubmissionView):
model = Ema _MODEL_CLS = Ema
redirect_url_base = "ema:detail" _FORM_CLS = EmaResubmissionModalForm
form_action_url_base = "ema:resubmission-create" _REDIRECT_URL = "ema:detail"
action_url = "ema:resubmission-create"
@method_decorator(login_required_modal) def _user_has_permission(self, user, **kwargs):
@method_decorator(login_required) return user.is_ets_user()
@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

@@ -5,29 +5,17 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import conservation_office_group_required, shared_access_required, login_required_modal
from konova.views.share import AbstractShareByTokenView, AbstractShareFormView from konova.views.share import AbstractShareByTokenView, AbstractShareFormView
class EmaShareByTokenView(AbstractShareByTokenView): class EmaShareByTokenView(AbstractShareByTokenView):
model = Ema _MODEL_CLS = Ema
redirect_url = "ema:detail" _REDIRECT_URL = "ema:detail"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EmaShareFormView(AbstractShareFormView): class EmaShareFormView(AbstractShareFormView):
model = Ema _MODEL_CLS = Ema
_REDIRECT_URL = "ema:detail"
@method_decorator(login_required_modal) def _user_has_permission(self, user, **kwargs):
@method_decorator(login_required) return user.is_ets_user()
@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

@@ -5,46 +5,30 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import conservation_office_group_required, shared_access_required, login_required_modal
from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \ from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \
AbstractRemoveCompensationStateView AbstractRemoveCompensationStateView
class NewEmaStateView(AbstractNewCompensationStateView): class NewEmaStateView(AbstractNewCompensationStateView):
model = Ema _MODEL_CLS = Ema
redirect_url = "ema:detail" _REDIRECT_URL = "ema:detail"
@method_decorator(login_required_modal) def _user_has_permission(self, user, **kwargs):
@method_decorator(login_required) return user.is_ets_user()
@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)
class EditEmaStateView(AbstractEditCompensationStateView): class EditEmaStateView(AbstractEditCompensationStateView):
model = Ema _MODEL_CLS = Ema
redirect_url = "ema:detail" _REDIRECT_URL = "ema:detail"
@method_decorator(login_required_modal) def _user_has_permission(self, user, **kwargs):
@method_decorator(login_required) return user.is_ets_user()
@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)
class RemoveEmaStateView(AbstractRemoveCompensationStateView): class RemoveEmaStateView(AbstractRemoveCompensationStateView):
model = Ema _MODEL_CLS = Ema
redirect_url = "ema:detail" _REDIRECT_URL = "ema:detail"
@method_decorator(login_required_modal) def _user_has_permission(self, user, **kwargs):
@method_decorator(login_required) return user.is_ets_user()
@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

@@ -37,14 +37,6 @@ class InterventionAdmin(BaseObjectAdmin):
"geometry", "geometry",
] ]
def get_actions(self, request):
DELETE_ACTION_IDENTIFIER = "delete_selected"
actions = super().get_actions(request)
if DELETE_ACTION_IDENTIFIER in actions:
del actions[DELETE_ACTION_IDENTIFIER]
return actions
class InterventionDocumentAdmin(AbstractDocumentAdmin): class InterventionDocumentAdmin(AbstractDocumentAdmin):
pass pass

View File

@@ -172,7 +172,8 @@ class EditEcoAccountDeductionModalForm(NewEcoAccountDeductionModalForm):
deduction = None deduction = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.deduction = kwargs.pop("deduction", None) deduction_id = kwargs.pop("deduction_id", None)
self.deduction = EcoAccountDeduction.objects.get(id=deduction_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit Deduction") self.form_title = _("Edit Deduction")
form_data = { form_data = {
@@ -252,19 +253,20 @@ class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
Can be used for anything, where removing shall be confirmed by the user a second time. Can be used for anything, where removing shall be confirmed by the user a second time.
""" """
deduction = None _DEDUCTION_OBJ = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
deduction = kwargs.pop("deduction", None) deduction_id = kwargs.pop("deduction_id", None)
self.deduction = deduction deduction = EcoAccountDeduction.objects.get(id=deduction_id)
self._DEDUCTION_OBJ = deduction
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):
with transaction.atomic(): with transaction.atomic():
self.deduction.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED) self._DEDUCTION_OBJ.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
self.deduction.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED) self._DEDUCTION_OBJ.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
self.deduction.delete() self._DEDUCTION_OBJ.delete()
def check_for_recorded_instance(self): def check_for_recorded_instance(self):
if self.deduction.intervention.is_recorded: if self._DEDUCTION_OBJ.intervention.is_recorded:
self.block_form() self.block_form()

View File

@@ -6,11 +6,11 @@ Created on: 18.08.22
""" """
from intervention.models import InterventionDocument from intervention.models import InterventionDocument
from konova.forms.modals import NewDocumentModalForm from konova.forms.modals import NewDocumentModalForm, EditDocumentModalForm, RemoveDocumentModalForm
class NewInterventionDocumentModalForm(NewDocumentModalForm): class NewInterventionDocumentModalForm(NewDocumentModalForm):
document_model = InterventionDocument _DOCUMENT_CLS = InterventionDocument
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" Extension of regular NewDocumentModalForm """ Extension of regular NewDocumentModalForm
@@ -28,3 +28,31 @@ class NewInterventionDocumentModalForm(NewDocumentModalForm):
self.instance.send_data_to_egon() self.instance.send_data_to_egon()
return doc return doc
class EditInterventionDocumentModalForm(EditDocumentModalForm):
_DOCUMENT_CLS = InterventionDocument
def save(self, *args, **kwargs):
""" Extension of regular EditDocumentModalForm
Checks whether payments exist on the intervention and sends the data to EGON
Args:
*args ():
**kwargs ():
Returns:
"""
doc = super().save(*args, **kwargs)
self.instance.send_data_to_egon()
return doc
class RemoveInterventionDocumentModalForm(RemoveDocumentModalForm):
_DOCUMENT_CLS = InterventionDocument
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.instance.send_data_to_egon()

View File

@@ -0,0 +1,22 @@
"""
Author: Michel Peltriaux
Created on: 08.11.25
"""
from django.shortcuts import get_object_or_404
from compensation.models import Compensation
from konova.forms.modals import RemoveModalForm
class RemoveCompensationFromInterventionModalForm(RemoveModalForm):
""" Specific form for removing a compensation from an intervention
"""
def __init__(self, *args, **kwargs):
# The 'instance' that is pushed into the constructor by the generic base class points to the
# intervention instead of the compensation, which shall be deleted. Therefore we need to fetch the compensation
# and replace the instance parameter with that object
instance = get_object_or_404(Compensation, id=kwargs.pop("comp_id"))
kwargs["instance"] = instance
super().__init__(*args, **kwargs)

View File

@@ -0,0 +1,11 @@
"""
Author: Michel Peltriaux
Created on: 21.10.25
"""
from intervention.models import Intervention
from konova.forms.modals import ResubmissionModalForm
class InterventionResubmissionModalForm(ResubmissionModalForm):
_MODEL_CLS = Intervention

View File

@@ -7,9 +7,10 @@ Created on: 18.08.22
""" """
from django import forms from django import forms
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from intervention.models import RevocationDocument from intervention.models import RevocationDocument, Revocation
from konova.forms.modals import BaseModalForm, RemoveModalForm from konova.forms.modals import BaseModalForm, RemoveModalForm
from konova.utils import validators from konova.utils import validators
from konova.utils.message_templates import REVOCATION_ADDED, REVOCATION_EDITED from konova.utils.message_templates import REVOCATION_ADDED, REVOCATION_EDITED
@@ -75,7 +76,8 @@ class EditRevocationModalForm(NewRevocationModalForm):
revocation = None revocation = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.revocation = kwargs.pop("revocation", None) revocation_id = kwargs.pop("revocation_id", None)
self.revocation = get_object_or_404(Revocation, id=revocation_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit revocation") self.form_title = _("Edit revocation")
try: try:
@@ -104,8 +106,8 @@ class RemoveRevocationModalForm(RemoveModalForm):
revocation = None revocation = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
revocation = kwargs.pop("revocation", None) revocation_id = kwargs.pop("revocation_id", None)
self.revocation = revocation self.revocation = get_object_or_404(Revocation, id=revocation_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):

View File

@@ -280,7 +280,7 @@ class EditRevocationModalFormTestCase(NewRevocationModalFormTestCase):
data, data,
request=self.request, request=self.request,
instance=self.intervention, instance=self.intervention,
revocation=self.revoc revocation_id=self.revoc.id
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
obj = form.save() obj = form.save()
@@ -302,7 +302,7 @@ class RemoveRevocationModalFormTestCase(EditRevocationModalFormTestCase):
form = RemoveRevocationModalForm( form = RemoveRevocationModalForm(
request=self.request, request=self.request,
instance=self.intervention, instance=self.intervention,
revocation=self.revoc, revocation_id=self.revoc.id,
) )
self.assertEqual(form.instance, self.intervention) self.assertEqual(form.instance, self.intervention)
self.assertEqual(form.revocation, self.revoc) self.assertEqual(form.revocation, self.revoc)
@@ -317,7 +317,7 @@ class RemoveRevocationModalFormTestCase(EditRevocationModalFormTestCase):
data, data,
request=self.request, request=self.request,
instance=self.intervention, instance=self.intervention,
revocation=self.revoc revocation_id=self.revoc.id
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
form.save() form.save()

View File

@@ -14,32 +14,30 @@ from intervention.views.deduction import NewInterventionDeductionView, EditInter
RemoveInterventionDeductionView RemoveInterventionDeductionView
from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \ from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \
RemoveInterventionDocumentView, EditInterventionDocumentView RemoveInterventionDocumentView, EditInterventionDocumentView
from intervention.views.intervention import IndexInterventionView, InterventionIdentifierGeneratorView, \ from intervention.views.intervention import InterventionIndexView, InterventionIdentifierGeneratorView, \
NewInterventionView, EditInterventionView InterventionDetailView, NewInterventionFormView, EditInterventionFormView, RemoveInterventionView
from intervention.views.remove import RemoveInterventionView
from intervention.views.detail import DetailInterventionView
from intervention.views.log import InterventionLogView from intervention.views.log import InterventionLogView
from intervention.views.record import InterventionRecordView from intervention.views.record import InterventionRecordView
from intervention.views.report import InterventionPublicReportView from intervention.views.report import InterventionReportView
from intervention.views.resubmission import InterventionResubmissionView from intervention.views.resubmission import InterventionResubmissionView
from intervention.views.revocation import NewInterventionRevocationView, GetInterventionRevocationView, \ from intervention.views.revocation import NewRevocationView, GetRevocationDocumentView, EditRevocationView, \
EditInterventionRevocationView, RemoveInterventionRevocationView RemoveRevocationView
from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView
app_name = "intervention" app_name = "intervention"
urlpatterns = [ urlpatterns = [
path("", IndexInterventionView.as_view(), name="index"), path("", InterventionIndexView.as_view(), name="index"),
path('new/', NewInterventionView.as_view(), name='new'), path('new/', NewInterventionFormView.as_view(), name='new'),
path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'), path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'),
path('<id>', DetailInterventionView.as_view(), name='detail'), path('<id>', InterventionDetailView.as_view(), name='detail'),
path('<id>/log', InterventionLogView.as_view(), name='log'), path('<id>/log', InterventionLogView.as_view(), name='log'),
path('<id>/edit', EditInterventionView.as_view(), name='edit'), path('<id>/edit', EditInterventionFormView.as_view(), name='edit'),
path('<id>/remove', RemoveInterventionView.as_view(), name='remove'), path('<id>/remove', RemoveInterventionView.as_view(), name='remove'),
path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'), path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'),
path('<id>/share', InterventionShareFormView.as_view(), name='share-form'), path('<id>/share', InterventionShareFormView.as_view(), name='share-form'),
path('<id>/check', InterventionCheckView.as_view(), name='check'), path('<id>/check', InterventionCheckView.as_view(), name='check'),
path('<id>/record', InterventionRecordView.as_view(), name='record'), path('<id>/record', InterventionRecordView.as_view(), name='record'),
path('<id>/report', InterventionPublicReportView.as_view(), name='report'), path('<id>/report', InterventionReportView.as_view(), name='report'),
path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'), path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'),
# Compensations # Compensations
@@ -57,10 +55,10 @@ urlpatterns = [
path('<id>/deduction/<deduction_id>/remove', RemoveInterventionDeductionView.as_view(), name='remove-deduction'), path('<id>/deduction/<deduction_id>/remove', RemoveInterventionDeductionView.as_view(), name='remove-deduction'),
# Revocation routes # Revocation routes
path('<id>/revocation/new', NewInterventionRevocationView.as_view(), name='new-revocation'), path('<id>/revocation/new', NewRevocationView.as_view(), name='new-revocation'),
path('<id>/revocation/<revocation_id>/edit', EditInterventionRevocationView.as_view(), name='edit-revocation'), path('<id>/revocation/<revocation_id>/edit', EditRevocationView.as_view(), name='edit-revocation'),
path('<id>/revocation/<revocation_id>/remove', RemoveInterventionRevocationView.as_view(), name='remove-revocation'), path('<id>/revocation/<revocation_id>/remove', RemoveRevocationView.as_view(), name='remove-revocation'),
path('revocation/<doc_id>', GetInterventionRevocationView.as_view(), name='get-doc-revocation'), path('revocation/<doc_id>', GetRevocationDocumentView.as_view(), name='get-doc-revocation'),
# Autocomplete # Autocomplete
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="autocomplete"), path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="autocomplete"),

View File

@@ -6,43 +6,23 @@ Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from intervention.forms.modals.check import CheckModalForm from intervention.forms.modals.check import CheckModalForm
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import registration_office_group_required, shared_access_required from konova.views.base import BaseModalFormView
from konova.utils.message_templates import INTERVENTION_INVALID
class InterventionCheckView(LoginRequiredMixin, View):
def __process_request(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: class InterventionCheckView(LoginRequiredMixin, BaseModalFormView):
""" Renders check form for an intervention _MODEL_CLS = Intervention
_FORM_CLS = CheckModalForm
_MSG_SUCCESS = _("Check performed")
_REDIRECT_URL = "intervention:detail"
Args: def _user_has_permission(self, user, **kwargs):
request (HttpRequest): The incoming request return user.is_zb_user()
id (str): Intervention's id
Returns: def _get_redirect_url(self, *args, **kwargs):
redirect_url = super()._get_redirect_url(*args, **kwargs)
""" redirect_url += "#related_data"
intervention = get_object_or_404(Intervention, id=id) return redirect_url
form = CheckModalForm(request.POST or None, instance=intervention, request=request)
return form.process_request(
request,
msg_success=_("Check performed"),
msg_error=INTERVENTION_INVALID
)
@method_decorator(registration_office_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
return self.__process_request(request, id, *args, **kwargs)
@method_decorator(registration_office_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
return self.__process_request(request, id, *args, **kwargs)

View File

@@ -6,49 +6,35 @@ Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpRequest, Http404, HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views import View
from compensation.models import Compensation
from intervention.forms.modals.remove import RemoveCompensationFromInterventionModalForm
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import shared_access_required
from konova.forms.modals import RemoveModalForm
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
from konova.views.remove import BaseRemoveModalFormView
class RemoveCompensationFromInterventionView(LoginRequiredMixin, View): class RemoveCompensationFromInterventionView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = Intervention
_FORM_CLS = RemoveCompensationFromInterventionModalForm
_MSG_SUCCESS = COMPENSATION_REMOVED_TEMPLATE
_REDIRECT_URL = "intervention:detail"
def __process_request(self, request: HttpRequest, id: str, comp_id: str, *args, **kwargs) -> HttpResponse: def _user_has_shared_access(self, user, **kwargs):
""" Renders a modal view for removing the compensation compensation_id = kwargs.get("comp_id", None)
compensation = get_object_or_404(Compensation, id=compensation_id)
return compensation.is_shared_with(user)
Args: def _user_has_permission(self, user, **kwargs):
request (HttpRequest): The incoming request return user.is_default_user()
id (str): The compensation's id
Returns: def _get_msg_success(self, *args, **kwargs):
compensation_id = kwargs.get("comp_id", None)
compensation = get_object_or_404(Compensation, id=compensation_id)
return self._MSG_SUCCESS.format(compensation.identifier)
""" def _get_redirect_url(self, *args, **kwargs):
intervention = get_object_or_404(Intervention, id=id) obj = kwargs.get("obj")
try: return reverse(self._REDIRECT_URL, args=(obj.id,)) + "#related_data"
comp = intervention.compensations.get(
id=comp_id
)
except ObjectDoesNotExist:
raise Http404("Unknown compensation")
form = RemoveModalForm(request.POST or None, instance=comp, request=request)
return form.process_request(
request=request,
msg_success=COMPENSATION_REMOVED_TEMPLATE.format(comp.identifier),
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data",
)
@method_decorator(shared_access_required(Intervention, "id"))
def get(self, request, id: str, comp_id: str, *args, **kwargs) -> HttpResponse:
return self.__process_request(request, id, comp_id, *args, **kwargs)
@method_decorator(shared_access_required(Intervention, "id"))
def post(self, request, id: str, comp_id: str, *args, **kwargs) -> HttpResponse:
return self.__process_request(request, id, comp_id, *args, **kwargs)

View File

@@ -5,51 +5,27 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import default_group_required, shared_access_required from konova.utils.message_templates import DEDUCTION_ADDED, DEDUCTION_EDITED, DEDUCTION_REMOVED
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
_INTERVENTION_DETAIL_URL_NAME = "intervention:detail"
class NewInterventionDeductionView(AbstractNewDeductionView): class NewInterventionDeductionView(LoginRequiredMixin, AbstractNewDeductionView):
def _custom_check(self, obj): _MODEL_CLS = Intervention
pass _MSG_SUCCESS = DEDUCTION_ADDED
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
model = Intervention
redirect_url = "intervention:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditInterventionDeductionView(AbstractEditDeductionView): class EditInterventionDeductionView(LoginRequiredMixin, AbstractEditDeductionView):
def _custom_check(self, obj): _MODEL_CLS = Intervention
pass _MSG_SUCCESS = DEDUCTION_EDITED
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
model = Intervention
redirect_url = "intervention:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveInterventionDeductionView(AbstractRemoveDeductionView): class RemoveInterventionDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView):
def _custom_check(self, obj): _MODEL_CLS = Intervention
pass _MSG_SUCCESS = DEDUCTION_REMOVED
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
model = Intervention
redirect_url = "intervention:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -1,79 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.contrib import messages
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from intervention.models import Intervention
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
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, DO_NOT_FORGET_TO_SHARE
from konova.views.detail import AbstractDetailView
class DetailInterventionView(AbstractDetailView):
_TEMPLATE = "intervention/detail/view.html"
def get(self, request, id: str, *args, **kwargs) -> HttpResponse:
# Fetch data, filter out deleted related data
intervention = get_object_or_404(
Intervention.objects.select_related(
"geometry",
"legal",
"responsible",
).prefetch_related(
"legal__revocations",
),
id=id,
deleted=None
)
compensations = intervention.compensations.filter(
deleted=None,
)
_user = request.user
is_data_shared = intervention.is_shared_with(user=_user)
geom_form = SimpleGeomForm(
instance=intervention,
)
last_checked = intervention.get_last_checked_action()
last_checked_tooltip = ""
if last_checked:
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(
last_checked.get_timestamp_str_formatted(),
last_checked.user
)
has_payment_without_document = intervention.payments.exists() and not intervention.get_documents()[1].exists()
requesting_user_is_only_shared_user = intervention.is_only_shared_with(_user)
if requesting_user_is_only_shared_user:
messages.info(
request,
DO_NOT_FORGET_TO_SHARE
)
context = {
"obj": intervention,
"last_checked": last_checked,
"last_checked_tooltip": last_checked_tooltip,
"compensations": compensations,
"is_entry_shared": is_data_shared,
"geom_form": geom_form,
"is_default_member": _user.in_group(DEFAULT_GROUP),
"is_zb_member": _user.in_group(ZB_GROUP),
"is_ets_member": _user.in_group(ETS_GROUP),
"LANIS_LINK": intervention.get_LANIS_link(),
"has_payment_without_document": has_payment_without_document,
TAB_TITLE_IDENTIFIER: f"{intervention.identifier} - {intervention.title}",
}
request = intervention.set_status_messages(request)
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)

View File

@@ -5,59 +5,33 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from intervention.forms.modals.document import NewInterventionDocumentModalForm, EditInterventionDocumentModalForm, \
from django.utils.decorators import method_decorator RemoveInterventionDocumentModalForm
from intervention.forms.modals.document import NewInterventionDocumentModalForm
from intervention.models import Intervention, InterventionDocument from intervention.models import Intervention, InterventionDocument
from konova.decorators import default_group_required, shared_access_required
from konova.forms.modals import EditDocumentModalForm
from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \ from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \
AbstractEditDocumentView AbstractEditDocumentView
class NewInterventionDocumentView(AbstractNewDocumentView): class NewInterventionDocumentView(AbstractNewDocumentView):
model = Intervention _MODEL_CLS = Intervention
form = NewInterventionDocumentModalForm _DOCUMENT_MODEL = InterventionDocument
redirect_url = "intervention:detail" _FORM_CLS = NewInterventionDocumentModalForm
_REDIRECT_URL = "intervention:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class GetInterventionDocumentView(AbstractGetDocumentView): class GetInterventionDocumentView(AbstractGetDocumentView):
model = Intervention _MODEL_CLS = Intervention
document_model = InterventionDocument _DOCUMENT_CLS = InterventionDocument
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveInterventionDocumentView(AbstractRemoveDocumentView): class RemoveInterventionDocumentView(AbstractRemoveDocumentView):
model = Intervention _MODEL_CLS = Intervention
document_model = InterventionDocument _DOCUMENT_CLS = InterventionDocument
_FORM_CLS = RemoveInterventionDocumentModalForm
@method_decorator(login_required) _REDIRECT_URL = "intervention:detail"
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditInterventionDocumentView(AbstractEditDocumentView): class EditInterventionDocumentView(AbstractEditDocumentView):
model = Intervention _MODEL_CLS = Intervention
document_model = InterventionDocument _DOCUMENT_CLS = InterventionDocument
form = EditDocumentModalForm _FORM_CLS = EditInterventionDocumentModalForm
redirect_url = "intervention:detail" _REDIRECT_URL = "intervention:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -8,11 +8,9 @@ Created on: 19.08.22
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest
from django.shortcuts import get_object_or_404, render, redirect 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.utils.translation import gettext_lazy as _
from django.views import View
from intervention.forms.intervention import EditInterventionForm, NewInterventionForm from intervention.forms.intervention import EditInterventionForm, NewInterventionForm
from intervention.models import Intervention from intervention.models import Intervention
@@ -21,189 +19,132 @@ from konova.contexts import BaseContext
from konova.decorators import default_group_required, shared_access_required from konova.decorators import default_group_required, shared_access_required
from konova.forms import SimpleGeomForm from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, \ from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, RECORDED_BLOCKS_EDIT, \
CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, \ CHECK_STATE_RESET, FORM_INVALID, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
GEOMETRIES_IGNORED_TEMPLATE from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \
from konova.views.identifier import AbstractIdentifierGeneratorView BaseEditSpatialLocatedObjectFormView
from konova.views.index import AbstractIndexView from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class IndexInterventionView(AbstractIndexView): class InterventionIndexView(LoginRequiredMixin, BaseIndexView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: _INDEX_TABLE_CLS = InterventionTable
""" _TAB_TITLE = _("Interventions - Overview")
Renders the index view for Interventions
Args: def _get_queryset(self):
request (HttpRequest): The incoming request qs = Intervention.objects.filter(
deleted=None,
Returns:
A rendered view
"""
# Filtering by user access is performed in table filter inside InterventionTableFilter class
interventions = Intervention.objects.filter(
deleted=None, # not deleted
).select_related( ).select_related(
"legal" "legal"
).order_by( ).order_by(
"-modified__timestamp" "-modified__timestamp"
) )
table = InterventionTable( return qs
request=request,
queryset=interventions
class NewInterventionFormView(BaseNewSpatialLocatedObjectFormView):
_MODEL_CLS = Intervention
_FORM_CLS = NewInterventionForm
_TEMPLATE = "intervention/form/view.html"
_REDIRECT_URL = "intervention:detail"
_TAB_TITLE = _("New intervention")
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):
_MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:index"
class InterventionDetailView(BaseDetailView):
_MODEL_CLS = Intervention
_TEMPLATE = "intervention/detail/view.html"
def _get_object(self, id: str):
""" Returns the intervention
Args:
id (str): The intervention's id
Returns:
obj (Intervention): The intervention
"""
# Fetch data, filter out deleted related data
obj = get_object_or_404(
self._MODEL_CLS.objects.select_related(
"geometry",
"legal",
"responsible",
).prefetch_related(
"legal__revocations",
),
id=id,
deleted=None
) )
context = { return obj
"table": table,
TAB_TITLE_IDENTIFIER: _("Interventions - Overview"),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
def _get_detail_context(self, obj: Intervention):
class NewInterventionView(LoginRequiredMixin, View): """ Generate object specific detail context for view
_TEMPLATE = "intervention/form/view.html"
@method_decorator(default_group_required)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""
Renders a view for a new intervention creation
Args: Args:
request (HttpRequest): The incoming request obj (): The record
Returns: Returns:
""" """
data_form = NewInterventionForm() compensations = obj.compensations.filter(deleted=None)
geom_form = SimpleGeomForm(read_only=False) last_checked = obj.get_last_checked_action()
context = { last_checked_tooltip = ""
"form": data_form, if last_checked:
"geom_form": geom_form, last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(
TAB_TITLE_IDENTIFIER: _("New intervention"), last_checked.get_timestamp_str_formatted(),
} last_checked.user
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)
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", )
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
class EditInterventionView(LoginRequiredMixin, View):
_TEMPLATE = "intervention/form/view.html"
@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
Args:
request (HttpRequest): The incoming request
id (str): The intervention identifier
Returns:
HttpResponse: The rendered view
"""
# 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 has_payment_without_document = obj.payments.exists() and not obj.get_documents()[1].exists()
data_form = EditInterventionForm(request.POST or None, instance=intervention)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention)
context = { context = {
"form": data_form, "last_checked": last_checked,
"geom_form": geom_form, "last_checked_tooltip": last_checked_tooltip,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier), "compensations": compensations,
"has_payment_without_document": has_payment_without_document,
} }
context = BaseContext(request, context).context return context
return render(request, self._TEMPLATE, context)
@method_decorator(default_group_required) @login_required
@method_decorator(shared_access_required(Intervention, "id")) @default_group_required
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: @shared_access_required(Intervention, "id")
""" def edit_view(request: HttpRequest, id: str):
Process saved form content """
Renders a view for editing interventions
Args: Args:
request (HttpRequest): The incoming request request (HttpRequest): The incoming request
id (str): The intervention id
Returns: 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) template = "intervention/form/view.html"
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention) # 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 request.method == "POST":
if data_form.is_valid() and geom_form.is_valid(): 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 # 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 # Save the current state of recorded|checked to inform the user in case of a status reset due to editing
@@ -217,17 +158,28 @@ class EditInterventionView(LoginRequiredMixin, View):
request, request,
GEOMETRY_SIMPLIFIED GEOMETRY_SIMPLIFIED
) )
num_ignored_geometries = geom_form.get_num_geometries_ignored() num_ignored_geometries = geom_form.get_num_geometries_ignored()
if num_ignored_geometries > 0: if num_ignored_geometries > 0:
messages.info( messages.info(
request, request,
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries) GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
) )
return redirect("intervention:detail", id=intervention.id) return redirect("intervention:detail", id=intervention.id)
context = { else:
"form": data_form, messages.error(request, FORM_INVALID, extra_tags="danger",)
"geom_form": geom_form, else:
TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier), # For clarification: nothing in this case
} pass
context = BaseContext(request, context).context context = {
return render(request, self._TEMPLATE, 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)
class RemoveInterventionView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:index"

View File

@@ -5,19 +5,11 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import shared_access_required, default_group_required
from konova.views.log import AbstractLogView from konova.views.log import AbstractLogView
class InterventionLogView(AbstractLogView): class InterventionLogView(LoginRequiredMixin, AbstractLogView):
model = Intervention _MODEL_CLS = Intervention
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -5,19 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import conservation_office_group_required, shared_access_required
from konova.views.record import AbstractRecordView from konova.views.record import AbstractRecordView
class InterventionRecordView(AbstractRecordView): class InterventionRecordView(LoginRequiredMixin, AbstractRecordView):
model = Intervention _MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:detail"
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -1,20 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.http import HttpRequest, HttpResponse
from django.utils.decorators import method_decorator
from intervention.models import Intervention
from konova.decorators import shared_access_required
from konova.views.remove import AbstractRemoveView
class RemoveInterventionView(AbstractRemoveView):
_MODEL = Intervention
_REDIRECT_URL = "intervention:index"
@method_decorator(shared_access_required(Intervention, "id"))
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
return super().get(request, *args, **kwargs)

View File

@@ -5,78 +5,41 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from intervention.models import Intervention from intervention.models import Intervention
from konova.contexts import BaseContext from konova.sub_settings.django_settings import BASE_URL
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.qrcode import QrCode from konova.utils.qrcode import QrCode
from konova.views.report import AbstractPublicReportView from konova.views.report import BaseReportView
class InterventionPublicReportView(AbstractPublicReportView): class InterventionReportView(BaseReportView):
_TEMPLATE = "intervention/report/report.html" _TEMPLATE = 'intervention/report/report.html'
_MODEL = Intervention
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: def _get_report_context(self, obj: Intervention):
""" Renders the public report view """ Returns the specific context needed for an intervention report
Args: Args:
request (HttpRequest): The incoming request obj (Intervention): The object for the report
id (str): The id of the intervention
Returns: Returns:
dict: The object specific context for rendering the report
""" """
intervention = get_object_or_404(Intervention, id=id) distinct_deductions = obj.deductions.all().distinct("account")
report_url = BASE_URL + reverse("intervention:report", args=(obj.id,))
qrcode_report = QrCode(report_url, 10)
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
tab_title = _("Report {}").format(intervention.identifier) return {
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not intervention.is_ready_for_publish():
template = "report/unavailable.html"
context = {
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=intervention
)
parcels = intervention.get_underlying_parcels()
distinct_deductions = intervention.deductions.all().distinct(
"account"
)
qrcode = QrCode(
content=request.build_absolute_uri(reverse("intervention:report", args=(id,))),
size=10
)
qrcode_lanis = QrCode(
content=intervention.get_LANIS_link(),
size=7
)
context = {
"obj": intervention,
"deductions": distinct_deductions, "deductions": distinct_deductions,
"qrcode": { "qrcode": {
"img": qrcode.get_img(), "img": qrcode_report.get_img(),
"url": qrcode.get_content(), "url": qrcode_report.get_content(),
}, },
"qrcode_lanis": { "qrcode_lanis": {
"img": qrcode_lanis.get_img(), "img": qrcode_lanis.get_img(),
"url": qrcode_lanis.get_content(), "url": qrcode_lanis.get_content(),
}, },
"geom_form": geom_form,
"parcels": parcels,
"tables_scrollable": False, "tables_scrollable": False,
TAB_TITLE_IDENTIFIER: tab_title,
} }
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)

View File

@@ -5,22 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required from intervention.forms.modals.resubmission import InterventionResubmissionModalForm
from django.utils.decorators import method_decorator
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import default_group_required, shared_access_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView from konova.views.resubmission import AbstractResubmissionView
class InterventionResubmissionView(AbstractResubmissionView): class InterventionResubmissionView(AbstractResubmissionView):
model = Intervention _MODEL_CLS = Intervention
redirect_url_base = "intervention:detail" _FORM_CLS = InterventionResubmissionModalForm
form_action_url_base = "intervention:resubmission-create" _REDIRECT_URL = "intervention:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -7,65 +7,52 @@ Created on: 19.08.22
""" """
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views import View
from intervention.forms.modals.revocation import NewRevocationModalForm, EditRevocationModalForm, \ from intervention.forms.modals.revocation import NewRevocationModalForm, EditRevocationModalForm, \
RemoveRevocationModalForm RemoveRevocationModalForm
from intervention.models import Intervention, RevocationDocument, Revocation from intervention.models import Intervention, RevocationDocument
from konova.decorators import default_group_required, shared_access_required, login_required_modal
from konova.utils.documents import get_document from konova.utils.documents import get_document
from konova.utils.message_templates import REVOCATION_ADDED, DATA_UNSHARED, REVOCATION_EDITED, REVOCATION_REMOVED from konova.utils.message_templates import DATA_UNSHARED, REVOCATION_EDITED, REVOCATION_REMOVED, REVOCATION_ADDED
from konova.views.base import BaseModalFormView, BaseView
class NewInterventionRevocationView(LoginRequiredMixin, View): class BaseRevocationView(LoginRequiredMixin, BaseModalFormView):
def __process_request(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse: _MODEL_CLS = Intervention
""" Renders sharing form for an intervention _REDIRECT_URL = "intervention:detail"
Args: class Meta:
request (HttpRequest): The incoming request abstract = True
id (str): Intervention's id
Returns: def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
""" def _get_redirect_url(self, *args, **kwargs):
intervention = get_object_or_404(Intervention, id=id) url = super()._get_redirect_url(*args, **kwargs)
form = NewRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, return f"{url}#related_data"
request=request)
return form.process_request(
request,
msg_success=REVOCATION_ADDED,
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
return self.__process_request(request, id, *args, **kwargs)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
return self.__process_request(request, id, *args, **kwargs)
class GetInterventionRevocationView(LoginRequiredMixin, View): class NewRevocationView(BaseRevocationView):
@method_decorator(default_group_required) _FORM_CLS = NewRevocationModalForm
def get(self, request: HttpRequest, doc_id: str, *args, **kwargs) -> HttpResponse: _MSG_SUCCESS = REVOCATION_ADDED
""" Returns the revocation document as downloadable file
Wraps the generic document fetcher function from konova.utils.
Args: class EditRevocationView(BaseRevocationView):
request (HttpRequest): The incoming request _FORM_CLS = EditRevocationModalForm
doc_id (str): The document id _MSG_SUCCESS = REVOCATION_EDITED
Returns:
""" class RemoveRevocationView(BaseRevocationView):
_FORM_CLS = RemoveRevocationModalForm
_MSG_SUCCESS = REVOCATION_REMOVED
class GetRevocationDocumentView(LoginRequiredMixin, BaseView):
_MODEL_CLS = RevocationDocument
_REDIRECT_URL = "intervention:detail"
def get(self, request: HttpRequest, doc_id: str):
doc = get_object_or_404(RevocationDocument, id=doc_id) doc = get_object_or_404(RevocationDocument, id=doc_id)
# File download only possible if related instance is shared with user # File download only possible if related instance is shared with user
if not doc.instance.legal.intervention.users.filter(id=request.user.id): if not doc.instance.legal.intervention.users.filter(id=request.user.id):
@@ -76,70 +63,14 @@ class GetInterventionRevocationView(LoginRequiredMixin, View):
return redirect("intervention:detail", id=doc.instance.id) return redirect("intervention:detail", id=doc.instance.id)
return get_document(doc) return get_document(doc)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
class EditInterventionRevocationView(LoginRequiredMixin, View): def _user_has_shared_access(self, user, **kwargs):
def __process_request(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse: obj = get_object_or_404(self._MODEL_CLS, id=kwargs.get("doc_id"))
""" Renders a edit view for a revocation assert obj is not None
return obj.instance.intervention.is_shared_with(user)
Args: def _get_redirect_url(self, *args, **kwargs):
request (HttpRequest): The incoming request url = super()._get_redirect_url(*args, **kwargs)
id (str): The intervention's id as string return f"{url}#related_data"
revocation_id (str): The revocation's id as string
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
revocation = get_object_or_404(Revocation, id=revocation_id)
form = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention,
revocation=revocation, request=request)
return form.process_request(
request,
REVOCATION_EDITED,
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def get(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
return self.__process_request(request, id, revocation_id, *args, **kwargs)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def post(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
return self.__process_request(request, id, revocation_id, *args, **kwargs)
class RemoveInterventionRevocationView(LoginRequiredMixin, View):
def __process_request(self, request, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
""" Renders a remove view for a revocation
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id as string
revocation_id (str): The revocation's id as string
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
revocation = get_object_or_404(Revocation, id=revocation_id)
form = RemoveRevocationModalForm(request.POST or None, instance=intervention, revocation=revocation,
request=request)
return form.process_request(
request,
REVOCATION_REMOVED,
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def get(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
return self.__process_request(request, id, revocation_id, *args, **kwargs)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def post(self, request, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
return self.__process_request(request, id, revocation_id, *args, **kwargs)

View File

@@ -5,29 +5,15 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import default_group_required, shared_access_required, login_required_modal
from konova.views.share import AbstractShareByTokenView, AbstractShareFormView from konova.views.share import AbstractShareByTokenView, AbstractShareFormView
class InterventionShareByTokenView(AbstractShareByTokenView): class InterventionShareByTokenView(AbstractShareByTokenView):
model = Intervention _MODEL_CLS = Intervention
redirect_url = "intervention:detail" _REDIRECT_URL = "intervention:detail"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class InterventionShareFormView(AbstractShareFormView): class InterventionShareFormView(AbstractShareFormView):
model = Intervention _MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@@ -10,21 +10,18 @@ from abc import abstractmethod
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount
from konova.models import BaseObject
class BaseForm(forms.Form): class BaseForm(forms.Form):
""" """
Basic form for that holds attributes needed in all other forms Basic form for that holds attributes needed in all other forms
""" """
template = None
action_url = None action_url = None
action_btn_label = _("Save") action_btn_label = _("Save")
form_title = None form_title = None
cancel_redirect = None cancel_redirect = None
form_caption = None form_caption = None
instance = None # The data holding model object instance = None # The data holding model object
user = None # The performing user
request = None request = None
form_attrs = {} # Holds additional attributes, that can be used in the template form_attrs = {} # Holds additional attributes, that can be used in the template
has_required_fields = False # Automatically set. Triggers hint rendering in templates has_required_fields = False # Automatically set. Triggers hint rendering in templates
@@ -33,6 +30,7 @@ class BaseForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.instance = kwargs.pop("instance", None) self.instance = kwargs.pop("instance", None)
self.user = kwargs.pop("user", None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.request is not None: if self.request is not None:
self.user = self.request.user self.user = self.request.user
@@ -42,11 +40,10 @@ class BaseForm(forms.Form):
self.has_required_fields = True self.has_required_fields = True
break break
self.check_for_recorded_instance()
self.__check_valid_label_input_ratio() self.__check_valid_label_input_ratio()
@abstractmethod @abstractmethod
def save(self): def save(self, *arg, **kwargs):
# To be implemented in subclasses! # To be implemented in subclasses!
pass pass
@@ -136,34 +133,3 @@ class BaseForm(forms.Form):
set_class = self.fields[field].widget.attrs.get("class", "") set_class = self.fields[field].widget.attrs.get("class", "")
set_class = set_class.replace(cls, "") set_class = set_class.replace(cls, "")
self.fields[field].widget.attrs["class"] = set_class self.fields[field].widget.attrs["class"] = set_class
def check_for_recorded_instance(self):
""" Checks if the instance is recorded and runs some special logic if yes
If the instance is recorded, the form shall not display any possibility to
edit any data. Instead, the users should get some information about why they can not edit anything.
There are situations where the form should be rendered regularly,
e.g deduction forms for (recorded) eco accounts.
Returns:
"""
is_none = self.instance is None
is_other_data_type = not isinstance(self.instance, BaseObject)
if is_none or is_other_data_type:
# Do nothing
return
if self.instance.is_recorded:
self.block_form()
def block_form(self):
"""
Overwrites template, providing no actions
Returns:
"""
self.template = "form/recorded_no_edit.html"

View File

@@ -35,7 +35,6 @@ class SimpleGeomForm(BaseForm):
disabled=False, disabled=False,
) )
_num_geometries_ignored: int = 0 _num_geometries_ignored: int = 0
empty = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.read_only = kwargs.pop("read_only", True) self.read_only = kwargs.pop("read_only", True)
@@ -50,11 +49,11 @@ class SimpleGeomForm(BaseForm):
raise AttributeError raise AttributeError
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP) geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
geojson = self._set_geojson_properties(geojson, title=self.instance.identifier or None) self._set_geojson_properties(geojson, title=self.instance.identifier or None)
geom = json.dumps(geojson) geom = json.dumps(geojson)
except AttributeError: except AttributeError:
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level # If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
geom = json.dumps({}) geom = ""
self.empty = True self.empty = True
self.initialize_form_field("output", geom) self.initialize_form_field("output", geom)
@@ -63,18 +62,18 @@ class SimpleGeomForm(BaseForm):
super().is_valid() super().is_valid()
is_valid = True is_valid = True
# Make sure invalid geometry is properly rendered again to the user # Get geojson from form
# Therefore: write submitted data back into form field geom = self.data.get("output", None)
# (does not matter whether we know if it is valid or invalid) if geom is None or len(geom) == 0:
submitted_data = self.data["output"] # empty geometry is a valid geometry
submitted_data = json.loads(submitted_data) self.cleaned_data["output"] = MultiPolygon(srid=DEFAULT_SRID_RLP).ewkt
submitted_data = self._set_geojson_properties(submitted_data) return is_valid
self.initialize_form_field("output", json.dumps(submitted_data))
# Get geojson from form for validity checking
geom = self.data.get("output", json.dumps({}))
geom = json.loads(geom) geom = json.loads(geom)
# Write submitted data back into form field to make sure invalid geometry
# will be rendered again on failed submit
self.initialize_form_field("output", self.data["output"])
# Initialize features list with empty MultiPolygon, so that an empty input will result in a # Initialize features list with empty MultiPolygon, so that an empty input will result in a
# proper empty MultiPolygon object # proper empty MultiPolygon object
features = [] features = []
@@ -85,23 +84,20 @@ class SimpleGeomForm(BaseForm):
"MultiPolygon", "MultiPolygon",
"MultiPolygon25D", "MultiPolygon25D",
] ]
# Check validity for each feature of the geometry
for feature in features_json: for feature in features_json:
feature_geom = feature.get("geometry", feature) feature_geom = feature.get("geometry", feature)
if feature_geom is None: if feature_geom is None:
# Fallback for rare cases where a feature does not contain any geometry # Fallback for rare cases where a feature does not contain any geometry
continue continue
# Try to create a geometry object from the single feature
feature_geom = json.dumps(feature_geom) feature_geom = json.dumps(feature_geom)
g = gdal.OGRGeometry(feature_geom, srs=DEFAULT_SRID_RLP) g = gdal.OGRGeometry(feature_geom, srs=DEFAULT_SRID_RLP)
geometry_has_unwanted_dimensions = g.coord_dim > 2 flatten_geometry = g.coord_dim > 2
if geometry_has_unwanted_dimensions: if flatten_geometry:
g = self.__flatten_geom_to_2D(g) g = self.__flatten_geom_to_2D(g)
geometry_type_is_accepted = g.geom_type not in accepted_ogr_types if g.geom_type not in accepted_ogr_types:
if geometry_type_is_accepted:
self.add_error("output", _("Only surfaces allowed. Points or lines must be buffered.")) self.add_error("output", _("Only surfaces allowed. Points or lines must be buffered."))
is_valid &= False is_valid &= False
return is_valid return is_valid
@@ -113,33 +109,27 @@ class SimpleGeomForm(BaseForm):
self._num_geometries_ignored += 1 self._num_geometries_ignored += 1
continue continue
# Whatever this geometry object is -> try to create a Polygon from it
# The resulting polygon object automatically detects whether a valid polygon has been created or not
g = Polygon.from_ewkt(g.ewkt) g = Polygon.from_ewkt(g.ewkt)
is_valid &= g.valid is_valid &= g.valid
if not g.valid: if not g.valid:
self.add_error("output", g.valid_reason) self.add_error("output", g.valid_reason)
return is_valid return is_valid
# If the resulting polygon is just a single polygon, we add it to the list of properly casted features
if isinstance(g, Polygon): if isinstance(g, Polygon):
features.append(g) features.append(g)
elif isinstance(g, MultiPolygon): elif isinstance(g, MultiPolygon):
# The resulting polygon could be of type MultiPolygon (due to multiple surfaces)
# If so, we extract all polygons from the MultiPolygon and extend the casted features list
features.extend(list(g)) features.extend(list(g))
# Unionize all polygon features into one new MultiPolygon # Unionize all geometry features into one new MultiPolygon
if features: if features:
form_geom = MultiPolygon(*features, srid=DEFAULT_SRID_RLP).unary_union form_geom = MultiPolygon(*features, srid=DEFAULT_SRID_RLP).unary_union
else: else:
# If no features have been processed, this indicates an empty geometry - so we store an empty geometry
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP) form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided. # Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
form_geom = Geometry.cast_to_multipolygon(form_geom) form_geom = Geometry.cast_to_multipolygon(form_geom)
# Write unionized Multipolygon back into cleaned data # Write unioned Multipolygon into cleaned data
if self.cleaned_data is None: if self.cleaned_data is None:
self.cleaned_data = {} self.cleaned_data = {}
self.cleaned_data["output"] = form_geom.ewkt self.cleaned_data["output"] = form_geom.ewkt
@@ -262,8 +252,6 @@ class SimpleGeomForm(BaseForm):
""" """
features = geojson.get("features", []) features = geojson.get("features", [])
for feature in features: for feature in features:
if not feature.get("properties", None):
feature["properties"] = {}
feature["properties"]["editable"] = not self.read_only feature["properties"]["editable"] = not self.read_only
if title: if title:
feature["properties"]["title"] = title feature["properties"]["title"] = title

View File

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

View File

@@ -8,10 +8,10 @@ Created on: 15.08.22
from django import forms from django import forms
from django.db import transaction from django.db import transaction
from django.db.models.fields.files import FieldFile from django.db.models.fields.files import FieldFile
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from konova.forms.modals.base_form import BaseModalForm from konova.forms.modals.base_form import BaseModalForm
from konova.models import AbstractDocument
from konova.utils import validators from konova.utils import validators
from konova.utils.message_templates import DOCUMENT_EDITED, FILE_SIZE_TOO_LARGE, FILE_TYPE_UNSUPPORTED from konova.utils.message_templates import DOCUMENT_EDITED, FILE_SIZE_TOO_LARGE, FILE_TYPE_UNSUPPORTED
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
@@ -69,7 +69,7 @@ class NewDocumentModalForm(BaseModalForm):
} }
) )
) )
document_model = None _DOCUMENT_CLS = None
class Meta: class Meta:
abstract = True abstract = True
@@ -81,7 +81,7 @@ class NewDocumentModalForm(BaseModalForm):
self.form_attrs = { self.form_attrs = {
"enctype": "multipart/form-data", # important for file upload "enctype": "multipart/form-data", # important for file upload
} }
if not self.document_model: if not self._DOCUMENT_CLS:
raise NotImplementedError("Unsupported document type for {}".format(self.instance.__class__)) raise NotImplementedError("Unsupported document type for {}".format(self.instance.__class__))
def is_valid(self): def is_valid(self):
@@ -93,14 +93,14 @@ class NewDocumentModalForm(BaseModalForm):
# FieldFile declares that no new file has been uploaded and we do not need to check on the file again # FieldFile declares that no new file has been uploaded and we do not need to check on the file again
return super_valid return super_valid
mime_type_valid = self.document_model.is_mime_type_valid(_file) mime_type_valid = self._DOCUMENT_CLS.is_mime_type_valid(_file)
if not mime_type_valid: if not mime_type_valid:
self.add_error( self.add_error(
"file", "file",
FILE_TYPE_UNSUPPORTED FILE_TYPE_UNSUPPORTED
) )
file_size_valid = self.document_model.is_file_size_valid(_file) file_size_valid = self._DOCUMENT_CLS.is_file_size_valid(_file)
if not file_size_valid: if not file_size_valid:
self.add_error( self.add_error(
"file", "file",
@@ -115,7 +115,7 @@ class NewDocumentModalForm(BaseModalForm):
action = UserActionLogEntry.get_created_action(self.user) action = UserActionLogEntry.get_created_action(self.user)
edited_action = UserActionLogEntry.get_edited_action(self.user, _("Added document")) edited_action = UserActionLogEntry.get_edited_action(self.user, _("Added document"))
doc = self.document_model.objects.create( doc = self._DOCUMENT_CLS.objects.create(
created=action, created=action,
title=self.cleaned_data["title"], title=self.cleaned_data["title"],
comment=self.cleaned_data["comment"], comment=self.cleaned_data["comment"],
@@ -133,10 +133,12 @@ class NewDocumentModalForm(BaseModalForm):
class EditDocumentModalForm(NewDocumentModalForm): class EditDocumentModalForm(NewDocumentModalForm):
document = None document = None
document_model = AbstractDocument _DOCUMENT_CLS = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.document = kwargs.pop("document", None) doc_id = kwargs.pop("doc_id", None)
self.document = get_object_or_404(self._DOCUMENT_CLS, id=doc_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit document") self.form_title = _("Edit document")
form_data = { form_data = {

View File

@@ -6,10 +6,11 @@ Created on: 15.08.22
""" """
from django import forms from django import forms
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from konova.forms.modals.base_form import BaseModalForm from konova.forms.modals.base_form import BaseModalForm
from konova.models import BaseObject from konova.models import BaseObject, Deadline
class RemoveModalForm(BaseModalForm): class RemoveModalForm(BaseModalForm):
@@ -51,9 +52,19 @@ class RemoveDeadlineModalForm(RemoveModalForm):
deadline = None deadline = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
deadline = kwargs.pop("deadline", None) deadline_id = kwargs.pop("deadline_id", None)
self.deadline = deadline self.deadline = get_object_or_404(Deadline, id=deadline_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):
self.instance.remove_deadline(self) self.instance.remove_deadline(self)
class RemoveDocumentModalForm(RemoveModalForm):
instance = None
_DOCUMENT_CLS = None
def __init__(self, *args, **kwargs):
document_id = kwargs.pop("doc_id", None)
super().__init__(*args, **kwargs)
self.instance = get_object_or_404(self._DOCUMENT_CLS, id=document_id)

View File

@@ -10,7 +10,6 @@ import json
from django.contrib.gis.db.models import MultiPolygonField from django.contrib.gis.db.models import MultiPolygonField
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.db import models, transaction from django.db import models, transaction
from django.db.models import Q
from django.utils import timezone from django.utils import timezone
from django.contrib.gis.geos import MultiPolygon from django.contrib.gis.geos import MultiPolygon
@@ -103,41 +102,24 @@ class Geometry(BaseResource):
resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts) resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts)
resolved_conflicts.delete() resolved_conflicts.delete()
def get_data_objects(self, limit_to_attrs: list = None): def get_data_objects(self):
""" Getter for all objects which are related to this geometry """ Getter for all objects which are related to this geometry
Using the limit_to_attrs we can limit the amount of returned data directly onto the data object attributes
we want to have. Reduces memory consumption and runtime.
Returns: Returns:
objs (list): The list of objects objs (list): The list of objects
""" """
objs = [] objs = []
sets = [
# Some related data sets can be processed rather easily
regular_sets = [
self.intervention_set, self.intervention_set,
self.compensation_set,
self.ema_set, self.ema_set,
self.ecoaccount_set, self.ecoaccount_set,
] ]
for _set in regular_sets: for _set in sets:
set_objs = _set.filter( set_objs = _set.filter(
deleted=None deleted=None
) )
if limit_to_attrs: objs += set_objs
objs += set_objs.values_list(*limit_to_attrs, flat=True)
else:
objs += set_objs
# ... but we need a special treatment for compensations, since they can be deleted directly OR inherit their
# de-facto-deleted status from their deleted parent intervention
comp_objs = self.compensation_set.filter(
Q(deleted=None) & Q(intervention__deleted=None)
)
if limit_to_attrs:
objs += comp_objs.values_list(*limit_to_attrs, flat=True)
else:
objs += comp_objs
return objs return objs
def get_data_object(self): def get_data_object(self):
@@ -415,10 +397,7 @@ class Geometry(BaseResource):
""" """
output_geom = input_geom output_geom = input_geom
if not isinstance(input_geom, MultiPolygon): if not isinstance(input_geom, MultiPolygon):
try: output_geom = MultiPolygon(input_geom, srid=DEFAULT_SRID_RLP)
output_geom = MultiPolygon(input_geom, srid=DEFAULT_SRID_RLP)
except TypeError as e:
raise AssertionError(f"Only (Multi)Polygon allowed! Could not convert {input_geom.geom_type} to MultiPolygon")
return output_geom return output_geom
@staticmethod @staticmethod

View File

@@ -677,23 +677,19 @@ class GeoReferencedMixin(models.Model):
return request return request
instance_objs = [] instance_objs = []
needed_data_object_attrs = [ conflicts = self.geometry.conflicts_geometries.all()
"identifier"
]
conflicts = self.geometry.conflicts_geometries.iterator()
for conflict in conflicts: for conflict in conflicts:
# Only check the affected geometry of this conflict, since we know the conflicting geometry is self.geometry instance_objs += conflict.affected_geometry.get_data_objects()
instance_objs += conflict.affected_geometry.get_data_objects(needed_data_object_attrs)
conflicts = self.geometry.conflicted_by_geometries.iterator() conflicts = self.geometry.conflicted_by_geometries.all()
for conflict in conflicts: for conflict in conflicts:
# Only check the conflicting geometry of this conflict, since we know the affected geometry is self.geometry instance_objs += conflict.conflicting_geometry.get_data_objects()
instance_objs += conflict.conflicting_geometry.get_data_objects(needed_data_object_attrs)
add_message = len(instance_objs) > 0 add_message = len(instance_objs) > 0
if add_message: if add_message:
instance_identifiers = ", ".join(instance_objs) instance_identifiers = [x.identifier for x in instance_objs]
instance_identifiers = ", ".join(instance_identifiers)
message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers) message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers)
messages.info(request, message_str) messages.info(request, message_str)
return request return request

View File

@@ -291,5 +291,5 @@ Overwrites netgis.css attributes
} }
.netgis-menu{ .netgis-menu{
z-index: 1 !important; z-index: 100 !important;
} }

View File

@@ -11,4 +11,4 @@ BASE_TITLE = "KSP - Kompensationsverzeichnis Service Portal"
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal" BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
TAB_TITLE_IDENTIFIER = "tab_title" TAB_TITLE_IDENTIFIER = "tab_title"
HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp2:start" HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp2:start"
IMPRESSUM_LINK = "https://naturschutz.rlp.de/ueber-uns/impressum" IMPRESSUM_LINK = "https://naturschutz.rlp.de/index.php?q=impressum"

View File

@@ -543,7 +543,7 @@ class BaseViewTestCase(BaseTestCase):
for url, redirect_to in urls.items(): for url, redirect_to in urls.items():
response = client.get(url, follow=True) response = client.get(url, follow=True)
# Expect redirects to the landing page # Expect redirects to the landing page
self.assertEqual(response.redirect_chain[0], (redirect_to, 302), msg=f"Failed for {url}") self.assertEqual(response.redirect_chain[0], (redirect_to, 302), msg=f"Failed for {url}. Expected {redirect_to}")
def assert_url_fail(self, client: Client, urls: list): def assert_url_fail(self, client: Client, urls: list):
""" Assert for all given urls a direct 302 response """ Assert for all given urls a direct 302 response

View File

@@ -103,7 +103,7 @@ class EditDeadlineModalFormTestCase(NewDeadlineModalFormTestCase):
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
deadline=self.finished_deadline, deadline_id=self.finished_deadline.id,
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)

View File

@@ -17,9 +17,9 @@ from django.utils.timezone import now
from compensation.forms.modals.document import NewEcoAccountDocumentModalForm, NewCompensationDocumentModalForm from compensation.forms.modals.document import NewEcoAccountDocumentModalForm, NewCompensationDocumentModalForm
from compensation.models import Payment from compensation.models import Payment
from ema.forms import NewEmaDocumentModalForm from ema.forms import NewEmaDocumentModalForm
from intervention.forms.modals.document import NewInterventionDocumentModalForm from intervention.forms.modals.document import NewInterventionDocumentModalForm, EditInterventionDocumentModalForm
from intervention.models import InterventionDocument from intervention.models import InterventionDocument
from konova.forms.modals import EditDocumentModalForm, NewDocumentModalForm, RecordModalForm, RemoveModalForm, \ from konova.forms.modals import NewDocumentModalForm, RecordModalForm, RemoveModalForm, \
RemoveDeadlineModalForm, ResubmissionModalForm RemoveDeadlineModalForm, ResubmissionModalForm
from konova.models import Resubmission from konova.models import Resubmission
from konova.tests.test_views import BaseTestCase from konova.tests.test_views import BaseTestCase
@@ -106,12 +106,12 @@ class EditDocumentModalFormTestCase(NewDocumentModalFormTestCase):
InterventionDocument, InterventionDocument,
instance=self.intervention instance=self.intervention
) )
self.form = EditDocumentModalForm( self.form = EditInterventionDocumentModalForm(
self.data, self.data,
dummy_file_dict, dummy_file_dict,
request=self.request, request=self.request,
instance=self.intervention, instance=self.intervention,
document=self.doc doc_id=self.doc.id
) )
def test_init(self): def test_init(self):
@@ -122,7 +122,6 @@ class EditDocumentModalFormTestCase(NewDocumentModalFormTestCase):
self.assertEqual(self.form.fields["title"].initial, self.doc.title) self.assertEqual(self.form.fields["title"].initial, self.doc.title)
self.assertEqual(self.form.fields["comment"].initial, self.doc.comment) self.assertEqual(self.form.fields["comment"].initial, self.doc.comment)
self.assertEqual(self.form.fields["creation_date"].initial, self.doc.date_of_creation) self.assertEqual(self.form.fields["creation_date"].initial, self.doc.date_of_creation)
self.assertEqual(self.form.fields["file"].initial, self.doc.file)
def test_save(self): def test_save(self):
self.assertTrue(self.form.is_valid(), msg=self.form.errors) self.assertTrue(self.form.is_valid(), msg=self.form.errors)
@@ -256,7 +255,7 @@ class RemoveDeadlineTestCase(BaseTestCase):
form = RemoveDeadlineModalForm( form = RemoveDeadlineModalForm(
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
deadline=self.finished_deadline deadline_id=self.finished_deadline.id
) )
self.assertEqual(form.form_title, str(_("Remove"))) self.assertEqual(form.form_title, str(_("Remove")))
self.assertEqual(form.form_caption, str(_("Are you sure?"))) self.assertEqual(form.form_caption, str(_("Are you sure?")))
@@ -273,7 +272,7 @@ class RemoveDeadlineTestCase(BaseTestCase):
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
deadline=self.finished_deadline deadline_id=self.finished_deadline.id
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
form.save() form.save()

View File

@@ -7,9 +7,7 @@ Created on: 01.09.21
""" """
from django.http import FileResponse, HttpRequest, Http404 from django.http import FileResponse, HttpRequest, Http404
from konova.forms.modals import RemoveModalForm
from konova.models import AbstractDocument from konova.models import AbstractDocument
from konova.utils.message_templates import DOCUMENT_REMOVED_TEMPLATE
def get_document(doc: AbstractDocument): def get_document(doc: AbstractDocument):
@@ -26,28 +24,3 @@ def get_document(doc: AbstractDocument):
return FileResponse(doc.file, as_attachment=True) return FileResponse(doc.file, as_attachment=True)
except FileNotFoundError: except FileNotFoundError:
raise Http404() raise Http404()
def remove_document(request: HttpRequest, doc: AbstractDocument):
""" Renders a form for uploading new documents
This function works using a modal. We are not using the regular way, the django bootstrap modal forms are
intended to be used. Instead of View classes we work using the classic way of dealing with forms (see below).
It is important to mention, that modal forms, which should reload the page afterwards, must provide a
'reload_page' bool in the context. This way, the modal may reload the page or not.
For further details see the comments in templates/modal or
https://github.com/trco/django-bootstrap-modal-forms
Args:
request (HttpRequest): The incoming request
Returns:
"""
title = doc.title
form = RemoveModalForm(request.POST or None, instance=doc, request=request)
return form.process_request(
request=request,
msg_success=DOCUMENT_REMOVED_TEMPLATE.format(title),
)

View File

@@ -5,9 +5,6 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 11.12.23 Created on: 11.12.23
""" """
import json
from json import JSONDecodeError
from django.views.debug import ExceptionReporter from django.views.debug import ExceptionReporter
@@ -33,7 +30,7 @@ class KonovaExceptionReporter(ExceptionReporter):
""" """
whitelist = [ whitelist = [
"is_email", "is_email",
"unicode_hint", "unicdoe_hint",
"frames", "frames",
"request", "request",
"user_str", "user_str",
@@ -42,8 +39,6 @@ class KonovaExceptionReporter(ExceptionReporter):
"raising_view_name", "raising_view_name",
"exception_type", "exception_type",
"exception_value", "exception_value",
"filtered_GET_items",
"filtered_POST_items",
] ]
clean_data = dict() clean_data = dict()
for entry in whitelist: for entry in whitelist:
@@ -61,28 +56,7 @@ class KonovaExceptionReporter(ExceptionReporter):
""" """
tb_data = super().get_traceback_data() tb_data = super().get_traceback_data()
return_data = tb_data
if self.is_email: if self.is_email:
filtered_data = dict() tb_data = self._filter_traceback_data(tb_data)
filtered_data.update(self._filter_traceback_data(tb_data))
filtered_data.update(self._filter_POST_body(tb_data))
return_data = filtered_data
return return_data
def _filter_POST_body(self, tb_data: dict): return tb_data
""" Filters POST body from traceback data
"""
post_data = tb_data.get("request", None)
if post_data:
post_data = post_data.body
try:
post_data = json.loads(post_data)
except JSONDecodeError:
pass
post_data = {
"filtered_POST_items": [
("body", post_data),
]
}
return post_data

View File

@@ -5,6 +5,11 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 17.09.21 Created on: 17.09.21
""" """
from uuid import UUID
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from django.http import HttpRequest, Http404
def format_german_float(num) -> str: def format_german_float(num) -> str:
@@ -19,3 +24,27 @@ def format_german_float(num) -> str:
num (str): The number as german Gleitkommazahl num (str): The number as german Gleitkommazahl
""" """
return format(num, "0,.2f").replace(",", "X").replace(".", ",").replace("X", ".") return format(num, "0,.2f").replace(",", "X").replace(".", ",").replace("X", ".")
def check_user_is_in_any_group(request: HttpRequest):
"""
Checks for any group membership. Adds a message in case of having none.
"""
user = request.user
# Inform user about missing group privileges!
groups = user.groups.all()
if not groups:
messages.info(
request,
_("+++ Attention: You are not part of any group. You won't be able to create, edit or do anything. Please contact an administrator. +++")
)
return request
def check_id_is_valid_uuid(uuid: str):
if uuid:
try:
# Check whether the id is a proper uuid or something that would break a db fetch
UUID(uuid)
except ValueError:
raise Http404

View File

@@ -7,10 +7,6 @@ Created on: 09.11.20
""" """
import random import random
import string import string
import qrcode
import qrcode.image.svg
from io import BytesIO
def generate_token() -> str: def generate_token() -> str:
@@ -42,24 +38,3 @@ def generate_random_string(length: int, use_numbers: bool = False, use_letters_l
ret_val = "".join(random.choice(elements) for i in range(length)) ret_val = "".join(random.choice(elements) for i in range(length))
return ret_val return ret_val
class IdentifierGenerator:
_MODEL = None
def __init__(self, model):
from konova.models import BaseObject
if not issubclass(model, BaseObject):
raise AssertionError("Model must be a subclass of BaseObject!")
self._MODEL = model
def generate_id(self) -> str:
""" Generates a unique identifier
Returns:
"""
unpersisted_object = self._MODEL()
identifier = unpersisted_object.generate_new_identifier()
while self._MODEL.objects.filter(identifier=identifier).exists():
identifier = unpersisted_object.generate_new_identifier()
return identifier

View File

@@ -19,7 +19,20 @@ IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since ano
ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office users are allowed to remove entries.") ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office users are allowed to remove entries.")
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.") MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
CHECK_STATE_RESET = _("Status of Checked reset") CHECK_STATE_RESET = _("Status of Checked reset")
# USER | TEAM
TEAM_ADDED = _("New team added")
TEAM_EDITED = _("Team edited")
TEAM_REMOVED = _("Team removed")
TEAM_LEFT = _("Left Team")
# REMOVED
GENERIC_REMOVED_TEMPLATE = _("{} removed")
# RECORDING
RECORDED_BLOCKS_EDIT = _("Entry is recorded. To edit data, the entry first needs to be unrecorded.") RECORDED_BLOCKS_EDIT = _("Entry is recorded. To edit data, the entry first needs to be unrecorded.")
ENTRY_RECORDED = _("{} recorded")
ENTRY_UNRECORDED = _("{} unrecorded")
# SHARE # SHARE
DATA_UNSHARED = _("This data is not shared with you") DATA_UNSHARED = _("This data is not shared with you")
@@ -94,4 +107,7 @@ DATA_CHECKED_PREVIOUSLY_TEMPLATE = _("Data has changed since last check on {} by
DATA_IS_UNCHECKED = _("Current data not checked yet") DATA_IS_UNCHECKED = _("Current data not checked yet")
# API TOKEN SETTINGS # API TOKEN SETTINGS
NEW_API_TOKEN_GENERATED = _("New token generated. Administrators need to validate.") NEW_API_TOKEN_GENERATED = _("New token generated. Administrators need to validate.")
# RESUBMISSION
NEW_RESUBMISSION_CREATED = _("Resubmission set")

View File

@@ -1,6 +1,6 @@
""" """
Author: Michel Peltriaux Author: Michel Peltriaux
Created on: 14.12.25 Created on: 17.10.25
""" """
from io import BytesIO from io import BytesIO

Some files were not shown because too many files have changed in this diff Show More