Compare commits

...

15 Commits

Author SHA1 Message Date
f146aa983a Merge pull request '# Boost geometry conflict message fetch' (#523) from 503_Improve_performance_on_geometry_conflict_message into master
Reviewed-on: #523
2026-01-14 08:03:01 +00:00
60e9430542 # Boost geometry conflict message fetch
* reduces runtime of geometry conflict info message generating to ~45% to prior runtime
2026-01-14 09:02:41 +01:00
970d0e79fa Merge pull request '490_View_refactoring_II' (#520) from 490_View_refactoring_II into master
Reviewed-on: #520
2026-01-13 09:36:11 +00:00
3f33de3626 # Analysis, API and Payment views
* refactors payment creation, editing and removing into class based views
* refactors analysis report methods into class based views
* drops unused method view on api app (token generating has been de facto moved into users app long time ago)
2026-01-13 10:35:09 +01:00
9e5bb84ab4 Merge pull request '# Compensation sum fix' (#518) from 517_Compensation_sum_wrong into master
Reviewed-on: #518
2026-01-10 10:03:27 +00:00
4c372c1a04 # Compensation sum fix
* fixes sum of compensations on landing page
2026-01-10 11:00:26 +01:00
ee2c859a9e Merge pull request '# Improve exception reporting for API' (#515) from improve_exception_reporting into master
Reviewed-on: #515
2025-12-19 14:17:37 +01:00
328f672ec0 # Improve exception reporting for API
* fixes typo in exception_reporter.py
* properly catches error on geometry cast into multipolygon if input are no valid polygons
* extends error response on malicious api calls
* specifies different exceptions on try-catch while initializing api data
2025-12-19 14:17:15 +01:00
88058d7caf # EcoAccount New and Edit
* refactors new and edit method views into classes
2025-12-17 14:34:04 +01:00
0e6f8d5b55 # Compensation New and Edit
* refactors compensation new and edit method views into classes
2025-12-17 14:24:43 +01:00
047c9489fe Merge pull request '# ExceptionReporter adjustment' (#513) from improve_exception_reporting into master
Reviewed-on: #513
2025-12-17 14:03:23 +01:00
38b81996ed # ExceptionReporter adjustment
* extends the KonovaExceptionReporter to hold POST body content (practical for debugging broken content on API)
2025-12-17 14:02:08 +01:00
3966521cd4 # Revocation Intervention views
* refactors revocation method views for intervention into classes
2025-12-16 16:34:44 +01:00
e70a8b51d1 # Remove-KOM-from-EIV view
* refactors view method into class
2025-12-16 16:25:46 +01:00
02dc0d0a59 # Check view
* refactors method based view into class
2025-12-16 16:21:42 +01:00
21 changed files with 704 additions and 437 deletions

View File

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

View File

@@ -1,8 +1,12 @@
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
@@ -42,10 +46,59 @@ 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): Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "analysis/reports/index.html"
form = TimespanReportForm(None)
context = {
"form": form
}
context = BaseContext(request, context).context
return render(request, template, context)
@method_decorator(conservation_office_group_required)
def post(self, request: HttpRequest) -> HttpResponse:
"""
Args:
request (HttpRequest): The incoming request
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 """ Renders the detailed report for a conservation office
Args: Args:
@@ -79,20 +132,26 @@ def detail_report_view(request: HttpRequest, id: str):
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": cons_office, "office": 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={cons_office.long_name}_{df}_{dt}.xlsx' response['Content-Disposition'] = f'attachment; filename={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, 500, msg=response.content) self.assertEqual(response.status_code, 400, 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

@@ -7,11 +7,8 @@ Created on: 21.01.22
""" """
from django.urls import path, include from django.urls import path, include
from api.views.method_views import generate_new_token_view
app_name = "api" app_name = "api"
urlpatterns = [ urlpatterns = [
path("v1/", include("api.urls.v1.urls", namespace="v1")), path("v1/", include("api.urls.v1.urls", namespace="v1")),
path("token/generate", generate_new_token_view, name="generate-new-token"),
] ]

View File

@@ -1,35 +0,0 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 27.01.22
"""
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest, JsonResponse
from api.models import APIUserToken
@login_required
def generate_new_token_view(request: HttpRequest):
""" Handles request for fetching
Args:
request (HttpRequest): The incoming request
Returns:
"""
if request.method == "GET":
token = APIUserToken()
while APIUserToken.objects.filter(token=token.token).exists():
token = APIUserToken()
return JsonResponse(
data={
"gen_data": token.token
}
)
else:
raise NotImplementedError

View File

@@ -6,7 +6,9 @@ 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
@@ -66,8 +68,12 @@ 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 Exception as e: except (JSONDecodeError,
return self._return_error_response(e, 500) AssertionError,
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,9 +81,7 @@ class AbstractAPIView(View):
Returns: Returns:
""" """
content = [error.__str__()] content = [f"{error.__class__.__name__}: {str(error)}"]
if hasattr(error, "messages"):
content = error.messages
return JsonResponse( return JsonResponse(
{ {
"errors": content "errors": content

View File

@@ -19,19 +19,19 @@ from compensation.views.compensation.action import NewCompensationActionView, Ed
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 new_view, edit_view, \ from compensation.views.compensation.compensation import IndexCompensationView, CompensationIdentifierGeneratorView, \
IndexCompensationView, CompensationIdentifierGeneratorView EditCompensationView, NewCompensationView
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("", IndexCompensationView.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>', new_view, name='new'), path('new/<intervention_id>', NewCompensationView.as_view(), name='new'),
path('new', new_view, name='new'), path('new', NewCompensationView.as_view(), name='new'),
path('<id>', DetailCompensationView.as_view(), name='detail'), path('<id>', DetailCompensationView.as_view(), name='detail'),
path('<id>/log', CompensationLogView.as_view(), name='log'), path('<id>/log', CompensationLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'), path('<id>/edit', EditCompensationView.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'),

View File

@@ -9,8 +9,8 @@ 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.detail import DetailEcoAccountView
from compensation.views.eco_account.eco_account import new_view, edit_view, \ from compensation.views.eco_account.eco_account import IndexEcoAccountView, EcoAccountIdentifierGeneratorView, \
IndexEcoAccountView, EcoAccountIdentifierGeneratorView 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.remove import RemoveEcoAccountView
@@ -31,13 +31,13 @@ from compensation.views.eco_account.deduction import NewEcoAccountDeductionView,
app_name = "acc" app_name = "acc"
urlpatterns = [ urlpatterns = [
path("", IndexEcoAccountView.as_view(), name="index"), path("", IndexEcoAccountView.as_view(), name="index"),
path('new/', new_view, name='new'), path('new/', NewEcoAccountView.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>', DetailEcoAccountView.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', EcoAccountPublicReportView.as_view(), name='report'),
path('<id>/edit', edit_view, name='edit'), path('<id>/edit', EditEcoAccountView.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

@@ -10,7 +10,7 @@ from compensation.views.payment import *
app_name = "pay" app_name = "pay"
urlpatterns = [ urlpatterns = [
path('<id>/new', new_payment_view, name='new'), path('<id>/new', NewPaymentView.as_view(), name='new'),
path('<id>/remove/<payment_id>', payment_remove_view, name='remove'), path('<id>/remove/<payment_id>', RemovePaymentView.as_view(), name='remove'),
path('<id>/edit/<payment_id>', payment_edit_view, name='edit'), path('<id>/edit/<payment_id>', EditPaymentView.as_view(), name='edit'),
] ]

View File

@@ -6,11 +6,12 @@ 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.core.exceptions import ObjectDoesNotExist
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
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 compensation.forms.compensation import EditCompensationForm, NewCompensationForm from compensation.forms.compensation import EditCompensationForm, NewCompensationForm
from compensation.models import Compensation from compensation.models import Compensation
@@ -54,10 +55,54 @@ class IndexCompensationView(AbstractIndexView):
context = BaseContext(request, context).context context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context) return render(request, self._TEMPLATE, context)
@login_required
@default_group_required class NewCompensationView(LoginRequiredMixin, View):
@shared_access_required(Intervention, "intervention_id") _TEMPLATE = "compensation/form/view.html"
def new_view(request: HttpRequest, intervention_id: str = None):
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "intervention_id"))
def get(self, request: HttpRequest, intervention_id: str = None, *args, **kwargs) -> HttpResponse:
"""
Renders a view for new compensation
A compensation creation may be called directly from the parent-intervention object. If so - we may take
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)
recording_state_blocks_actions = intervention.is_recorded
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)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New compensation"),
}
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 Renders a view for a new compensation creation
@@ -67,14 +112,12 @@ def new_view(request: HttpRequest, intervention_id: str = None):
Returns: Returns:
""" """
template = "compensation/form/view.html" if intervention_id:
if intervention_id is not None: # 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)
except ObjectDoesNotExist: recording_state_blocks_actions = intervention.is_recorded
messages.error(request, PARAMS_INVALID) if recording_state_blocks_actions:
return redirect("home")
if intervention.is_recorded:
messages.info( messages.info(
request, request,
RECORDED_BLOCKS_EDIT RECORDED_BLOCKS_EDIT
@@ -83,7 +126,7 @@ def new_view(request: HttpRequest, intervention_id: str = None):
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id) data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
geom_form = SimpleGeomForm(request.POST or None, read_only=False) geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
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)
comp = data_form.save(request.user, geom_form) comp = data_form.save(request.user, geom_form)
@@ -101,37 +144,72 @@ def new_view(request: HttpRequest, intervention_id: str = None):
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:detail", id=comp.id) return redirect("compensation:detail", id=comp.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 = { context = {
"form": data_form, "form": data_form,
"geom_form": geom_form, "geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New compensation"), TAB_TITLE_IDENTIFIER: _("New compensation"),
} }
context = BaseContext(request, context).context context = BaseContext(request, context).context
return render(request, template, context)
return render(request, self._TEMPLATE, context)
class CompensationIdentifierGeneratorView(AbstractIdentifierGeneratorView): class CompensationIdentifierGeneratorView(AbstractIdentifierGeneratorView):
_MODEL = Compensation _MODEL = Compensation
@login_required class EditCompensationView(LoginRequiredMixin, View):
@default_group_required _TEMPLATE = "compensation/form/view.html"
@shared_access_required(Compensation, "id")
def edit_view(request: HttpRequest, id: str): @method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def get(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
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)
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)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" """
Renders a view for editing compensations Renders a view for editing compensations
@@ -141,7 +219,6 @@ def edit_view(request: HttpRequest, id: str):
Returns: Returns:
""" """
template = "compensation/form/view.html"
# Get object from db # Get object from db
comp = get_object_or_404(Compensation, id=id) comp = get_object_or_404(Compensation, id=id)
if comp.is_recorded: if comp.is_recorded:
@@ -154,12 +231,10 @@ def edit_view(request: HttpRequest, id: str):
# Create forms, initialize with values from db/from POST request # Create forms, initialize with values from db/from POST request
data_form = EditCompensationForm(request.POST or None, instance=comp) data_form = EditCompensationForm(request.POST or None, instance=comp)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp) geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid(): 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 # Preserve state of intervention checked to determine whether the user must be informed or not
# about a change of the check state # about a change of the check state
intervention_is_checked = comp.intervention.checked is not None intervention_is_checked = comp.intervention.checked is not None
# 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
comp = data_form.save(request.user, geom_form) comp = data_form.save(request.user, geom_form)
if intervention_is_checked: if intervention_is_checked:
@@ -170,24 +245,21 @@ def edit_view(request: HttpRequest, id: str):
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:detail", id=comp.id) return redirect("compensation:detail", id=comp.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 = { context = {
"form": data_form, "form": data_form,
"geom_form": geom_form, "geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier), TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier),
} }
context = BaseContext(request, context).context context = BaseContext(request, context).context
return render(request, template, context)
return render(request, self._TEMPLATE, context)

