Compare commits

..

No commits in common. "a9b402862b91de708ac1c1094df5afcd57c003e9" and "f2baa054bf6389da790c6df559c9cc699dfe8878" have entirely different histories.

13 changed files with 302 additions and 359 deletions

View File

@ -17,8 +17,8 @@ from compensation.views.compensation.action import NewCompensationActionView, Ed
RemoveCompensationActionView RemoveCompensationActionView
from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \ from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \
RemoveCompensationStateView RemoveCompensationStateView
from compensation.views.compensation.compensation import new_view, edit_view, \ from compensation.views.compensation.compensation import new_view, detail_view, edit_view, \
remove_view, CompensationIndexView, CompensationIdentifierGeneratorView, CompensationDetailView remove_view, CompensationIndexView, CompensationIdentifierGeneratorView
from compensation.views.compensation.log import CompensationLogView from compensation.views.compensation.log import CompensationLogView
urlpatterns = [ urlpatterns = [
@ -27,7 +27,7 @@ urlpatterns = [
path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'), path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'),
path('new/<intervention_id>', new_view, name='new'), path('new/<intervention_id>', new_view, name='new'),
path('new', new_view, name='new'), path('new', new_view, name='new'),
path('<id>', CompensationDetailView.as_view(), name='detail'), path('<id>', detail_view, name='detail'),
path('<id>/log', CompensationLogView.as_view(), name='log'), path('<id>/log', CompensationLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'), path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', remove_view, name='remove'), 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.autocomplete.eco_account import EcoAccountAutocomplete
from compensation.views.eco_account.eco_account import new_view, edit_view, remove_view, \ from compensation.views.eco_account.eco_account import new_view, edit_view, remove_view, \
EcoAccountIndexView, EcoAccountIdentifierGeneratorView, EcoAccountDetailView detail_view, EcoAccountIndexView, EcoAccountIdentifierGeneratorView
from compensation.views.eco_account.log import EcoAccountLogView from compensation.views.eco_account.log import EcoAccountLogView
from compensation.views.eco_account.record import EcoAccountRecordView from compensation.views.eco_account.record import EcoAccountRecordView
from compensation.views.eco_account.report import EcoAccountReportView from compensation.views.eco_account.report import EcoAccountReportView
@ -31,7 +31,7 @@ urlpatterns = [
path("", EcoAccountIndexView.as_view(), name="index"), path("", EcoAccountIndexView.as_view(), name="index"),
path('new/', new_view, name='new'), path('new/', new_view, name='new'),
path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'), path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'),
path('<id>', EcoAccountDetailView.as_view(), name='detail'), path('<id>', detail_view, name='detail'),
path('<id>/log', EcoAccountLogView.as_view(), name='log'), path('<id>/log', EcoAccountLogView.as_view(), name='log'),
path('<id>/record', EcoAccountRecordView.as_view(), name='record'), path('<id>/record', EcoAccountRecordView.as_view(), name='record'),
path('<id>/report', EcoAccountReportView.as_view(), name='report'), path('<id>/report', EcoAccountReportView.as_view(), name='report'),

View File

@ -19,15 +19,16 @@ from compensation.models import Compensation
from compensation.tables.compensation import CompensationTable from compensation.tables.compensation import CompensationTable
from intervention.models import Intervention from intervention.models import Intervention
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import shared_access_required, default_group_required, login_required_modal from konova.decorators import shared_access_required, default_group_required, any_group_check, login_required_modal, \
uuid_required
from konova.forms import SimpleGeomForm from konova.forms import SimpleGeomForm
from konova.forms.modals import RemoveModalForm 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.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, DATA_CHECKED_PREVIOUSLY_TEMPLATE, \
RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, IDENTIFIER_REPLACED, \ RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, IDENTIFIER_REPLACED, \
COMPENSATION_ADDED_TEMPLATE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE COMPENSATION_ADDED_TEMPLATE, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView
from konova.views.detail import BaseDetailView
class CompensationIndexView(LoginRequiredMixin, BaseIndexView): class CompensationIndexView(LoginRequiredMixin, BaseIndexView):
@ -184,71 +185,82 @@ def edit_view(request: HttpRequest, id: str):
return render(request, template, context) return render(request, template, context)
class CompensationDetailView(BaseDetailView): @login_required
_MODEL_CLS = Compensation @any_group_check
_TEMPLATE = "compensation/detail/compensation/view.html" @uuid_required
def detail_view(request: HttpRequest, id: str):
""" Renders a detail view for a compensation
def _get_object(self, id: str): Args:
""" Returns the compensation request (HttpRequest): The incoming request
id (str): The compensation's id
Args: Returns:
id (str): The compensation's id
Returns: """
obj (Compensation): The compensation template = "compensation/detail/compensation/view.html"
""" comp = get_object_or_404(
comp = get_object_or_404( Compensation.objects.select_related(
Compensation.objects.select_related( "modified",
"modified", "created",
"created", "geometry"
"geometry" ),
), id=id,
id=id, deleted=None,
deleted=None, intervention__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
) )
return comp
def _get_detail_context(self, obj: Compensation): context = {
""" Generate object specific detail context for view "obj": comp,
"last_checked": last_checked,
Args: "last_checked_tooltip": last_checked_tooltip,
obj (): The record "geom_form": geom_form,
"parcels": parcels,
Returns: "is_entry_shared": is_data_shared,
"actions": actions,
""" "before_states": before_states,
# Order states according to surface "after_states": after_states,
before_states = obj.before_states.all().prefetch_related("biotope_type").order_by("-surface") "sum_before_states": sum_before_states,
after_states = obj.after_states.all().prefetch_related("biotope_type").order_by("-surface") "sum_after_states": sum_after_states,
actions = obj.actions.all().prefetch_related("action_type") "diff_states": diff_states,
"is_default_member": _user.in_group(DEFAULT_GROUP),
# Precalculate logical errors between before- and after-states "is_zb_member": _user.in_group(ZB_GROUP),
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling "is_ets_member": _user.in_group(ETS_GROUP),
sum_before_states = obj.get_surface_before_states() "LANIS_LINK": comp.get_LANIS_link(),
sum_after_states = obj.get_surface_after_states() TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}",
diff_states = abs(sum_before_states - sum_after_states) "has_finished_deadlines": comp.get_finished_deadlines().exists(),
}
last_checked = obj.intervention.get_last_checked_action() context = BaseContext(request, context).context
last_checked_tooltip = "" return render(request, template, context)
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 @login_required_modal

View File

@ -8,7 +8,7 @@ Created on: 19.08.22
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest from django.http import HttpRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ 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.models import EcoAccount
from compensation.tables.eco_account import EcoAccountTable from compensation.tables.eco_account import EcoAccountTable
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import shared_access_required, default_group_required, login_required_modal from konova.decorators import shared_access_required, default_group_required, any_group_check, login_required_modal, \
uuid_required
from konova.forms import SimpleGeomForm from konova.forms import SimpleGeomForm
from konova.settings import ETS_GROUP from konova.settings import ETS_GROUP, DEFAULT_GROUP, ZB_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER 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, \ from konova.utils.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECORDED_BLOCKS_EDIT, FORM_INVALID, \
IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView
from konova.views.detail import BaseDetailView
class EcoAccountIndexView(LoginRequiredMixin, BaseIndexView): class EcoAccountIndexView(LoginRequiredMixin, BaseIndexView):
@ -162,72 +162,86 @@ def edit_view(request: HttpRequest, id: str):
return render(request, template, context) return render(request, template, context)
class EcoAccountDetailView(BaseDetailView): @login_required
_MODEL_CLS = EcoAccount @any_group_check
_TEMPLATE = "compensation/detail/eco_account/view.html" @uuid_required
def detail_view(request: HttpRequest, id: str):
""" Renders a detail view for a compensation
def _get_object(self, id: str): Args:
""" Fetch object for detail view request (HttpRequest): The incoming request
id (str): The compensation's id
Args: Returns:
id (str): The record'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
acc = get_object_or_404( before_states = acc.before_states.order_by("-surface")
EcoAccount.objects.prefetch_related( after_states = acc.after_states.order_by("-surface")
"deadlines",
).select_related( # Precalculate logical errors between before- and after-states
'geometry', # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
'responsible', sum_before_states = acc.get_surface_before_states()
), sum_after_states = acc.get_surface_after_states()
id=id, diff_states = abs(sum_before_states - sum_after_states)
deleted=None, # 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
) )
return acc
def _get_detail_context(self, obj: EcoAccount): context = {
""" Generate object specific detail context for view "obj": acc,
"geom_form": geom_form,
Args: "parcels": parcels,
obj (): The record "is_entry_shared": is_data_shared,
"before_states": before_states,
Returns: "after_states": after_states,
"sum_before_states": sum_before_states,
""" "sum_after_states": sum_after_states,
# Order states according to surface "diff_states": diff_states,
before_states = obj.before_states.order_by("-surface") "available": available_relative,
after_states = obj.after_states.order_by("-surface") "available_total": available_total,
"is_default_member": _user.in_group(DEFAULT_GROUP),
# Precalculate logical errors between before- and after-states "is_zb_member": _user.in_group(ZB_GROUP),
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling "is_ets_member": _user.in_group(ETS_GROUP),
sum_before_states = obj.get_surface_before_states() "LANIS_LINK": acc.get_LANIS_link(),
sum_after_states = obj.get_surface_after_states() "deductions": deductions,
diff_states = abs(sum_before_states - sum_after_states) "actions": actions,
# Calculate rest of available surface for deductions TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}",
available_total = obj.deductable_rest "has_finished_deadlines": acc.get_finished_deadlines().exists(),
available_relative = obj.get_deductable_rest_relative() }
context = BaseContext(request, context).context
# Prefetch related data to decrease the amount of db connections return render(request, template, context)
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 @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.action import NewEmaActionView, EditEmaActionView, RemoveEmaActionView
from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView
from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView
from ema.views.ema import new_view, edit_view, remove_view, EmaIndexView, \ from ema.views.ema import new_view, detail_view, edit_view, remove_view, EmaIndexView, \
EmaIdentifierGeneratorView, EmaDetailView EmaIdentifierGeneratorView
from ema.views.log import EmaLogView from ema.views.log import EmaLogView
from ema.views.record import EmaRecordView from ema.views.record import EmaRecordView
from ema.views.report import EmaReportView from ema.views.report import EmaReportView
@ -24,7 +24,7 @@ urlpatterns = [
path("", EmaIndexView.as_view(), name="index"), path("", EmaIndexView.as_view(), name="index"),
path("new/", new_view, name="new"), path("new/", new_view, name="new"),
path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"), path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"),
path("<id>", EmaDetailView.as_view(), name="detail"), path("<id>", detail_view, name="detail"),
path('<id>/log', EmaLogView.as_view(), name='log'), path('<id>/log', EmaLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'), path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', remove_view, name='remove'), 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 import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest from django.http import HttpRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -17,14 +17,15 @@ from ema.forms import NewEmaForm, EditEmaForm
from ema.models import Ema from ema.models import Ema
from ema.tables import EmaTable from ema.tables import EmaTable
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal, \
uuid_required
from konova.forms import SimpleGeomForm from konova.forms import SimpleGeomForm
from konova.forms.modals import RemoveModalForm 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.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \ from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \
GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE, MISSING_GROUP_PERMISSION
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView
from konova.views.detail import BaseDetailView
class EmaIndexView(LoginRequiredMixin, BaseIndexView): class EmaIndexView(LoginRequiredMixin, BaseIndexView):
@ -103,50 +104,64 @@ class EmaIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView
return user.is_ets_user() return user.is_ets_user()
class EmaDetailView(BaseDetailView): @login_required
_MODEL_CLS = Ema @uuid_required
_TEMPLATE = "ema/detail/view.html" def detail_view(request: HttpRequest, id: str):
""" Renders the detail view of an EMA
def _get_object(self, id: str): Args:
""" Fetch object for detail view request (HttpRequest): The incoming request
id (str): The EMA id
Args: Returns:
id (str): The record's id'
Returns: """
template = "ema/detail/view.html"
ema = get_object_or_404(Ema, id=id, deleted=None)
""" geom_form = SimpleGeomForm(instance=ema)
ema = get_object_or_404(Ema, id=id, deleted=None) parcels = ema.get_underlying_parcels()
return ema _user = request.user
is_entry_shared = ema.is_shared_with(_user)
def _get_detail_context(self, obj: Ema): # Order states according to surface
""" Generate object specific detail context for view before_states = ema.before_states.all().order_by("-surface")
after_states = ema.after_states.all().order_by("-surface")
Args: # Precalculate logical errors between before- and after-states
obj (): The record # 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)
Returns: ema.set_status_messages(request)
""" requesting_user_is_only_shared_user = ema.is_only_shared_with(_user)
# Order states according to surface if requesting_user_is_only_shared_user:
before_states = obj.before_states.all().order_by("-surface") messages.info(
after_states = obj.after_states.all().order_by("-surface") request,
DO_NOT_FORGET_TO_SHARE
)
# Precalculate logical errors between before- and after-states context = {
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling "obj": ema,
sum_before_states = obj.get_surface_before_states() "geom_form": geom_form,
sum_after_states = obj.get_surface_after_states() "parcels": parcels,
diff_states = abs(sum_before_states - sum_after_states) "is_entry_shared": is_entry_shared,
"before_states": before_states,
context = { "after_states": after_states,
"before_states": before_states, "sum_before_states": sum_before_states,
"after_states": after_states, "sum_after_states": sum_after_states,
"sum_before_states": sum_before_states, "diff_states": diff_states,
"sum_after_states": sum_after_states, "is_default_member": _user.in_group(DEFAULT_GROUP),
"diff_states": diff_states, "is_zb_member": _user.in_group(ZB_GROUP),
"has_finished_deadlines": obj.get_finished_deadlines().exists(), "is_ets_member": _user.in_group(ETS_GROUP),
} "LANIS_LINK": ema.get_LANIS_link(),
return context 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)
@login_required @login_required

