# Detail View

* introduces BaseDetailView
* refactors detail views for EIV, KOM, OEK, EMA from function based to class based
* refactors already class based HomeView to inherit from new BaseView
This commit is contained in:
mpeltriaux 2025-10-17 15:40:45 +02:00
parent 61ec9c8c9b
commit a9b402862b
12 changed files with 357 additions and 294 deletions

View File

@ -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/<intervention_id>', new_view, name='new'),
path('new', new_view, name='new'),
path('<id>', detail_view, name='detail'),
path('<id>', CompensationDetailView.as_view(), name='detail'),
path('<id>/log', CompensationLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', remove_view, name='remove'),

View File

@ -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('<id>', detail_view, name='detail'),
path('<id>', EcoAccountDetailView.as_view(), name='detail'),
path('<id>/log', EcoAccountLogView.as_view(), name='log'),
path('<id>/record', EcoAccountRecordView.as_view(), name='record'),
path('<id>/report', EcoAccountReportView.as_view(), name='report'),

View File

@ -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

View File

@ -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

View File

@ -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("<id>", detail_view, name="detail"),
path("<id>", EmaDetailView.as_view(), name="detail"),
path('<id>/log', EmaLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', remove_view, name='remove'),

View File

@ -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

View File

@ -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('<id>', detail_view, name='detail'),
path('<id>', InterventionDetailView.as_view(), name='detail'),
path('<id>/log', InterventionLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', remove_view, name='remove'),

View File

@ -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

View File

@ -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

View File

@ -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(

107
konova/views/detail.py Normal file
View File

@ -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

View File

@ -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