From 3f33de3626bf2d5c2234d29350493349e3affeae Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Tue, 13 Jan 2026 10:35:09 +0100 Subject: [PATCH] # 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) --- analysis/urls.py | 4 +- analysis/views.py | 129 ++++++++++++++++++++++--------- api/urls/urls.py | 3 - api/views/method_views.py | 35 --------- compensation/urls/payment.py | 6 +- compensation/views/payment.py | 139 ++++++++++++++++++++-------------- 6 files changed, 182 insertions(+), 134 deletions(-) delete mode 100644 api/views/method_views.py diff --git a/analysis/urls.py b/analysis/urls.py index 71eb6665..c51d70f0 100644 --- a/analysis/urls.py +++ b/analysis/urls.py @@ -10,6 +10,6 @@ from analysis.views import * app_name = "analysis" urlpatterns = [ - path("reports/", index_reports_view, name="reports"), - path("reports/", detail_report_view, name="report-detail"), + path("reports/", ReportIndexView.as_view(), name="reports"), + path("reports/", ReportDetailView.as_view(), name="report-detail"), ] \ No newline at end of file diff --git a/analysis/views.py b/analysis/views.py index 6da4a586..525fdd36 100644 --- a/analysis/views.py +++ b/analysis/views.py @@ -1,8 +1,12 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import LoginRequiredMixin from django.http import HttpRequest, HttpResponse from django.shortcuts import render, redirect, get_object_or_404 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.utils.excel.excel import TempExcelFile @@ -42,57 +46,112 @@ def index_reports_view(request: HttpRequest): context = BaseContext(request, context).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: - request (HttpRequest): The incoming request - id (str): The conservation_office KonovaCode id + Args: + request (HttpRequest): The incoming request - Returns: + Returns: - """ - # Try to resolve the requested office id - cons_office = get_object_or_404( - KonovaCode, - id=id - ) - # Try to resolve the date parameters into Date objects -> redirect if this fails - 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", + """ + 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 + + 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 - format_param = request.GET.get("format", "html") - report = TimespanReport(id, date_from, date_to) + # Check whether the html default rendering is requested or an alternative + format_param = request.GET.get("format", "html") + 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" context = { - "office": cons_office, + "office": office, "report": report, } context = BaseContext(request, context).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) response = HttpResponse( content=file.stream, 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 - else: - raise NotImplementedError diff --git a/api/urls/urls.py b/api/urls/urls.py index abe9eacf..fe0ddbbc 100644 --- a/api/urls/urls.py +++ b/api/urls/urls.py @@ -7,11 +7,8 @@ Created on: 21.01.22 """ from django.urls import path, include -from api.views.method_views import generate_new_token_view - app_name = "api" urlpatterns = [ path("v1/", include("api.urls.v1.urls", namespace="v1")), - path("token/generate", generate_new_token_view, name="generate-new-token"), ] \ No newline at end of file diff --git a/api/views/method_views.py b/api/views/method_views.py deleted file mode 100644 index 7c6904a5..00000000 --- a/api/views/method_views.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/compensation/urls/payment.py b/compensation/urls/payment.py index b51384dd..d051423b 100644 --- a/compensation/urls/payment.py +++ b/compensation/urls/payment.py @@ -10,7 +10,7 @@ from compensation.views.payment import * app_name = "pay" urlpatterns = [ - path('/new', new_payment_view, name='new'), - path('/remove/', payment_remove_view, name='remove'), - path('/edit/', payment_edit_view, name='edit'), + path('/new', NewPaymentView.as_view(), name='new'), + path('/remove/', RemovePaymentView.as_view(), name='remove'), + path('/edit/', EditPaymentView.as_view(), name='edit'), ] diff --git a/compensation/views/payment.py b/compensation/views/payment.py index 29e89ac3..3059bc06 100644 --- a/compensation/views/payment.py +++ b/compensation/views/payment.py @@ -5,10 +5,12 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 09.08.21 """ +from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse -from django.contrib.auth.decorators import login_required from django.http import HttpRequest from django.shortcuts import get_object_or_404 +from django.utils.decorators import method_decorator +from django.views import View from compensation.forms.modals.payment import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm from compensation.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 -@login_required -@default_group_required -@shared_access_required(Intervention, "id") -def new_payment_view(request: HttpRequest, id: str): - """ Renders a modal view for adding new payments +class NewPaymentView(LoginRequiredMixin, View): - Args: - request (HttpRequest): The incoming request - id (str): The intervention's id for which a new payment shall be added + def __process_request(self, request: HttpRequest, id: str): + """ Renders a modal view for adding new payments - Returns: + Args: + request (HttpRequest): The incoming request + id (str): The intervention's id for which a new payment shall be added - """ - intervention = get_object_or_404(Intervention, id=id) - form = NewPaymentForm(request.POST or None, instance=intervention, request=request) - return form.process_request( - request, - msg_success=PAYMENT_ADDED, - redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data" - ) + Returns: + + """ + intervention = get_object_or_404(Intervention, id=id) + form = NewPaymentForm(request.POST or None, instance=intervention, request=request) + return form.process_request( + request, + msg_success=PAYMENT_ADDED, + redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data" + ) + + @method_decorator(default_group_required) + @method_decorator(shared_access_required(Intervention, "id")) + def get(self, request: HttpRequest, id: str): + return self.__process_request(request, id=id) + + @method_decorator(default_group_required) + @method_decorator(shared_access_required(Intervention, "id")) + def post(self, request: HttpRequest, id: str): + return self.__process_request(request, id=id) -@login_required -@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 +class RemovePaymentView(LoginRequiredMixin, View): - Args: - request (HttpRequest): The incoming request - id (str): The intervention's id - payment_id (str): The payment's id + def __process_request(self, request: HttpRequest, id: str, payment_id: str): + """ Renders a modal view for removing payments - Returns: + Args: + request (HttpRequest): The incoming request + id (str): The intervention's id + payment_id (str): The payment's id - """ - intervention = get_object_or_404(Intervention, id=id) - payment = get_object_or_404(Payment, id=payment_id) - form = RemovePaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request) - return form.process_request( - request=request, - msg_success=PAYMENT_REMOVED, - redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data" - ) + Returns: + + """ + intervention = get_object_or_404(Intervention, id=id) + payment = get_object_or_404(Payment, id=payment_id) + form = RemovePaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request) + return form.process_request( + request=request, + msg_success=PAYMENT_REMOVED, + redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data" + ) + + @method_decorator(default_group_required) + @method_decorator(shared_access_required(Intervention, "id")) + def get(self, request: HttpRequest, id: str, payment_id: str): + return self.__process_request(request, id=id, payment_id=payment_id) + + @method_decorator(default_group_required) + @method_decorator(shared_access_required(Intervention, "id")) + def post(self, request: HttpRequest, id: str, payment_id: str): + return self.__process_request(request, id=id, payment_id=payment_id) -@login_required -@default_group_required -@shared_access_required(Intervention, "id") -def payment_edit_view(request: HttpRequest, id: str, payment_id: str): - """ Renders a modal view for editing payments +class EditPaymentView(LoginRequiredMixin, View): + def __process_request(self, request: HttpRequest, id: str, payment_id: str): + """ Renders a modal view for editing payments - Args: - request (HttpRequest): The incoming request - id (str): The intervention's id - payment_id (str): The payment's id + Args: + request (HttpRequest): The incoming request + id (str): The intervention's id + payment_id (str): The payment's id - Returns: + Returns: - """ - intervention = get_object_or_404(Intervention, id=id) - payment = get_object_or_404(Payment, id=payment_id) - form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request) - return form.process_request( - request=request, - msg_success=PAYMENT_EDITED, - redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data" - ) + """ + intervention = get_object_or_404(Intervention, id=id) + payment = get_object_or_404(Payment, id=payment_id) + form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request) + return form.process_request( + request=request, + msg_success=PAYMENT_EDITED, + redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data" + ) + @method_decorator(default_group_required) + @method_decorator(shared_access_required(Intervention, "id")) + def get(self, request: HttpRequest, id: str, payment_id: str): + return self.__process_request(request, id=id, payment_id=payment_id) + + @method_decorator(default_group_required) + @method_decorator(shared_access_required(Intervention, "id")) + def post(self, request: HttpRequest, id: str, payment_id: str): + return self.__process_request(request, id=id, payment_id=payment_id)