View File

@ -14,8 +14,8 @@ from intervention.views.deduction import NewInterventionDeductionView, EditInter
RemoveInterventionDeductionView RemoveInterventionDeductionView
from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \ from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \
RemoveInterventionDocumentView, EditInterventionDocumentView RemoveInterventionDocumentView, EditInterventionDocumentView
from intervention.views.intervention import new_view, edit_view, remove_view, \ from intervention.views.intervention import new_view, detail_view, edit_view, remove_view, \
InterventionIndexView, InterventionIdentifierGeneratorView, InterventionDetailView InterventionIndexView, InterventionIdentifierGeneratorView
from intervention.views.log import InterventionLogView from intervention.views.log import InterventionLogView
from intervention.views.record import InterventionRecordView from intervention.views.record import InterventionRecordView
from intervention.views.report import InterventionReportView from intervention.views.report import InterventionReportView
@ -29,7 +29,7 @@ urlpatterns = [
path("", InterventionIndexView.as_view(), name="index"), path("", InterventionIndexView.as_view(), name="index"),
path('new/', new_view, name='new'), path('new/', new_view, name='new'),
path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'), path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'),
path('<id>', InterventionDetailView.as_view(), name='detail'), path('<id>', detail_view, name='detail'),
path('<id>/log', InterventionLogView.as_view(), name='log'), path('<id>/log', InterventionLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'), path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', remove_view, name='remove'), path('<id>/remove', remove_view, name='remove'),