View File

@@ -6,10 +6,12 @@ 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.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
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.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
from compensation.models import EcoAccount from compensation.models import EcoAccount
@@ -52,9 +54,35 @@ class IndexEcoAccountView(AbstractIndexView):
return render(request, self._TEMPLATE, context) return render(request, self._TEMPLATE, context)
@login_required class NewEcoAccountView(LoginRequiredMixin, View):
@default_group_required _TEMPLATE = "compensation/form/view.html"
def new_view(request: HttpRequest):
@method_decorator(default_group_required)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""
Renders a view for a new eco account creation
Args:
request (HttpRequest): The incoming request
Returns:
"""
data_form = NewEcoAccountForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
}
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 eco account creation Renders a view for a new eco account creation
@@ -64,10 +92,8 @@ def new_view(request: HttpRequest):
Returns: Returns:
""" """
template = "compensation/form/view.html"
data_form = NewEcoAccountForm(request.POST or None) data_form = NewEcoAccountForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False) geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
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)
@@ -85,35 +111,36 @@ def new_view(request: HttpRequest):
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 = { context = {
"form": data_form, "form": data_form,
"geom_form": geom_form, "geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New Eco-Account"), TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
} }
context = BaseContext(request, context).context context = BaseContext(request, context).context
return render(request, template, context)
return render(request, self._TEMPLATE, context)
class EcoAccountIdentifierGeneratorView(AbstractIdentifierGeneratorView): class EcoAccountIdentifierGeneratorView(AbstractIdentifierGeneratorView):
_MODEL = EcoAccount _MODEL = EcoAccount
@login_required
@default_group_required class EditEcoAccountView(LoginRequiredMixin, View):
@shared_access_required(EcoAccount, "id") _TEMPLATE = "compensation/form/view.html"
def edit_view(request: HttpRequest, id: str):
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" """
Renders a view for editing compensations Renders a view for editing compensations
@@ -123,7 +150,6 @@ def edit_view(request: HttpRequest, id: str):
Returns: Returns:
""" """
template = "compensation/form/view.html"
# Get object from db # Get object from db
acc = get_object_or_404(EcoAccount, id=id) acc = get_object_or_404(EcoAccount, id=id)
if acc.is_recorded: if acc.is_recorded:
@@ -136,7 +162,41 @@ def edit_view(request: HttpRequest, id: str):
# Create forms, initialize with values from db/from POST request # Create forms, initialize with values from db/from POST request
data_form = EditEcoAccountForm(request.POST or None, instance=acc) data_form = EditEcoAccountForm(request.POST or None, instance=acc)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc) geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
if request.method == "POST":
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)
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:
@@ -148,24 +208,21 @@ def edit_view(request: HttpRequest, id: str):
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 = { context = {
"form": data_form, "form": data_form,
"geom_form": geom_form, "geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier), TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier),
} }
context = BaseContext(request, context).context context = BaseContext(request, context).context
return render(request, template, context)
return render(request, self._TEMPLATE, context)

