diff --git a/compensation/urls/compensation.py b/compensation/urls/compensation.py index 4b74a405..adec4b7f 100644 --- a/compensation/urls/compensation.py +++ b/compensation/urls/compensation.py @@ -17,8 +17,8 @@ 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, detail_view, edit_view, \ - remove_view, CompensationIndexView, CompensationIdentifierGeneratorView +from compensation.views.compensation.compensation import new_view, edit_view, \ + remove_view, CompensationIndexView, CompensationIdentifierGeneratorView, CompensationDetailView from compensation.views.compensation.log import CompensationLogView urlpatterns = [ @@ -27,7 +27,7 @@ urlpatterns = [ path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'), path('new/', new_view, name='new'), path('new', new_view, name='new'), - path('', detail_view, name='detail'), + path('', CompensationDetailView.as_view(), name='detail'), path('/log', CompensationLogView.as_view(), name='log'), path('/edit', edit_view, name='edit'), path('/remove', remove_view, name='remove'), diff --git a/compensation/urls/eco_account.py b/compensation/urls/eco_account.py index 1825d96a..f4989be7 100644 --- a/compensation/urls/eco_account.py +++ b/compensation/urls/eco_account.py @@ -9,7 +9,7 @@ from django.urls import path from compensation.autocomplete.eco_account import EcoAccountAutocomplete from compensation.views.eco_account.eco_account import new_view, edit_view, remove_view, \ - detail_view, EcoAccountIndexView, EcoAccountIdentifierGeneratorView + EcoAccountIndexView, EcoAccountIdentifierGeneratorView, EcoAccountDetailView from compensation.views.eco_account.log import EcoAccountLogView from compensation.views.eco_account.record import EcoAccountRecordView from compensation.views.eco_account.report import EcoAccountReportView @@ -31,7 +31,7 @@ urlpatterns = [ path("", EcoAccountIndexView.as_view(), name="index"), path('new/', new_view, name='new'), path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'), - path('', detail_view, name='detail'), + path('', EcoAccountDetailView.as_view(), name='detail'), path('/log', EcoAccountLogView.as_view(), name='log'), path('/record', EcoAccountRecordView.as_view(), name='record'), path('/report', EcoAccountReportView.as_view(), name='report'), diff --git a/compensation/views/compensation/compensation.py b/compensation/views/compensation/compensation.py index 749a88aa..2403d2b8 100644 --- a/compensation/views/compensation/compensation.py +++ b/compensation/views/compensation/compensation.py @@ -19,16 +19,15 @@ 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, \ 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.base import BaseIndexView, BaseIdentifierGeneratorView +from konova.views.detail import BaseDetailView class CompensationIndexView(LoginRequiredMixin, BaseIndexView): @@ -185,82 +184,71 @@ 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 +class CompensationDetailView(BaseDetailView): + _MODEL_CLS = Compensation + _TEMPLATE = "compensation/detail/compensation/view.html" - Args: - request (HttpRequest): The incoming request - id (str): The compensation's id + def _get_object(self, id: str): + """ Returns the compensation - Returns: + Args: + id (str): The compensation's id - """ - template = "compensation/detail/compensation/view.html" - comp = get_object_or_404( - Compensation.objects.select_related( - "modified", - "created", - "geometry" - ), - id=id, - deleted=None, - intervention__deleted=None, - ) - geom_form = SimpleGeomForm(instance=comp) - parcels = comp.get_underlying_parcels() - _user = request.user - is_data_shared = comp.intervention.is_shared_with(_user) - - # Order states according to surface - before_states = comp.before_states.all().prefetch_related("biotope_type").order_by("-surface") - after_states = comp.after_states.all().prefetch_related("biotope_type").order_by("-surface") - actions = comp.actions.all().prefetch_related("action_type") - - # Precalculate logical errors between before- and after-states - # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling - sum_before_states = comp.get_surface_before_states() - sum_after_states = comp.get_surface_after_states() - diff_states = abs(sum_before_states - sum_after_states) - - request = comp.set_status_messages(request) - - last_checked = comp.intervention.get_last_checked_action() - last_checked_tooltip = "" - if last_checked: - last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(last_checked.get_timestamp_str_formatted(), last_checked.user) - - requesting_user_is_only_shared_user = comp.is_only_shared_with(_user) - if requesting_user_is_only_shared_user: - messages.info( - request, - DO_NOT_FORGET_TO_SHARE + Returns: + obj (Compensation): The compensation + """ + comp = get_object_or_404( + Compensation.objects.select_related( + "modified", + "created", + "geometry" + ), + id=id, + deleted=None, + intervention__deleted=None, ) + return comp - context = { - "obj": comp, - "last_checked": last_checked, - "last_checked_tooltip": last_checked_tooltip, - "geom_form": geom_form, - "parcels": parcels, - "is_entry_shared": is_data_shared, - "actions": actions, - "before_states": before_states, - "after_states": after_states, - "sum_before_states": sum_before_states, - "sum_after_states": sum_after_states, - "diff_states": diff_states, - "is_default_member": _user.in_group(DEFAULT_GROUP), - "is_zb_member": _user.in_group(ZB_GROUP), - "is_ets_member": _user.in_group(ETS_GROUP), - "LANIS_LINK": comp.get_LANIS_link(), - TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}", - "has_finished_deadlines": comp.get_finished_deadlines().exists(), - } - context = BaseContext(request, context).context - return render(request, template, context) + def _get_detail_context(self, obj: Compensation): + """ Generate object specific detail context for view + + Args: + obj (): The record + + Returns: + + """ + # Order states according to surface + before_states = obj.before_states.all().prefetch_related("biotope_type").order_by("-surface") + after_states = obj.after_states.all().prefetch_related("biotope_type").order_by("-surface") + actions = obj.actions.all().prefetch_related("action_type") + + # Precalculate logical errors between before- and after-states + # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling + sum_before_states = obj.get_surface_before_states() + sum_after_states = obj.get_surface_after_states() + diff_states = abs(sum_before_states - sum_after_states) + + last_checked = obj.intervention.get_last_checked_action() + last_checked_tooltip = "" + if last_checked: + last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format( + last_checked.get_timestamp_str_formatted(), + last_checked.user + ) + + context = { + "last_checked": last_checked, + "last_checked_tooltip": last_checked_tooltip, + "actions": actions, + "before_states": before_states, + "after_states": after_states, + "sum_before_states": sum_before_states, + "sum_after_states": sum_after_states, + "diff_states": diff_states, + "has_finished_deadlines": obj.get_finished_deadlines().exists(), + } + return context @login_required_modal diff --git a/compensation/views/eco_account/eco_account.py b/compensation/views/eco_account/eco_account.py index 55a56a2e..42eb569e 100644 --- a/compensation/views/eco_account/eco_account.py +++ b/compensation/views/eco_account/eco_account.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.contrib.auth.mixins import LoginRequiredMixin -from django.http import HttpRequest, JsonResponse +from django.http import HttpRequest from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -17,14 +17,14 @@ 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.base import BaseIndexView, BaseIdentifierGeneratorView +from konova.views.detail import BaseDetailView class EcoAccountIndexView(LoginRequiredMixin, BaseIndexView): @@ -162,86 +162,72 @@ 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 +class EcoAccountDetailView(BaseDetailView): + _MODEL_CLS = EcoAccount + _TEMPLATE = "compensation/detail/eco_account/view.html" - Args: - request (HttpRequest): The incoming request - id (str): The compensation's id + def _get_object(self, id: str): + """ Fetch object for detail view - Returns: + Args: + id (str): The record's id' - """ - 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) + Returns: - # 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 + """ + acc = get_object_or_404( + EcoAccount.objects.prefetch_related( + "deadlines", + ).select_related( + 'geometry', + 'responsible', + ), + id=id, + deleted=None, ) + return acc - 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) + def _get_detail_context(self, obj: EcoAccount): + """ Generate object specific detail context for view + + Args: + obj (): The record + + Returns: + + """ + # Order states according to surface + before_states = obj.before_states.order_by("-surface") + after_states = obj.after_states.order_by("-surface") + + # Precalculate logical errors between before- and after-states + # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling + sum_before_states = obj.get_surface_before_states() + sum_after_states = obj.get_surface_after_states() + diff_states = abs(sum_before_states - sum_after_states) + # Calculate rest of available surface for deductions + available_total = obj.deductable_rest + available_relative = obj.get_deductable_rest_relative() + + # Prefetch related data to decrease the amount of db connections + deductions = obj.deductions.filter( + intervention__deleted=None, + ) + actions = obj.actions.all() + + context = { + "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, + "deductions": deductions, + "actions": actions, + "has_finished_deadlines": obj.get_finished_deadlines().exists(), + } + return context @login_required_modal diff --git a/ema/urls.py b/ema/urls.py index f4f8f79a..3469ed13 100644 --- a/ema/urls.py +++ b/ema/urls.py @@ -10,8 +10,8 @@ from django.urls import path from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActionView from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView -from ema.views.ema import new_view, detail_view, edit_view, remove_view, EmaIndexView, \ - EmaIdentifierGeneratorView +from ema.views.ema import new_view, edit_view, remove_view, EmaIndexView, \ + EmaIdentifierGeneratorView, EmaDetailView from ema.views.log import EmaLogView from ema.views.record import EmaRecordView from ema.views.report import EmaReportView @@ -24,7 +24,7 @@ urlpatterns = [ path("", EmaIndexView.as_view(), name="index"), path("new/", new_view, name="new"), path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"), - path("", detail_view, name="detail"), + path("", EmaDetailView.as_view(), name="detail"), path('/log', EmaLogView.as_view(), name='log'), path('/edit', edit_view, name='edit'), path('/remove', remove_view, name='remove'), diff --git a/ema/views/ema.py b/ema/views/ema.py index 8ec9a8b1..d0e04126 100644 --- a/ema/views/ema.py +++ b/ema/views/ema.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.contrib.auth.mixins import LoginRequiredMixin -from django.http import HttpRequest, JsonResponse +from django.http import HttpRequest from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -17,15 +17,14 @@ 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, MISSING_GROUP_PERMISSION + GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView +from konova.views.detail import BaseDetailView class EmaIndexView(LoginRequiredMixin, BaseIndexView): @@ -104,64 +103,50 @@ class EmaIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView return user.is_ets_user() -@login_required -@uuid_required -def detail_view(request: HttpRequest, id: str): - """ Renders the detail view of an EMA +class EmaDetailView(BaseDetailView): + _MODEL_CLS = Ema + _TEMPLATE = "ema/detail/view.html" - Args: - request (HttpRequest): The incoming request - id (str): The EMA id + def _get_object(self, id: str): + """ Fetch object for detail view - Returns: + Args: + id (str): The record's id' - """ - template = "ema/detail/view.html" - ema = get_object_or_404(Ema, id=id, deleted=None) + Returns: - geom_form = SimpleGeomForm(instance=ema) - parcels = ema.get_underlying_parcels() - _user = request.user - is_entry_shared = ema.is_shared_with(_user) + """ + ema = get_object_or_404(Ema, id=id, deleted=None) + return ema - # Order states according to surface - before_states = ema.before_states.all().order_by("-surface") - after_states = ema.after_states.all().order_by("-surface") + def _get_detail_context(self, obj: Ema): + """ Generate object specific detail context for view - # Precalculate logical errors between before- and after-states - # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling - sum_before_states = ema.get_surface_before_states() - sum_after_states = ema.get_surface_after_states() - diff_states = abs(sum_before_states - sum_after_states) + Args: + obj (): The record - ema.set_status_messages(request) + Returns: - requesting_user_is_only_shared_user = ema.is_only_shared_with(_user) - if requesting_user_is_only_shared_user: - messages.info( - request, - DO_NOT_FORGET_TO_SHARE - ) + """ + # Order states according to surface + before_states = obj.before_states.all().order_by("-surface") + after_states = obj.after_states.all().order_by("-surface") - context = { - "obj": ema, - "geom_form": geom_form, - "parcels": parcels, - "is_entry_shared": is_entry_shared, - "before_states": before_states, - "after_states": after_states, - "sum_before_states": sum_before_states, - "sum_after_states": sum_after_states, - "diff_states": diff_states, - "is_default_member": _user.in_group(DEFAULT_GROUP), - "is_zb_member": _user.in_group(ZB_GROUP), - "is_ets_member": _user.in_group(ETS_GROUP), - "LANIS_LINK": ema.get_LANIS_link(), - TAB_TITLE_IDENTIFIER: f"{ema.identifier} - {ema.title}", - "has_finished_deadlines": ema.get_finished_deadlines().exists(), - } - context = BaseContext(request, context).context - return render(request, template, context) + # Precalculate logical errors between before- and after-states + # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling + sum_before_states = obj.get_surface_before_states() + sum_after_states = obj.get_surface_after_states() + diff_states = abs(sum_before_states - sum_after_states) + + context = { + "before_states": before_states, + "after_states": after_states, + "sum_before_states": sum_before_states, + "sum_after_states": sum_after_states, + "diff_states": diff_states, + "has_finished_deadlines": obj.get_finished_deadlines().exists(), + } + return context @login_required diff --git a/intervention/urls.py b/intervention/urls.py index db5829fd..3a21df5f 100644 --- a/intervention/urls.py +++ b/intervention/urls.py @@ -14,8 +14,8 @@ from intervention.views.deduction import NewInterventionDeductionView, EditInter RemoveInterventionDeductionView from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \ RemoveInterventionDocumentView, EditInterventionDocumentView -from intervention.views.intervention import new_view, detail_view, edit_view, remove_view, \ - InterventionIndexView, InterventionIdentifierGeneratorView +from intervention.views.intervention import new_view, edit_view, remove_view, \ + InterventionIndexView, InterventionIdentifierGeneratorView, InterventionDetailView from intervention.views.log import InterventionLogView from intervention.views.record import InterventionRecordView from intervention.views.report import InterventionReportView @@ -29,7 +29,7 @@ urlpatterns = [ path("", InterventionIndexView.as_view(), name="index"), path('new/', new_view, name='new'), path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'), - path('', detail_view, name='detail'), + path('', InterventionDetailView.as_view(), name='detail'), path('/log', InterventionLogView.as_view(), name='log'), path('/edit', edit_view, name='edit'), path('/remove', remove_view, name='remove'), diff --git a/intervention/views/intervention.py b/intervention/views/intervention.py index 80a92b51..e9d297f7 100644 --- a/intervention/views/intervention.py +++ b/intervention/views/intervention.py @@ -27,6 +27,7 @@ from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, REC CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, \ GEOMETRIES_IGNORED_TEMPLATE from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView +from konova.views.detail import BaseDetailView class InterventionIndexView(LoginRequiredMixin, BaseIndexView): @@ -105,78 +106,59 @@ class InterventionIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGene _REDIRECT_URL = "intervention:index" -@login_required -@any_group_check -@uuid_required -def detail_view(request: HttpRequest, id: str): - """ Renders a detail view for viewing an intervention's data +class InterventionDetailView(BaseDetailView): + _MODEL_CLS = Intervention + _TEMPLATE = "intervention/detail/view.html" - Args: - request (HttpRequest): The incoming request - id (str): The intervention's id + def _get_object(self, id: str): + """ Returns the intervention - Returns: + Args: + id (str): The intervention's id - """ - template = "intervention/detail/view.html" - - # 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 + Returns: + obj (Intervention): The intervention + """ + # Fetch data, filter out deleted related data + obj = get_object_or_404( + self._MODEL_CLS.objects.select_related( + "geometry", + "legal", + "responsible", + ).prefetch_related( + "legal__revocations", + ), + id=id, + deleted=None ) + return obj - has_payment_without_document = intervention.payments.exists() and not intervention.get_documents()[1].exists() + def _get_detail_context(self, obj: Intervention): + """ Generate object specific detail context for view - 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 - ) + Args: + obj (): The record - 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}", - } + Returns: - request = intervention.set_status_messages(request) + """ + compensations = obj.compensations.filter(deleted=None) + last_checked = obj.get_last_checked_action() + last_checked_tooltip = "" + if last_checked: + last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format( + last_checked.get_timestamp_str_formatted(), + last_checked.user + ) - context = BaseContext(request, context).context - return render(request, template, context) + has_payment_without_document = obj.payments.exists() and not obj.get_documents()[1].exists() + context = { + "last_checked": last_checked, + "last_checked_tooltip": last_checked_tooltip, + "compensations": compensations, + "has_payment_without_document": has_payment_without_document, + } + return context @login_required diff --git a/konova/utils/general.py b/konova/utils/general.py index 43341ffd..666d4811 100644 --- a/konova/utils/general.py +++ b/konova/utils/general.py @@ -5,9 +5,11 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 17.09.21 """ +from uuid import UUID + from django.contrib import messages from django.utils.translation import gettext_lazy as _ -from django.http import HttpRequest +from django.http import HttpRequest, Http404 def format_german_float(num) -> str: @@ -38,3 +40,12 @@ def check_user_is_in_any_group(request: HttpRequest): _("+++ Attention: You are not part of any group. You won't be able to create, edit or do anything. Please contact an administrator. +++") ) return request + +def check_id_is_valid_uuid(**kwargs: dict): + uuid = kwargs.get("uuid", None) or kwargs.get("id", None) + if uuid: + try: + # Check whether the id is a proper uuid or something that would break a db fetch + UUID(uuid) + except ValueError: + raise Http404 diff --git a/konova/views/base.py b/konova/views/base.py index 9a6099c1..74a0bdd5 100644 --- a/konova/views/base.py +++ b/konova/views/base.py @@ -27,12 +27,16 @@ class BaseView(View): abstract = True def dispatch(self, request, *args, **kwargs): + request = check_user_is_in_any_group(request) + if not self._user_has_permission(request.user): messages.info(request, MISSING_GROUP_PERMISSION) return redirect(reverse(self._REDIRECT_URL_ERROR)) + if not self._user_has_shared_access(request.user, **kwargs): messages.info(request, DATA_UNSHARED) return redirect(reverse(self._REDIRECT_URL_ERROR)) + return super().dispatch(request, *args, **kwargs) def _user_has_permission(self, user): @@ -77,10 +81,6 @@ class BaseIndexView(BaseView): class Meta: abstract = True - def dispatch(self, request, *args, **kwargs): - request = check_user_is_in_any_group(request) - return super().dispatch(request, *args, **kwargs) - def get(self, request: HttpRequest): qs = self._get_queryset() table = self._INDEX_TABLE_CLS( diff --git a/konova/views/detail.py b/konova/views/detail.py new file mode 100644 index 00000000..6322ab9b --- /dev/null +++ b/konova/views/detail.py @@ -0,0 +1,107 @@ +""" +Author: Michel Peltriaux +Created on: 17.10.25 + +""" +from abc import abstractmethod + +from django.contrib import messages +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import HttpRequest +from django.shortcuts import render + +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.general import check_id_is_valid_uuid +from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE +from konova.views.base import BaseView + + +class BaseDetailView(LoginRequiredMixin, BaseView): + _MODEL_CLS = None + + class Meta: + abstract = True + + def dispatch(self, request, *args, **kwargs): + check_id_is_valid_uuid(**kwargs) + return super().dispatch(request, *args, **kwargs) + + def _user_has_shared_access(self, user, **kwargs): + """ Check if user has shared access to this object + + Args: + user (): + **kwargs (): + + Returns: + + """ + # Access to an entry's detail view is not restricted by the state of being-shared or not + return True + + def _user_has_permission(self, user): + # Detail views have no restrictions + return True + + def get(self, request: HttpRequest, id: str): + """ Get endpoint for detail view + + Args: + request (HttpRequest): The incoming request + id (str): The record's id + + Returns: + + """ + obj = self._get_object(id) + geom_form = SimpleGeomForm(instance=obj) + user = request.user + + requesting_user_is_only_shared_user = obj.is_only_shared_with(user) + if requesting_user_is_only_shared_user: + messages.info(request, DO_NOT_FORGET_TO_SHARE) + + obj.set_status_messages(request) + + detail_context = self._get_detail_context(obj) + context = BaseContext(request, detail_context).context + context.update( + { + "obj": obj, + "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": obj.get_LANIS_link(), + "is_entry_shared": obj.is_shared_with(user=user), + TAB_TITLE_IDENTIFIER: f"{obj.identifier} - {obj.title}" + } + ) + return render(request,self._TEMPLATE, context) + + @abstractmethod + def _get_detail_context(self, obj): + """ Generate object specific detail context for view + + Args: + obj (): The record + + Returns: + + """ + raise NotImplementedError + + @abstractmethod + def _get_object(self, id: str): + """ Fetch object for detail view + + Args: + id (str): The record's id' + + Returns: + + """ + raise NotImplementedError diff --git a/konova/views/home.py b/konova/views/home.py index 5253bbfe..35cbcb1b 100644 --- a/konova/views/home.py +++ b/konova/views/home.py @@ -9,21 +9,19 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q from django.http import HttpRequest from django.shortcuts import render -from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ -from django.views import View from compensation.models import EcoAccount, Compensation from intervention.models import Intervention from konova.contexts import BaseContext -from konova.decorators import any_group_check from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER +from konova.views.base import BaseView from news.models import ServerMessage -class HomeView(LoginRequiredMixin, View): +class HomeView(LoginRequiredMixin, BaseView): + _TEMPLATE = "konova/home.html" - @method_decorator(any_group_check) def get(self, request: HttpRequest): """ Renders the landing page @@ -34,7 +32,6 @@ class HomeView(LoginRequiredMixin, View): Returns: A redirect """ - template = "konova/home.html" user = request.user user_teams = user.shared_teams @@ -75,5 +72,12 @@ class HomeView(LoginRequiredMixin, View): TAB_TITLE_IDENTIFIER: _("Home"), } context = BaseContext(request, additional_context).context - return render(request, template, context) + return render(request, self._TEMPLATE, context) + def _user_has_permission(self, user): + # No specific permission needed for home view + return True + + def _user_has_shared_access(self, user, **kwargs): + # No specific constraint needed for home view + return True