View File

@ -27,7 +27,6 @@ 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, \ CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, \
GEOMETRIES_IGNORED_TEMPLATE GEOMETRIES_IGNORED_TEMPLATE
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView
from konova.views.detail import BaseDetailView
class InterventionIndexView(LoginRequiredMixin, BaseIndexView): class InterventionIndexView(LoginRequiredMixin, BaseIndexView):
@ -106,59 +105,78 @@ class InterventionIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGene
_REDIRECT_URL = "intervention:index" _REDIRECT_URL = "intervention:index"
class InterventionDetailView(BaseDetailView): @login_required
_MODEL_CLS = Intervention @any_group_check
_TEMPLATE = "intervention/detail/view.html" @uuid_required
def detail_view(request: HttpRequest, id: str):
""" Renders a detail view for viewing an intervention's data
def _get_object(self, id: str): Args:
""" Returns the intervention request (HttpRequest): The incoming request
id (str): The intervention's id
Args: Returns:
id (str): The intervention's id
Returns: """
obj (Intervention): The intervention template = "intervention/detail/view.html"
"""
# Fetch data, filter out deleted related data # Fetch data, filter out deleted related data
obj = get_object_or_404( intervention = get_object_or_404(
self._MODEL_CLS.objects.select_related( Intervention.objects.select_related(
"geometry", "geometry",
"legal", "legal",
"responsible", "responsible",
).prefetch_related( ).prefetch_related(
"legal__revocations", "legal__revocations",
), ),
id=id, id=id,
deleted=None 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
) )
return obj
def _get_detail_context(self, obj: Intervention): has_payment_without_document = intervention.payments.exists() and not intervention.get_documents()[1].exists()
""" Generate object specific detail context for view
Args: requesting_user_is_only_shared_user = intervention.is_only_shared_with(_user)
obj (): The record if requesting_user_is_only_shared_user:
messages.info(
request,
DO_NOT_FORGET_TO_SHARE
)
Returns: 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)
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
)
has_payment_without_document = obj.payments.exists() and not obj.get_documents()[1].exists() context = BaseContext(request, context).context
context = { return render(request, template, 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 @login_required

View File

@ -5,11 +5,9 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 17.09.21 Created on: 17.09.21
""" """
from uuid import UUID
from django.contrib import messages from django.contrib import messages
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.http import HttpRequest, Http404 from django.http import HttpRequest
def format_german_float(num) -> str: def format_german_float(num) -> str:
@ -40,12 +38,3 @@ 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. +++") _("+++ 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 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,16 +27,12 @@ class BaseView(View):
abstract = True abstract = True
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
request = check_user_is_in_any_group(request)
if not self._user_has_permission(request.user): if not self._user_has_permission(request.user):
messages.info(request, MISSING_GROUP_PERMISSION) messages.info(request, MISSING_GROUP_PERMISSION)
return redirect(reverse(self._REDIRECT_URL_ERROR)) return redirect(reverse(self._REDIRECT_URL_ERROR))
if not self._user_has_shared_access(request.user, **kwargs): if not self._user_has_shared_access(request.user, **kwargs):
messages.info(request, DATA_UNSHARED) messages.info(request, DATA_UNSHARED)
return redirect(reverse(self._REDIRECT_URL_ERROR)) return redirect(reverse(self._REDIRECT_URL_ERROR))
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def _user_has_permission(self, user): def _user_has_permission(self, user):
@ -81,6 +77,10 @@ class BaseIndexView(BaseView):
class Meta: class Meta:
abstract = True 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): def get(self, request: HttpRequest):
qs = self._get_queryset() qs = self._get_queryset()
table = self._INDEX_TABLE_CLS( table = self._INDEX_TABLE_CLS(

View File

@ -1,107 +0,0 @@
"""
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,19 +9,21 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q from django.db.models import Q
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import render from django.shortcuts import render
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View
from compensation.models import EcoAccount, Compensation from compensation.models import EcoAccount, Compensation
from intervention.models import Intervention from intervention.models import Intervention
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import any_group_check
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.views.base import BaseView
from news.models import ServerMessage from news.models import ServerMessage
class HomeView(LoginRequiredMixin, BaseView): class HomeView(LoginRequiredMixin, View):
_TEMPLATE = "konova/home.html"
@method_decorator(any_group_check)
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
""" """
Renders the landing page Renders the landing page
@ -32,6 +34,7 @@ class HomeView(LoginRequiredMixin, BaseView):
Returns: Returns:
A redirect A redirect
""" """
template = "konova/home.html"
user = request.user user = request.user
user_teams = user.shared_teams user_teams = user.shared_teams
@ -72,12 +75,5 @@ class HomeView(LoginRequiredMixin, BaseView):
TAB_TITLE_IDENTIFIER: _("Home"), TAB_TITLE_IDENTIFIER: _("Home"),
} }
context = BaseContext(request, additional_context).context context = BaseContext(request, additional_context).context
return render(request, self._TEMPLATE, context) return render(request, 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

View File

@ -10,8 +10,9 @@ from json import JSONDecodeError
import requests import requests
import urllib3.util import urllib3.util
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.http import JsonResponse, HttpRequest, HttpResponse from django.http import JsonResponse, HttpRequest, HttpResponse
from django.utils.decorators import method_decorator
from django.utils.http import urlencode from django.utils.http import urlencode
from django.views import View from django.views import View
@ -21,13 +22,17 @@ from konova.sub_settings.lanis_settings import MAP_PROXY_HOST_WHITELIST
from konova.sub_settings.proxy_settings import PROXIES, GEOPORTAL_RLP_USER, GEOPORTAL_RLP_PASSWORD from konova.sub_settings.proxy_settings import PROXIES, GEOPORTAL_RLP_USER, GEOPORTAL_RLP_PASSWORD
class BaseClientProxyView(LoginRequiredMixin, View): class BaseClientProxyView(View):
""" Provides proxy functionality for NETGIS map client. """ Provides proxy functionality for NETGIS map client.
""" """
class Meta: class Meta:
abstract = True abstract = True
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def _check_with_whitelist(self, url): def _check_with_whitelist(self, url):
parsed_url = urllib3.util.parse_url(url) parsed_url = urllib3.util.parse_url(url)
parsed_url_host = parsed_url.host parsed_url_host = parsed_url.host
@ -62,6 +67,7 @@ class BaseClientProxyView(LoginRequiredMixin, View):
class ClientProxyParcelSearch(BaseClientProxyView): class ClientProxyParcelSearch(BaseClientProxyView):
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
url = request.META.get("QUERY_STRING") url = request.META.get("QUERY_STRING")