View File

@@ -5,10 +5,12 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 09.08.21 Created on: 09.08.21
""" """
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse from django.urls import reverse
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import get_object_or_404 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 compensation.models import Payment
@@ -17,10 +19,9 @@ 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
@login_required class NewPaymentView(LoginRequiredMixin, View):
@default_group_required
@shared_access_required(Intervention, "id") def __process_request(self, request: HttpRequest, id: str):
def new_payment_view(request: HttpRequest, id: str):
""" Renders a modal view for adding new payments """ Renders a modal view for adding new payments
Args: Args:
@@ -38,11 +39,20 @@ def new_payment_view(request: HttpRequest, id: str):
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data" 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)
@login_required @method_decorator(default_group_required)
@default_group_required @method_decorator(shared_access_required(Intervention, "id"))
@shared_access_required(Intervention, "id") def post(self, request: HttpRequest, id: str):
def payment_remove_view(request: HttpRequest, id: str, payment_id: str): return self.__process_request(request, id=id)
class RemovePaymentView(LoginRequiredMixin, View):
def __process_request(self, request: HttpRequest, id: str, payment_id: str):
""" Renders a modal view for removing payments """ Renders a modal view for removing payments
Args: Args:
@@ -62,11 +72,19 @@ def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data" 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)
@login_required @method_decorator(default_group_required)
@default_group_required @method_decorator(shared_access_required(Intervention, "id"))
@shared_access_required(Intervention, "id") def post(self, request: HttpRequest, id: str, payment_id: str):
def payment_edit_view(request: HttpRequest, id: str, payment_id: str): return self.__process_request(request, id=id, payment_id=payment_id)
class EditPaymentView(LoginRequiredMixin, View):
def __process_request(self, request: HttpRequest, id: str, payment_id: str):
""" Renders a modal view for editing payments """ Renders a modal view for editing payments
Args: Args:
@@ -86,3 +104,12 @@ def payment_edit_view(request: HttpRequest, id: str, payment_id: str):
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data" 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

