diff --git a/compensation/urls/compensation.py b/compensation/urls/compensation.py index ca6029b5..1f171f53 100644 --- a/compensation/urls/compensation.py +++ b/compensation/urls/compensation.py @@ -18,14 +18,14 @@ from compensation.views.compensation.action import NewCompensationActionView, Ed RemoveCompensationActionView from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \ RemoveCompensationStateView -from compensation.views.compensation.compensation import new_view, new_id_view, edit_view, \ - remove_view, IndexCompensationView +from compensation.views.compensation.compensation import new_view, edit_view, \ + remove_view, IndexCompensationView, CompensationIdentifierGeneratorView from compensation.views.compensation.log import CompensationLogView urlpatterns = [ # Main compensation path("", IndexCompensationView.as_view(), name="index"), - path('new/id', new_id_view, name='new-id'), + path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'), path('new/', new_view, name='new'), path('new', new_view, name='new'), path('', DetailCompensationView.as_view(), name='detail'), diff --git a/compensation/urls/eco_account.py b/compensation/urls/eco_account.py index 8c8a27fd..9b0263c8 100644 --- a/compensation/urls/eco_account.py +++ b/compensation/urls/eco_account.py @@ -9,8 +9,8 @@ from django.urls import path from compensation.autocomplete.eco_account import EcoAccountAutocomplete from compensation.views.eco_account.detail import DetailEcoAccountView -from compensation.views.eco_account.eco_account import new_view, new_id_view, edit_view, remove_view, \ - IndexEcoAccountView +from compensation.views.eco_account.eco_account import new_view, edit_view, remove_view, \ + IndexEcoAccountView, EcoAccountIdentifierGeneratorView from compensation.views.eco_account.log import EcoAccountLogView from compensation.views.eco_account.record import EcoAccountRecordView from compensation.views.eco_account.report import report_view @@ -31,7 +31,7 @@ app_name = "acc" urlpatterns = [ path("", IndexEcoAccountView.as_view(), name="index"), path('new/', new_view, name='new'), - path('new/id', new_id_view, name='new-id'), + path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'), path('', DetailEcoAccountView.as_view(), name='detail'), path('/log', EcoAccountLogView.as_view(), name='log'), path('/record', EcoAccountRecordView.as_view(), name='record'), diff --git a/compensation/views/compensation/compensation.py b/compensation/views/compensation/compensation.py index bd669672..94fce5f9 100644 --- a/compensation/views/compensation/compensation.py +++ b/compensation/views/compensation/compensation.py @@ -8,7 +8,7 @@ Created on: 19.08.22 from django.contrib import messages from django.contrib.auth.decorators import login_required from django.core.exceptions import ObjectDoesNotExist -from django.http import HttpRequest, JsonResponse, HttpResponse +from django.http import HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, render, redirect from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -18,15 +18,14 @@ from compensation.models import Compensation from compensation.tables.compensation import CompensationTable from intervention.models import Intervention from konova.contexts import BaseContext -from konova.decorators import shared_access_required, default_group_required, any_group_check, login_required_modal, \ - uuid_required +from konova.decorators import shared_access_required, default_group_required, login_required_modal from konova.forms import SimpleGeomForm from konova.forms.modals import RemoveModalForm -from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER -from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE, \ +from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE, \ RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, IDENTIFIER_REPLACED, \ - COMPENSATION_ADDED_TEMPLATE, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE + COMPENSATION_ADDED_TEMPLATE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE +from konova.views.identifier import AbstractIdentifierGeneratorView from konova.views.index import AbstractIndexView @@ -128,23 +127,8 @@ def new_view(request: HttpRequest, intervention_id: str = None): return render(request, template, context) -@login_required -@default_group_required -def new_id_view(request: HttpRequest): - """ JSON endpoint - - Provides fetching of free identifiers for e.g. AJAX calls - - """ - tmp = Compensation() - identifier = tmp.generate_new_identifier() - while Compensation.objects.filter(identifier=identifier).exists(): - identifier = tmp.generate_new_identifier() - return JsonResponse( - data={ - "gen_data": identifier - } - ) +class CompensationIdentifierGeneratorView(AbstractIdentifierGeneratorView): + _MODEL = Compensation @login_required diff --git a/compensation/views/eco_account/eco_account.py b/compensation/views/eco_account/eco_account.py index 7fd7ff2f..3acd5714 100644 --- a/compensation/views/eco_account/eco_account.py +++ b/compensation/views/eco_account/eco_account.py @@ -7,7 +7,7 @@ Created on: 19.08.22 """ from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.http import HttpRequest, JsonResponse, HttpResponse +from django.http import HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -16,13 +16,13 @@ from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm from compensation.models import EcoAccount from compensation.tables.eco_account import EcoAccountTable from konova.contexts import BaseContext -from konova.decorators import shared_access_required, default_group_required, any_group_check, login_required_modal, \ - uuid_required +from konova.decorators import shared_access_required, default_group_required, login_required_modal from konova.forms import SimpleGeomForm -from konova.settings import ETS_GROUP, DEFAULT_GROUP, ZB_GROUP +from konova.settings import ETS_GROUP from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER from konova.utils.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECORDED_BLOCKS_EDIT, FORM_INVALID, \ - IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE + IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE +from konova.views.identifier import AbstractIdentifierGeneratorView from konova.views.index import AbstractIndexView @@ -109,25 +109,8 @@ def new_view(request: HttpRequest): context = BaseContext(request, context).context return render(request, template, context) - -@login_required -@default_group_required -def new_id_view(request: HttpRequest): - """ JSON endpoint - - Provides fetching of free identifiers for e.g. AJAX calls - - """ - tmp = EcoAccount() - identifier = tmp.generate_new_identifier() - while EcoAccount.objects.filter(identifier=identifier).exists(): - identifier = tmp.generate_new_identifier() - return JsonResponse( - data={ - "gen_data": identifier - } - ) - +class EcoAccountIdentifierGeneratorView(AbstractIdentifierGeneratorView): + _MODEL = EcoAccount @login_required @default_group_required @@ -190,88 +173,6 @@ def edit_view(request: HttpRequest, id: str): return render(request, template, context) -@login_required -@any_group_check -@uuid_required -def detail_view(request: HttpRequest, id: str): - """ Renders a detail view for a compensation - - Args: - request (HttpRequest): The incoming request - id (str): The compensation's id - - Returns: - - """ - template = "compensation/detail/eco_account/view.html" - acc = get_object_or_404( - EcoAccount.objects.prefetch_related( - "deadlines", - ).select_related( - 'geometry', - 'responsible', - ), - id=id, - deleted=None, - ) - geom_form = SimpleGeomForm(instance=acc) - parcels = acc.get_underlying_parcels() - _user = request.user - is_data_shared = acc.is_shared_with(_user) - - # Order states according to surface - before_states = acc.before_states.order_by("-surface") - after_states = acc.after_states.order_by("-surface") - - # Precalculate logical errors between before- and after-states - # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling - sum_before_states = acc.get_surface_before_states() - sum_after_states = acc.get_surface_after_states() - diff_states = abs(sum_before_states - sum_after_states) - # Calculate rest of available surface for deductions - available_total = acc.deductable_rest - available_relative = acc.get_deductable_rest_relative() - - # Prefetch related data to decrease the amount of db connections - deductions = acc.deductions.filter( - intervention__deleted=None, - ) - actions = acc.actions.all() - - request = acc.set_status_messages(request) - - requesting_user_is_only_shared_user = acc.is_only_shared_with(_user) - if requesting_user_is_only_shared_user: - messages.info( - request, - DO_NOT_FORGET_TO_SHARE - ) - - context = { - "obj": acc, - "geom_form": geom_form, - "parcels": parcels, - "is_entry_shared": is_data_shared, - "before_states": before_states, - "after_states": after_states, - "sum_before_states": sum_before_states, - "sum_after_states": sum_after_states, - "diff_states": diff_states, - "available": available_relative, - "available_total": available_total, - "is_default_member": _user.in_group(DEFAULT_GROUP), - "is_zb_member": _user.in_group(ZB_GROUP), - "is_ets_member": _user.in_group(ETS_GROUP), - "LANIS_LINK": acc.get_LANIS_link(), - "deductions": deductions, - "actions": actions, - TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}", - "has_finished_deadlines": acc.get_finished_deadlines().exists(), - } - context = BaseContext(request, context).context - return render(request, template, context) - - @login_required_modal @login_required @default_group_required diff --git a/ema/urls.py b/ema/urls.py index 0c4308b5..5e20b14c 100644 --- a/ema/urls.py +++ b/ema/urls.py @@ -11,7 +11,7 @@ from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActio from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView from ema.views.detail import DetailEmaView from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView -from ema.views.ema import new_view, new_id_view, edit_view, remove_view, IndexEmaView +from ema.views.ema import new_view, edit_view, remove_view, IndexEmaView, EmaIdentifierGeneratorView from ema.views.log import EmaLogView from ema.views.record import EmaRecordView from ema.views.report import report_view @@ -23,7 +23,7 @@ app_name = "ema" urlpatterns = [ path("", IndexEmaView.as_view(), name="index"), path("new/", new_view, name="new"), - path("new/id", new_id_view, name="new-id"), + path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"), path("", DetailEmaView.as_view(), name="detail"), path('/log', EmaLogView.as_view(), name='log'), path('/edit', edit_view, name='edit'), diff --git a/ema/views/ema.py b/ema/views/ema.py index 1ea59120..9c0a216f 100644 --- a/ema/views/ema.py +++ b/ema/views/ema.py @@ -10,20 +10,20 @@ from django.contrib.auth.decorators import login_required from django.http import HttpRequest, JsonResponse, HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse +from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ from ema.forms import NewEmaForm, EditEmaForm from ema.models import Ema from ema.tables import EmaTable from konova.contexts import BaseContext -from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal, \ - uuid_required +from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal from konova.forms import SimpleGeomForm from konova.forms.modals import RemoveModalForm -from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \ - DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE + GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE +from konova.views.identifier import AbstractIdentifierGeneratorView from konova.views.index import AbstractIndexView @@ -110,24 +110,12 @@ def new_view(request: HttpRequest): return render(request, template, context) -@login_required -@conservation_office_group_required -def new_id_view(request: HttpRequest): - """ JSON endpoint - - Provides fetching of free identifiers for e.g. AJAX calls - - """ - tmp = Ema() - identifier = tmp.generate_new_identifier() - while Ema.objects.filter(identifier=identifier).exists(): - identifier = tmp.generate_new_identifier() - return JsonResponse( - data={ - "gen_data": identifier - } - ) +class EmaIdentifierGeneratorView(AbstractIdentifierGeneratorView): + _MODEL = Ema + @method_decorator(conservation_office_group_required) + def dispatch(self, request, *args, **kwargs): + return super().dispatch(request, *args, **kwargs) @login_required @conservation_office_group_required diff --git a/intervention/urls.py b/intervention/urls.py index 136057dc..f9249fa9 100644 --- a/intervention/urls.py +++ b/intervention/urls.py @@ -14,8 +14,9 @@ from intervention.views.deduction import NewInterventionDeductionView, EditInter RemoveInterventionDeductionView from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \ RemoveInterventionDocumentView, EditInterventionDocumentView -from intervention.views.intervention import new_view, new_id_view, edit_view, remove_view, \ - IndexInterventionView, DetailInterventionView +from intervention.views.intervention import new_view, edit_view, remove_view, \ + IndexInterventionView, InterventionIdentifierGeneratorView +from intervention.views.detail import DetailInterventionView from intervention.views.log import InterventionLogView from intervention.views.record import InterventionRecordView from intervention.views.report import report_view @@ -28,7 +29,7 @@ app_name = "intervention" urlpatterns = [ path("", IndexInterventionView.as_view(), name="index"), path('new/', new_view, name='new'), - path('new/id', new_id_view, name='new-id'), + path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'), path('', DetailInterventionView.as_view(), name='detail'), path('/log', InterventionLogView.as_view(), name='log'), path('/edit', edit_view, name='edit'), diff --git a/intervention/views/detail.py b/intervention/views/detail.py new file mode 100644 index 00000000..68f98708 --- /dev/null +++ b/intervention/views/detail.py @@ -0,0 +1,79 @@ +""" +Author: Michel Peltriaux +Created on: 14.12.25 + +""" +from django.contrib import messages +from django.http import HttpResponse +from django.shortcuts import get_object_or_404, render + +from intervention.models import Intervention +from konova.contexts import BaseContext +from konova.forms import SimpleGeomForm +from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP +from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER +from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, DO_NOT_FORGET_TO_SHARE +from konova.views.detail import AbstractDetailView + + +class DetailInterventionView(AbstractDetailView): + _TEMPLATE = "intervention/detail/view.html" + + def get(self, request, id: str, *args, **kwargs) -> HttpResponse: + # Fetch data, filter out deleted related data + intervention = get_object_or_404( + Intervention.objects.select_related( + "geometry", + "legal", + "responsible", + ).prefetch_related( + "legal__revocations", + ), + id=id, + deleted=None + ) + compensations = intervention.compensations.filter( + deleted=None, + ) + _user = request.user + is_data_shared = intervention.is_shared_with(user=_user) + + geom_form = SimpleGeomForm( + instance=intervention, + ) + last_checked = intervention.get_last_checked_action() + last_checked_tooltip = "" + if last_checked: + last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format( + last_checked.get_timestamp_str_formatted(), + last_checked.user + ) + + has_payment_without_document = intervention.payments.exists() and not intervention.get_documents()[1].exists() + + requesting_user_is_only_shared_user = intervention.is_only_shared_with(_user) + if requesting_user_is_only_shared_user: + messages.info( + request, + DO_NOT_FORGET_TO_SHARE + ) + + context = { + "obj": intervention, + "last_checked": last_checked, + "last_checked_tooltip": last_checked_tooltip, + "compensations": compensations, + "is_entry_shared": is_data_shared, + "geom_form": geom_form, + "is_default_member": _user.in_group(DEFAULT_GROUP), + "is_zb_member": _user.in_group(ZB_GROUP), + "is_ets_member": _user.in_group(ETS_GROUP), + "LANIS_LINK": intervention.get_LANIS_link(), + "has_payment_without_document": has_payment_without_document, + TAB_TITLE_IDENTIFIER: f"{intervention.identifier} - {intervention.title}", + } + + request = intervention.set_status_messages(request) + + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) diff --git a/intervention/views/intervention.py b/intervention/views/intervention.py index 4d8e44ce..24d25983 100644 --- a/intervention/views/intervention.py +++ b/intervention/views/intervention.py @@ -7,7 +7,7 @@ Created on: 19.08.22 """ from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.http import JsonResponse, HttpRequest, HttpResponse +from django.http import HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, render, redirect from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -16,16 +16,14 @@ from intervention.forms.intervention import EditInterventionForm, NewInterventio from intervention.models import Intervention from intervention.tables import InterventionTable from konova.contexts import BaseContext -from konova.decorators import default_group_required, shared_access_required, any_group_check, login_required_modal, \ - uuid_required +from konova.decorators import default_group_required, shared_access_required, login_required_modal from konova.forms import SimpleGeomForm from konova.forms.modals import RemoveModalForm -from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER -from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, RECORDED_BLOCKS_EDIT, \ - CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, \ +from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, \ + CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, \ GEOMETRIES_IGNORED_TEMPLATE -from konova.views.detail import AbstractDetailView +from konova.views.identifier import AbstractIdentifierGeneratorView from konova.views.index import AbstractIndexView @@ -115,87 +113,9 @@ def new_view(request: HttpRequest): context = BaseContext(request, context).context return render(request, template, context) +class InterventionIdentifierGeneratorView(AbstractIdentifierGeneratorView): + _MODEL = Intervention -@login_required -@default_group_required -def new_id_view(request: HttpRequest): - """ JSON endpoint - - Provides fetching of free identifiers for e.g. AJAX calls - - """ - tmp_intervention = Intervention() - identifier = tmp_intervention.generate_new_identifier() - while Intervention.objects.filter(identifier=identifier).exists(): - identifier = tmp_intervention.generate_new_identifier() - return JsonResponse( - data={ - "gen_data": identifier - } - ) - - -class DetailInterventionView(AbstractDetailView): - _TEMPLATE = "intervention/detail/view.html" - - def get(self, request, id: str, *args, **kwargs) -> HttpResponse: - # Fetch data, filter out deleted related data - intervention = get_object_or_404( - Intervention.objects.select_related( - "geometry", - "legal", - "responsible", - ).prefetch_related( - "legal__revocations", - ), - id=id, - deleted=None - ) - compensations = intervention.compensations.filter( - deleted=None, - ) - _user = request.user - is_data_shared = intervention.is_shared_with(user=_user) - - geom_form = SimpleGeomForm( - instance=intervention, - ) - last_checked = intervention.get_last_checked_action() - last_checked_tooltip = "" - if last_checked: - last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format( - last_checked.get_timestamp_str_formatted(), - last_checked.user - ) - - has_payment_without_document = intervention.payments.exists() and not intervention.get_documents()[1].exists() - - requesting_user_is_only_shared_user = intervention.is_only_shared_with(_user) - if requesting_user_is_only_shared_user: - messages.info( - request, - DO_NOT_FORGET_TO_SHARE - ) - - context = { - "obj": intervention, - "last_checked": last_checked, - "last_checked_tooltip": last_checked_tooltip, - "compensations": compensations, - "is_entry_shared": is_data_shared, - "geom_form": geom_form, - "is_default_member": _user.in_group(DEFAULT_GROUP), - "is_zb_member": _user.in_group(ZB_GROUP), - "is_ets_member": _user.in_group(ETS_GROUP), - "LANIS_LINK": intervention.get_LANIS_link(), - "has_payment_without_document": has_payment_without_document, - TAB_TITLE_IDENTIFIER: f"{intervention.identifier} - {intervention.title}", - } - - request = intervention.set_status_messages(request) - - context = BaseContext(request, context).context - return render(request, self._TEMPLATE, context) @login_required @default_group_required diff --git a/konova/utils/generators.py b/konova/utils/generators.py index 78e075ad..d21b0437 100644 --- a/konova/utils/generators.py +++ b/konova/utils/generators.py @@ -62,3 +62,26 @@ def generate_qr_code(content: str, size: int = 20) -> str: stream = BytesIO() qrcode_img.save(stream) return stream.getvalue().decode() + + +class IdentifierGenerator: + _MODEL = None + + def __init__(self, model): + from konova.models import BaseObject + if not issubclass(model, BaseObject): + raise AssertionError("Model must be a subclass of BaseObject!") + + self._MODEL = model + + def generate_id(self) -> str: + """ Generates a unique identifier + + Returns: + + """ + unpersisted_object = self._MODEL() + identifier = unpersisted_object.generate_new_identifier() + while self._MODEL.objects.filter(identifier=identifier).exists(): + identifier = unpersisted_object.generate_new_identifier() + return identifier diff --git a/konova/views/identifier.py b/konova/views/identifier.py new file mode 100644 index 00000000..b5fdd4be --- /dev/null +++ b/konova/views/identifier.py @@ -0,0 +1,32 @@ +""" +Author: Michel Peltriaux +Created on: 14.12.25 + +""" +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import HttpRequest, JsonResponse +from django.utils.decorators import method_decorator +from django.views import View + +from konova.decorators import default_group_required +from konova.utils.generators import IdentifierGenerator + + +class AbstractIdentifierGeneratorView(LoginRequiredMixin, View): + _MODEL = None + + class Meta: + abstract = True + + @method_decorator(default_group_required) + def dispatch(self, request, *args, **kwargs): + return super().dispatch(request, *args, **kwargs) + + def get(self, request: HttpRequest, *args, **kwargs): + generator = IdentifierGenerator(model=self._MODEL) + identifier = generator.generate_id() + return JsonResponse( + data={ + "gen_data": identifier + } + ) \ No newline at end of file