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,57 +46,112 @@ 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:
""" """
# Try to resolve the requested office id template = "analysis/reports/index.html"
cons_office = get_object_or_404( form = TimespanReportForm(None)
KonovaCode, context = {
id=id "form": form
) }
# Try to resolve the date parameters into Date objects -> redirect if this fails context = BaseContext(request, context).context
try: return render(request, template, context)
df = request.GET.get("df", None)
dt = request.GET.get("dt", None) @method_decorator(conservation_office_group_required)
date_from = timezone.make_aware(timezone.datetime.fromisoformat(df)) def post(self, request: HttpRequest) -> HttpResponse:
date_to = timezone.make_aware(timezone.datetime.fromisoformat(dt))
except ValueError: """
messages.error(
request, Args:
PARAMS_INVALID, request (HttpRequest): The incoming request
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
) )
return redirect("analysis:reports") # Try to resolve the date parameters into Date objects -> redirect if this fails
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": 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,36 +55,78 @@ 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
@shared_access_required(Intervention, "intervention_id")
def new_view(request: HttpRequest, intervention_id: str = None):
"""
Renders a view for a new compensation creation
Args: class NewCompensationView(LoginRequiredMixin, View):
request (HttpRequest): The incoming request _TEMPLATE = "compensation/form/view.html"
Returns: @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
template = "compensation/form/view.html" the intervention's id and directly link the compensation to it.
if intervention_id is not None:
try: Args:
intervention = Intervention.objects.get(id=intervention_id) request (HttpRequest): The incoming request
except ObjectDoesNotExist: intervention_id (str): The intervention identifier
messages.error(request, PARAMS_INVALID)
return redirect("home") Returns:
if intervention.is_recorded:
messages.info( """
request, if intervention_id:
RECORDED_BLOCKS_EDIT # 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!
return redirect("intervention:detail", id=intervention_id) 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
Args:
request (HttpRequest): The incoming request
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)
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid(): 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,65 +144,97 @@ 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 context = {
pass "form": data_form,
context = { "geom_form": geom_form,
"form": data_form, TAB_TITLE_IDENTIFIER: _("New compensation"),
"geom_form": geom_form, }
TAB_TITLE_IDENTIFIER: _("New compensation"), context = BaseContext(request, context).context
}
context = BaseContext(request, context).context return render(request, self._TEMPLATE, context)
return render(request, 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):
"""
Renders a view for editing compensations
Args: @method_decorator(default_group_required)
request (HttpRequest): The incoming request @method_decorator(shared_access_required(Compensation, "id"))
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
Returns: """
Renders a view for editing compensations
""" Args:
template = "compensation/form/view.html" request (HttpRequest): The incoming request
# 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 Returns:
data_form = EditCompensationForm(request.POST or None, instance=comp)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp) """
if request.method == "POST": # 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
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(): 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 context = {
pass "form": data_form,
context = { "geom_form": geom_form,
"form": data_form, TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier),
"geom_form": geom_form, }
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier), context = BaseContext(request, context).context
}
context = BaseContext(request, context).context return render(request, self._TEMPLATE, context)
return render(request, 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,22 +54,46 @@ 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):
"""
Renders a view for a new eco account creation
Args: @method_decorator(default_group_required)
request (HttpRequest): The incoming request def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""
Renders a view for a new eco account creation
Returns: Args:
request (HttpRequest): The incoming request
""" Returns:
template = "compensation/form/view.html"
data_form = NewEcoAccountForm(request.POST or None) """
geom_form = SimpleGeomForm(request.POST or None, read_only=False) data_form = NewEcoAccountForm(request.POST or None)
if request.method == "POST": 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
Args:
request (HttpRequest): The incoming request
Returns:
"""
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)
@@ -85,58 +111,92 @@ 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 context = {
pass "form": data_form,
context = { "geom_form": geom_form,
"form": data_form, TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
"geom_form": geom_form, }
TAB_TITLE_IDENTIFIER: _("New Eco-Account"), context = BaseContext(request, context).context
}
context = BaseContext(request, context).context return render(request, self._TEMPLATE, context)
return render(request, template, context)
class EcoAccountIdentifierGeneratorView(AbstractIdentifierGeneratorView): class EcoAccountIdentifierGeneratorView(AbstractIdentifierGeneratorView):
_MODEL = EcoAccount _MODEL = EcoAccount
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args: class EditEcoAccountView(LoginRequiredMixin, View):
request (HttpRequest): The incoming request _TEMPLATE = "compensation/form/view.html"
Returns: @method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" """
template = "compensation/form/view.html" Renders a view for editing compensations
# Get object from db
acc = get_object_or_404(EcoAccount, id=id) Args:
if acc.is_recorded: request (HttpRequest): The incoming request
messages.info(
request, Returns:
RECORDED_BLOCKS_EDIT
) """
return redirect("compensation:acc:detail", id=id) # 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)
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:
@@ -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 context = {
pass "form": data_form,
context = { "geom_form": geom_form,
"form": data_form, TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier),
"geom_form": geom_form, }
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier), context = BaseContext(request, context).context
}
context = BaseContext(request, context).context return render(request, self._TEMPLATE, context)
return render(request, 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,72 +19,97 @@ 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 new_payment_view(request: HttpRequest, id: str):
""" Renders a modal view for adding new payments
Args: def __process_request(self, request: HttpRequest, id: str):
request (HttpRequest): The incoming request """ Renders a modal view for adding new payments
id (str): The intervention's id for which a new payment shall be added
Returns: Args:
request (HttpRequest): The incoming request
id (str): The intervention's id for which a new payment shall be added
""" Returns:
intervention = get_object_or_404(Intervention, id=id)
form = NewPaymentForm(request.POST or None, instance=intervention, request=request) """
return form.process_request( intervention = get_object_or_404(Intervention, id=id)
request, form = NewPaymentForm(request.POST or None, instance=intervention, request=request)
msg_success=PAYMENT_ADDED, return form.process_request(
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data" 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)
@login_required class RemovePaymentView(LoginRequiredMixin, View):
@default_group_required
@shared_access_required(Intervention, "id")
def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
""" Renders a modal view for removing payments
Args: def __process_request(self, request: HttpRequest, id: str, payment_id: str):
request (HttpRequest): The incoming request """ Renders a modal view for removing payments
id (str): The intervention's id
payment_id (str): The payment's id
Returns: 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) intervention = get_object_or_404(Intervention, id=id)
return form.process_request( payment = get_object_or_404(Payment, id=payment_id)
request=request, form = RemovePaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
msg_success=PAYMENT_REMOVED, return form.process_request(
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data" 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)
@login_required class EditPaymentView(LoginRequiredMixin, View):
@default_group_required def __process_request(self, request: HttpRequest, id: str, payment_id: str):
@shared_access_required(Intervention, "id") """ Renders a modal view for editing payments
def payment_edit_view(request: HttpRequest, id: str, payment_id: str):
""" Renders a modal view for editing payments
Args: Args:
request (HttpRequest): The incoming request request (HttpRequest): The incoming request
id (str): The intervention's id id (str): The intervention's id
payment_id (str): The payment's id payment_id (str): The payment's id
Returns: Returns:
""" """
intervention = get_object_or_404(Intervention, id=id) intervention = get_object_or_404(Intervention, id=id)
payment = get_object_or_404(Payment, id=payment_id) payment = get_object_or_404(Payment, id=payment_id)
form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request) form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
return form.process_request( return form.process_request(
request=request, request=request,
msg_success=PAYMENT_EDITED, msg_success=PAYMENT_EDITED,
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,35 +5,44 @@ 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 """ Renders check form for an intervention
@shared_access_required(Intervention, "id")
def check_view(request: HttpRequest, id: str):
""" Renders check form for an intervention
Args: Args:
request (HttpRequest): The incoming request request (HttpRequest): The incoming request
id (str): Intervention's id id (str): Intervention's id
Returns: Returns:
""" """
intervention = get_object_or_404(Intervention, id=id) intervention = get_object_or_404(Intervention, id=id)
form = CheckModalForm(request.POST or None, instance=intervention, request=request) form = CheckModalForm(request.POST or None, instance=intervention, request=request)
return form.process_request( return form.process_request(
request, request,
msg_success=_("Check performed"), msg_success=_("Check performed"),
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,42 +5,50 @@ 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 remove_compensation_view(request: HttpRequest, id: str, comp_id: str):
""" Renders a modal view for removing the compensation
Args: def __process_request(self, request: HttpRequest, id: str, comp_id: str, *args, **kwargs) -> HttpResponse:
request (HttpRequest): The incoming request """ Renders a modal view for removing the compensation
id (str): The compensation's id
Returns: Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
""" Returns:
intervention = get_object_or_404(Intervention, id=id)
try: """
comp = intervention.compensations.get( intervention = get_object_or_404(Intervention, id=id)
id=comp_id try:
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",
) )
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

@@ -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,100 +21,125 @@ 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") """ Renders sharing form for an intervention
def new_revocation_view(request: HttpRequest, id: str):
""" Renders sharing form for an intervention
Args: Args:
request (HttpRequest): The incoming request request (HttpRequest): The incoming request
id (str): Intervention's id id (str): Intervention's id
Returns: Returns:
""" """
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,
return form.process_request( request=request)
request, return form.process_request(
msg_success=REVOCATION_ADDED,
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
)
@login_required
@default_group_required
def get_revocation_view(request: HttpRequest, doc_id: str):
""" Returns the revocation document as downloadable file
Wraps the generic document fetcher function from konova.utils.
Args:
request (HttpRequest): The incoming request
doc_id (str): The document id
Returns:
"""
doc = get_object_or_404(RevocationDocument, id=doc_id)
# File download only possible if related instance is shared with user
if not doc.instance.legal.intervention.users.filter(id=request.user.id):
messages.info(
request, request,
DATA_UNSHARED msg_success=REVOCATION_ADDED,
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
) )
return redirect("intervention:detail", id=doc.instance.id)
return get_document(doc) @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)
@login_required class GetInterventionRevocationView(LoginRequiredMixin, View):
@default_group_required @method_decorator(default_group_required)
@shared_access_required(Intervention, "id") def get(self, request: HttpRequest, doc_id: str, *args, **kwargs) -> HttpResponse:
def edit_revocation_view(request: HttpRequest, id: str, revocation_id: str): """ Returns the revocation document as downloadable file
""" Renders a edit view for a revocation
Args: Wraps the generic document fetcher function from konova.utils.
request (HttpRequest): The incoming request
id (str): The intervention's id as string
revocation_id (str): The revocation's id as string
Returns: Args:
request (HttpRequest): The incoming request
doc_id (str): The document id
""" 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( doc = get_object_or_404(RevocationDocument, id=doc_id)
request, # File download only possible if related instance is shared with user
REVOCATION_EDITED, if not doc.instance.legal.intervention.users.filter(id=request.user.id):
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data" messages.info(
) request,
DATA_UNSHARED
)
return redirect("intervention:detail", id=doc.instance.id)
return get_document(doc)
@login_required_modal class EditInterventionRevocationView(LoginRequiredMixin, View):
@login_required def __process_request(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
@default_group_required """ Renders a edit view for a revocation
@shared_access_required(Intervention, "id")
def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
""" Renders a remove view for a revocation
Args: Args:
request (HttpRequest): The incoming request request (HttpRequest): The incoming request
id (str): The intervention's id as string id (str): The intervention's id as string
revocation_id (str): The revocation's id as string revocation_id (str): The revocation's id as string
Returns: Returns:
""" """
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 = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention,
return form.process_request( revocation=revocation, request=request)
request, return form.process_request(
REVOCATION_REMOVED, request,
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data" 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

@@ -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,15 +124,20 @@ class Geometry(BaseResource):
set_objs = _set.filter( set_objs = _set.filter(
deleted=None deleted=None
) )
objs += set_objs if limit_to_attrs:
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 # ... 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 # de-facto-deleted status from their deleted parent intervention
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)
) )
objs += comp_objs 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):
@@ -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):
output_geom = MultiPolygon(input_geom, srid=DEFAULT_SRID_RLP) try:
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)