@@ -8,8 +8,8 @@ Created on: 30.11.20
from django.urls import path from django.urls import path
from intervention.autocomplete.intervention import InterventionAutocomplete from intervention.autocomplete.intervention import InterventionAutocomplete
from intervention.views.check import check_view from intervention.views.check import InterventionCheckView
from intervention.views.compensation import remove_compensation_view from intervention.views.compensation import RemoveCompensationFromInterventionView
from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \ from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \
RemoveInterventionDeductionView RemoveInterventionDeductionView
from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \ from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \
@@ -22,8 +22,8 @@ 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 InterventionPublicReportView
from intervention.views.resubmission import InterventionResubmissionView from intervention.views.resubmission import InterventionResubmissionView
from intervention.views.revocation import new_revocation_view, edit_revocation_view, remove_revocation_view, \ from intervention.views.revocation import NewInterventionRevocationView, GetInterventionRevocationView, \
get_revocation_view EditInterventionRevocationView, RemoveInterventionRevocationView
from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView
app_name = "intervention" app_name = "intervention"
@@ -37,13 +37,13 @@ urlpatterns = [
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', check_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', InterventionPublicReportView.as_view(), name='report'),
path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'), path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'),
# Compensations # Compensations
path('<id>/compensation/<comp_id>/remove', remove_compensation_view, name='remove-compensation'), path('<id>/compensation/<comp_id>/remove', RemoveCompensationFromInterventionView.as_view(), name='remove-compensation'),
# Documents # Documents
path('<id>/document/new/', NewInterventionDocumentView.as_view(), name='new-doc'), path('<id>/document/new/', NewInterventionDocumentView.as_view(), name='new-doc'),
@@ -57,10 +57,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', new_revocation_view, name='new-revocation'), path('<id>/revocation/new', NewInterventionRevocationView.as_view(), name='new-revocation'),
path('<id>/revocation/<revocation_id>/edit', edit_revocation_view, name='edit-revocation'), path('<id>/revocation/<revocation_id>/edit', EditInterventionRevocationView.as_view(), name='edit-revocation'),
path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'), path('<id>/revocation/<revocation_id>/remove', RemoveInterventionRevocationView.as_view(), name='remove-revocation'),
path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'), path('revocation/<doc_id>', GetInterventionRevocationView.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

@@ -5,21 +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.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404 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.decorators import registration_office_group_required, shared_access_required
from konova.utils.message_templates import INTERVENTION_INVALID from konova.utils.message_templates import INTERVENTION_INVALID
class InterventionCheckView(LoginRequiredMixin, View):
@login_required def __process_request(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
@registration_office_group_required
@shared_access_required(Intervention, "id")
def check_view(request: HttpRequest, id: str):
""" Renders check form for an intervention """ Renders check form for an intervention
Args: Args:
@@ -37,3 +37,12 @@ def check_view(request: HttpRequest, id: str):
msg_error=INTERVENTION_INVALID 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

@@ -5,22 +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.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpRequest, Http404 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 intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import shared_access_required, login_required_modal from konova.decorators import shared_access_required
from konova.forms.modals import RemoveModalForm from konova.forms.modals import RemoveModalForm
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
@login_required_modal class RemoveCompensationFromInterventionView(LoginRequiredMixin, View):
@login_required
@shared_access_required(Intervention, "id") def __process_request(self, request: HttpRequest, id: str, comp_id: str, *args, **kwargs) -> HttpResponse:
def remove_compensation_view(request: HttpRequest, id: str, comp_id: str):
""" Renders a modal view for removing the compensation """ Renders a modal view for removing the compensation
Args: Args:
@@ -44,3 +45,10 @@ def remove_compensation_view(request: HttpRequest, id: str, comp_id: str):
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data", 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

@@ -6,10 +6,12 @@ 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.http import HttpRequest from django.http import HttpRequest, HttpResponse
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.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
@@ -19,10 +21,8 @@ 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 REVOCATION_ADDED, DATA_UNSHARED, REVOCATION_EDITED, REVOCATION_REMOVED
@login_required class NewInterventionRevocationView(LoginRequiredMixin, View):
@default_group_required def __process_request(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
@shared_access_required(Intervention, "id")
def new_revocation_view(request: HttpRequest, id: str):
""" Renders sharing form for an intervention """ Renders sharing form for an intervention
Args: Args:
@@ -33,17 +33,28 @@ def new_revocation_view(request: HttpRequest, id: str):
""" """
intervention = get_object_or_404(Intervention, id=id) intervention = get_object_or_404(Intervention, id=id)
form = NewRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, request=request) form = NewRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention,
request=request)
return form.process_request( return form.process_request(
request, request,
msg_success=REVOCATION_ADDED, msg_success=REVOCATION_ADDED,
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data" 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)
@login_required @method_decorator(default_group_required)
@default_group_required @method_decorator(shared_access_required(Intervention, "id"))
def get_revocation_view(request: HttpRequest, doc_id: str): def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
return self.__process_request(request, id, *args, **kwargs)
class GetInterventionRevocationView(LoginRequiredMixin, View):
@method_decorator(default_group_required)
def get(self, request: HttpRequest, doc_id: str, *args, **kwargs) -> HttpResponse:
""" Returns the revocation document as downloadable file """ Returns the revocation document as downloadable file
Wraps the generic document fetcher function from konova.utils. Wraps the generic document fetcher function from konova.utils.
@@ -66,10 +77,8 @@ def get_revocation_view(request: HttpRequest, doc_id: str):
return get_document(doc) return get_document(doc)
@login_required class EditInterventionRevocationView(LoginRequiredMixin, View):
@default_group_required def __process_request(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
@shared_access_required(Intervention, "id")
def edit_revocation_view(request: HttpRequest, id: str, revocation_id: str):
""" Renders a edit view for a revocation """ Renders a edit view for a revocation
Args: Args:
@@ -83,19 +92,27 @@ def edit_revocation_view(request: HttpRequest, id: str, revocation_id: str):
intervention = get_object_or_404(Intervention, id=id) intervention = get_object_or_404(Intervention, id=id)
revocation = get_object_or_404(Revocation, id=revocation_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) form = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention,
revocation=revocation, request=request)
return form.process_request( return form.process_request(
request, request,
REVOCATION_EDITED, REVOCATION_EDITED,
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data" 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)
@login_required_modal @method_decorator(default_group_required)
@login_required @method_decorator(shared_access_required(Intervention, "id"))
@default_group_required def post(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
@shared_access_required(Intervention, "id") return self.__process_request(request, id, revocation_id, *args, **kwargs)
def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
class RemoveInterventionRevocationView(LoginRequiredMixin, View):
def __process_request(self, request, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
""" Renders a remove view for a revocation """ Renders a remove view for a revocation
Args: Args:
@@ -109,10 +126,20 @@ def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
intervention = get_object_or_404(Intervention, id=id) intervention = get_object_or_404(Intervention, id=id)
revocation = get_object_or_404(Revocation, id=revocation_id) revocation = get_object_or_404(Revocation, id=revocation_id)
form = RemoveRevocationModalForm(request.POST or None, instance=intervention, revocation=revocation, request=request) form = RemoveRevocationModalForm(request.POST or None, instance=intervention, revocation=revocation,
request=request)
return form.process_request( return form.process_request(
request, request,
REVOCATION_REMOVED, REVOCATION_REMOVED,
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data" 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

@@ -103,9 +103,12 @@ 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): def get_data_objects(self, limit_to_attrs: list = None):
""" 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
""" """
@@ -121,6 +124,9 @@ class Geometry(BaseResource):
set_objs = _set.filter( set_objs = _set.filter(
deleted=None deleted=None
) )
if limit_to_attrs:
objs += set_objs.values_list(*limit_to_attrs, flat=True)
else:
objs += set_objs objs += set_objs
# ... but we need a special treatment for compensations, since they can be deleted directly OR inherit their # ... but we need a special treatment for compensations, since they can be deleted directly OR inherit their
@@ -128,8 +134,10 @@ class Geometry(BaseResource):
comp_objs = self.compensation_set.filter( comp_objs = self.compensation_set.filter(
Q(deleted=None) & Q(intervention__deleted=None) 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 objs += comp_objs
return objs return objs
def get_data_object(self): def get_data_object(self):
@@ -407,7 +415,10 @@ 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,19 +677,23 @@ class GeoReferencedMixin(models.Model):
return request return request
instance_objs = [] instance_objs = []
needed_data_object_attrs = [
"identifier"
]
conflicts = self.geometry.conflicts_geometries.iterator() conflicts = self.geometry.conflicts_geometries.iterator()
for conflict in conflicts: for conflict in conflicts:
instance_objs += conflict.affected_geometry.get_data_objects() # 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(needed_data_object_attrs)
conflicts = self.geometry.conflicted_by_geometries.iterator() conflicts = self.geometry.conflicted_by_geometries.iterator()
for conflict in conflicts: for conflict in conflicts:
instance_objs += conflict.conflicting_geometry.get_data_objects() # 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(needed_data_object_attrs)
add_message = len(instance_objs) > 0 add_message = len(instance_objs) > 0
if add_message: if add_message:
instance_identifiers = [x.identifier for x in instance_objs] instance_identifiers = ", ".join(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

@@ -5,6 +5,9 @@ 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
@@ -30,7 +33,7 @@ class KonovaExceptionReporter(ExceptionReporter):
""" """
whitelist = [ whitelist = [
"is_email", "is_email",
"unicdoe_hint", "unicode_hint",
"frames", "frames",
"request", "request",
"user_str", "user_str",
@@ -39,6 +42,8 @@ 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:
@@ -56,7 +61,28 @@ 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:
tb_data = self._filter_traceback_data(tb_data) filtered_data = dict()
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
return tb_data def _filter_POST_body(self, tb_data: dict):
""" 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

@@ -53,6 +53,7 @@ class HomeView(LoginRequiredMixin, View):
# Repeat for other objects # Repeat for other objects
comps = Compensation.objects.filter( comps = Compensation.objects.filter(
deleted=None, deleted=None,
intervention__deleted=None,
) )
user_comps = comps.filter( user_comps = comps.filter(
Q(intervention__users__in=[user]) | Q(intervention__teams__in=user_teams) Q(intervention__users__in=[user]) | Q(intervention__teams__in=user_teams)