Compare commits

..

6 Commits

Author SHA1 Message Date
210f3fcafa Merge pull request '# HOTFIX' (#495) from update_map_config into master
Reviewed-on: #495
2025-10-23 16:12:45 +02:00
e7d67560f2 # HOTFIX
* fixes bug on netgis map client where importing a geometry would result in an error message
* THIS IS JUST A WORKAROUND AND HAS TO BE REPLACED BY A PROPER FIX FROM THE DEVS ASAP
2025-10-23 16:12:05 +02:00
b5e991fb95 Merge pull request '# Map layer update' (#494) from update_map_config into master
Reviewed-on: #494
2025-10-22 16:02:02 +02:00
d3a555d406 # Map layer update
* updates rp_dop layer from deprecated to newest
2025-10-22 16:00:35 +02:00
9a374f50de Merge pull request '# CSS bugfix' (#492) from fix_css_map_menu_bar_overlap into master
Reviewed-on: #492
2025-10-21 15:59:37 +02:00
ce63dd30bc # CSS bugfix
* fixes a rendering bug where the menu bar of the map client overlapped modal forms
2025-10-21 15:57:25 +02:00
104 changed files with 11368 additions and 3252 deletions

0
codelist/views.py Normal file
View File

View File

@ -168,17 +168,6 @@ class NewCompensationForm(AbstractCompensationForm,
comp.log.add(action) comp.log.add(action)
return comp, action return comp, action
def is_valid(self):
valid = super().is_valid()
intervention = self.cleaned_data.get("intervention", None)
if intervention.is_recorded:
valid &= False
self.add_error(
"intervention",
_("This intervention is currently recorded. You cannot add further compensations as long as it is recorded.")
)
return valid
def save(self, user: User, geom_form: SimpleGeomForm): def save(self, user: User, geom_form: SimpleGeomForm):
with transaction.atomic(): with transaction.atomic():
comp, action = self.__create_comp(user) comp, action = self.__create_comp(user)

View File

@ -7,12 +7,10 @@ Created on: 18.08.22
""" """
from dal import autocomplete from dal import autocomplete
from django import forms from django import forms
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from codelist.models import KonovaCode from codelist.models import KonovaCode
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID
from compensation.models import CompensationAction
from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple
from konova.forms.modals import BaseModalForm, RemoveModalForm from konova.forms.modals import BaseModalForm, RemoveModalForm
from konova.utils.message_templates import COMPENSATION_ACTION_EDITED, ADDED_COMPENSATION_ACTION from konova.utils.message_templates import COMPENSATION_ACTION_EDITED, ADDED_COMPENSATION_ACTION
@ -116,8 +114,7 @@ class EditCompensationActionModalForm(NewCompensationActionModalForm):
action = None action = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
action_id = kwargs.pop("action_id", None) self.action = kwargs.pop("action", None)
self.action = get_object_or_404(CompensationAction, id=action_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit action") self.form_title = _("Edit action")
form_data = { form_data = {
@ -150,8 +147,8 @@ class RemoveCompensationActionModalForm(RemoveModalForm):
action = None action = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
action_id = kwargs.pop("action_id", None) action = kwargs.pop("action", None)
self.action = get_object_or_404(CompensationAction, id=action_id) self.action = action
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):

View File

@ -6,11 +6,10 @@ Created on: 18.08.22
""" """
from django import forms from django import forms
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from konova.forms.modals import BaseModalForm from konova.forms.modals import BaseModalForm
from konova.models import DeadlineType, Deadline from konova.models import DeadlineType
from konova.utils import validators from konova.utils import validators
from konova.utils.message_templates import DEADLINE_EDITED from konova.utils.message_templates import DEADLINE_EDITED
@ -91,8 +90,7 @@ class EditDeadlineModalForm(NewDeadlineModalForm):
deadline = None deadline = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
deadline_id = kwargs.pop("deadline_id", None) self.deadline = kwargs.pop("deadline", None)
self.deadline = get_object_or_404(Deadline, id=deadline_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit deadline") self.form_title = _("Edit deadline")
form_data = { form_data = {

View File

@ -6,27 +6,12 @@ Created on: 18.08.22
""" """
from compensation.models import CompensationDocument, EcoAccountDocument from compensation.models import CompensationDocument, EcoAccountDocument
from konova.forms.modals import NewDocumentModalForm, EditDocumentModalForm, RemoveDocumentModalForm from konova.forms.modals import NewDocumentModalForm
class NewCompensationDocumentModalForm(NewDocumentModalForm): class NewCompensationDocumentModalForm(NewDocumentModalForm):
_DOCUMENT_CLS = CompensationDocument document_model = CompensationDocument
class EditCompensationDocumentModalForm(EditDocumentModalForm):
_DOCUMENT_CLS = CompensationDocument
class RemoveCompensationDocumentModalForm(RemoveDocumentModalForm):
_DOCUMENT_CLS = CompensationDocument
class NewEcoAccountDocumentModalForm(NewDocumentModalForm): class NewEcoAccountDocumentModalForm(NewDocumentModalForm):
_DOCUMENT_CLS = EcoAccountDocument document_model = EcoAccountDocument
class EditEcoAccountDocumentModalForm(EditDocumentModalForm):
_DOCUMENT_CLS = EcoAccountDocument
class RemoveEcoAccountDocumentModalForm(RemoveDocumentModalForm):
_DOCUMENT_CLS = EcoAccountDocument

View File

@ -6,10 +6,8 @@ Created on: 18.08.22
""" """
from django import forms from django import forms
from django.shortcuts import get_object_or_404
from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _ from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _
from compensation.models import Payment
from konova.forms.modals import RemoveModalForm, BaseModalForm from konova.forms.modals import RemoveModalForm, BaseModalForm
from konova.utils import validators from konova.utils import validators
from konova.utils.message_templates import PAYMENT_EDITED from konova.utils.message_templates import PAYMENT_EDITED
@ -105,8 +103,7 @@ class EditPaymentModalForm(NewPaymentForm):
payment = None payment = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
payment_id = kwargs.pop("payment_id", None) self.payment = kwargs.pop("payment", None)
self.payment = get_object_or_404(Payment, id=payment_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit payment") self.form_title = _("Edit payment")
form_date = { form_date = {
@ -136,8 +133,8 @@ class RemovePaymentModalForm(RemoveModalForm):
payment = None payment = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
payment_id = kwargs.pop("payment_id", None) payment = kwargs.pop("payment", None)
self.payment = get_object_or_404(Payment, id=payment_id) self.payment = payment
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):

View File

@ -1,15 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 21.10.25
"""
from compensation.models import Compensation, EcoAccount
from konova.forms.modals import ResubmissionModalForm
class CompensationResubmissionModalForm(ResubmissionModalForm):
_MODEL_CLS = Compensation
class EcoAccountResubmissionModalForm(ResubmissionModalForm):
_MODEL_CLS = EcoAccount

View File

@ -5,17 +5,21 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 18.08.22 Created on: 18.08.22
""" """
from bootstrap_modal_forms.mixins import is_ajax
from dal import autocomplete from dal import autocomplete
from django import forms from django import forms
from django.contrib import messages
from django.http import HttpResponseRedirect, HttpRequest
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from codelist.models import KonovaCode from codelist.models import KonovaCode
from codelist.settings import CODELIST_BIOTOPES_ID, \ from codelist.settings import CODELIST_BIOTOPES_ID, \
CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID
from compensation.models import CompensationState
from intervention.inputs import CompensationStateTreeRadioSelect from intervention.inputs import CompensationStateTreeRadioSelect
from konova.contexts import BaseContext
from konova.forms.modals import RemoveModalForm, BaseModalForm from konova.forms.modals import RemoveModalForm, BaseModalForm
from konova.utils.message_templates import COMPENSATION_STATE_EDITED, ADDED_COMPENSATION_STATE from konova.utils.message_templates import COMPENSATION_STATE_EDITED, FORM_INVALID, ADDED_COMPENSATION_STATE
class NewCompensationStateModalForm(BaseModalForm): class NewCompensationStateModalForm(BaseModalForm):
@ -64,13 +68,10 @@ class NewCompensationStateModalForm(BaseModalForm):
) )
) )
_is_before_state: bool = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("New state") self.form_title = _("New state")
self.form_caption = _("Insert data for the new state") self.form_caption = _("Insert data for the new state")
self._is_before_state = bool(self.request.GET.get("before", False))
choices = KonovaCode.objects.filter( choices = KonovaCode.objects.filter(
code_lists__in=[CODELIST_BIOTOPES_ID], code_lists__in=[CODELIST_BIOTOPES_ID],
is_archived=False, is_archived=False,
@ -82,19 +83,65 @@ class NewCompensationStateModalForm(BaseModalForm):
] ]
self.fields["biotope_type"].choices = choices self.fields["biotope_type"].choices = choices
def save(self): def save(self, is_before_state: bool = False):
state = self.instance.add_state(self, self._is_before_state) state = self.instance.add_state(self, is_before_state)
self.instance.mark_as_edited(self.user, self.request, ADDED_COMPENSATION_STATE) self.instance.mark_as_edited(self.user, self.request, ADDED_COMPENSATION_STATE)
return state return state
def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
""" Generic processing of request
Wraps the request processing logic, so we don't need the same code everywhere a RemoveModalForm is being used
+++
The generic method from super class can not be used, since we need to do some request parameter check in here.
+++
Args:
request (HttpRequest): The incoming request
msg_success (str): The message in case of successful removing
msg_error (str): The message in case of an error
Returns:
"""
redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
template = self.template
if request.method == "POST":
if self.is_valid():
# Modal forms send one POST for checking on data validity. This can be used to return possible errors
# on the form. A second POST (if no errors occured) is sent afterwards and needs to process the
# saving/commiting of the data to the database. is_ajax() performs this check. The first request is
# an ajax call, the second is a regular form POST.
if not is_ajax(request.META):
is_before_state = bool(request.GET.get("before", False))
self.save(is_before_state=is_before_state)
messages.success(
request,
msg_success
)
return HttpResponseRedirect(redirect_url)
else:
context = {
"form": self,
}
context = BaseContext(request, context).context
return render(request, template, context)
elif request.method == "GET":
context = {
"form": self,
}
context = BaseContext(request, context).context
return render(request, template, context)
else:
raise NotImplementedError
class EditCompensationStateModalForm(NewCompensationStateModalForm): class EditCompensationStateModalForm(NewCompensationStateModalForm):
state = None state = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
state_id = kwargs.pop("state_id", None) self.state = kwargs.pop("state", None)
self.state = CompensationState.objects.get(id=state_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit state") self.form_title = _("Edit state")
biotope_type_id = self.state.biotope_type.id if self.state.biotope_type else None biotope_type_id = self.state.biotope_type.id if self.state.biotope_type else None
@ -125,8 +172,8 @@ class RemoveCompensationStateModalForm(RemoveModalForm):
state = None state = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
state_id = kwargs.pop("state_id", None) state = kwargs.pop("state", None)
self.state = CompensationState.objects.get(id=state_id) self.state = state
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):

View File

@ -80,11 +80,7 @@ class EditCompensationActionModalFormTestCase(NewCompensationActionModalFormTest
self.compensation.actions.add(self.comp_action) self.compensation.actions.add(self.comp_action)
def test_init(self): def test_init(self):
form = EditCompensationActionModalForm( form = EditCompensationActionModalForm(request=self.request, instance=self.compensation, action=self.comp_action)
request=self.request,
instance=self.compensation,
action_id=self.comp_action.id
)
self.assertEqual(form.form_title, str(_("Edit action"))) self.assertEqual(form.form_title, str(_("Edit action")))
self.assertEqual(len(form.fields["action_type"].initial), self.comp_action.action_type.count()) self.assertEqual(len(form.fields["action_type"].initial), self.comp_action.action_type.count())
self.assertEqual(len(form.fields["action_type_details"].initial), self.comp_action.action_type_details.count()) self.assertEqual(len(form.fields["action_type_details"].initial), self.comp_action.action_type_details.count())
@ -105,7 +101,7 @@ class EditCompensationActionModalFormTestCase(NewCompensationActionModalFormTest
"comment": comment, "comment": comment,
} }
form = EditCompensationActionModalForm(data, request=self.request, instance=self.compensation, action_id=self.comp_action.id) form = EditCompensationActionModalForm(data, request=self.request, instance=self.compensation, action=self.comp_action)
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
action = form.save() action = form.save()
@ -130,7 +126,7 @@ class RemoveCompensationActionModalFormTestCase(EditCompensationActionModalFormT
def test_init(self): def test_init(self):
self.assertIn(self.comp_action, self.compensation.actions.all()) self.assertIn(self.comp_action, self.compensation.actions.all())
form = RemoveCompensationActionModalForm(request=self.request, instance=self.compensation, action_id=self.comp_action.id) form = RemoveCompensationActionModalForm(request=self.request, instance=self.compensation, action=self.comp_action)
self.assertEqual(form.action, self.comp_action) self.assertEqual(form.action, self.comp_action)
def test_save(self): def test_save(self):
@ -141,7 +137,7 @@ class RemoveCompensationActionModalFormTestCase(EditCompensationActionModalFormT
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
action_id=self.comp_action.id action=self.comp_action
) )
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
self.assertIn(self.comp_action, self.compensation.actions.all()) self.assertIn(self.comp_action, self.compensation.actions.all())
@ -190,20 +186,12 @@ class NewCompensationStateModalFormTestCase(BaseTestCase):
self.assertEqual(self.compensation.before_states.count(), 0) self.assertEqual(self.compensation.before_states.count(), 0)
self.assertEqual(self.compensation.after_states.count(), 0) self.assertEqual(self.compensation.after_states.count(), 0)
self.request.GET._mutable = True form = NewCompensationStateModalForm(data, request=self.request, instance=self.compensation)
self.request.GET.update(
{
"before": True,
}
)
self.request.GET._mutable = False
form = NewCompensationStateModalForm(
data,
request=self.request,
instance=self.compensation,
)
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
state = form.save()
is_before_state = True
state = form.save(is_before_state)
self.assertEqual(self.compensation.before_states.count(), 1) self.assertEqual(self.compensation.before_states.count(), 1)
self.assertEqual(self.compensation.after_states.count(), 0) self.assertEqual(self.compensation.after_states.count(), 0)
@ -217,16 +205,8 @@ class NewCompensationStateModalFormTestCase(BaseTestCase):
self.assertEqual(last_log.action, UserAction.EDITED) self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.comment, ADDED_COMPENSATION_STATE) self.assertEqual(last_log.comment, ADDED_COMPENSATION_STATE)
self.request.GET._mutable = True is_before_state = False
del self.request.GET["before"] state = form.save(is_before_state)
self.request.GET._mutable = False
form = NewCompensationStateModalForm(
data,
request=self.request,
instance=self.compensation,
)
self.assertTrue(form.is_valid(), msg=form.errors)
state = form.save()
self.assertEqual(self.compensation.before_states.count(), 1) self.assertEqual(self.compensation.before_states.count(), 1)
self.assertEqual(self.compensation.after_states.count(), 1) self.assertEqual(self.compensation.after_states.count(), 1)
@ -250,11 +230,7 @@ class EditCompensationStateModalFormTestCase(NewCompensationStateModalFormTestCa
self.compensation.after_states.add(self.comp_state) self.compensation.after_states.add(self.comp_state)
def test_init(self): def test_init(self):
form = EditCompensationStateModalForm( form = EditCompensationStateModalForm(request=self.request, instance=self.compensation, state=self.comp_state)
request=self.request,
instance=self.compensation,
state_id=self.comp_state.id
)
self.assertEqual(form.state, self.comp_state) self.assertEqual(form.state, self.comp_state)
self.assertEqual(form.form_title, str(_("Edit state"))) self.assertEqual(form.form_title, str(_("Edit state")))
@ -285,7 +261,7 @@ class EditCompensationStateModalFormTestCase(NewCompensationStateModalFormTestCa
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
state_id=self.comp_state.id state=self.comp_state
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
@ -306,11 +282,7 @@ class RemoveCompensationStateModalFormTestCase(EditCompensationStateModalFormTes
super().setUp() super().setUp()
def test_init(self): def test_init(self):
form = RemoveCompensationStateModalForm( form = RemoveCompensationStateModalForm(request=self.request, instance=self.compensation, state=self.comp_state)
request=self.request,
instance=self.compensation,
state_id=self.comp_state.id
)
self.assertEqual(form.state, self.comp_state) self.assertEqual(form.state, self.comp_state)
@ -322,7 +294,7 @@ class RemoveCompensationStateModalFormTestCase(EditCompensationStateModalFormTes
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
state_id=self.comp_state.id state=self.comp_state
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)

View File

@ -36,7 +36,7 @@ class AbstractCompensationModelTestCase(BaseTestCase):
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
deadline_id=self.finished_deadline.id, deadline=self.finished_deadline,
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
self.assertIn(self.finished_deadline, self.compensation.deadlines.all()) self.assertIn(self.finished_deadline, self.compensation.deadlines.all())

View File

@ -10,28 +10,27 @@ from django.urls import path
from compensation.views.compensation.document import EditCompensationDocumentView, NewCompensationDocumentView, \ from compensation.views.compensation.document import EditCompensationDocumentView, NewCompensationDocumentView, \
GetCompensationDocumentView, RemoveCompensationDocumentView GetCompensationDocumentView, RemoveCompensationDocumentView
from compensation.views.compensation.resubmission import CompensationResubmissionView from compensation.views.compensation.resubmission import CompensationResubmissionView
from compensation.views.compensation.report import CompensationReportView from compensation.views.compensation.report import report_view
from compensation.views.compensation.deadline import NewCompensationDeadlineView, EditCompensationDeadlineView, \ from compensation.views.compensation.deadline import NewCompensationDeadlineView, EditCompensationDeadlineView, \
RemoveCompensationDeadlineView RemoveCompensationDeadlineView
from compensation.views.compensation.action import NewCompensationActionView, EditCompensationActionView, \ from compensation.views.compensation.action import NewCompensationActionView, EditCompensationActionView, \
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 \ from compensation.views.compensation.compensation import index_view, new_view, new_id_view, detail_view, edit_view, \
CompensationIndexView, CompensationIdentifierGeneratorView, CompensationDetailView, \ remove_view
NewCompensationFormView, EditCompensationFormView, RemoveCompensationView
from compensation.views.compensation.log import CompensationLogView from compensation.views.compensation.log import CompensationLogView
urlpatterns = [ urlpatterns = [
# Main compensation # Main compensation
path("", CompensationIndexView.as_view(), name="index"), path("", index_view, name="index"),
path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'), path('new/id', new_id_view, name='new-id'),
path('new/<intervention_id>', NewCompensationFormView.as_view(), name='new'), path('new/<intervention_id>', new_view, name='new'),
path('new', NewCompensationFormView.as_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', EditCompensationFormView.as_view(), name='edit'), path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', RemoveCompensationView.as_view(), name='remove'), path('<id>/remove', remove_view, name='remove'),
path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'), path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'),
path('<id>/state/<state_id>/edit', EditCompensationStateView.as_view(), name='state-edit'), path('<id>/state/<state_id>/edit', EditCompensationStateView.as_view(), name='state-edit'),
@ -44,7 +43,7 @@ urlpatterns = [
path('<id>/deadline/new', NewCompensationDeadlineView.as_view(), name="new-deadline"), path('<id>/deadline/new', NewCompensationDeadlineView.as_view(), name="new-deadline"),
path('<id>/deadline/<deadline_id>/edit', EditCompensationDeadlineView.as_view(), name='deadline-edit'), path('<id>/deadline/<deadline_id>/edit', EditCompensationDeadlineView.as_view(), name='deadline-edit'),
path('<id>/deadline/<deadline_id>/remove', RemoveCompensationDeadlineView.as_view(), name='deadline-remove'), path('<id>/deadline/<deadline_id>/remove', RemoveCompensationDeadlineView.as_view(), name='deadline-remove'),
path('<id>/report', CompensationReportView.as_view(), name='report'), path('<id>/report', report_view, name='report'),
path('<id>/resub', CompensationResubmissionView.as_view(), name='resubmission-create'), path('<id>/resub', CompensationResubmissionView.as_view(), name='resubmission-create'),
# Documents # Documents

View File

@ -8,11 +8,11 @@ Created on: 24.08.21
from django.urls import path 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 EcoAccountIndexView, EcoAccountIdentifierGeneratorView, \ from compensation.views.eco_account.eco_account import index_view, new_view, new_id_view, edit_view, remove_view, \
EcoAccountDetailView, NewEcoAccountFormView, EditEcoAccountFormView, RemoveEcoAccountView detail_view
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 report_view
from compensation.views.eco_account.resubmission import EcoAccountResubmissionView from compensation.views.eco_account.resubmission import EcoAccountResubmissionView
from compensation.views.eco_account.state import NewEcoAccountStateView, EditEcoAccountStateView, \ from compensation.views.eco_account.state import NewEcoAccountStateView, EditEcoAccountStateView, \
RemoveEcoAccountStateView RemoveEcoAccountStateView
@ -28,15 +28,15 @@ from compensation.views.eco_account.deduction import NewEcoAccountDeductionView,
app_name = "acc" app_name = "acc"
urlpatterns = [ urlpatterns = [
path("", EcoAccountIndexView.as_view(), name="index"), path("", index_view, name="index"),
path('new/', NewEcoAccountFormView.as_view(), name='new'), path('new/', new_view, name='new'),
path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'), path('new/id', new_id_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', report_view, name='report'),
path('<id>/edit', EditEcoAccountFormView.as_view(), name='edit'), path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', RemoveEcoAccountView.as_view(), name='remove'), path('<id>/remove', remove_view, name='remove'),
path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'), path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'),
path('<id>/state/new', NewEcoAccountStateView.as_view(), name='new-state'), path('<id>/state/new', NewEcoAccountStateView.as_view(), name='new-state'),

View File

@ -6,11 +6,11 @@ Created on: 24.08.21
""" """
from django.urls import path from django.urls import path
from compensation.views.payment import NewPaymentView, RemovePaymentView, EditPaymentView from compensation.views.payment import *
app_name = "pay" app_name = "pay"
urlpatterns = [ urlpatterns = [
path('<id>/new', NewPaymentView.as_view(), name='new'), path('<id>/new', new_payment_view, name='new'),
path('<id>/remove/<payment_id>', RemovePaymentView.as_view(), name='remove'), path('<id>/remove/<payment_id>', payment_remove_view, name='remove'),
path('<id>/edit/<payment_id>', EditPaymentView.as_view(), name='edit'), path('<id>/edit/<payment_id>', payment_edit_view, name='edit'),
] ]

View File

@ -5,23 +5,53 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.decorators import method_decorator
from compensation.models import Compensation from compensation.forms.modals.compensation_action import RemoveCompensationActionModalForm, \
EditCompensationActionModalForm, NewCompensationActionModalForm
from compensation.models import Compensation, CompensationAction
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.utils.message_templates import COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_EDITED, \
COMPENSATION_ACTION_ADDED
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \ from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView AbstractRemoveCompensationActionView
_COMPENSATION_DETAIL_URL_NAME = "compensation:detail"
class NewCompensationActionView(AbstractNewCompensationActionView): class NewCompensationActionView(AbstractNewCompensationActionView):
_MODEL_CLS = Compensation model = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationActionView(AbstractEditCompensationActionView): class EditCompensationActionView(AbstractEditCompensationActionView):
_MODEL_CLS = Compensation model = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationActionView(AbstractRemoveCompensationActionView): class RemoveCompensationActionView(AbstractRemoveCompensationActionView):
_MODEL_CLS = Compensation model = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -7,8 +7,8 @@ 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.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Sum
from django.http import HttpRequest, JsonResponse from django.http import HttpRequest, JsonResponse
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.urls import reverse from django.urls import reverse
@ -18,157 +18,300 @@ from compensation.forms.compensation import EditCompensationForm, NewCompensatio
from compensation.models import Compensation 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.decorators import shared_access_required, default_group_required, login_required_modal 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.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.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, PARAMS_INVALID RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, IDENTIFIER_REPLACED, \
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \ COMPENSATION_ADDED_TEMPLATE, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class CompensationIndexView(LoginRequiredMixin, BaseIndexView): @login_required
_TAB_TITLE = _("Compensations - Overview") @any_group_check
_INDEX_TABLE_CLS = CompensationTable def index_view(request: HttpRequest):
"""
Renders the index view for compensation
def _get_queryset(self): Args:
qs = Compensation.objects.filter( request (HttpRequest): The incoming request
deleted=None, # only show those which are not deleted individually
intervention__deleted=None, # and don't show the ones whose intervention has been deleted Returns:
).order_by( A rendered view
"-modified__timestamp" """
) template = "generic_index.html"
return qs compensations = Compensation.objects.filter(
deleted=None, # only show those which are not deleted individually
intervention__deleted=None, # and don't show the ones whose intervention has been deleted
).order_by(
"-modified__timestamp"
)
table = CompensationTable(
request=request,
queryset=compensations
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("Compensations - Overview"),
}
context = BaseContext(request, context).context
return render(request, template, context)
class NewCompensationFormView(BaseNewSpatialLocatedObjectFormView): @login_required
_FORM_CLS = NewCompensationForm @default_group_required
_MODEL_CLS = Compensation @shared_access_required(Intervention, "intervention_id")
_TEMPLATE = "compensation/form/view.html" def new_view(request: HttpRequest, intervention_id: str = None):
_TAB_TITLE = _("New Compensation") """
_REDIRECT_URL = "compensation:detail" Renders a view for a new compensation creation
def _user_has_shared_access(self, user, **kwargs): Args:
# On a new compensation make sure the intervention (if call came directly through an intervention's detail request (HttpRequest): The incoming request
# view) is shared with the user
intervention_id = kwargs.get("intervention_id", None)
if not intervention_id:
return True
else:
intervention = get_object_or_404(Intervention, id=intervention_id)
return intervention.is_shared_with(user)
def _user_has_permission(self, user, **kwargs): Returns:
# User has to be an ets user
return user.is_default_user()
def dispatch(self, request, *args, **kwargs): """
# Make sure there is an existing intervention based on the given id template = "compensation/form/view.html"
# Compensations can not exist without an intervention if intervention_id is not None:
intervention_id = kwargs.get("intervention_id", None) try:
if intervention_id: intervention = Intervention.objects.get(id=intervention_id)
try: except ObjectDoesNotExist:
intervention = Intervention.objects.get(id=intervention_id) messages.error(request, PARAMS_INVALID)
if intervention.is_recorded: return redirect("home")
messages.info( if intervention.is_recorded:
request, messages.info(
RECORDED_BLOCKS_EDIT request,
) RECORDED_BLOCKS_EDIT
return redirect("intervention:detail", id=intervention_id)
except ObjectDoesNotExist:
messages.error(request, PARAMS_INVALID, extra_tags="danger")
return redirect("home")
return super().dispatch(request, *args, **kwargs)
class EditCompensationFormView(BaseEditSpatialLocatedObjectFormView):
_MODEL_CLS = Compensation
_FORM_CLS = EditCompensationForm
_TEMPLATE = "compensation/form/view.html"
_REDIRECT_URL = "compensation:detail"
def _user_has_permission(self, user, **kwargs):
# User has to be a default user
return user.is_default_user()
class CompensationIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView):
_MODEL_CLS = Compensation
_REDIRECT_URL = "compensation:index"
class CompensationDetailView(BaseDetailView):
_MODEL_CLS = Compensation
_TEMPLATE = "compensation/detail/compensation/view.html"
def _get_object(self, id: str):
""" Returns the compensation
Args:
id (str): The compensation's id
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
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
) )
return redirect("intervention:detail", id=intervention_id)
context = { data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
"last_checked": last_checked, geom_form = SimpleGeomForm(request.POST or None, read_only=False)
"last_checked_tooltip": last_checked_tooltip, if request.method == "POST":
"actions": actions, if data_form.is_valid() and geom_form.is_valid():
"before_states": before_states, generated_identifier = data_form.cleaned_data.get("identifier", None)
"after_states": after_states, comp = data_form.save(request.user, geom_form)
"sum_before_states": sum_before_states, if generated_identifier != comp.identifier:
"sum_after_states": sum_after_states, messages.info(
"diff_states": diff_states, request,
"has_finished_deadlines": obj.get_finished_deadlines().exists(), IDENTIFIER_REPLACED.format(
generated_identifier,
comp.identifier
)
)
messages.success(request, COMPENSATION_ADDED_TEMPLATE.format(comp.identifier))
if geom_form.has_geometry_simplified():
messages.info(
request,
GEOMETRY_SIMPLIFIED
)
num_ignored_geometries = geom_form.get_num_geometries_ignored()
if num_ignored_geometries > 0:
messages.info(
request,
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
)
return redirect("compensation:detail", id=comp.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New compensation"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
def new_id_view(request: HttpRequest):
""" JSON endpoint
Provides fetching of free identifiers for e.g. AJAX calls
"""
tmp = Compensation()
identifier = tmp.generate_new_identifier()
while Compensation.objects.filter(identifier=identifier).exists():
identifier = tmp.generate_new_identifier()
return JsonResponse(
data={
"gen_data": identifier
} }
return context )
class RemoveCompensationView(LoginRequiredMixin, BaseRemoveModalFormView): @login_required
_MODEL_CLS = Compensation @default_group_required
_FORM_CLS = RemoveModalForm @shared_access_required(Compensation, "id")
_REDIRECT_URL = "compensation:index" def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
# Get object from db
comp = get_object_or_404(Compensation, id=id)
if comp.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("compensation:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditCompensationForm(request.POST or None, instance=comp)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
# Preserve state of intervention checked to determine whether the user must be informed or not
# about a change of the check state
intervention_is_checked = comp.intervention.checked is not None
# The data form takes the geom form for processing, as well as the performing user
comp = data_form.save(request.user, geom_form)
if intervention_is_checked:
messages.info(request, CHECK_STATE_RESET)
messages.success(request, _("Compensation {} edited").format(comp.identifier))
if geom_form.has_geometry_simplified():
messages.info(
request,
GEOMETRY_SIMPLIFIED
)
num_ignored_geometries = geom_form.get_num_geometries_ignored()
if num_ignored_geometries > 0:
messages.info(
request,
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
)
return redirect("compensation:detail", id=comp.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@any_group_check
@uuid_required
def detail_view(request: HttpRequest, id: str):
""" Renders a detail view for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
template = "compensation/detail/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
)
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)
@login_required_modal
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
comp = get_object_or_404(Compensation, id=id)
form = RemoveModalForm(request.POST or None, instance=comp, request=request)
return form.process_request(
request=request,
msg_success=COMPENSATION_REMOVED_TEMPLATE.format(comp.identifier),
redirect_url=reverse("compensation:index"),
)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()

View File

@ -5,21 +5,45 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import Compensation from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.deadline import AbstractRemoveDeadlineView, AbstractEditDeadlineView, AbstractNewDeadlineView from konova.views.deadline import AbstractRemoveDeadlineView, AbstractEditDeadlineView, AbstractNewDeadlineView
_COMPENSATION_DETAIL_URL_NAME = "compensation:detail"
class NewCompensationDeadlineView(AbstractNewDeadlineView): class NewCompensationDeadlineView(AbstractNewDeadlineView):
_MODEL_CLS = Compensation model = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationDeadlineView(AbstractEditDeadlineView): class EditCompensationDeadlineView(AbstractEditDeadlineView):
_MODEL_CLS = Compensation model = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationDeadlineView(AbstractRemoveDeadlineView): class RemoveCompensationDeadlineView(AbstractRemoveDeadlineView):
_MODEL_CLS = Compensation model = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,33 +5,62 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from compensation.forms.modals.document import NewCompensationDocumentModalForm, EditCompensationDocumentModalForm, \ from django.contrib.auth.decorators import login_required
RemoveCompensationDocumentModalForm from django.utils.decorators import method_decorator
from compensation.forms.modals.document import NewCompensationDocumentModalForm
from compensation.models import Compensation, CompensationDocument from compensation.models import Compensation, CompensationDocument
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.forms.modals import EditDocumentModalForm
from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \ from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \
AbstractEditDocumentView AbstractEditDocumentView
class NewCompensationDocumentView(AbstractNewDocumentView): class NewCompensationDocumentView(AbstractNewDocumentView):
_MODEL_CLS = Compensation model = Compensation
_FORM_CLS = NewCompensationDocumentModalForm form = NewCompensationDocumentModalForm
_REDIRECT_URL = "compensation:detail" redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class GetCompensationDocumentView(AbstractGetDocumentView): class GetCompensationDocumentView(AbstractGetDocumentView):
_MODEL_CLS = Compensation model = Compensation
_DOCUMENT_CLS = CompensationDocument document_model = CompensationDocument
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationDocumentView(AbstractRemoveDocumentView): class RemoveCompensationDocumentView(AbstractRemoveDocumentView):
_MODEL_CLS = Compensation model = Compensation
_DOCUMENT_CLS = CompensationDocument document_model = CompensationDocument
_FORM_CLS = RemoveCompensationDocumentModalForm
_REDIRECT_URL = "compensation:detail" @method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationDocumentView(AbstractEditDocumentView): class EditCompensationDocumentView(AbstractEditDocumentView):
_MODEL_CLS = Compensation model = Compensation
_DOCUMENT_CLS = CompensationDocument document_model = CompensationDocument
_FORM_CLS = EditCompensationDocumentModalForm form = EditDocumentModalForm
_REDIRECT_URL = "compensation:detail" redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,11 +5,20 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import Compensation from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.log import AbstractLogView from konova.views.log import AbstractLogView
class CompensationLogView(LoginRequiredMixin, AbstractLogView): class CompensationLogView(AbstractLogView):
_MODEL_CLS = Compensation model = Compensation
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,48 +5,77 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.http import HttpRequest
from django.shortcuts import get_object_or_404, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.models import Compensation from compensation.models import Compensation
from konova.sub_settings.django_settings import BASE_URL from konova.contexts import BaseContext
from konova.utils.qrcode import QrCode from konova.decorators import uuid_required
from konova.views.report import BaseReportView from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.generators import generate_qr_code
@uuid_required
def report_view(request: HttpRequest, id: str):
""" Renders the public report view
class BaseCompensationReportView(BaseReportView): Args:
def _get_compensation_report_context(self, obj): request (HttpRequest): The incoming request
# Order states by surface id (str): The id of the intervention
before_states = obj.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = obj.after_states.all().order_by("-surface").prefetch_related("biotope_type")
actions = obj.actions.all().prefetch_related("action_type")
return { Returns:
"before_states": before_states,
"after_states": after_states, """
"actions": actions, # Reuse the compensation report template since compensations are structurally identical
template = "compensation/report/compensation/report.html"
comp = get_object_or_404(Compensation, id=id)
tab_title = _("Report {}").format(comp.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not comp.is_ready_for_publish():
template = "report/unavailable.html"
context = {
TAB_TITLE_IDENTIFIER: tab_title,
} }
context = BaseContext(request, context).context
return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=comp
)
parcels = comp.get_underlying_parcels()
class CompensationReportView(BaseCompensationReportView): qrcode_url = request.build_absolute_uri(reverse("compensation:report", args=(id,)))
_MODEL = Compensation qrcode_img = generate_qr_code(qrcode_url, 10)
_TEMPLATE = "compensation/report/compensation/report.html" qrcode_lanis_url = comp.get_LANIS_link()
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
def _get_report_context(self, obj): # Order states by surface
report_url = BASE_URL + reverse("compensation:report", args=(obj.id,)) before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type")
qrcode_report = QrCode(report_url, 10) after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type")
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7) actions = comp.actions.all().prefetch_related("action_type")
report_context = { context = {
"qrcode": { "obj": comp,
"img": qrcode_report.get_img(), "qrcode": {
"url": qrcode_report.get_content(), "img": qrcode_img,
}, "url": qrcode_url,
"qrcode_lanis": { },
"img": qrcode_lanis.get_img(), "qrcode_lanis": {
"url": qrcode_lanis.get_content(), "img": qrcode_img_lanis,
}, "url": qrcode_lanis_url,
"is_entry_shared": False, # disables action buttons during rendering },
"tables_scrollable": False, "is_entry_shared": False, # disables action buttons during rendering
} "before_states": before_states,
report_context.update(self._get_compensation_report_context(obj)) "after_states": after_states,
return report_context "geom_form": geom_form,
"parcels": parcels,
"actions": actions,
"tables_scrollable": False,
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)

View File

@ -5,12 +5,22 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from compensation.forms.modals.resubmission import CompensationResubmissionModalForm from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import Compensation from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView from konova.views.resubmission import AbstractResubmissionView
class CompensationResubmissionView(AbstractResubmissionView): class CompensationResubmissionView(AbstractResubmissionView):
_MODEL_CLS = Compensation model = Compensation
_FORM_CLS = CompensationResubmissionModalForm redirect_url_base = "compensation:detail"
_REDIRECT_URL = "compensation:detail" form_action_url_base = "compensation:resubmission-create"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,21 +5,46 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import Compensation from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \ from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \
AbstractRemoveCompensationStateView AbstractRemoveCompensationStateView
class NewCompensationStateView(AbstractNewCompensationStateView): class NewCompensationStateView(AbstractNewCompensationStateView):
_MODEL_CLS = Compensation model = Compensation
_REDIRECT_URL = "compensation:detail" redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditCompensationStateView(AbstractEditCompensationStateView): class EditCompensationStateView(AbstractEditCompensationStateView):
_MODEL_CLS = Compensation model = Compensation
_REDIRECT_URL = "compensation:detail" redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveCompensationStateView(AbstractRemoveCompensationStateView): class RemoveCompensationStateView(AbstractRemoveCompensationStateView):
_MODEL_CLS = Compensation model = Compensation
_REDIRECT_URL = "compensation:detail" redirect_url = "compensation:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Compensation, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,22 +5,46 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \ from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView AbstractRemoveCompensationActionView
_ECO_ACCOUNT_DETAIL_URL_NAME = "compensation:acc:detail"
class NewEcoAccountActionView(AbstractNewCompensationActionView): class NewEcoAccountActionView(AbstractNewCompensationActionView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountActionView(AbstractEditCompensationActionView): class EditEcoAccountActionView(AbstractEditCompensationActionView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountActionView(AbstractRemoveCompensationActionView): class RemoveEcoAccountActionView(AbstractRemoveCompensationActionView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,22 +5,45 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.deadline import AbstractNewDeadlineView, AbstractEditDeadlineView, AbstractRemoveDeadlineView from konova.views.deadline import AbstractNewDeadlineView, AbstractEditDeadlineView, AbstractRemoveDeadlineView
_ECO_ACCOUNT_DETAIL_URL_NAME = "compensation:acc:detail"
class NewEcoAccountDeadlineView(AbstractNewDeadlineView): class NewEcoAccountDeadlineView(AbstractNewDeadlineView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountDeadlineView(AbstractEditDeadlineView): class EditEcoAccountDeadlineView(AbstractEditDeadlineView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountDeadlineView(AbstractRemoveDeadlineView): class RemoveEcoAccountDeadlineView(AbstractRemoveDeadlineView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,33 +5,54 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.http import Http404 from django.http import Http404
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import default_group_required, login_required_modal
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
_ECO_ACCOUNT_DETAIl_URL_NAME = "compensation:acc:detail"
class NewEcoAccountDeductionView(LoginRequiredMixin, AbstractNewDeductionView): class NewEcoAccountDeductionView(AbstractNewDeductionView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def _custom_check(self, obj): def _custom_check(self, obj):
# New deductions can only be created if the eco account has been recorded
if not obj.recorded: if not obj.recorded:
raise Http404() raise Http404()
def _check_for_recorded_instance(self, obj):
# Deductions can be created on recorded as well as on non-recorded entries class EditEcoAccountDeductionView(AbstractEditDeductionView):
return None def _custom_check(self, obj):
pass
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountDeductionView(LoginRequiredMixin, AbstractEditDeductionView): class RemoveEcoAccountDeductionView(AbstractRemoveDeductionView):
_MODEL_CLS = EcoAccount def _custom_check(self, obj):
_REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME pass
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView):
_MODEL_CLS = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME

View File

@ -5,33 +5,65 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from compensation.forms.modals.document import NewEcoAccountDocumentModalForm, RemoveEcoAccountDocumentModalForm, \ from django.contrib.auth.decorators import login_required
EditEcoAccountDocumentModalForm from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.decorators import method_decorator
from compensation.forms.modals.document import NewEcoAccountDocumentModalForm
from compensation.models import EcoAccount, EcoAccountDocument from compensation.models import EcoAccount, EcoAccountDocument
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.forms.modals import EditDocumentModalForm
from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \ from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \
AbstractEditDocumentView AbstractEditDocumentView
class NewEcoAccountDocumentView(AbstractNewDocumentView): class NewEcoAccountDocumentView(AbstractNewDocumentView):
_MODEL_CLS = EcoAccount model = EcoAccount
_FORM_CLS = NewEcoAccountDocumentModalForm form = NewEcoAccountDocumentModalForm
_REDIRECT_URL = "compensation:acc:detail" redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class GetEcoAccountDocumentView(AbstractGetDocumentView): class GetEcoAccountDocumentView(AbstractGetDocumentView):
_MODEL_CLS = EcoAccount model = EcoAccount
_DOCUMENT_CLS = EcoAccountDocument document_model = EcoAccountDocument
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountDocumentView(AbstractRemoveDocumentView): class RemoveEcoAccountDocumentView(AbstractRemoveDocumentView):
_MODEL_CLS = EcoAccount model = EcoAccount
_DOCUMENT_CLS = EcoAccountDocument document_model = EcoAccountDocument
_FORM_CLS = RemoveEcoAccountDocumentModalForm
_REDIRECT_URL = "compensation:acc:detail" @method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountDocumentView(AbstractEditDocumentView): class EditEcoAccountDocumentView(AbstractEditDocumentView):
_MODEL_CLS = EcoAccount model = EcoAccount
_DOCUMENT_CLS = EcoAccountDocument document_model = EcoAccountDocument
_FORM_CLS = EditEcoAccountDocumentModalForm form = EditDocumentModalForm
_REDIRECT_URL = "compensation:acc:detail" redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -7,8 +7,8 @@ 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.db.models import Sum
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,52 +17,43 @@ 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, BaseNewSpatialLocatedObjectFormView, \
BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class EcoAccountIndexView(LoginRequiredMixin, BaseIndexView): @login_required
_INDEX_TABLE_CLS = EcoAccountTable @any_group_check
_TAB_TITLE = _("Eco-account - Overview") def index_view(request: HttpRequest):
"""
Renders the index view for eco accounts
def _get_queryset(self): Args:
qs = EcoAccount.objects.filter( request (HttpRequest): The incoming request
deleted=None,
).order_by(
"-modified__timestamp"
)
return qs
Returns:
class NewEcoAccountFormView(BaseNewSpatialLocatedObjectFormView): A rendered view
_FORM_CLS = NewEcoAccountForm """
_MODEL_CLS = EcoAccount template = "generic_index.html"
_TEMPLATE = "compensation/form/view.html" eco_accounts = EcoAccount.objects.filter(
_TAB_TITLE = _("New Eco-Account") deleted=None,
_REDIRECT_URL = "compensation:acc:detail" ).order_by(
"-modified__timestamp"
def _user_has_permission(self, user, **kwargs): )
# User has to be a default user table = EcoAccountTable(
return user.is_default_user() request=request,
queryset=eco_accounts
)
class EditEcoAccountFormView(BaseEditSpatialLocatedObjectFormView): context = {
_FORM_CLS = EditEcoAccountForm "table": table,
_MODEL_CLS = EcoAccount TAB_TITLE_IDENTIFIER: _("Eco-account - Overview"),
_TEMPLATE = "compensation/form/view.html" }
_REDIRECT_URL = "compensation:acc:detail" context = BaseContext(request, context).context
return render(request, template, context)
def _user_has_permission(self, user, **kwargs):
# User has to be a default user
return user.is_default_user()
@login_required @login_required
@ -121,9 +112,23 @@ def new_view(request: HttpRequest):
return render(request, template, context) return render(request, template, context)
class EcoAccountIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView): @login_required
_MODEL_CLS = EcoAccount @default_group_required
_REDIRECT_URL = "compensation:acc:index" def new_id_view(request: HttpRequest):
""" JSON endpoint
Provides fetching of free identifiers for e.g. AJAX calls
"""
tmp = EcoAccount()
identifier = tmp.generate_new_identifier()
while EcoAccount.objects.filter(identifier=identifier).exists():
identifier = tmp.generate_new_identifier()
return JsonResponse(
data={
"gen_data": identifier
}
)
@login_required @login_required
@ -187,78 +192,116 @@ 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
class RemoveEcoAccountView(LoginRequiredMixin, BaseRemoveModalFormView): @login_required_modal
_MODEL_CLS = EcoAccount @login_required
_FORM_CLS = RemoveEcoAccountModalForm @default_group_required
_REDIRECT_URL = "compensation:acc:index" @shared_access_required(EcoAccount, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the eco account
Args:
request (HttpRequest): The incoming request
id (str): The account's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
# If the eco account has already been recorded OR there are already deductions, it can not be deleted by a regular
# default group user
if acc.recorded is not None or acc.deductions.exists():
user = request.user
if not user.in_group(ETS_GROUP):
messages.info(request, CANCEL_ACC_RECORDED_OR_DEDUCTED)
return redirect("compensation:acc:detail", id=id)
form = RemoveEcoAccountModalForm(request.POST or None, instance=acc, request=request)
return form.process_request(
request=request,
msg_success=_("Eco-account removed"),
redirect_url=reverse("compensation:acc:index"),
)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()

View File

@ -5,11 +5,20 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.log import AbstractLogView from konova.views.log import AbstractLogView
class EcoAccountLogView(LoginRequiredMixin, AbstractLogView): class EcoAccountLogView(AbstractLogView):
_MODEL_CLS = EcoAccount model = EcoAccount
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,12 +5,20 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.record import AbstractRecordView from konova.views.record import AbstractRecordView
class EcoAccountRecordView(LoginRequiredMixin, AbstractRecordView): class EcoAccountRecordView(AbstractRecordView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,41 +5,85 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.http import HttpRequest
from django.shortcuts import get_object_or_404, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount from compensation.models import EcoAccount
from compensation.views.compensation.report import BaseCompensationReportView from konova.contexts import BaseContext
from konova.sub_settings.django_settings import BASE_URL from konova.decorators import uuid_required
from konova.utils.qrcode import QrCode from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.generators import generate_qr_code
class EcoAccountReportView(BaseCompensationReportView): @uuid_required
_MODEL = EcoAccount def report_view(request: HttpRequest, id: str):
_TEMPLATE = "compensation/report/eco_account/report.html" """ Renders the public report view
def _get_report_context(self, obj): Args:
report_url = BASE_URL + reverse("compensation:acc:report", args=(obj.id,)) request (HttpRequest): The incoming request
qrcode_report = QrCode(report_url, 10) id (str): The id of the intervention
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier) Returns:
deductions = obj.deductions.all() \
.distinct("intervention") \
.select_related("intervention") \
.values_list("intervention__id", "intervention__identifier", "intervention__title", named=True)
report_context = { """
"qrcode": { # Reuse the compensation report template since EcoAccounts are structurally identical
"img": qrcode_report.get_img(), template = "compensation/report/eco_account/report.html"
"url": qrcode_report.get_content(), acc = get_object_or_404(EcoAccount, id=id)
},
"qrcode_lanis": { tab_title = _("Report {}").format(acc.identifier)
"img": qrcode_lanis.get_img(), # If intervention is not recorded (yet or currently) we need to render another template without any data
"url": qrcode_lanis.get_content(), if not acc.is_ready_for_publish():
}, template = "report/unavailable.html"
"is_entry_shared": False, # disables action buttons during rendering context = {
"deductions": deductions, TAB_TITLE_IDENTIFIER: tab_title,
"tables_scrollable": False,
} }
report_context.update(self._get_compensation_report_context(obj)) context = BaseContext(request, context).context
return report_context return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=acc
)
parcels = acc.get_underlying_parcels()
qrcode_url = request.build_absolute_uri(reverse("compensation:acc:report", args=(id,)))
qrcode_img = generate_qr_code(qrcode_url, 10)
qrcode_lanis_url = acc.get_LANIS_link()
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
# Order states by surface
before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent")
after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent")
actions = acc.actions.all().prefetch_related("action_type__parent")
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier)
deductions = acc.deductions.all()\
.distinct("intervention")\
.select_related("intervention")\
.values_list("intervention__id", "intervention__identifier", "intervention__title", named=True)
context = {
"obj": acc,
"qrcode": {
"img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"is_entry_shared": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
"geom_form": geom_form,
"parcels": parcels,
"actions": actions,
"deductions": deductions,
"tables_scrollable": False,
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)

View File

@ -5,12 +5,22 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from compensation.forms.modals.resubmission import EcoAccountResubmissionModalForm from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView from konova.views.resubmission import AbstractResubmissionView
class EcoAccountResubmissionView(AbstractResubmissionView): class EcoAccountResubmissionView(AbstractResubmissionView):
_MODEL_CLS = EcoAccount model = EcoAccount
_FORM_CLS = EcoAccountResubmissionModalForm redirect_url_base = "compensation:acc:detail"
_REDIRECT_URL = "compensation:acc:detail" form_action_url_base = "compensation:acc:resubmission-create"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,15 +5,29 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.share import AbstractShareByTokenView, AbstractShareFormView from konova.views.share import AbstractShareByTokenView, AbstractShareFormView
class EcoAccountShareByTokenView(AbstractShareByTokenView): class EcoAccountShareByTokenView(AbstractShareByTokenView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = "compensation:acc:detail" redirect_url = "compensation:acc:detail"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EcoAccountShareFormView(AbstractShareFormView): class EcoAccountShareFormView(AbstractShareFormView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,21 +5,46 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \ from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \
AbstractRemoveCompensationStateView AbstractRemoveCompensationStateView
class NewEcoAccountStateView(AbstractNewCompensationStateView): class NewEcoAccountStateView(AbstractNewCompensationStateView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = "compensation:acc:detail" redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEcoAccountStateView(AbstractEditCompensationStateView): class EditEcoAccountStateView(AbstractEditCompensationStateView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = "compensation:acc:detail" redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEcoAccountStateView(AbstractRemoveCompensationStateView): class RemoveEcoAccountStateView(AbstractRemoveCompensationStateView):
_MODEL_CLS = EcoAccount model = EcoAccount
_REDIRECT_URL = "compensation:acc:detail" redirect_url = "compensation:acc:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(EcoAccount, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,38 +5,84 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 09.08.21 Created on: 09.08.21
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from compensation.forms.modals.payment import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm from compensation.forms.modals.payment import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
from compensation.models import Payment
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import default_group_required, shared_access_required
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED
from konova.views.base import BaseModalFormView
class BasePaymentView(LoginRequiredMixin, BaseModalFormView): @login_required
_MODEL_CLS = Intervention @default_group_required
_REDIRECT_URL = "intervention:detail" @shared_access_required(Intervention, "id")
def new_payment_view(request: HttpRequest, id: str):
""" Renders a modal view for adding new payments
class Meta: Args:
abstract = True request (HttpRequest): The incoming request
id (str): The intervention's id for which a new payment shall be added
def _get_redirect_url(self, *args, **kwargs): Returns:
url = super()._get_redirect_url(*args, **kwargs)
return f"{url}#related_data"
def _user_has_permission(self, user, **kwargs): """
return user.is_default_user() intervention = get_object_or_404(Intervention, id=id)
form = NewPaymentForm(request.POST or None, instance=intervention, request=request)
return form.process_request(
request,
msg_success=PAYMENT_ADDED,
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
)
class NewPaymentView(BasePaymentView): @login_required
_FORM_CLS = NewPaymentForm @default_group_required
_MSG_SUCCESS = PAYMENT_ADDED @shared_access_required(Intervention, "id")
def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
""" Renders a modal view for removing payments
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id
payment_id (str): The payment's id
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
payment = get_object_or_404(Payment, id=payment_id)
form = RemovePaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
return form.process_request(
request=request,
msg_success=PAYMENT_REMOVED,
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
)
class EditPaymentView(BasePaymentView): @login_required
_MSG_SUCCESS = PAYMENT_EDITED @default_group_required
_FORM_CLS = EditPaymentModalForm @shared_access_required(Intervention, "id")
def payment_edit_view(request: HttpRequest, id: str, payment_id: str):
""" Renders a modal view for editing payments
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id
payment_id (str): The payment's id
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
payment = get_object_or_404(Payment, id=payment_id)
form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
return form.process_request(
request=request,
msg_success=PAYMENT_EDITED,
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
)
class RemovePaymentView(BasePaymentView):
_MSG_SUCCESS = PAYMENT_REMOVED
_FORM_CLS = RemovePaymentModalForm

View File

@ -15,8 +15,7 @@ from compensation.forms.compensation import AbstractCompensationForm
from ema.models import Ema, EmaDocument from ema.models import Ema, EmaDocument
from intervention.models import Responsibility, Handler from intervention.models import Responsibility, Handler
from konova.forms import SimpleGeomForm from konova.forms import SimpleGeomForm
from konova.forms.modals import NewDocumentModalForm, EditDocumentModalForm, RemoveDocumentModalForm, \ from konova.forms.modals import NewDocumentModalForm
ResubmissionModalForm
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
@ -171,13 +170,4 @@ class EditEmaForm(NewEmaForm):
class NewEmaDocumentModalForm(NewDocumentModalForm): class NewEmaDocumentModalForm(NewDocumentModalForm):
_DOCUMENT_CLS = EmaDocument document_model = EmaDocument
class EditEmaDocumentModalForm(EditDocumentModalForm):
_DOCUMENT_CLS = EmaDocument
class RemoveEmaDocumentModalForm(RemoveDocumentModalForm):
_DOCUMENT_CLS = EmaDocument
class EmaResubmissionModalForm(ResubmissionModalForm):
_MODEL_CLS = Ema

View File

@ -10,26 +10,25 @@ 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 EmaIndexView, EmaIdentifierGeneratorView, EmaDetailView, EditEmaFormView, NewEmaFormView, \ from ema.views.ema import index_view, new_view, new_id_view, detail_view, edit_view, remove_view
RemoveEmaView
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 report_view
from ema.views.resubmission import EmaResubmissionView from ema.views.resubmission import EmaResubmissionView
from ema.views.share import EmaShareFormView, EmaShareByTokenView from ema.views.share import EmaShareFormView, EmaShareByTokenView
from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateView from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateView
app_name = "ema" app_name = "ema"
urlpatterns = [ urlpatterns = [
path("", EmaIndexView.as_view(), name="index"), path("", index_view, name="index"),
path("new/", NewEmaFormView.as_view(), name="new"), path("new/", new_view, name="new"),
path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"), path("new/id", new_id_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', EditEmaFormView.as_view(), name='edit'), path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', RemoveEmaView.as_view(), name='remove'), path('<id>/remove', remove_view, name='remove'),
path('<id>/record', EmaRecordView.as_view(), name='record'), path('<id>/record', EmaRecordView.as_view(), name='record'),
path('<id>/report', EmaReportView.as_view(), name='report'), path('<id>/report', report_view, name='report'),
path('<id>/resub', EmaResubmissionView.as_view(), name='resubmission-create'), path('<id>/resub', EmaResubmissionView.as_view(), name='resubmission-create'),
path('<id>/state/new', NewEmaStateView.as_view(), name='new-state'), path('<id>/state/new', NewEmaStateView.as_view(), name='new-state'),

View File

@ -5,31 +5,46 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \ from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView AbstractRemoveCompensationActionView
_EMA_ACCOUNT_DETAIL_URL_NAME = "ema:detail"
class NewEmaActionView(AbstractNewCompensationActionView): class NewEmaActionView(AbstractNewCompensationActionView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME redirect_url = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class EditEmaActionView(AbstractEditCompensationActionView): class EditEmaActionView(AbstractEditCompensationActionView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME redirect_url = "ema:detail"
def _user_has_permission(self, user, **kwargs): @method_decorator(login_required_modal)
return user.is_ets_user() @method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEmaActionView(AbstractRemoveCompensationActionView): class RemoveEmaActionView(AbstractRemoveCompensationActionView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME redirect_url = "ema:detail"
def _user_has_permission(self, user, **kwargs): @method_decorator(login_required_modal)
return user.is_ets_user() @method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,30 +5,46 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.deadline import AbstractNewDeadlineView, AbstractRemoveDeadlineView, AbstractEditDeadlineView from konova.views.deadline import AbstractNewDeadlineView, AbstractRemoveDeadlineView, AbstractEditDeadlineView
_EMA_DETAIL_URL_NAME = "ema:detail"
class NewEmaDeadlineView(AbstractNewDeadlineView): class NewEmaDeadlineView(AbstractNewDeadlineView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = _EMA_DETAIL_URL_NAME redirect_url = "ema:detail"
def _user_has_permission(self, user, **kwargs): @method_decorator(login_required_modal)
return user.is_ets_user() @method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEmaDeadlineView(AbstractEditDeadlineView): class EditEmaDeadlineView(AbstractEditDeadlineView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = _EMA_DETAIL_URL_NAME redirect_url = "ema:detail"
def _user_has_permission(self, user, **kwargs): @method_decorator(login_required_modal)
return user.is_ets_user() @method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEmaDeadlineView(AbstractRemoveDeadlineView): class RemoveEmaDeadlineView(AbstractRemoveDeadlineView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = _EMA_DETAIL_URL_NAME redirect_url = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()

View File

@ -5,41 +5,62 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from ema.forms import NewEmaDocumentModalForm, RemoveEmaDocumentModalForm, EditEmaDocumentModalForm from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.forms import NewEmaDocumentModalForm
from ema.models import Ema, EmaDocument from ema.models import Ema, EmaDocument
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.forms.modals import EditDocumentModalForm
from konova.views.document import AbstractEditDocumentView, AbstractRemoveDocumentView, AbstractGetDocumentView, \ from konova.views.document import AbstractEditDocumentView, AbstractRemoveDocumentView, AbstractGetDocumentView, \
AbstractNewDocumentView AbstractNewDocumentView
class NewEmaDocumentView(AbstractNewDocumentView): class NewEmaDocumentView(AbstractNewDocumentView):
_MODEL_CLS = Ema model = Ema
_FORM_CLS = NewEmaDocumentModalForm form = NewEmaDocumentModalForm
_REDIRECT_URL = "ema:detail" redirect_url = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class GetEmaDocumentView(AbstractGetDocumentView): class GetEmaDocumentView(AbstractGetDocumentView):
_MODEL_CLS = Ema model = Ema
_DOCUMENT_CLS = EmaDocument document_model = EmaDocument
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class RemoveEmaDocumentView(AbstractRemoveDocumentView): class RemoveEmaDocumentView(AbstractRemoveDocumentView):
_MODEL_CLS = Ema model = Ema
_DOCUMENT_CLS = EmaDocument document_model = EmaDocument
_FORM_CLS = RemoveEmaDocumentModalForm
_REDIRECT_URL = "ema:detail" @method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class EditEmaDocumentView(AbstractEditDocumentView): class EditEmaDocumentView(AbstractEditDocumentView):
_MODEL_CLS = Ema model = Ema
_FORM_CLS = EditEmaDocumentModalForm document_model = EmaDocument
_DOCUMENT_CLS = EmaDocument form = EditDocumentModalForm
_REDIRECT_URL = "ema:detail" redirect_url = "ema:detail"
def _user_has_permission(self, user, **kwargs): @method_decorator(login_required_modal)
return user.is_ets_user() @method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,112 +5,269 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib import messages
from django.shortcuts import get_object_or_404 from django.contrib.auth.decorators import login_required
from django.db.models import Sum
from django.http import HttpRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from ema.forms import NewEmaForm, EditEmaForm 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.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \ from konova.contexts import BaseContext
BaseEditSpatialLocatedObjectFormView from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal, \
from konova.views.detail import BaseDetailView uuid_required
from konova.views.remove import BaseRemoveModalFormView 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
class EmaIndexView(LoginRequiredMixin, BaseIndexView): @login_required
_TAB_TITLE = _("EMAs - Overview") def index_view(request: HttpRequest):
_INDEX_TABLE_CLS = EmaTable """ Renders the index view for EMAs
def _get_queryset(self): Args:
qs = Ema.objects.filter( request (HttpRequest): The incoming request
deleted=None,
).order_by( Returns:
"-modified__timestamp"
) """
return qs template = "generic_index.html"
emas = Ema.objects.filter(
deleted=None,
).order_by(
"-modified__timestamp"
)
table = EmaTable(
request,
queryset=emas
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("EMAs - Overview"),
}
context = BaseContext(request, context).context
return render(request, template, context)
class NewEmaFormView(BaseNewSpatialLocatedObjectFormView): @login_required
_FORM_CLS = NewEmaForm @conservation_office_group_required
_MODEL_CLS = Ema def new_view(request: HttpRequest):
_TEMPLATE = "ema/form/view.html" """
_TAB_TITLE = _("New EMA") Renders a view for a new eco account creation
_REDIRECT_URL = "ema:detail"
def _user_has_permission(self, user, **kwargs): Args:
# User has to be an ets user request (HttpRequest): The incoming request
return user.is_ets_user()
Returns:
"""
template = "ema/form/view.html"
data_form = NewEmaForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
generated_identifier = data_form.cleaned_data.get("identifier", None)
ema = data_form.save(request.user, geom_form)
if generated_identifier != ema.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
ema.identifier
)
)
messages.success(request, _("EMA {} added").format(ema.identifier))
if geom_form.has_geometry_simplified():
messages.info(
request,
GEOMETRY_SIMPLIFIED
)
num_ignored_geometries = geom_form.get_num_geometries_ignored()
if num_ignored_geometries > 0:
messages.info(
request,
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
)
return redirect("ema:detail", id=ema.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New EMA"),
}
context = BaseContext(request, context).context
return render(request, template, context)
class EditEmaFormView(BaseEditSpatialLocatedObjectFormView): @login_required
_MODEL_CLS = Ema @conservation_office_group_required
_FORM_CLS = EditEmaForm def new_id_view(request: HttpRequest):
_TEMPLATE = "ema/form/view.html" """ JSON endpoint
_REDIRECT_URL = "ema:detail"
_TAB_TITLE = _("Edit {}")
def _user_has_permission(self, user, **kwargs): Provides fetching of free identifiers for e.g. AJAX calls
# User has to be an ets user
return user.is_ets_user()
"""
class EmaIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView): tmp = Ema()
_MODEL_CLS = Ema identifier = tmp.generate_new_identifier()
_REDIRECT_URL = "ema:index" while Ema.objects.filter(identifier=identifier).exists():
identifier = tmp.generate_new_identifier()
def _user_has_permission(self, user, **kwargs): return JsonResponse(
return user.is_ets_user() data={
"gen_data": identifier
class EmaDetailView(BaseDetailView):
_MODEL_CLS = Ema
_TEMPLATE = "ema/detail/view.html"
def _get_object(self, id: str):
""" Fetch object for detail view
Args:
id (str): The record's id'
Returns:
"""
ema = get_object_or_404(Ema, id=id, deleted=None)
return ema
def _get_detail_context(self, obj: Ema):
""" Generate object specific detail context for view
Args:
obj (): The record
Returns:
"""
# Order states according to surface
before_states = obj.before_states.all().order_by("-surface")
after_states = obj.after_states.all().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)
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 )
class RemoveEmaView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = Ema
_REDIRECT_URL = "ema:index"
def _user_has_permission(self, user, **kwargs): @login_required
return user.is_ets_user() @uuid_required
def detail_view(request: HttpRequest, id: str):
""" Renders the detail view of an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA id
Returns:
"""
template = "ema/detail/view.html"
ema = get_object_or_404(Ema, id=id, deleted=None)
geom_form = SimpleGeomForm(instance=ema)
parcels = ema.get_underlying_parcels()
_user = request.user
is_entry_shared = ema.is_shared_with(_user)
# Order states according to surface
before_states = ema.before_states.all().order_by("-surface")
after_states = ema.after_states.all().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 = ema.get_surface_before_states()
sum_after_states = ema.get_surface_after_states()
diff_states = abs(sum_before_states - sum_after_states)
ema.set_status_messages(request)
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
)
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)
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "compensation/form/view.html"
# Get object from db
ema = get_object_or_404(Ema, id=id)
if ema.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("ema:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditEmaForm(request.POST or None, instance=ema)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
# The data form takes the geom form for processing, as well as the performing user
ema = data_form.save(request.user, geom_form)
messages.success(request, _("EMA {} edited").format(ema.identifier))
if geom_form.has_geometry_simplified():
messages.info(
request,
GEOMETRY_SIMPLIFIED
)
num_ignored_geometries = geom_form.get_num_geometries_ignored()
if num_ignored_geometries > 0:
messages.info(
request,
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
)
return redirect("ema:detail", id=ema.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required_modal
@login_required
@conservation_office_group_required
@shared_access_required(Ema, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = RemoveModalForm(request.POST or None, instance=ema, request=request)
return form.process_request(
request=request,
msg_success=_("EMA removed"),
redirect_url=reverse("ema:index"),
)

View File

@ -5,14 +5,20 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.log import AbstractLogView from konova.views.log import AbstractLogView
class EmaLogView(LoginRequiredMixin, AbstractLogView): class EmaLogView(AbstractLogView):
_MODEL_CLS = Ema model = Ema
def _user_has_permission(self, user, **kwargs): @method_decorator(login_required_modal)
return user.is_ets_user() @method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,12 +5,20 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.record import AbstractRecordView from konova.views.record import AbstractRecordView
class EmaRecordView(LoginRequiredMixin, AbstractRecordView): class EmaRecordView(AbstractRecordView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = "ema:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,36 +5,77 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.http import HttpRequest
from django.shortcuts import get_object_or_404, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.views.compensation.report import BaseCompensationReportView
from ema.models import Ema from ema.models import Ema
from konova.sub_settings.django_settings import BASE_URL from konova.contexts import BaseContext
from konova.utils.qrcode import QrCode from konova.decorators import uuid_required
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.generators import generate_qr_code
@uuid_required
def report_view(request:HttpRequest, id: str):
""" Renders the public report view
class EmaReportView(BaseCompensationReportView): Args:
_TEMPLATE = "ema/report/report.html" request (HttpRequest): The incoming request
_MODEL = Ema id (str): The id of the intervention
def _get_report_context(self, obj): Returns:
report_url = BASE_URL + reverse("ema:report", args=(obj.id,))
qrcode_report = QrCode(report_url, 10)
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
generic_compensation_report_context = self._get_compensation_report_context(obj) """
# Reuse the compensation report template since EMAs are structurally identical
template = "ema/report/report.html"
ema = get_object_or_404(Ema, id=id)
report_context = { tab_title = _("Report {}").format(ema.identifier)
"qrcode": { # If intervention is not recorded (yet or currently) we need to render another template without any data
"img": qrcode_report.get_img(), if not ema.is_ready_for_publish():
"url": qrcode_report.get_content(), template = "report/unavailable.html"
}, context = {
"qrcode_lanis": { TAB_TITLE_IDENTIFIER: tab_title,
"img": qrcode_lanis.get_img(),
"url": qrcode_lanis.get_content(),
},
"is_entry_shared": False, # disables action buttons during rendering
"tables_scrollable": False,
} }
report_context.update(generic_compensation_report_context) context = BaseContext(request, context).context
return report_context return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=ema,
)
parcels = ema.get_underlying_parcels()
qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,)))
qrcode_img = generate_qr_code(qrcode_url, 10)
qrcode_lanis_url = ema.get_LANIS_link()
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
# Order states by surface
before_states = ema.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = ema.after_states.all().order_by("-surface").prefetch_related("biotope_type")
actions = ema.actions.all().prefetch_related("action_type")
context = {
"obj": ema,
"qrcode": {
"img": qrcode_img,
"url": qrcode_url
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url
},
"is_entry_shared": False, # disables action buttons during rendering
"before_states": before_states,
"after_states": after_states,
"geom_form": geom_form,
"parcels": parcels,
"actions": actions,
"tables_scrollable": False,
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)

View File

@ -5,16 +5,22 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from ema.forms import EmaResubmissionModalForm from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView from konova.views.resubmission import AbstractResubmissionView
class EmaResubmissionView(AbstractResubmissionView): class EmaResubmissionView(AbstractResubmissionView):
_MODEL_CLS = Ema model = Ema
_FORM_CLS = EmaResubmissionModalForm redirect_url_base = "ema:detail"
_REDIRECT_URL = "ema:detail" form_action_url_base = "ema:resubmission-create"
action_url = "ema:resubmission-create"
def _user_has_permission(self, user, **kwargs): @method_decorator(login_required_modal)
return user.is_ets_user() @method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,17 +5,29 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import conservation_office_group_required, shared_access_required, login_required_modal
from konova.views.share import AbstractShareByTokenView, AbstractShareFormView from konova.views.share import AbstractShareByTokenView, AbstractShareFormView
class EmaShareByTokenView(AbstractShareByTokenView): class EmaShareByTokenView(AbstractShareByTokenView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = "ema:detail" redirect_url = "ema:detail"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EmaShareFormView(AbstractShareFormView): class EmaShareFormView(AbstractShareFormView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = "ema:detail"
def _user_has_permission(self, user, **kwargs): @method_decorator(login_required_modal)
return user.is_ets_user() @method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,30 +5,46 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.models import Ema from ema.models import Ema
from konova.decorators import conservation_office_group_required, shared_access_required, login_required_modal
from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \ from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \
AbstractRemoveCompensationStateView AbstractRemoveCompensationStateView
class NewEmaStateView(AbstractNewCompensationStateView): class NewEmaStateView(AbstractNewCompensationStateView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = "ema:detail" redirect_url = "ema:detail"
def _user_has_permission(self, user, **kwargs): @method_decorator(login_required_modal)
return user.is_ets_user() @method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditEmaStateView(AbstractEditCompensationStateView): class EditEmaStateView(AbstractEditCompensationStateView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = "ema:detail" redirect_url = "ema:detail"
def _user_has_permission(self, user, **kwargs): @method_decorator(login_required_modal)
return user.is_ets_user() @method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveEmaStateView(AbstractRemoveCompensationStateView): class RemoveEmaStateView(AbstractRemoveCompensationStateView):
_MODEL_CLS = Ema model = Ema
_REDIRECT_URL = "ema:detail" redirect_url = "ema:detail"
def _user_has_permission(self, user, **kwargs): @method_decorator(login_required_modal)
return user.is_ets_user() @method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -172,8 +172,7 @@ class EditEcoAccountDeductionModalForm(NewEcoAccountDeductionModalForm):
deduction = None deduction = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
deduction_id = kwargs.pop("deduction_id", None) self.deduction = kwargs.pop("deduction", None)
self.deduction = EcoAccountDeduction.objects.get(id=deduction_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit Deduction") self.form_title = _("Edit Deduction")
form_data = { form_data = {
@ -253,20 +252,19 @@ class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
Can be used for anything, where removing shall be confirmed by the user a second time. Can be used for anything, where removing shall be confirmed by the user a second time.
""" """
_DEDUCTION_OBJ = None deduction = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
deduction_id = kwargs.pop("deduction_id", None) deduction = kwargs.pop("deduction", None)
deduction = EcoAccountDeduction.objects.get(id=deduction_id) self.deduction = deduction
self._DEDUCTION_OBJ = deduction
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):
with transaction.atomic(): with transaction.atomic():
self._DEDUCTION_OBJ.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED) self.deduction.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
self._DEDUCTION_OBJ.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED) self.deduction.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
self._DEDUCTION_OBJ.delete() self.deduction.delete()
def check_for_recorded_instance(self): def check_for_recorded_instance(self):
if self._DEDUCTION_OBJ.intervention.is_recorded: if self.deduction.intervention.is_recorded:
self.block_form() self.block_form()

View File

@ -6,11 +6,11 @@ Created on: 18.08.22
""" """
from intervention.models import InterventionDocument from intervention.models import InterventionDocument
from konova.forms.modals import NewDocumentModalForm, EditDocumentModalForm, RemoveDocumentModalForm from konova.forms.modals import NewDocumentModalForm
class NewInterventionDocumentModalForm(NewDocumentModalForm): class NewInterventionDocumentModalForm(NewDocumentModalForm):
_DOCUMENT_CLS = InterventionDocument document_model = InterventionDocument
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" Extension of regular NewDocumentModalForm """ Extension of regular NewDocumentModalForm
@ -28,31 +28,3 @@ class NewInterventionDocumentModalForm(NewDocumentModalForm):
self.instance.send_data_to_egon() self.instance.send_data_to_egon()
return doc return doc
class EditInterventionDocumentModalForm(EditDocumentModalForm):
_DOCUMENT_CLS = InterventionDocument
def save(self, *args, **kwargs):
""" Extension of regular EditDocumentModalForm
Checks whether payments exist on the intervention and sends the data to EGON
Args:
*args ():
**kwargs ():
Returns:
"""
doc = super().save(*args, **kwargs)
self.instance.send_data_to_egon()
return doc
class RemoveInterventionDocumentModalForm(RemoveDocumentModalForm):
_DOCUMENT_CLS = InterventionDocument
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.instance.send_data_to_egon()

View File

@ -1,11 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 21.10.25
"""
from intervention.models import Intervention
from konova.forms.modals import ResubmissionModalForm
class InterventionResubmissionModalForm(ResubmissionModalForm):
_MODEL_CLS = Intervention

View File

@ -7,10 +7,9 @@ Created on: 18.08.22
""" """
from django import forms from django import forms
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from intervention.models import RevocationDocument, Revocation from intervention.models import RevocationDocument
from konova.forms.modals import BaseModalForm, RemoveModalForm from konova.forms.modals import BaseModalForm, RemoveModalForm
from konova.utils import validators from konova.utils import validators
from konova.utils.message_templates import REVOCATION_ADDED, REVOCATION_EDITED from konova.utils.message_templates import REVOCATION_ADDED, REVOCATION_EDITED
@ -76,8 +75,7 @@ class EditRevocationModalForm(NewRevocationModalForm):
revocation = None revocation = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
revocation_id = kwargs.pop("revocation_id", None) self.revocation = kwargs.pop("revocation", None)
self.revocation = get_object_or_404(Revocation, id=revocation_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit revocation") self.form_title = _("Edit revocation")
try: try:
@ -106,8 +104,8 @@ class RemoveRevocationModalForm(RemoveModalForm):
revocation = None revocation = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
revocation_id = kwargs.pop("revocation_id", None) revocation = kwargs.pop("revocation", None)
self.revocation = get_object_or_404(Revocation, id=revocation_id) self.revocation = revocation
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):

View File

@ -280,7 +280,7 @@ class EditRevocationModalFormTestCase(NewRevocationModalFormTestCase):
data, data,
request=self.request, request=self.request,
instance=self.intervention, instance=self.intervention,
revocation_id=self.revoc.id revocation=self.revoc
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
obj = form.save() obj = form.save()
@ -302,7 +302,7 @@ class RemoveRevocationModalFormTestCase(EditRevocationModalFormTestCase):
form = RemoveRevocationModalForm( form = RemoveRevocationModalForm(
request=self.request, request=self.request,
instance=self.intervention, instance=self.intervention,
revocation_id=self.revoc.id, revocation=self.revoc,
) )
self.assertEqual(form.instance, self.intervention) self.assertEqual(form.instance, self.intervention)
self.assertEqual(form.revocation, self.revoc) self.assertEqual(form.revocation, self.revoc)
@ -317,7 +317,7 @@ class RemoveRevocationModalFormTestCase(EditRevocationModalFormTestCase):
data, data,
request=self.request, request=self.request,
instance=self.intervention, instance=self.intervention,
revocation_id=self.revoc.id revocation=self.revoc
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
form.save() form.save()

View File

@ -8,36 +8,35 @@ Created on: 30.11.20
from django.urls import path from django.urls import path
from intervention.autocomplete.intervention import InterventionAutocomplete from intervention.autocomplete.intervention import InterventionAutocomplete
from intervention.views.check import InterventionCheckView from intervention.views.check import check_view
from intervention.views.compensation import remove_compensation_view from intervention.views.compensation import remove_compensation_view
from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \ from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \
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 InterventionIndexView, InterventionIdentifierGeneratorView, \ from intervention.views.intervention import index_view, new_view, new_id_view, detail_view, edit_view, remove_view
InterventionDetailView, NewInterventionFormView, EditInterventionFormView, RemoveInterventionView
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 report_view
from intervention.views.resubmission import InterventionResubmissionView from intervention.views.resubmission import InterventionResubmissionView
from intervention.views.revocation import NewRevocationView, GetRevocationDocumentView, EditRevocationView, \ from intervention.views.revocation import new_revocation_view, edit_revocation_view, remove_revocation_view, \
RemoveRevocationView get_revocation_view
from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView
app_name = "intervention" app_name = "intervention"
urlpatterns = [ urlpatterns = [
path("", InterventionIndexView.as_view(), name="index"), path("", index_view, name="index"),
path('new/', NewInterventionFormView.as_view(), name='new'), path('new/', new_view, name='new'),
path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'), path('new/id', new_id_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', EditInterventionFormView.as_view(), name='edit'), path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', RemoveInterventionView.as_view(), name='remove'), path('<id>/remove', remove_view, name='remove'),
path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'), path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'),
path('<id>/share', InterventionShareFormView.as_view(), name='share-form'), path('<id>/share', InterventionShareFormView.as_view(), name='share-form'),
path('<id>/check', InterventionCheckView.as_view(), name='check'), path('<id>/check', check_view, name='check'),
path('<id>/record', InterventionRecordView.as_view(), name='record'), path('<id>/record', InterventionRecordView.as_view(), name='record'),
path('<id>/report', InterventionReportView.as_view(), name='report'), path('<id>/report', report_view, name='report'),
path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'), path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'),
# Compensations # Compensations
@ -55,10 +54,10 @@ urlpatterns = [
path('<id>/deduction/<deduction_id>/remove', RemoveInterventionDeductionView.as_view(), name='remove-deduction'), path('<id>/deduction/<deduction_id>/remove', RemoveInterventionDeductionView.as_view(), name='remove-deduction'),
# Revocation routes # Revocation routes
path('<id>/revocation/new', NewRevocationView.as_view(), name='new-revocation'), path('<id>/revocation/new', new_revocation_view, name='new-revocation'),
path('<id>/revocation/<revocation_id>/edit', EditRevocationView.as_view(), name='edit-revocation'), path('<id>/revocation/<revocation_id>/edit', edit_revocation_view, name='edit-revocation'),
path('<id>/revocation/<revocation_id>/remove', RemoveRevocationView.as_view(), name='remove-revocation'), path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'),
path('revocation/<doc_id>', GetRevocationDocumentView.as_view(), name='get-doc-revocation'), path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
# Autocomplete # Autocomplete
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="autocomplete"), path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="autocomplete"),

View File

@ -5,24 +5,35 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from intervention.forms.modals.check import CheckModalForm from intervention.forms.modals.check import CheckModalForm
from intervention.models import Intervention from intervention.models import Intervention
from konova.views.base import BaseModalFormView from konova.decorators import registration_office_group_required, shared_access_required
from konova.utils.message_templates import INTERVENTION_INVALID
class InterventionCheckView(LoginRequiredMixin, BaseModalFormView): @login_required
_MODEL_CLS = Intervention @registration_office_group_required
_FORM_CLS = CheckModalForm @shared_access_required(Intervention, "id")
_MSG_SUCCESS = _("Check performed") def check_view(request: HttpRequest, id: str):
_REDIRECT_URL = "intervention:detail" """ Renders check form for an intervention
def _user_has_permission(self, user, **kwargs): Args:
return user.is_zb_user() request (HttpRequest): The incoming request
id (str): Intervention's id
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
form = CheckModalForm(request.POST or None, instance=intervention, request=request)
return form.process_request(
request,
msg_success=_("Check performed"),
msg_error=INTERVENTION_INVALID
)
def _get_redirect_url(self, *args, **kwargs):
redirect_url = super()._get_redirect_url(*args, **kwargs)
redirect_url += "#related_data"
return redirect_url

View File

@ -5,27 +5,51 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from intervention.models import Intervention from intervention.models import Intervention
from konova.utils.message_templates import DEDUCTION_ADDED, DEDUCTION_EDITED, DEDUCTION_REMOVED from konova.decorators import default_group_required, shared_access_required
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
_INTERVENTION_DETAIL_URL_NAME = "intervention:detail"
class NewInterventionDeductionView(LoginRequiredMixin, AbstractNewDeductionView): class NewInterventionDeductionView(AbstractNewDeductionView):
_MODEL_CLS = Intervention def _custom_check(self, obj):
_MSG_SUCCESS = DEDUCTION_ADDED pass
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
model = Intervention
redirect_url = "intervention:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditInterventionDeductionView(LoginRequiredMixin, AbstractEditDeductionView): class EditInterventionDeductionView(AbstractEditDeductionView):
_MODEL_CLS = Intervention def _custom_check(self, obj):
_MSG_SUCCESS = DEDUCTION_EDITED pass
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
model = Intervention
redirect_url = "intervention:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveInterventionDeductionView(LoginRequiredMixin, AbstractRemoveDeductionView): class RemoveInterventionDeductionView(AbstractRemoveDeductionView):
_MODEL_CLS = Intervention def _custom_check(self, obj):
_MSG_SUCCESS = DEDUCTION_REMOVED pass
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
model = Intervention
redirect_url = "intervention:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,33 +5,59 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from intervention.forms.modals.document import NewInterventionDocumentModalForm, EditInterventionDocumentModalForm, \ from django.contrib.auth.decorators import login_required
RemoveInterventionDocumentModalForm from django.utils.decorators import method_decorator
from intervention.forms.modals.document import NewInterventionDocumentModalForm
from intervention.models import Intervention, InterventionDocument from intervention.models import Intervention, InterventionDocument
from konova.decorators import default_group_required, shared_access_required
from konova.forms.modals import EditDocumentModalForm
from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \ from konova.views.document import AbstractNewDocumentView, AbstractGetDocumentView, AbstractRemoveDocumentView, \
AbstractEditDocumentView AbstractEditDocumentView
class NewInterventionDocumentView(AbstractNewDocumentView): class NewInterventionDocumentView(AbstractNewDocumentView):
_MODEL_CLS = Intervention model = Intervention
_DOCUMENT_MODEL = InterventionDocument form = NewInterventionDocumentModalForm
_FORM_CLS = NewInterventionDocumentModalForm redirect_url = "intervention:detail"
_REDIRECT_URL = "intervention:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class GetInterventionDocumentView(AbstractGetDocumentView): class GetInterventionDocumentView(AbstractGetDocumentView):
_MODEL_CLS = Intervention model = Intervention
_DOCUMENT_CLS = InterventionDocument document_model = InterventionDocument
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class RemoveInterventionDocumentView(AbstractRemoveDocumentView): class RemoveInterventionDocumentView(AbstractRemoveDocumentView):
_MODEL_CLS = Intervention model = Intervention
_DOCUMENT_CLS = InterventionDocument document_model = InterventionDocument
_FORM_CLS = RemoveInterventionDocumentModalForm
_REDIRECT_URL = "intervention:detail" @method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class EditInterventionDocumentView(AbstractEditDocumentView): class EditInterventionDocumentView(AbstractEditDocumentView):
_MODEL_CLS = Intervention model = Intervention
_DOCUMENT_CLS = InterventionDocument document_model = InterventionDocument
_FORM_CLS = EditInterventionDocumentModalForm form = EditDocumentModalForm
_REDIRECT_URL = "intervention:detail" redirect_url = "intervention:detail"
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -7,115 +7,207 @@ 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.http import JsonResponse, HttpRequest
from django.http import HttpRequest
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from intervention.forms.intervention import EditInterventionForm, NewInterventionForm from intervention.forms.intervention import EditInterventionForm, NewInterventionForm
from intervention.models import Intervention from intervention.models import Intervention
from intervention.tables import InterventionTable from intervention.tables import InterventionTable
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import default_group_required, shared_access_required from konova.decorators import default_group_required, shared_access_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.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 DATA_CHECKED_PREVIOUSLY_TEMPLATE, RECORDED_BLOCKS_EDIT, \ from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, RECORDED_BLOCKS_EDIT, \
CHECK_STATE_RESET, FORM_INVALID, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, \
from konova.views.base import BaseIndexView, BaseIdentifierGeneratorView, BaseNewSpatialLocatedObjectFormView, \ GEOMETRIES_IGNORED_TEMPLATE
BaseEditSpatialLocatedObjectFormView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class InterventionIndexView(LoginRequiredMixin, BaseIndexView): @login_required
_INDEX_TABLE_CLS = InterventionTable @any_group_check
_TAB_TITLE = _("Interventions - Overview") def index_view(request: HttpRequest):
"""
Renders the index view for Interventions
def _get_queryset(self): Args:
qs = Intervention.objects.filter( request (HttpRequest): The incoming request
deleted=None,
).select_related( Returns:
"legal" A rendered view
).order_by( """
"-modified__timestamp" template = "generic_index.html"
)
return qs # Filtering by user access is performed in table filter inside InterventionTableFilter class
interventions = Intervention.objects.filter(
deleted=None, # not deleted
).select_related(
"legal"
).order_by(
"-modified__timestamp"
)
table = InterventionTable(
request=request,
queryset=interventions
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("Interventions - Overview"),
}
context = BaseContext(request, context).context
return render(request, template, context)
class NewInterventionFormView(BaseNewSpatialLocatedObjectFormView): @login_required
_MODEL_CLS = Intervention @default_group_required
_FORM_CLS = NewInterventionForm def new_view(request: HttpRequest):
_TEMPLATE = "intervention/form/view.html" """
_REDIRECT_URL = "intervention:detail" Renders a view for a new intervention creation
_TAB_TITLE = _("New intervention")
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "intervention/form/view.html"
data_form = NewInterventionForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
generated_identifier = data_form.cleaned_data.get("identifier", None)
intervention = data_form.save(request.user, geom_form)
if generated_identifier != intervention.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
intervention.identifier
)
)
messages.success(request, _("Intervention {} added").format(intervention.identifier))
if geom_form.has_geometry_simplified():
messages.info(
request,
GEOMETRY_SIMPLIFIED
)
num_ignored_geometries = geom_form.get_num_geometries_ignored()
if num_ignored_geometries > 0:
messages.info(
request,
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
)
return redirect("intervention:detail", id=intervention.id)
else:
messages.error(request, FORM_INVALID, extra_tags="danger",)
else:
# For clarification: nothing in this case
pass
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New intervention"),
}
context = BaseContext(request, context).context
return render(request, template, context)
class EditInterventionFormView(BaseEditSpatialLocatedObjectFormView): @login_required
_MODEL_CLS = Intervention @default_group_required
_FORM_CLS = EditInterventionForm def new_id_view(request: HttpRequest):
_TEMPLATE = "intervention/form/view.html" """ JSON endpoint
_REDIRECT_URL = "intervention:detail"
_TAB_TITLE = _("Edit {}")
Provides fetching of free identifiers for e.g. AJAX calls
class InterventionIdentifierGeneratorView(LoginRequiredMixin, BaseIdentifierGeneratorView): """
_MODEL_CLS = Intervention tmp_intervention = Intervention()
_REDIRECT_URL = "intervention:index" identifier = tmp_intervention.generate_new_identifier()
while Intervention.objects.filter(identifier=identifier).exists():
identifier = tmp_intervention.generate_new_identifier()
class InterventionDetailView(BaseDetailView): return JsonResponse(
_MODEL_CLS = Intervention data={
_TEMPLATE = "intervention/detail/view.html" "gen_data": identifier
def _get_object(self, id: str):
""" Returns the intervention
Args:
id (str): The intervention's id
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
def _get_detail_context(self, obj: Intervention):
""" Generate object specific detail context for view
Args:
obj (): The record
Returns:
"""
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 = {
"last_checked": last_checked,
"last_checked_tooltip": last_checked_tooltip,
"compensations": compensations,
"has_payment_without_document": has_payment_without_document,
} }
return context )
@login_required
@any_group_check
@uuid_required
def detail_view(request: HttpRequest, id: str):
""" Renders a detail view for viewing an intervention's data
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id
Returns:
"""
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
)
has_payment_without_document = intervention.payments.exists() and not intervention.get_documents()[1].exists()
requesting_user_is_only_shared_user = intervention.is_only_shared_with(_user)
if requesting_user_is_only_shared_user:
messages.info(
request,
DO_NOT_FORGET_TO_SHARE
)
context = {
"obj": intervention,
"last_checked": last_checked,
"last_checked_tooltip": last_checked_tooltip,
"compensations": compensations,
"is_entry_shared": is_data_shared,
"geom_form": geom_form,
"is_default_member": _user.in_group(DEFAULT_GROUP),
"is_zb_member": _user.in_group(ZB_GROUP),
"is_ets_member": _user.in_group(ETS_GROUP),
"LANIS_LINK": intervention.get_LANIS_link(),
"has_payment_without_document": has_payment_without_document,
TAB_TITLE_IDENTIFIER: f"{intervention.identifier} - {intervention.title}",
}
request = intervention.set_status_messages(request)
context = BaseContext(request, context).context
return render(request, template, context)
@login_required @login_required
@ -180,6 +272,26 @@ def edit_view(request: HttpRequest, id: str):
context = BaseContext(request, context).context context = BaseContext(request, context).context
return render(request, template, context) return render(request, template, context)
class RemoveInterventionView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = Intervention @login_required_modal
_REDIRECT_URL = "intervention:index" @login_required
@default_group_required
@shared_access_required(Intervention, "id")
def remove_view(request: HttpRequest, id: str):
""" Renders a remove view for this intervention
Args:
request (HttpRequest): The incoming request
id (str): The uuid id as string
Returns:
"""
obj = Intervention.objects.get(id=id)
identifier = obj.identifier
form = RemoveModalForm(request.POST or None, instance=obj, request=request)
return form.process_request(
request,
_("{} removed").format(identifier),
redirect_url=reverse("intervention:index")
)

View File

@ -5,11 +5,19 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import shared_access_required, default_group_required
from konova.views.log import AbstractLogView from konova.views.log import AbstractLogView
class InterventionLogView(LoginRequiredMixin, AbstractLogView): class InterventionLogView(AbstractLogView):
_MODEL_CLS = Intervention model = Intervention
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,12 +5,19 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import conservation_office_group_required, shared_access_required
from konova.views.record import AbstractRecordView from konova.views.record import AbstractRecordView
class InterventionRecordView(LoginRequiredMixin, AbstractRecordView): class InterventionRecordView(AbstractRecordView):
_MODEL_CLS = Intervention model = Intervention
_REDIRECT_URL = "intervention:detail"
@method_decorator(login_required)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -5,41 +5,72 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.http import HttpRequest
from django.shortcuts import get_object_or_404, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from intervention.models import Intervention from intervention.models import Intervention
from konova.sub_settings.django_settings import BASE_URL from konova.contexts import BaseContext
from konova.utils.qrcode import QrCode from konova.decorators import uuid_required
from konova.views.report import BaseReportView from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.generators import generate_qr_code
class InterventionReportView(BaseReportView): @uuid_required
_TEMPLATE = 'intervention/report/report.html' def report_view(request: HttpRequest, id: str):
_MODEL = Intervention """ Renders the public report view
def _get_report_context(self, obj: Intervention): Args:
""" Returns the specific context needed for an intervention report request (HttpRequest): The incoming request
id (str): The id of the intervention
Args: Returns:
obj (Intervention): The object for the report
Returns: """
dict: The object specific context for rendering the report template = "intervention/report/report.html"
""" intervention = get_object_or_404(Intervention, id=id)
distinct_deductions = obj.deductions.all().distinct("account")
report_url = BASE_URL + reverse("intervention:report", args=(obj.id,))
qrcode_report = QrCode(report_url, 10)
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
return { tab_title = _("Report {}").format(intervention.identifier)
"deductions": distinct_deductions, # If intervention is not recorded (yet or currently) we need to render another template without any data
"qrcode": { if not intervention.is_ready_for_publish():
"img": qrcode_report.get_img(), template = "report/unavailable.html"
"url": qrcode_report.get_content(), context = {
}, TAB_TITLE_IDENTIFIER: tab_title,
"qrcode_lanis": {
"img": qrcode_lanis.get_img(),
"url": qrcode_lanis.get_content(),
},
"tables_scrollable": False,
} }
context = BaseContext(request, context).context
return render(request, template, context)
# Prepare data for map viewer
geom_form = SimpleGeomForm(
instance=intervention
)
parcels = intervention.get_underlying_parcels()
distinct_deductions = intervention.deductions.all().distinct(
"account"
)
qrcode_url = request.build_absolute_uri(reverse("intervention:report", args=(id,)))
qrcode_img = generate_qr_code(qrcode_url, 10)
qrcode_lanis_url = intervention.get_LANIS_link()
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
context = {
"obj": intervention,
"deductions": distinct_deductions,
"qrcode": {
"img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"geom_form": geom_form,
"parcels": parcels,
"tables_scrollable": False,
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)

View File

@ -5,12 +5,22 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from intervention.forms.modals.resubmission import InterventionResubmissionModalForm from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import default_group_required, shared_access_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView from konova.views.resubmission import AbstractResubmissionView
class InterventionResubmissionView(AbstractResubmissionView): class InterventionResubmissionView(AbstractResubmissionView):
_MODEL_CLS = Intervention model = Intervention
_FORM_CLS = InterventionResubmissionModalForm redirect_url_base = "intervention:detail"
_REDIRECT_URL = "intervention:detail" form_action_url_base = "intervention:resubmission-create"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -6,71 +6,113 @@ Created on: 19.08.22
""" """
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from intervention.forms.modals.revocation import NewRevocationModalForm, EditRevocationModalForm, \ from intervention.forms.modals.revocation import NewRevocationModalForm, EditRevocationModalForm, \
RemoveRevocationModalForm RemoveRevocationModalForm
from intervention.models import Intervention, RevocationDocument from intervention.models import Intervention, RevocationDocument, Revocation
from konova.decorators import default_group_required, shared_access_required, login_required_modal
from konova.utils.documents import get_document from konova.utils.documents import get_document
from konova.utils.message_templates import DATA_UNSHARED, REVOCATION_EDITED, REVOCATION_REMOVED, REVOCATION_ADDED from konova.utils.message_templates import REVOCATION_ADDED, DATA_UNSHARED, REVOCATION_EDITED, REVOCATION_REMOVED
from konova.views.base import BaseModalFormView, BaseView
class BaseRevocationView(LoginRequiredMixin, BaseModalFormView): @login_required
_MODEL_CLS = Intervention @default_group_required
_REDIRECT_URL = "intervention:detail" @shared_access_required(Intervention, "id")
def new_revocation_view(request: HttpRequest, id: str):
""" Renders sharing form for an intervention
class Meta: Args:
abstract = True request (HttpRequest): The incoming request
id (str): Intervention's id
def _user_has_permission(self, user, **kwargs): Returns:
return user.is_default_user()
def _get_redirect_url(self, *args, **kwargs): """
url = super()._get_redirect_url(*args, **kwargs) intervention = get_object_or_404(Intervention, id=id)
return f"{url}#related_data" form = NewRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, request=request)
return form.process_request(
request,
msg_success=REVOCATION_ADDED,
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
)
class NewRevocationView(BaseRevocationView): @login_required
_FORM_CLS = NewRevocationModalForm @default_group_required
_MSG_SUCCESS = REVOCATION_ADDED def get_revocation_view(request: HttpRequest, doc_id: str):
""" Returns the revocation document as downloadable file
Wraps the generic document fetcher function from konova.utils.
Args:
request (HttpRequest): The incoming request
doc_id (str): The document id
Returns:
"""
doc = get_object_or_404(RevocationDocument, id=doc_id)
# File download only possible if related instance is shared with user
if not doc.instance.legal.intervention.users.filter(id=request.user.id):
messages.info(
request,
DATA_UNSHARED
)
return redirect("intervention:detail", id=doc.instance.id)
return get_document(doc)
class EditRevocationView(BaseRevocationView): @login_required
_FORM_CLS = EditRevocationModalForm @default_group_required
_MSG_SUCCESS = REVOCATION_EDITED @shared_access_required(Intervention, "id")
def edit_revocation_view(request: HttpRequest, id: str, revocation_id: str):
""" Renders a edit view for a revocation
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id as string
revocation_id (str): The revocation's id as string
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
revocation = get_object_or_404(Revocation, id=revocation_id)
form = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, revocation=revocation, request=request)
return form.process_request(
request,
REVOCATION_EDITED,
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
)
class RemoveRevocationView(BaseRevocationView): @login_required_modal
_FORM_CLS = RemoveRevocationModalForm @login_required
_MSG_SUCCESS = REVOCATION_REMOVED @default_group_required
@shared_access_required(Intervention, "id")
def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
""" Renders a remove view for a revocation
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id as string
revocation_id (str): The revocation's id as string
class GetRevocationDocumentView(LoginRequiredMixin, BaseView): Returns:
_MODEL_CLS = RevocationDocument
_REDIRECT_URL = "intervention:detail"
def get(self, request: HttpRequest, doc_id: str): """
doc = get_object_or_404(RevocationDocument, id=doc_id) intervention = get_object_or_404(Intervention, id=id)
# File download only possible if related instance is shared with user revocation = get_object_or_404(Revocation, id=revocation_id)
if not doc.instance.legal.intervention.users.filter(id=request.user.id):
messages.info(
request,
DATA_UNSHARED
)
return redirect("intervention:detail", id=doc.instance.id)
return get_document(doc)
def _user_has_permission(self, user, **kwargs): form = RemoveRevocationModalForm(request.POST or None, instance=intervention, revocation=revocation, request=request)
return user.is_default_user() return form.process_request(
request,
REVOCATION_REMOVED,
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
)
def _user_has_shared_access(self, user, **kwargs):
obj = get_object_or_404(self._MODEL_CLS, id=kwargs.get("doc_id"))
assert obj is not None
return obj.instance.intervention.is_shared_with(user)
def _get_redirect_url(self, *args, **kwargs):
url = super()._get_redirect_url(*args, **kwargs)
return f"{url}#related_data"

View File

@ -5,15 +5,29 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import default_group_required, shared_access_required, login_required_modal
from konova.views.share import AbstractShareByTokenView, AbstractShareFormView from konova.views.share import AbstractShareByTokenView, AbstractShareFormView
class InterventionShareByTokenView(AbstractShareByTokenView): class InterventionShareByTokenView(AbstractShareByTokenView):
_MODEL_CLS = Intervention model = Intervention
_REDIRECT_URL = "intervention:detail" redirect_url = "intervention:detail"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class InterventionShareFormView(AbstractShareFormView): class InterventionShareFormView(AbstractShareFormView):
_MODEL_CLS = Intervention model = Intervention
_REDIRECT_URL = "intervention:detail"
@method_decorator(login_required_modal)
@method_decorator(login_required)
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

View File

@ -10,18 +10,21 @@ from abc import abstractmethod
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount
from konova.models import BaseObject
class BaseForm(forms.Form): class BaseForm(forms.Form):
""" """
Basic form for that holds attributes needed in all other forms Basic form for that holds attributes needed in all other forms
""" """
template = None
action_url = None action_url = None
action_btn_label = _("Save") action_btn_label = _("Save")
form_title = None form_title = None
cancel_redirect = None cancel_redirect = None
form_caption = None form_caption = None
instance = None # The data holding model object instance = None # The data holding model object
user = None # The performing user
request = None request = None
form_attrs = {} # Holds additional attributes, that can be used in the template form_attrs = {} # Holds additional attributes, that can be used in the template
has_required_fields = False # Automatically set. Triggers hint rendering in templates has_required_fields = False # Automatically set. Triggers hint rendering in templates
@ -30,7 +33,6 @@ class BaseForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.instance = kwargs.pop("instance", None) self.instance = kwargs.pop("instance", None)
self.user = kwargs.pop("user", None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.request is not None: if self.request is not None:
self.user = self.request.user self.user = self.request.user
@ -40,10 +42,11 @@ class BaseForm(forms.Form):
self.has_required_fields = True self.has_required_fields = True
break break
self.check_for_recorded_instance()
self.__check_valid_label_input_ratio() self.__check_valid_label_input_ratio()
@abstractmethod @abstractmethod
def save(self, *arg, **kwargs): def save(self):
# To be implemented in subclasses! # To be implemented in subclasses!
pass pass
@ -133,3 +136,34 @@ class BaseForm(forms.Form):
set_class = self.fields[field].widget.attrs.get("class", "") set_class = self.fields[field].widget.attrs.get("class", "")
set_class = set_class.replace(cls, "") set_class = set_class.replace(cls, "")
self.fields[field].widget.attrs["class"] = set_class self.fields[field].widget.attrs["class"] = set_class
def check_for_recorded_instance(self):
""" Checks if the instance is recorded and runs some special logic if yes
If the instance is recorded, the form shall not display any possibility to
edit any data. Instead, the users should get some information about why they can not edit anything.
There are situations where the form should be rendered regularly,
e.g deduction forms for (recorded) eco accounts.
Returns:
"""
is_none = self.instance is None
is_other_data_type = not isinstance(self.instance, BaseObject)
if is_none or is_other_data_type:
# Do nothing
return
if self.instance.is_recorded:
self.block_form()
def block_form(self):
"""
Overwrites template, providing no actions
Returns:
"""
self.template = "form/recorded_no_edit.html"

View File

@ -23,7 +23,7 @@ class BaseModalForm(BaseForm, BSModalForm):
""" """
is_modal_form = True is_modal_form = True
render_submit = True render_submit = True
_TEMPLATE = "modal/modal_form.html" template = "modal/modal_form.html"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -43,7 +43,7 @@ class BaseModalForm(BaseForm, BSModalForm):
""" """
redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home") redirect_url = redirect_url if redirect_url is not None else request.META.get("HTTP_REFERER", "home")
template = self._TEMPLATE template = self.template
if request.method == "POST": if request.method == "POST":
if self.is_valid(): if self.is_valid():
if not is_ajax(request.META): if not is_ajax(request.META):

View File

@ -8,10 +8,10 @@ Created on: 15.08.22
from django import forms from django import forms
from django.db import transaction from django.db import transaction
from django.db.models.fields.files import FieldFile from django.db.models.fields.files import FieldFile
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from konova.forms.modals.base_form import BaseModalForm from konova.forms.modals.base_form import BaseModalForm
from konova.models import AbstractDocument
from konova.utils import validators from konova.utils import validators
from konova.utils.message_templates import DOCUMENT_EDITED, FILE_SIZE_TOO_LARGE, FILE_TYPE_UNSUPPORTED from konova.utils.message_templates import DOCUMENT_EDITED, FILE_SIZE_TOO_LARGE, FILE_TYPE_UNSUPPORTED
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
@ -69,7 +69,7 @@ class NewDocumentModalForm(BaseModalForm):
} }
) )
) )
_DOCUMENT_CLS = None document_model = None
class Meta: class Meta:
abstract = True abstract = True
@ -81,7 +81,7 @@ class NewDocumentModalForm(BaseModalForm):
self.form_attrs = { self.form_attrs = {
"enctype": "multipart/form-data", # important for file upload "enctype": "multipart/form-data", # important for file upload
} }
if not self._DOCUMENT_CLS: if not self.document_model:
raise NotImplementedError("Unsupported document type for {}".format(self.instance.__class__)) raise NotImplementedError("Unsupported document type for {}".format(self.instance.__class__))
def is_valid(self): def is_valid(self):
@ -93,14 +93,14 @@ class NewDocumentModalForm(BaseModalForm):
# FieldFile declares that no new file has been uploaded and we do not need to check on the file again # FieldFile declares that no new file has been uploaded and we do not need to check on the file again
return super_valid return super_valid
mime_type_valid = self._DOCUMENT_CLS.is_mime_type_valid(_file) mime_type_valid = self.document_model.is_mime_type_valid(_file)
if not mime_type_valid: if not mime_type_valid:
self.add_error( self.add_error(
"file", "file",
FILE_TYPE_UNSUPPORTED FILE_TYPE_UNSUPPORTED
) )
file_size_valid = self._DOCUMENT_CLS.is_file_size_valid(_file) file_size_valid = self.document_model.is_file_size_valid(_file)
if not file_size_valid: if not file_size_valid:
self.add_error( self.add_error(
"file", "file",
@ -115,7 +115,7 @@ class NewDocumentModalForm(BaseModalForm):
action = UserActionLogEntry.get_created_action(self.user) action = UserActionLogEntry.get_created_action(self.user)
edited_action = UserActionLogEntry.get_edited_action(self.user, _("Added document")) edited_action = UserActionLogEntry.get_edited_action(self.user, _("Added document"))
doc = self._DOCUMENT_CLS.objects.create( doc = self.document_model.objects.create(
created=action, created=action,
title=self.cleaned_data["title"], title=self.cleaned_data["title"],
comment=self.cleaned_data["comment"], comment=self.cleaned_data["comment"],
@ -133,12 +133,10 @@ class NewDocumentModalForm(BaseModalForm):
class EditDocumentModalForm(NewDocumentModalForm): class EditDocumentModalForm(NewDocumentModalForm):
document = None document = None
_DOCUMENT_CLS = None document_model = AbstractDocument
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
doc_id = kwargs.pop("doc_id", None) self.document = kwargs.pop("document", None)
self.document = get_object_or_404(self._DOCUMENT_CLS, id=doc_id)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form_title = _("Edit document") self.form_title = _("Edit document")
form_data = { form_data = {

View File

@ -6,11 +6,10 @@ Created on: 15.08.22
""" """
from django import forms from django import forms
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from konova.forms.modals.base_form import BaseModalForm from konova.forms.modals.base_form import BaseModalForm
from konova.models import BaseObject, Deadline from konova.models import BaseObject
class RemoveModalForm(BaseModalForm): class RemoveModalForm(BaseModalForm):
@ -52,19 +51,9 @@ class RemoveDeadlineModalForm(RemoveModalForm):
deadline = None deadline = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
deadline_id = kwargs.pop("deadline_id", None) deadline = kwargs.pop("deadline", None)
self.deadline = get_object_or_404(Deadline, id=deadline_id) self.deadline = deadline
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def save(self): def save(self):
self.instance.remove_deadline(self) self.instance.remove_deadline(self)
class RemoveDocumentModalForm(RemoveModalForm):
instance = None
_DOCUMENT_CLS = None
def __init__(self, *args, **kwargs):
document_id = kwargs.pop("doc_id", None)
super().__init__(*args, **kwargs)
self.instance = get_object_or_404(self._DOCUMENT_CLS, id=document_id)

View File

@ -291,5 +291,5 @@ Overwrites netgis.css attributes
} }
.netgis-menu{ .netgis-menu{
z-index: 100 !important; z-index: 1 !important;
} }

View File

@ -543,7 +543,7 @@ class BaseViewTestCase(BaseTestCase):
for url, redirect_to in urls.items(): for url, redirect_to in urls.items():
response = client.get(url, follow=True) response = client.get(url, follow=True)
# Expect redirects to the landing page # Expect redirects to the landing page
self.assertEqual(response.redirect_chain[0], (redirect_to, 302), msg=f"Failed for {url}. Expected {redirect_to}") self.assertEqual(response.redirect_chain[0], (redirect_to, 302), msg=f"Failed for {url}")
def assert_url_fail(self, client: Client, urls: list): def assert_url_fail(self, client: Client, urls: list):
""" Assert for all given urls a direct 302 response """ Assert for all given urls a direct 302 response

View File

@ -103,7 +103,7 @@ class EditDeadlineModalFormTestCase(NewDeadlineModalFormTestCase):
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
deadline_id=self.finished_deadline.id, deadline=self.finished_deadline,
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)

View File

@ -17,9 +17,9 @@ from django.utils.timezone import now
from compensation.forms.modals.document import NewEcoAccountDocumentModalForm, NewCompensationDocumentModalForm from compensation.forms.modals.document import NewEcoAccountDocumentModalForm, NewCompensationDocumentModalForm
from compensation.models import Payment from compensation.models import Payment
from ema.forms import NewEmaDocumentModalForm from ema.forms import NewEmaDocumentModalForm
from intervention.forms.modals.document import NewInterventionDocumentModalForm, EditInterventionDocumentModalForm from intervention.forms.modals.document import NewInterventionDocumentModalForm
from intervention.models import InterventionDocument from intervention.models import InterventionDocument
from konova.forms.modals import NewDocumentModalForm, RecordModalForm, RemoveModalForm, \ from konova.forms.modals import EditDocumentModalForm, NewDocumentModalForm, RecordModalForm, RemoveModalForm, \
RemoveDeadlineModalForm, ResubmissionModalForm RemoveDeadlineModalForm, ResubmissionModalForm
from konova.models import Resubmission from konova.models import Resubmission
from konova.tests.test_views import BaseTestCase from konova.tests.test_views import BaseTestCase
@ -106,12 +106,12 @@ class EditDocumentModalFormTestCase(NewDocumentModalFormTestCase):
InterventionDocument, InterventionDocument,
instance=self.intervention instance=self.intervention
) )
self.form = EditInterventionDocumentModalForm( self.form = EditDocumentModalForm(
self.data, self.data,
dummy_file_dict, dummy_file_dict,
request=self.request, request=self.request,
instance=self.intervention, instance=self.intervention,
doc_id=self.doc.id document=self.doc
) )
def test_init(self): def test_init(self):
@ -122,6 +122,7 @@ class EditDocumentModalFormTestCase(NewDocumentModalFormTestCase):
self.assertEqual(self.form.fields["title"].initial, self.doc.title) self.assertEqual(self.form.fields["title"].initial, self.doc.title)
self.assertEqual(self.form.fields["comment"].initial, self.doc.comment) self.assertEqual(self.form.fields["comment"].initial, self.doc.comment)
self.assertEqual(self.form.fields["creation_date"].initial, self.doc.date_of_creation) self.assertEqual(self.form.fields["creation_date"].initial, self.doc.date_of_creation)
self.assertEqual(self.form.fields["file"].initial, self.doc.file)
def test_save(self): def test_save(self):
self.assertTrue(self.form.is_valid(), msg=self.form.errors) self.assertTrue(self.form.is_valid(), msg=self.form.errors)
@ -255,7 +256,7 @@ class RemoveDeadlineTestCase(BaseTestCase):
form = RemoveDeadlineModalForm( form = RemoveDeadlineModalForm(
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
deadline_id=self.finished_deadline.id deadline=self.finished_deadline
) )
self.assertEqual(form.form_title, str(_("Remove"))) self.assertEqual(form.form_title, str(_("Remove")))
self.assertEqual(form.form_caption, str(_("Are you sure?"))) self.assertEqual(form.form_caption, str(_("Are you sure?")))
@ -272,7 +273,7 @@ class RemoveDeadlineTestCase(BaseTestCase):
data, data,
request=self.request, request=self.request,
instance=self.compensation, instance=self.compensation,
deadline_id=self.finished_deadline.id deadline=self.finished_deadline
) )
self.assertTrue(form.is_valid(), msg=form.errors) self.assertTrue(form.is_valid(), msg=form.errors)
form.save() form.save()

View File

@ -7,7 +7,9 @@ Created on: 01.09.21
""" """
from django.http import FileResponse, HttpRequest, Http404 from django.http import FileResponse, HttpRequest, Http404
from konova.forms.modals import RemoveModalForm
from konova.models import AbstractDocument from konova.models import AbstractDocument
from konova.utils.message_templates import DOCUMENT_REMOVED_TEMPLATE
def get_document(doc: AbstractDocument): def get_document(doc: AbstractDocument):
@ -24,3 +26,28 @@ def get_document(doc: AbstractDocument):
return FileResponse(doc.file, as_attachment=True) return FileResponse(doc.file, as_attachment=True)
except FileNotFoundError: except FileNotFoundError:
raise Http404() raise Http404()
def remove_document(request: HttpRequest, doc: AbstractDocument):
""" Renders a form for uploading new documents
This function works using a modal. We are not using the regular way, the django bootstrap modal forms are
intended to be used. Instead of View classes we work using the classic way of dealing with forms (see below).
It is important to mention, that modal forms, which should reload the page afterwards, must provide a
'reload_page' bool in the context. This way, the modal may reload the page or not.
For further details see the comments in templates/modal or
https://github.com/trco/django-bootstrap-modal-forms
Args:
request (HttpRequest): The incoming request
Returns:
"""
title = doc.title
form = RemoveModalForm(request.POST or None, instance=doc, request=request)
return form.process_request(
request=request,
msg_success=DOCUMENT_REMOVED_TEMPLATE.format(title),
)

View File

@ -5,11 +5,6 @@ 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.utils.translation import gettext_lazy as _
from django.http import HttpRequest, Http404
def format_german_float(num) -> str: def format_german_float(num) -> str:
@ -24,27 +19,3 @@ def format_german_float(num) -> str:
num (str): The number as german Gleitkommazahl num (str): The number as german Gleitkommazahl
""" """
return format(num, "0,.2f").replace(",", "X").replace(".", ",").replace("X", ".") return format(num, "0,.2f").replace(",", "X").replace(".", ",").replace("X", ".")
def check_user_is_in_any_group(request: HttpRequest):
"""
Checks for any group membership. Adds a message in case of having none.
"""
user = request.user
# Inform user about missing group privileges!
groups = user.groups.all()
if not groups:
messages.info(
request,
_("+++ 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(uuid: str):
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

@ -7,6 +7,10 @@ Created on: 09.11.20
""" """
import random import random
import string import string
import qrcode
import qrcode.image.svg
from io import BytesIO
def generate_token() -> str: def generate_token() -> str:
@ -38,3 +42,23 @@ def generate_random_string(length: int, use_numbers: bool = False, use_letters_l
ret_val = "".join(random.choice(elements) for i in range(length)) ret_val = "".join(random.choice(elements) for i in range(length))
return ret_val return ret_val
def generate_qr_code(content: str, size: int = 20) -> str:
""" Generates a qr code from given content
Args:
content (str): The content for the qr code
size (int): The image size
Returns:
qrcode_svg (str): The qr code as svg
"""
qrcode_factory = qrcode.image.svg.SvgImage
qrcode_img = qrcode.make(
content,
image_factory=qrcode_factory,
box_size=size
)
stream = BytesIO()
qrcode_img.save(stream)
return stream.getvalue().decode()

View File

@ -19,20 +19,7 @@ IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since ano
ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office users are allowed to remove entries.") ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office users are allowed to remove entries.")
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.") MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
CHECK_STATE_RESET = _("Status of Checked reset") CHECK_STATE_RESET = _("Status of Checked reset")
# USER | TEAM
TEAM_ADDED = _("New team added")
TEAM_EDITED = _("Team edited")
TEAM_REMOVED = _("Team removed")
TEAM_LEFT = _("Left Team")
# REMOVED
GENERIC_REMOVED_TEMPLATE = _("{} removed")
# RECORDING
RECORDED_BLOCKS_EDIT = _("Entry is recorded. To edit data, the entry first needs to be unrecorded.") RECORDED_BLOCKS_EDIT = _("Entry is recorded. To edit data, the entry first needs to be unrecorded.")
ENTRY_RECORDED = _("{} recorded")
ENTRY_UNRECORDED = _("{} unrecorded")
# SHARE # SHARE
DATA_UNSHARED = _("This data is not shared with you") DATA_UNSHARED = _("This data is not shared with you")
@ -107,7 +94,4 @@ DATA_CHECKED_PREVIOUSLY_TEMPLATE = _("Data has changed since last check on {} by
DATA_IS_UNCHECKED = _("Current data not checked yet") DATA_IS_UNCHECKED = _("Current data not checked yet")
# API TOKEN SETTINGS # API TOKEN SETTINGS
NEW_API_TOKEN_GENERATED = _("New token generated. Administrators need to validate.") NEW_API_TOKEN_GENERATED = _("New token generated. Administrators need to validate.")
# RESUBMISSION
NEW_RESUBMISSION_CREATED = _("Resubmission set")

View File

@ -1,47 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 17.10.25
"""
from io import BytesIO
import qrcode
import qrcode.image.svg as svg
class QrCode:
""" A wrapping class for creating a qr code with content
"""
_content = None
_img = None
def __init__(self, content: str, size: int):
self._content = content
self._img = self._generate_qr_code(content, size)
def _generate_qr_code(self, content: str, size: int = 20) -> str:
""" Generates a qr code from given content
Args:
content (str): The content for the qr code
size (int): The image size
Returns:
qrcode_svg (str): The qr code as svg
"""
img_factory = svg.SvgImage
qrcode_img = qrcode.make(
content,
image_factory=img_factory,
box_size=size
)
stream = BytesIO()
qrcode_img.save(stream)
return stream.getvalue().decode()
def get_img(self):
return self._img
def get_content(self):
return self._content

View File

@ -5,47 +5,104 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 22.08.22 Created on: 22.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.views import View
from compensation.forms.modals.compensation_action import NewCompensationActionModalForm, \ from compensation.forms.modals.compensation_action import NewCompensationActionModalForm, \
EditCompensationActionModalForm, RemoveCompensationActionModalForm EditCompensationActionModalForm, RemoveCompensationActionModalForm
from compensation.models import CompensationAction
from konova.utils.message_templates import COMPENSATION_STATE_ADDED, COMPENSATION_STATE_EDITED, \ from konova.utils.message_templates import COMPENSATION_STATE_ADDED, COMPENSATION_STATE_EDITED, \
COMPENSATION_STATE_REMOVED COMPENSATION_STATE_REMOVED
from konova.views.base import BaseModalFormView
class AbstractCompensationActionView(LoginRequiredMixin, BaseModalFormView): class AbstractCompensationActionView(View):
_MODEL_CLS = None model = None
_REDIRECT_URL = None redirect_url = None
class Meta: class Meta:
abstract = True abstract = True
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
def _get_redirect_url(self, *args, **kwargs):
return super()._get_redirect_url(*args, **kwargs) + "#related_data"
class AbstractNewCompensationActionView(AbstractCompensationActionView): class AbstractNewCompensationActionView(AbstractCompensationActionView):
_FORM_CLS = NewCompensationActionModalForm
_MSG_SUCCESS = COMPENSATION_STATE_ADDED
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str):
""" Renders a form for adding new actions
Args:
request (HttpRequest): The incoming request
id (str): The object's id to which the new action will be related
Returns:
"""
obj = get_object_or_404(self.model, id=id)
form = NewCompensationActionModalForm(request.POST or None, instance=obj, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_ADDED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str):
return self.get(request, id)
class AbstractEditCompensationActionView(AbstractCompensationActionView): class AbstractEditCompensationActionView(AbstractCompensationActionView):
_FORM_CLS = EditCompensationActionModalForm
_MSG_SUCCESS = COMPENSATION_STATE_EDITED
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str, action_id: str):
""" Renders a form for editing a action
Args:
request (HttpRequest): The incoming request
id (str): The object id
action_id (str): The action's id
Returns:
"""
obj = get_object_or_404(self.model, id=id)
action = get_object_or_404(CompensationAction, id=action_id)
form = EditCompensationActionModalForm(request.POST or None, instance=obj, action=action, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_EDITED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str, action_id: str):
return self.get(request, id, action_id)
class AbstractRemoveCompensationActionView(AbstractCompensationActionView): class AbstractRemoveCompensationActionView(AbstractCompensationActionView):
_FORM_CLS = RemoveCompensationActionModalForm
_MSG_SUCCESS = COMPENSATION_STATE_REMOVED
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str, action_id: str):
""" Renders a form for removing aaction
Args:
request (HttpRequest): The incoming request
id (str): The object id
action_id (str): The action's id
Returns:
"""
obj = get_object_or_404(self.model, id=id)
action = get_object_or_404(CompensationAction, id=action_id)
form = RemoveCompensationActionModalForm(request.POST or None, instance=obj, action=action, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_REMOVED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str, action_id: str):
return self.get(request, id, action_id)

View File

@ -1,641 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 15.10.25
"""
from abc import abstractmethod
from bootstrap_modal_forms.mixins import is_ajax
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpRequest, JsonResponse, HttpResponseRedirect
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse
from django.views import View
from django.utils.translation import gettext_lazy as _
from konova.contexts import BaseContext
from konova.forms import BaseForm, SimpleGeomForm
from konova.models import BaseObject
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.general import check_user_is_in_any_group
from konova.utils.message_templates import MISSING_GROUP_PERMISSION, DATA_UNSHARED, IDENTIFIER_REPLACED, \
GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE, RECORDED_BLOCKS_EDIT, FORM_INVALID
class BaseView(View):
""" An abstract base view
This class represents the root of all views on this project. It defines private variables which have to be used
by inheriting classes for proper generic inheriting.
"""
_TEMPLATE: str = "CHANGE_ME" # Path to template file
_TAB_TITLE: str = "CHANGE_ME" # Title displayed on browser tab
_REDIRECT_URL: str = "CHANGE_ME" # Default URL to redirect after processing (notation as django url "namespace:endpoint")
_REDIRECT_URL_ERROR: str = "home" # Default URL to redirect in case of an error (same notation)
class Meta:
abstract = True
def dispatch(self, request, *args, **kwargs):
""" Dispatching requests before forwarding them into GET or POST endpoints.
Defines basic checks which need to be done before a user can get access to any view inheriting from
this class.
Args:
request (HttpRequest): The incoming request
*args ():
**kwargs ():
Returns:
"""
request = check_user_is_in_any_group(request)
if not self._user_has_permission(request.user, **kwargs):
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)
@abstractmethod
def _user_has_permission(self, user, **kwargs):
""" Checks whether the user has permission to get this view rendered.
If no specific check is needed, this method can be overwritten with a simple True returning.
Args:
user (User): The performing user
**kwargs ():
Returns:
has_permission (bool): Whether the user has permission to see this view
"""
raise NotImplementedError("User permission not checked!")
@abstractmethod
def _user_has_shared_access(self, user, **kwargs):
""" Checks whether the user has shared access to this object.
If no shared-access-check is needed, this method can be overwritten with a simple True returning.
Args:
user (User): The performing user
**kwargs ():
Returns:
has_shared_access (bool): Whether the user has shared access
"""
raise NotImplementedError("Shared access not checked!")
def _get_redirect_url(self, *args, **kwargs):
""" Getter to construct a more specific, data dependant redirect URL
By default the method simply returns the pre-defined redirect URL.
Args:
*args ():
**kwargs ():
Returns:
url (str): Reversed redirect url
"""
return self._REDIRECT_URL
def _get_redirect_url_error(self, *args, **kwargs):
""" Getter to construct a more specific, data dependant redirect URL in error cases
By default the method simply returns the pre-defined redirect URL for errors.
Args:
*args ():
**kwargs ():
Returns:
url (str): Reversed redirect url
"""
return self._REDIRECT_URL_ERROR
class BaseModalFormView(BaseView):
""" Abstract base view providing logic to perform most modal form based view renderings
"""
_TEMPLATE: str = "modal/modal_form.html"
_MODEL_CLS = None
_FORM_CLS = None
_MSG_SUCCESS = None
class Meta:
abstract = True
def _user_has_shared_access(self, user, **kwargs):
""" Checks whether the user has shared access to this object.
For objects inheriting from BaseObject class the method 'is_shared_with()' is a handy
wrapper for checking shared access. For any other circumstances this method should be overwritten
to provide custom shared-access-checking logic.
If no shared-access-check is needed, this method can be overwritten with a simple True returning.
Args:
user (User): The performing user
**kwargs ():
Returns:
has_shared_access (bool): Whether the user has shared access
"""
obj = get_object_or_404(self._MODEL_CLS, id=kwargs.get("id"))
return obj.is_shared_with(user)
def get(self, request: HttpRequest, *args, **kwargs):
""" GET endpoint for rendering a view holding a modal form
Args:
request (HttpRequest): The incoming request
*args ():
**kwargs ():
Returns:
"""
# If there is an id provided as mapped parameter from the URL take it ...
_id = kwargs.pop("id", None)
try:
# ... and try to resolve it into a record
obj = self._MODEL_CLS.objects.get(id=_id)
self._check_for_recorded_instance(obj)
except ObjectDoesNotExist:
# ... If there is none, maybe we are currently processing
# the creation of a new object (therefore no id yet), so let's continue
obj = None
form = self._FORM_CLS(
request.POST or None,
request.FILES or None,
instance=obj,
request=request,
**kwargs
)
context = {
"form": form,
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
def post(self, request: HttpRequest, *args, **kwargs):
""" POST endpoint for processing form contents of a view
Args:
request (HttpRequest): The incoming request
*args ():
**kwargs ():
Returns:
"""
# If there is an id provided as mapped parameter from the URL take it ...
_id = kwargs.pop("id", None)
try:
# ... and try to resolve it into a record
obj = self._MODEL_CLS.objects.get(id=_id)
self._check_for_recorded_instance(obj)
except ObjectDoesNotExist:
# ... If there is none, maybe we are currently processing
# the creation of a new object (therefore no id yet), so let's continue
obj = None
form = self._FORM_CLS(
request.POST or None,
request.FILES or None,
instance=obj,
request=request,
**kwargs
)
# Get now the redirect url and take specifics of the obj into account for that.
# We do not do this after saving the form to avoid side effects due to possibly changed data
redirect_url = self._get_redirect_url(obj=obj)
if form.is_valid():
# Modal forms send one POST for checking on data validity. This is used to evaluate possible errors
# on the form. The second POST (if no errors have been found) is the 'proper' one,
# which we want to process by saving/commiting of the data to the database.
if not is_ajax(request.META):
# Get now the success message and take specifics of the obj into account for that
msg_success = self._get_msg_success(obj=obj, *args, **kwargs)
form.save()
messages.success(
request,
msg_success
)
return HttpResponseRedirect(redirect_url)
else:
context = {
"form": form,
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
def _get_redirect_url(self, *args, **kwargs):
""" Getter to construct a more specific, data dependant redirect URL (if needed)
Args:
*args ():
**kwargs ():
Returns:
url (str): Reversed redirect url
"""
obj = kwargs.get("obj", None)
if obj:
return reverse(self._REDIRECT_URL, args=(obj.id,))
else:
return reverse(self._REDIRECT_URL)
def _get_msg_success(self, *args, **kwargs):
""" Getter to construct a more specific, data dependant success message
Args:
*args ():
**kwargs ():
Returns:
"""
return self._MSG_SUCCESS
def _check_for_recorded_instance(self, obj):
""" Checks if the object on this view is recorded and runs some special logic if so
If the instance is recorded, the view should provide some information about why the user can not edit anything.
This behaviour is only intended to mask any form for instances based on the BaseObject class.
There are situations where the form should be rendered regularly, despite the instance being recorded,
e.g. for rendering deduction form contents on (recorded) eco accounts.
Returns:
"""
is_none = obj is None
is_other_data_type = not isinstance(obj, BaseObject)
if is_none or is_other_data_type:
# Do nothing
return
if obj.is_recorded:
# Replace default template with a blocking one
self._TEMPLATE = "form/recorded_no_edit.html"
class BaseIndexView(BaseView):
""" Abstract base class for index views
"""
_TEMPLATE: str = "generic_index.html"
_INDEX_TABLE_CLS = None
_REDIRECT_URL: str = "home"
class Meta:
abstract = True
def get(self, request: HttpRequest, *args, **kwargs):
""" GET endpoint for rendering index views
Args:
request (HttpRequest): The incoming request
*args ():
**kwargs ():
Returns:
"""
qs = self._get_queryset()
table = self._INDEX_TABLE_CLS(
request=request,
queryset=qs
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: self._TAB_TITLE,
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
@abstractmethod
def _get_queryset(self):
""" Generic getter for the queryset of objects which shall be processed on this view
Returns:
"""
raise NotImplementedError
def _user_has_permission(self, user, **kwargs):
# No specific permissions needed for opening base index view
return True
def _user_has_shared_access(self, user, **kwargs):
# No specific constraints for shared access of index views
return True
class BaseIdentifierGeneratorView(BaseView):
_MODEL_CLS = None
_REDIRECT_URL: str = "home"
class Meta:
abstract = True
def get(self, request: HttpRequest):
tmp_obj = self._MODEL_CLS()
identifier = tmp_obj.generate_new_identifier()
while self._MODEL_CLS.objects.filter(identifier=identifier).exists():
identifier = tmp_obj.generate_new_identifier()
return JsonResponse(
data={
"gen_data": identifier
}
)
def _user_has_permission(self, user, **kwargs):
""" Should be overwritten in inheriting classes!
Args:
user ():
Returns:
"""
return user.is_default_user()
def _user_has_shared_access(self, user, **kwargs):
# No specific constraints for shared access
return True
class BaseFormView(BaseView):
""" Abstract base class for rendering form views
"""
_MODEL_CLS = None
_FORM_CLS = None
class Meta:
abstract = True
def _get_additional_context(self, **kwargs):
""" Getter for additional data, which is needed to properly render the current view
Args:
**kwargs ():
Returns:
context (dict): Additional context data for rendering
"""
return {}
class BaseSpatialLocatedObjectFormView(LoginRequiredMixin, BaseFormView):
""" Abstract base view for processing objects with spatial data
"""
_GEOMETRY_FORM_CLS = SimpleGeomForm
class Meta:
abstract = True
class BaseNewSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView):
""" Base view for creating new spatial data related to objects
"""
def _user_has_permission(self, user, **kwargs):
# User has to have default privilege to call this endpoint
return user.is_default_user()
def _user_has_shared_access(self, user, **kwargs):
# There is no shared access control since nothing exists yet
return True
def get(self, request: HttpRequest, *args, **kwargs):
""" GET endpoint for rendering a form view where object data and spatial data are processed
Args:
request (HttpRequest): The incoming request
**kwargs ():
Returns:
"""
# First initialize the regular object form and the geometry form based on request-bound data
form: BaseForm = self._FORM_CLS(None, **kwargs, user=request.user)
geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(None, user=request.user, read_only=False)
# Get some additional context and put everything into the rendering pipeline
context = self._get_additional_context()
context = BaseContext(request, additional_context=context).context
context.update(
{
"form": form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: self._TAB_TITLE,
}
)
return render(request, self._TEMPLATE, context)
def post(self, request: HttpRequest, *args, **kwargs):
""" POST endpoint for processing object and spatial data provided by forms
Args:
request (HttpRequest): The incoming request
**kwargs ():
Returns:
"""
# First initialize the regular object form and the geometry form based on request-bound data
form: BaseForm = self._FORM_CLS(request.POST or None, **kwargs, user=request.user)
geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(request.POST or None, user=request.user, read_only=False)
# Only continue if both forms are without errors
if form.is_valid() and geom_form.is_valid():
obj = form.save(request.user, geom_form)
obj_redirect_url = reverse(self._REDIRECT_URL, args=(obj.id,))
generated_identifier = form.cleaned_data.get("identifier", None)
# There is a rare chance that an identifier has been taken already between sending the form and processing
# the data. If the identifier can not be used anymore, we have to inform the user that another identifier
# had to be generated
if generated_identifier != obj.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
obj.identifier
)
)
messages.success(request, _("{} added").format(obj.identifier))
# Very complex geometries have to be simplified automatically while processing the spatial data. If this
# is the case, the user has to be informed. (They might want to check whether the stored geometry still
# fits their needs)
if geom_form.has_geometry_simplified():
messages.info(
request,
GEOMETRY_SIMPLIFIED
)
# If certain parts of the geometry do not pass the quality check (e.g. way too small and therefore more like
# cutting errors) we need to inform the user that some parts have been removed/ignored while storing the
# geometry
num_ignored_geometries = geom_form.get_num_geometries_ignored()
if num_ignored_geometries > 0:
messages.info(
request,
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
)
return redirect(obj_redirect_url)
else:
# Something was not properly entered on the forms, so we have to inform the user
context = self._get_additional_context()
messages.error(request, FORM_INVALID, extra_tags="danger",)
context = BaseContext(request, additional_context=context).context
context.update(
{
"form": form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: self._TAB_TITLE,
}
)
return render(request, self._TEMPLATE, context)
class BaseEditSpatialLocatedObjectFormView(BaseSpatialLocatedObjectFormView):
""" Base view for editing new spatial data related to objects
"""
_TAB_TITLE = _("Edit {}")
def get(self, request: HttpRequest, id: str, *args, **kwargs):
""" GET endpoint for rendering a form view where object data and spatial data are processed
Args:
request (HttpRequest): The incoming request
id (str): The id of the object (not the geometry)
Returns:
"""
# First fetch the object identified by the id
obj = get_object_or_404(
self._MODEL_CLS,
id=id
)
obj_redirect_url = reverse(self._REDIRECT_URL, args=(obj.id,))
# Check whether the object is recorded. If so - we can redirect the user and inform about the un-editability
# of this entry
if obj.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect(obj_redirect_url)
# Seems like the object is not recorded. Good - initialize the forms based on the obj and request-bound data
form: BaseForm = self._FORM_CLS(None, instance=obj, user=request.user)
geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(None, instance=obj, read_only=False)
# Get additional context for rendering and put everything in the rendering pipeline
context = self._get_additional_context()
context = BaseContext(request, additional_context=context).context
context.update(
{
"form": form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: self._TAB_TITLE.format(obj.identifier),
}
)
return render(request, self._TEMPLATE, context)
def post(self, request: HttpRequest, id: str, *args, **kwargs):
""" POST endpoint for processing object and spatial data provided by forms
Args:
request (HttpRequest): The incoming request
id (str): The object's id
*args ():
**kwargs ():
Returns:
"""
obj = get_object_or_404(
self._MODEL_CLS,
id=id
)
obj_redirect_url = reverse(self._REDIRECT_URL, args=(obj.id,))
# If the object is recorded, we abort the processing directly and inform the user
if obj.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect(obj_redirect_url)
# Initialize forms with obj and request-bound data
form: BaseForm = self._FORM_CLS(request.POST or None, instance=obj, user=request.user)
geom_form: SimpleGeomForm = self._GEOMETRY_FORM_CLS(request.POST or None, instance=obj, read_only=False)
if form.is_valid() and geom_form.is_valid():
obj = form.save(request.user, geom_form)
messages.success(request, _("{} edited").format(obj.identifier))
# Very complex geometries have to be simplified automatically while processing the spatial data. If this
# is the case, the user has to be informed. (They might want to check whether the stored geometry still
# fits their needs)
if geom_form.has_geometry_simplified():
messages.info(
request,
GEOMETRY_SIMPLIFIED
)
# If certain parts of the geometry do not pass the quality check (e.g. way too small and therefore more like
# cutting errors) we need to inform the user that some parts have been removed/ignored while storing the
# geometry
num_ignored_geometries = geom_form.get_num_geometries_ignored()
if num_ignored_geometries > 0:
messages.info(
request,
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
)
return redirect(obj_redirect_url)
else:
context = self._get_additional_context()
messages.error(request, FORM_INVALID, extra_tags="danger",)
context = BaseContext(request, additional_context=context).context
context.update(
{
"form": form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: self._TAB_TITLE.format(obj.identifier),
}
)
return render(request, self._TEMPLATE, context)
def _user_has_shared_access(self, user, **kwargs):
obj = get_object_or_404(self._MODEL_CLS, id=kwargs.get('id', None))
return obj.is_shared_with(user)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()

View File

@ -5,57 +5,102 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 22.08.22 Created on: 22.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.views import View
from compensation.forms.modals.deadline import NewDeadlineModalForm, EditDeadlineModalForm from compensation.forms.modals.deadline import NewDeadlineModalForm, EditDeadlineModalForm
from konova.forms.modals import RemoveDeadlineModalForm from konova.forms.modals import RemoveDeadlineModalForm
from konova.models import Deadline
from konova.utils.message_templates import DEADLINE_ADDED, DEADLINE_EDITED, DEADLINE_REMOVED from konova.utils.message_templates import DEADLINE_ADDED, DEADLINE_EDITED, DEADLINE_REMOVED
from konova.views.base import BaseModalFormView
class AbstractNewDeadlineView(LoginRequiredMixin, BaseModalFormView): class AbstractNewDeadlineView(View):
_MODEL_CLS = None model = None
_FORM_CLS = NewDeadlineModalForm redirect_url = None
_REDIRECT_URL = None
_MSG_SUCCESS = DEADLINE_ADDED
class Meta: class Meta:
abstract = True abstract = True
def _get_redirect_url(self, *args, **kwargs): def get(self, request, id: str):
return super()._get_redirect_url(*args, **kwargs) + "#related_data" """ Renders a form for adding new deadlines
def _user_has_permission(self, user, **kwargs): Args:
return user.is_default_user() request (HttpRequest): The incoming request
id (str): The account's id to which the new state will be related
Returns:
"""
obj = get_object_or_404(self.model, id=id)
form = NewDeadlineModalForm(request.POST or None, instance=obj, request=request)
return form.process_request(
request,
msg_success=DEADLINE_ADDED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str):
return self.get(request, id)
class AbstractEditDeadlineView(LoginRequiredMixin, BaseModalFormView): class AbstractEditDeadlineView(View):
_MODEL_CLS = None model = None
_FORM_CLS = EditDeadlineModalForm redirect_url = None
_REDIRECT_URL = None
_MSG_SUCCESS = DEADLINE_EDITED
class Meta: class Meta:
abstract = True abstract = True
def _get_redirect_url(self, *args, **kwargs): def get(self, request, id: str, deadline_id: str):
return super()._get_redirect_url(*args, **kwargs) + "#related_data" """ Renders a form for editing deadlines
def _user_has_permission(self, user, **kwargs): Args:
return user.is_default_user() request (HttpRequest): The incoming request
id (str): The compensation's id
deadline_id (str): The deadline's id
Returns:
"""
obj = get_object_or_404(self.model, id=id)
deadline = get_object_or_404(Deadline, id=deadline_id)
form = EditDeadlineModalForm(request.POST or None, instance=obj, deadline=deadline, request=request)
return form.process_request(
request,
msg_success=DEADLINE_EDITED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str, deadline_id: str):
return self.get(request, id, deadline_id)
class AbstractRemoveDeadlineView(LoginRequiredMixin, BaseModalFormView): class AbstractRemoveDeadlineView(View):
_MODEL_CLS = None model = None
_FORM_CLS = RemoveDeadlineModalForm redirect_url = None
_REDIRECT_URL = None
_MSG_SUCCESS = DEADLINE_REMOVED
class Meta: class Meta:
abstract = True abstract = True
def _get_redirect_url(self, *args, **kwargs): def get(self, request, id: str, deadline_id: str):
return super()._get_redirect_url(*args, **kwargs) + "#related_data" """ Renders a form for removing deadlines
def _user_has_permission(self, user, **kwargs): Args:
return user.is_default_user() request (HttpRequest): The incoming request
id (str): The compensation's id
deadline_id (str): The deadline's id
Returns:
"""
obj = get_object_or_404(self.model, id=id)
deadline = get_object_or_404(Deadline, id=deadline_id)
form = RemoveDeadlineModalForm(request.POST or None, instance=obj, deadline=deadline, request=request)
return form.process_request(
request,
msg_success=DEADLINE_REMOVED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str, deadline_id: str):
return self.get(request, id, deadline_id)

View File

@ -6,88 +6,126 @@ Created on: 22.08.22
""" """
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.urls import reverse from django.urls import reverse
from django.views import View
from intervention.forms.modals.deduction import NewEcoAccountDeductionModalForm, EditEcoAccountDeductionModalForm, \ from intervention.forms.modals.deduction import NewEcoAccountDeductionModalForm, EditEcoAccountDeductionModalForm, \
RemoveEcoAccountDeductionModalForm RemoveEcoAccountDeductionModalForm
from konova.utils.general import check_id_is_valid_uuid from konova.utils.message_templates import DEDUCTION_ADDED, DEDUCTION_EDITED, DEDUCTION_REMOVED, DEDUCTION_UNKNOWN
from konova.views.base import BaseModalFormView
class AbstractDeductionView(BaseModalFormView): class AbstractDeductionView(View):
_REDIRECT_URL = None model = None
redirect_url = None
def dispatch(self, request, *args, **kwargs):
check_id_is_valid_uuid(kwargs.get("id"))
return super().dispatch(request, *args, **kwargs)
def _custom_check(self, obj): def _custom_check(self, obj):
""" """
Can be used by inheriting classes to provide custom checks before further processing Can be used by inheriting classes to provide custom checks before further processing
""" """
pass raise NotImplementedError("Must be implemented in subclasses")
def _user_has_permission(self, user, **kwargs) -> bool:
"""
Args:
user ():
Returns:
"""
return user.is_default_user()
def _user_has_shared_access(self, user, **kwargs) -> bool:
""" A user has shared access on
Args:
user (User): The performing user
kwargs (dict): Parameters
Returns:
bool: True if the user has access to the requested object, False otherwise
"""
ret_val: bool = False
try:
obj = self._MODEL_CLS.objects.get(
id=kwargs.get("id")
)
ret_val = obj.is_shared_with(user)
except ObjectDoesNotExist:
ret_val = False
return ret_val
def _get_redirect_url(self, *args, **kwargs):
obj = kwargs.get("obj", None)
assert obj is not None
return reverse(self._REDIRECT_URL, args=(obj.id,)) + "#related_data"
class AbstractNewDeductionView(AbstractDeductionView): class AbstractNewDeductionView(AbstractDeductionView):
_FORM_CLS = NewEcoAccountDeductionModalForm
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str):
""" Renders a modal form view for creating deductions
Args:
request (HttpRequest): The incoming request
id (str): The obj's id which shall benefit from this deduction
Returns:
"""
obj = get_object_or_404(self.model, id=id)
self._custom_check(obj)
form = NewEcoAccountDeductionModalForm(request.POST or None, instance=obj, request=request)
return form.process_request(
request,
msg_success=DEDUCTION_ADDED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data",
)
def post(self, request, id: str):
return self.get(request, id)
class AbstractEditDeductionView(AbstractDeductionView): class AbstractEditDeductionView(AbstractDeductionView):
_FORM_CLS = EditEcoAccountDeductionModalForm
def dispatch(self, request, *args, **kwargs): def _custom_check(self, obj):
check_id_is_valid_uuid(kwargs.get("deduction_id")) pass
return super().dispatch(request, *args, **kwargs)
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str, deduction_id: str):
""" Renders a modal view for editing deductions
Args:
request (HttpRequest): The incoming request
id (str): The object's id
deduction_id (str): The deduction's id
Returns:
"""
obj = get_object_or_404(self.model, id=id)
self._custom_check(obj)
try:
eco_deduction = obj.deductions.get(id=deduction_id)
except ObjectDoesNotExist:
raise Http404(DEDUCTION_UNKNOWN)
form = EditEcoAccountDeductionModalForm(request.POST or None, instance=obj, deduction=eco_deduction,
request=request)
return form.process_request(
request=request,
msg_success=DEDUCTION_EDITED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str, deduction_id: str):
return self.get(request, id, deduction_id)
class AbstractRemoveDeductionView(AbstractDeductionView): class AbstractRemoveDeductionView(AbstractDeductionView):
_FORM_CLS = RemoveEcoAccountDeductionModalForm
def dispatch(self, request, *args, **kwargs): def _custom_check(self, obj):
check_id_is_valid_uuid(kwargs.get("deduction_id")) pass
return super().dispatch(request, *args, **kwargs)
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str, deduction_id: str):
""" Renders a modal view for removing deductions
Args:
request (HttpRequest): The incoming request
id (str): The object's id
deduction_id (str): The deduction's id
Returns:
"""
obj = get_object_or_404(self.model, id=id)
self._custom_check(obj)
try:
eco_deduction = obj.deductions.get(id=deduction_id)
except ObjectDoesNotExist:
raise Http404(DEDUCTION_UNKNOWN)
form = RemoveEcoAccountDeductionModalForm(request.POST or None, instance=obj, deduction=eco_deduction,
request=request)
return form.process_request(
request=request,
msg_success=DEDUCTION_REMOVED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str, deduction_id: str):
return self.get(request, id, deduction_id)

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.get('id'))
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, **kwargs):
# 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

@ -5,35 +5,46 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 22.08.22 Created on: 22.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.views import View
from konova.forms.modals import EditDocumentModalForm from konova.utils.documents import get_document, remove_document
from konova.utils.documents import get_document from konova.utils.message_templates import DOCUMENT_ADDED, DOCUMENT_EDITED
from konova.utils.message_templates import DOCUMENT_ADDED, DOCUMENT_EDITED, DOCUMENT_REMOVED_TEMPLATE
from konova.views.base import BaseModalFormView, BaseView
class AbstractNewDocumentView(LoginRequiredMixin, BaseModalFormView): class AbstractNewDocumentView(View):
_MODEL_CLS = None model = None
_FORM_CLS = None form = None
_REDIRECT_URL = None redirect_url = None
_MSG_SUCCESS = DOCUMENT_ADDED
class Meta: class Meta:
abstract = True abstract = True
def _get_redirect_url(self, *args, **kwargs): def get(self, request, id: str):
return super()._get_redirect_url(*args, **kwargs) + "#related_data" """ Renders a form for uploading new documents
def _user_has_permission(self, user, **kwargs): Args:
return user.is_default_user() request (HttpRequest): The incoming request
id (str): The object's id to which the new document will be related
Returns:
"""
intervention = get_object_or_404(self.model, id=id)
form = self.form(request.POST or None, request.FILES or None, instance=intervention, request=request)
return form.process_request(
request,
msg_success=DOCUMENT_ADDED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str):
return self.get(request, id)
class AbstractGetDocumentView(LoginRequiredMixin, BaseView): class AbstractGetDocumentView(View):
_MODEL_CLS = None model = None
_DOCUMENT_CLS = None document_model = None
class Meta: class Meta:
abstract = True abstract = True
@ -51,57 +62,77 @@ class AbstractGetDocumentView(LoginRequiredMixin, BaseView):
Returns: Returns:
""" """
get_object_or_404(self._MODEL_CLS, id=id) get_object_or_404(self.model, id=id)
doc = get_object_or_404(self._DOCUMENT_CLS, id=doc_id) doc = get_object_or_404(self.document_model, id=doc_id)
return get_document(doc) return get_document(doc)
def post(self, request, id: str, doc_id: str): def post(self, request, id: str, doc_id: str):
return self.get(request, id, doc_id) return self.get(request, id, doc_id)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
def _user_has_shared_access(self, user, **kwargs): class AbstractRemoveDocumentView(View):
obj = kwargs.get("id", None) model = None
assert obj is not None document_model = None
obj = get_object_or_404(self._MODEL_CLS, id=obj)
return obj.is_shared_with(user)
class AbstractRemoveDocumentView(LoginRequiredMixin, BaseModalFormView):
_MODEL_CLS = None
_DOCUMENT_CLS = None
_FORM_CLS = None
_MSG_SUCCESS = DOCUMENT_REMOVED_TEMPLATE
class Meta: class Meta:
abstract = True abstract = True
def _get_redirect_url(self, *args, **kwargs): def get(self, request, id: str, doc_id: str):
return super()._get_redirect_url(*args, **kwargs) + "#related_data" """ Removes the document from the database and file system
def _user_has_permission(self, user, **kwargs): Wraps the generic functionality from konova.utils.
return user.is_default_user()
def _get_msg_success(self, *args, **kwargs): Args:
doc_id = kwargs.get("doc_id", None) request (HttpRequest): The incoming request
assert doc_id is not None id (str): The intervention id
doc = get_object_or_404(self._DOCUMENT_CLS, id=doc_id) doc_id (str): The document id
return self._MSG_SUCCESS.format(doc.title)
Returns:
"""
get_object_or_404(self.model, id=id)
doc = get_object_or_404(self.document_model, id=doc_id)
return remove_document(
request,
doc
)
def post(self, request, id: str, doc_id: str):
return self.get(request, id, doc_id)
class AbstractEditDocumentView(LoginRequiredMixin, BaseModalFormView): class AbstractEditDocumentView(View):
_MODEL_CLS = None model = None
_DOCUMENT_CLS = None document_model = None
_FORM_CLS = EditDocumentModalForm form = None
_REDIRECT_URL = None redirect_url = None
_MSG_SUCCESS = DOCUMENT_EDITED
class Meta: class Meta:
abstract = True abstract = True
def _user_has_permission(self, user, **kwargs): def get(self, request, id: str, doc_id: str):
return user.is_default_user() """ GET handling for editing of existing document
Wraps the generic functionality from konova.utils.
Args:
request (HttpRequest): The incoming request
id (str): The intervention id
doc_id (str): The document id
Returns:
"""
obj = get_object_or_404(self.model, id=id)
doc = get_object_or_404(self.document_model, id=doc_id)
form = self.form(request.POST or None, request.FILES or None, instance=obj, document=doc,
request=request)
return form.process_request(
request,
DOCUMENT_EDITED,
redirect_url=reverse(self.redirect_url, args=(obj.id,)) + "#related_data"
)
def post(self, request, id: str, doc_id: str):
return self.get(request, id, doc_id)
def _get_redirect_url(self, *args, **kwargs):
return super()._get_redirect_url(*args, **kwargs) + "#related_data"

View File

@ -10,16 +10,15 @@ from django.http import HttpResponse, HttpRequest
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone from django.utils import timezone
from django.views import View
from konova.models import Geometry from konova.models import Geometry
from konova.settings import GEOM_THRESHOLD_RECALCULATION_SECONDS from konova.settings import GEOM_THRESHOLD_RECALCULATION_SECONDS
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from konova.tasks import celery_update_parcels from konova.tasks import celery_update_parcels
from konova.views.base import BaseView
class GeomParcelsView(BaseView): class GeomParcelsView(View):
_TEMPLATE = "konova/includes/parcels/parcel_table_frame.html"
def get(self, request: HttpRequest, id: str): def get(self, request: HttpRequest, id: str):
""" Getter for HTMX """ Getter for HTMX
@ -33,6 +32,7 @@ class GeomParcelsView(BaseView):
Returns: Returns:
A rendered piece of HTML A rendered piece of HTML
""" """
template = "konova/includes/parcels/parcel_table_frame.html"
geom = get_object_or_404(Geometry, id=id) geom = get_object_or_404(Geometry, id=id)
geos_geom = geom.geom or MultiPolygon(srid=DEFAULT_SRID_RLP) geos_geom = geom.geom or MultiPolygon(srid=DEFAULT_SRID_RLP)
@ -85,7 +85,7 @@ class GeomParcelsView(BaseView):
"geom_id": str(id), "geom_id": str(id),
"next_page": next_page, "next_page": next_page,
} }
html = render_to_string(self._TEMPLATE, context, request) html = render_to_string(template, context, request)
return HttpResponse(html, status=status_code) return HttpResponse(html, status=status_code)
else: else:
return HttpResponse(None, status=404) return HttpResponse(None, status=404)
@ -107,15 +107,8 @@ class GeomParcelsView(BaseView):
waiting_too_long = (pcs_diff >= wait_for_seconds) waiting_too_long = (pcs_diff >= wait_for_seconds)
return waiting_too_long return waiting_too_long
def _user_has_shared_access(self, user, **kwargs):
return True
def _user_has_permission(self, user, **kwargs): class GeomParcelsContentView(View):
return True
class GeomParcelsContentView(BaseView):
_TEMPLATE = "konova/includes/parcels/parcel_table_content.html"
def get(self, request: HttpRequest, id: str, page: int): def get(self, request: HttpRequest, id: str, page: int):
""" Getter for infinite scroll of HTMX """ Getter for infinite scroll of HTMX
@ -137,6 +130,7 @@ class GeomParcelsContentView(BaseView):
# HTTP code 286 states that the HTMX should stop polling for updates # HTTP code 286 states that the HTMX should stop polling for updates
# https://htmx.org/docs/#polling # https://htmx.org/docs/#polling
status_code = 286 status_code = 286
template = "konova/includes/parcels/parcel_table_content.html"
geom = get_object_or_404(Geometry, id=id) geom = get_object_or_404(Geometry, id=id)
parcels = geom.get_underlying_parcels() parcels = geom.get_underlying_parcels()
@ -154,11 +148,5 @@ class GeomParcelsContentView(BaseView):
"geom_id": str(id), "geom_id": str(id),
"next_page": next_page, "next_page": next_page,
} }
html = render_to_string(self._TEMPLATE, context, request) html = render_to_string(template, context, request)
return HttpResponse(html, status=status_code) return HttpResponse(html, status=status_code)
def _user_has_shared_access(self, user, **kwargs):
return True
def _user_has_permission(self, user, **kwargs):
return True

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, **kwargs):
# 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

@ -6,15 +6,14 @@ Created on: 19.08.22
""" """
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.views import View
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.views.base import BaseView
class AbstractLogView(BaseView): class AbstractLogView(View):
_MODEL_CLS = None model = None
_TEMPLATE = "modal/modal_generic.html"
class Meta: class Meta:
abstract = True abstract = True
@ -29,22 +28,14 @@ class AbstractLogView(BaseView):
Returns: Returns:
""" """
intervention = get_object_or_404(self._MODEL_CLS, id=id) intervention = get_object_or_404(self.model, id=id)
template = "modal/modal_generic.html"
body_template = "log.html" body_template = "log.html"
context = { context = {
"modal_body_template": body_template, "modal_body_template": body_template,
"log": intervention.log.iterator(), "log": intervention.log.all(),
"modal_title": _("Log"), "modal_title": _("Log"),
} }
context = BaseContext(request, context).context context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context) return render(request, template, context)
def _user_has_shared_access(self, user, **kwargs):
obj_id = kwargs.get('id', None)
assert obj_id is not None
obj = get_object_or_404(self._MODEL_CLS, id=obj_id)
return obj.is_shared_with(user)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()

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")

View File

@ -5,28 +5,46 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.shortcuts import get_object_or_404
from django.views import View
from django.utils.translation import gettext_lazy as _
from konova.forms.modals import RecordModalForm from konova.forms.modals import RecordModalForm
from konova.utils.message_templates import ENTRY_RECORDED, ENTRY_UNRECORDED
from konova.views.base import BaseModalFormView
class AbstractRecordView(BaseModalFormView): class AbstractRecordView(View):
_MODEL_CLS = None model = None
_FORM_CLS = RecordModalForm
_MSG_SUCCESS = None
def _user_has_permission(self, user, **kwargs): def get(self, request, id: str):
return user.is_ets_user() """ Renders a modal form for recording an object
def _get_msg_success(self, *args, **kwargs): Args:
obj = kwargs.get("obj") request (HttpRequest): The incoming request
assert obj is not None id (str): The object's id
if obj.is_recorded: Returns:
return ENTRY_RECORDED.format(obj.identifier)
else:
return ENTRY_UNRECORDED.format(obj.identifier)
def _check_for_recorded_instance(self, obj): """
# Do not block record view if instance might be recorded obj = get_object_or_404(self.model, id=id)
return None form = RecordModalForm(request.POST or None, instance=obj, request=request)
msg_succ = _("{} unrecorded") if obj.recorded else _("{} recorded")
msg_succ = msg_succ.format(obj.identifier)
return form.process_request(
request,
msg_succ,
msg_error=_("Errors found:")
)
def post(self, request, id: str):
"""
BaseModalForm provides the method process_request() which handles GET as well as POST requests. It was written
for easier handling of function based views. To support process_request() on class based views, the post()
call needs to be treated the same way as the get() call.
Args:
request (HttpRequest): The incoming request
id (str): Intervention's id
"""
return self.get(request, id)

View File

@ -1,28 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 21.10.25
"""
from django.urls import reverse
from konova.forms.modals import RemoveModalForm
from konova.utils.message_templates import GENERIC_REMOVED_TEMPLATE
from konova.views.base import BaseModalFormView
class BaseRemoveModalFormView(BaseModalFormView):
_MODEL_CLS = None
_FORM_CLS = RemoveModalForm
_MSG_SUCCESS = GENERIC_REMOVED_TEMPLATE
_REDIRECT_URL = None
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
def _get_redirect_url(self, *args, **kwargs):
return reverse(self._REDIRECT_URL)
def _get_msg_success(self, *args, **kwargs):
obj = kwargs.get("obj", None)
assert obj is not None
return self._MSG_SUCCESS.format(obj.identifier)

View File

@ -1,106 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 17.10.25
"""
from abc import abstractmethod
from uuid import UUID
from django.http import HttpRequest, Http404
from django.shortcuts import get_object_or_404, render
from django.utils.translation import gettext_lazy as _
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.views.base import BaseView
class BaseReportView(BaseView):
_TEMPLATE = None
_TAB_TITLE = _("Report {}")
_MODEL = None
class Meta:
abstract = True
def dispatch(self, request, *args, **kwargs):
# If the given id is not a uuid we act as the result was not found
try:
UUID(kwargs.get('id'))
except ValueError:
raise Http404()
return super().dispatch(request, *args, **kwargs)
def _return_unpublishable_content_response(self, request: HttpRequest, tab_title: str):
""" Handles HttpResponse return in case the object is not ready for publish
Args:
request ():
tab_title ():
Returns:
"""
template = "report/unavailable.html"
context = {
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, template, context)
def get(self, request: HttpRequest, id: str):
""" Renders the public report view
Args:
request (HttpRequest): The incoming request
id (str): The id of the intervention
Returns:
"""
obj = get_object_or_404(self._MODEL, id=id)
tab_title = self._TAB_TITLE.format(obj.identifier)
# If object is not recorded we need to render another template without any data
if not obj.is_ready_for_publish():
return self._return_unpublishable_content_response(request, tab_title)
# First get specific report context for different types of objects due to inheritance
report_context = self._get_report_context(obj)
# Then generate and add default report context (the same for all models)
geom_form = SimpleGeomForm(instance=obj)
parcels = obj.get_underlying_parcels()
report_context.update(
{
TAB_TITLE_IDENTIFIER: tab_title,
"parcels": parcels,
"geom_form": geom_form,
"obj": obj
}
)
# Then generate the general context based on the report specific data
context = BaseContext(request, report_context).context
return render(request, self._TEMPLATE, context)
@abstractmethod
def _get_report_context(self, obj):
""" Returns the specific context needed for this report view
Args:
obj (RecordableObjectMixin): The object for the report
Returns:
dict: The object specific context for rendering the report
"""
raise NotImplementedError
def _user_has_permission(self, user, **kwargs):
# Reports do not need specific permissions to be callable
return True
def _user_has_shared_access(self, user, **kwargs):
# Reports do not need specific share states to be callable
return True

View File

@ -5,24 +5,51 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22 Created on: 19.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.views import View
from django.utils.translation import gettext_lazy as _
from konova.utils.message_templates import NEW_RESUBMISSION_CREATED from konova.forms.modals import ResubmissionModalForm
from konova.views.base import BaseModalFormView
class AbstractResubmissionView(LoginRequiredMixin, BaseModalFormView): class AbstractResubmissionView(View):
_MODEL_CLS = None model = None
_FORM_CLS = None form_action_url_base = None
_REDIRECT_URL = None redirect_url_base = None
_MSG_SUCCESS = NEW_RESUBMISSION_CREATED
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str):
""" Renders resubmission form for an object
def _user_has_permission(self, user, **kwargs): Args:
return user.is_default_user() request (HttpRequest): The incoming request
id (str): Object's id
def _check_for_recorded_instance(self, obj): Returns:
# Resubmissions are allowed despite an entry being recorded
return None """
obj = get_object_or_404(self.model, id=id)
form = ResubmissionModalForm(request.POST or None, instance=obj, request=request)
form.action_url = reverse(self.form_action_url_base, args=(id,))
return form.process_request(
request,
msg_success=_("Resubmission set"),
redirect_url=reverse(self.redirect_url_base, args=(id,))
)
def post(self, request, id: str):
"""
BaseModalForm provides the method process_request() which handles GET as well as POST requests. It was written
for easier handling of function based views. To support process_request() on class based views, the post()
call needs to be treated the same way as the get() call.
Args:
request (HttpRequest): The incoming request
id (str): Intervention's id
"""
return self.get(request, id)

View File

@ -6,24 +6,24 @@ Created on: 22.08.22
""" """
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.views import View
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from intervention.forms.modals.share import ShareModalForm from intervention.forms.modals.share import ShareModalForm
from konova.utils.message_templates import DATA_SHARE_SET from konova.utils.message_templates import DATA_SHARE_SET
from konova.views.base import BaseView, BaseModalFormView
class AbstractShareByTokenView(LoginRequiredMixin, BaseView): class AbstractShareByTokenView(View):
_MODEL_CLS = None model = None
_REDIRECT_URL = None redirect_url = None
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str, token: str): def get(self, request, id: str, token: str):
""" Performs sharing of an entry
""" Performs sharing of an intervention
If token given in url is not valid, the user will be redirected to the dashboard If token given in url is not valid, the user will be redirected to the dashboard
@ -36,7 +36,7 @@ class AbstractShareByTokenView(LoginRequiredMixin, BaseView):
""" """
user = request.user user = request.user
obj = get_object_or_404(self._MODEL_CLS, id=id) obj = get_object_or_404(self.model, id=id)
# Check tokens # Check tokens
if obj.access_token == token: if obj.access_token == token:
# Send different messages in case user has already been added to list of sharing users # Send different messages in case user has already been added to list of sharing users
@ -51,7 +51,7 @@ class AbstractShareByTokenView(LoginRequiredMixin, BaseView):
_("{} has been shared with you").format(obj.identifier) _("{} has been shared with you").format(obj.identifier)
) )
obj.share_with_user(user) obj.share_with_user(user)
return redirect(self._REDIRECT_URL, id=id) return redirect(self.redirect_url, id=id)
else: else:
messages.error( messages.error(
request, request,
@ -60,22 +60,29 @@ class AbstractShareByTokenView(LoginRequiredMixin, BaseView):
) )
return redirect("home") return redirect("home")
def _user_has_permission(self, user, **kwargs):
# No permissions are needed to get shared access via token
return True
def _user_has_shared_access(self, user, **kwargs): class AbstractShareFormView(View):
# The user does not need to have shared access to call the endpoint which gives them shared access model = None
return True
class AbstractShareFormView(LoginRequiredMixin, BaseModalFormView):
_MODEL_CLS = None
_FORM_CLS = ShareModalForm
_MSG_SUCCESS = DATA_SHARE_SET
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str):
""" Renders sharing form
def _user_has_permission(self, user, **kwargs): Args:
return user.is_default_user() request (HttpRequest): The incoming request
id (str): Object's id
Returns:
"""
obj = get_object_or_404(self.model, id=id)
form = ShareModalForm(request.POST or None, instance=obj, request=request)
return form.process_request(
request,
msg_success=DATA_SHARE_SET
)
def post(self, request, id: str):
return self.get(request, id)

View File

@ -5,53 +5,103 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 22.08.22 Created on: 22.08.22
""" """
from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import get_object_or_404
from django.urls import reverse from django.urls import reverse
from django.views import View
from compensation.forms.modals.state import NewCompensationStateModalForm, EditCompensationStateModalForm, \ from compensation.forms.modals.state import NewCompensationStateModalForm, EditCompensationStateModalForm, \
RemoveCompensationStateModalForm RemoveCompensationStateModalForm
from compensation.models import CompensationState
from konova.utils.message_templates import COMPENSATION_STATE_ADDED, COMPENSATION_STATE_EDITED, \ from konova.utils.message_templates import COMPENSATION_STATE_ADDED, COMPENSATION_STATE_EDITED, \
COMPENSATION_STATE_REMOVED COMPENSATION_STATE_REMOVED
from konova.views.base import BaseModalFormView
class AbstractCompensationStateView(LoginRequiredMixin, BaseModalFormView): class AbstractCompensationStateView(View):
_MODEL_CLS = None model = None
_FORM_CLS = None redirect_url = None
_REDIRECT_URL = None
class Meta: class Meta:
abstract = True abstract = True
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
def _get_redirect_url(self, *args, **kwargs):
obj = kwargs.get("obj", None)
assert obj is not None
return reverse(self._REDIRECT_URL, args=(obj.id,)) + "#related_data"
class AbstractNewCompensationStateView(AbstractCompensationStateView): class AbstractNewCompensationStateView(AbstractCompensationStateView):
_MODEL_CLS = None
_FORM_CLS = NewCompensationStateModalForm
_MSG_SUCCESS = COMPENSATION_STATE_ADDED
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str):
""" Renders a form for adding new states
Args:
request (HttpRequest): The incoming request
id (str): The object's id to which the new state will be related
Returns:
"""
obj = get_object_or_404(self.model, id=id)
form = NewCompensationStateModalForm(request.POST or None, instance=obj, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_ADDED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str):
return self.get(request, id)
class AbstractEditCompensationStateView(AbstractCompensationStateView): class AbstractEditCompensationStateView(AbstractCompensationStateView):
_MODEL_CLS = None
_FORM_CLS = EditCompensationStateModalForm
_MSG_SUCCESS = COMPENSATION_STATE_EDITED
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str, state_id: str):
""" Renders a form for editing a state
Args:
request (HttpRequest): The incoming request
id (str): The object id
state_id (str): The state's id
Returns:
"""
obj = get_object_or_404(self.model, id=id)
state = get_object_or_404(CompensationState, id=state_id)
form = EditCompensationStateModalForm(request.POST or None, instance=obj, state=state, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_EDITED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str, state_id: str):
return self.get(request, id, state_id)
class AbstractRemoveCompensationStateView(AbstractCompensationStateView): class AbstractRemoveCompensationStateView(AbstractCompensationStateView):
_MODEL_CLS = None
_FORM_CLS = RemoveCompensationStateModalForm
_MSG_SUCCESS = COMPENSATION_STATE_REMOVED
class Meta: class Meta:
abstract = True abstract = True
def get(self, request, id: str, state_id: str):
""" Renders a form for removing astate
Args:
request (HttpRequest): The incoming request
id (str): The object id
state_id (str): The state's id
Returns:
"""
obj = get_object_or_404(self.model, id=id)
state = get_object_or_404(CompensationState, id=state_id)
form = RemoveCompensationStateModalForm(request.POST or None, instance=obj, state=state, request=request)
return form.process_request(
request,
msg_success=COMPENSATION_STATE_REMOVED,
redirect_url=reverse(self.redirect_url, args=(id,)) + "#related_data"
)
def post(self, request, id: str, state_id: str):
return self.get(request, id, state_id)

Binary file not shown.

View File

@ -45,7 +45,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-19 13:56+0200\n" "POT-Creation-Date: 2025-10-15 09:11+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -448,19 +448,11 @@ msgid "Select the intervention for which this compensation compensates"
msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist" msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist"
#: compensation/forms/compensation.py:114 #: compensation/forms/compensation.py:114
#: compensation/views/compensation/compensation.py:161 #: compensation/views/compensation/compensation.py:120
msgid "New compensation" msgid "New compensation"
msgstr "Neue Kompensation" msgstr "Neue Kompensation"
#: compensation/forms/compensation.py:179 #: compensation/forms/compensation.py:190
msgid ""
"This intervention is currently recorded. You cannot add further "
"compensations as long as it is recorded."
msgstr ""
"Dieser Eingriff ist derzeit verzeichnet. "
"Sie können keine weiteren Kompensationen hinzufügen, so lange er verzeichnet ist."
#: compensation/forms/compensation.py:202
msgid "Edit compensation" msgid "Edit compensation"
msgstr "Bearbeite Kompensation" msgstr "Bearbeite Kompensation"
@ -483,7 +475,7 @@ msgid "When did the parties agree on this?"
msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?" msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?"
#: compensation/forms/eco_account.py:72 #: compensation/forms/eco_account.py:72
#: compensation/views/eco_account/eco_account.py:93 #: compensation/views/eco_account/eco_account.py:101
msgid "New Eco-Account" msgid "New Eco-Account"
msgstr "Neues Ökokonto" msgstr "Neues Ökokonto"
@ -1296,45 +1288,44 @@ msgstr ""
msgid "Responsible data" msgid "Responsible data"
msgstr "Daten zu den verantwortlichen Stellen" msgstr "Daten zu den verantwortlichen Stellen"
#: compensation/views/compensation/compensation.py:35 #: compensation/views/compensation/compensation.py:58
msgid "Compensations - Overview" msgid "Compensations - Overview"
msgstr "Kompensationen - Übersicht" msgstr "Kompensationen - Übersicht"
#: compensation/views/compensation/compensation.py:52 #: compensation/views/compensation/compensation.py:181
#, fuzzy
#| msgid "New compensation"
msgid "New Compensation"
msgstr "Neue Kompensation"
#: compensation/views/compensation/compensation.py:208
#: konova/utils/message_templates.py:40 #: konova/utils/message_templates.py:40
msgid "Compensation {} edited" msgid "Compensation {} edited"
msgstr "Kompensation {} bearbeitet" msgstr "Kompensation {} bearbeitet"
#: compensation/views/compensation/compensation.py:231 #: compensation/views/compensation/compensation.py:196
#: compensation/views/eco_account/eco_account.py:159 ema/views/ema.py:59 #: compensation/views/eco_account/eco_account.py:173 ema/views/ema.py:238
#: intervention/views/intervention.py:59 intervention/views/intervention.py:179 #: intervention/views/intervention.py:253
#: konova/views/base.py:239
msgid "Edit {}" msgid "Edit {}"
msgstr "Bearbeite {}" msgstr "Bearbeite {}"
#: compensation/views/eco_account/eco_account.py:32 #: compensation/views/compensation/report.py:35
#: compensation/views/eco_account/report.py:36 ema/views/report.py:35
#: intervention/views/report.py:35
msgid "Report {}"
msgstr "Bericht {}"
#: compensation/views/eco_account/eco_account.py:53
msgid "Eco-account - Overview" msgid "Eco-account - Overview"
msgstr "Ökokonten - Übersicht" msgstr "Ökokonten - Übersicht"
#: compensation/views/eco_account/eco_account.py:70 #: compensation/views/eco_account/eco_account.py:86
msgid "Eco-Account {} added" msgid "Eco-Account {} added"
msgstr "Ökokonto {} hinzugefügt" msgstr "Ökokonto {} hinzugefügt"
#: compensation/views/eco_account/eco_account.py:136 #: compensation/views/eco_account/eco_account.py:158
msgid "Eco-Account {} edited" msgid "Eco-Account {} edited"
msgstr "Ökokonto {} bearbeitet" msgstr "Ökokonto {} bearbeitet"
#: compensation/views/eco_account/eco_account.py:260 #: compensation/views/eco_account/eco_account.py:288
msgid "Eco-account removed" msgid "Eco-account removed"
msgstr "Ökokonto entfernt" msgstr "Ökokonto entfernt"
#: ema/forms.py:42 ema/tests/unit/test_forms.py:27 ema/views/ema.py:42 #: ema/forms.py:42 ema/tests/unit/test_forms.py:27 ema/views/ema.py:108
msgid "New EMA" msgid "New EMA"
msgstr "Neue EMA hinzufügen" msgstr "Neue EMA hinzufügen"
@ -1362,11 +1353,19 @@ msgstr ""
msgid "Payment funded compensation" msgid "Payment funded compensation"
msgstr "Ersatzzahlungsmaßnahme" msgstr "Ersatzzahlungsmaßnahme"
#: ema/views/ema.py:26 #: ema/views/ema.py:53
msgid "EMAs - Overview" msgid "EMAs - Overview"
msgstr "EMAs - Übersicht" msgstr "EMAs - Übersicht"
#: ema/views/ema.py:138 #: ema/views/ema.py:86
msgid "EMA {} added"
msgstr "EMA {} hinzugefügt"
#: ema/views/ema.py:223
msgid "EMA {} edited"
msgstr "EMA {} bearbeitet"
#: ema/views/ema.py:262
msgid "EMA removed" msgid "EMA removed"
msgstr "EMA entfernt" msgstr "EMA entfernt"
@ -1430,7 +1429,7 @@ msgstr "Datum Bestandskraft bzw. Rechtskraft"
#: intervention/forms/intervention.py:216 #: intervention/forms/intervention.py:216
#: intervention/tests/unit/test_forms.py:36 #: intervention/tests/unit/test_forms.py:36
#: intervention/views/intervention.py:51 #: intervention/views/intervention.py:105
msgid "New intervention" msgid "New intervention"
msgstr "Neuer Eingriff" msgstr "Neuer Eingriff"
@ -1666,15 +1665,19 @@ msgstr ""
msgid "Check performed" msgid "Check performed"
msgstr "Prüfung durchgeführt" msgstr "Prüfung durchgeführt"
#: intervention/views/intervention.py:33 #: intervention/views/intervention.py:57
msgid "Interventions - Overview" msgid "Interventions - Overview"
msgstr "Eingriffe - Übersicht" msgstr "Eingriffe - Übersicht"
#: intervention/views/intervention.py:154 #: intervention/views/intervention.py:90
msgid "Intervention {} added"
msgstr "Eingriff {} hinzugefügt"
#: intervention/views/intervention.py:236
msgid "Intervention {} edited" msgid "Intervention {} edited"
msgstr "Eingriff {} bearbeitet" msgstr "Eingriff {} bearbeitet"
#: intervention/views/intervention.py:204 #: intervention/views/intervention.py:278
msgid "{} removed" msgid "{} removed"
msgstr "{} entfernt" msgstr "{} entfernt"
@ -1686,7 +1689,7 @@ msgstr "Hierfür müssen Sie Mitarbeiter sein!"
msgid "You need to be administrator to perform this action!" msgid "You need to be administrator to perform this action!"
msgstr "Hierfür müssen Sie Administrator sein!" msgstr "Hierfür müssen Sie Administrator sein!"
#: konova/decorators.py:65 konova/utils/general.py:40 #: konova/decorators.py:65
msgid "" msgid ""
"+++ Attention: You are not part of any group. You won't be able to create, " "+++ Attention: You are not part of any group. You won't be able to create, "
"edit or do anything. Please contact an administrator. +++" "edit or do anything. Please contact an administrator. +++"
@ -1798,7 +1801,7 @@ msgstr "Sucht nach Einträgen, an denen diese Person gearbeitet hat"
msgid "Save" msgid "Save"
msgstr "Speichern" msgstr "Speichern"
#: konova/forms/base_form.py:74 #: konova/forms/base_form.py:72
msgid "Not editable" msgid "Not editable"
msgstr "Nicht editierbar" msgstr "Nicht editierbar"
@ -1807,7 +1810,7 @@ msgstr "Nicht editierbar"
msgid "Geometry" msgid "Geometry"
msgstr "Geometrie" msgstr "Geometrie"
#: konova/forms/geometry_form.py:101 #: konova/forms/geometry_form.py:100
msgid "Only surfaces allowed. Points or lines must be buffered." msgid "Only surfaces allowed. Points or lines must be buffered."
msgstr "" msgstr ""
"Nur Flächen erlaubt. Punkte oder Linien müssen zu Flächen gepuffert werden." "Nur Flächen erlaubt. Punkte oder Linien müssen zu Flächen gepuffert werden."
@ -2265,9 +2268,8 @@ msgid ""
"too small to be valid). These parts have been removed. Please check the " "too small to be valid). These parts have been removed. Please check the "
"stored geometry." "stored geometry."
msgstr "" msgstr ""
"Die Geometrie enthielt {} invalide Bestandteile (z.B. unaussagekräftige " "Die Geometrie enthielt {} invalide Bestandteile (z.B. unaussagekräftige Kleinstflächen)."
"Kleinstflächen).Diese Bestandteile wurden automatisch entfernt. Bitte " "Diese Bestandteile wurden automatisch entfernt. Bitte überprüfen Sie die angepasste Geometrie."
"überprüfen Sie die angepasste Geometrie."
#: konova/utils/message_templates.py:89 #: konova/utils/message_templates.py:89
msgid "This intervention has {} revocations" msgid "This intervention has {} revocations"
@ -2308,15 +2310,7 @@ msgstr ""
"Dieses Datum ist unrealistisch. Geben Sie bitte das korrekte Datum ein " "Dieses Datum ist unrealistisch. Geben Sie bitte das korrekte Datum ein "
"(>1950)." "(>1950)."
#: konova/views/base.py:209 #: konova/views/home.py:75 templates/navbars/navbar.html:16
msgid "{} added"
msgstr "{} hinzugefügt"
#: konova/views/base.py:281
msgid "{} edited"
msgstr "{} bearbeitet"
#: konova/views/home.py:72 templates/navbars/navbar.html:16
msgid "Home" msgid "Home"
msgstr "Home" msgstr "Home"
@ -2336,10 +2330,6 @@ msgstr "{} verzeichnet"
msgid "Errors found:" msgid "Errors found:"
msgstr "Fehler gefunden:" msgstr "Fehler gefunden:"
#: konova/views/report.py:21
msgid "Report {}"
msgstr "Bericht {}"
#: konova/views/resubmission.py:39 #: konova/views/resubmission.py:39
msgid "Resubmission set" msgid "Resubmission set"
msgstr "Wiedervorlage gesetzt" msgstr "Wiedervorlage gesetzt"
@ -3066,7 +3056,7 @@ msgid "Manage teams"
msgstr "" msgstr ""
#: user/templates/user/index.html:53 user/templates/user/team/index.html:19 #: user/templates/user/index.html:53 user/templates/user/team/index.html:19
#: user/views/views.py:134 #: user/views/views.py:135
msgid "Teams" msgid "Teams"
msgstr "" msgstr ""
@ -3126,40 +3116,34 @@ msgstr "Läuft ab am"
msgid "User API token" msgid "User API token"
msgstr "API Nutzer Token" msgstr "API Nutzer Token"
#: user/views/views.py:31 #: user/views/views.py:33
msgid "User settings" msgid "User settings"
msgstr "Einstellungen" msgstr "Einstellungen"
#: user/views/views.py:44 #: user/views/views.py:59
msgid "User notifications"
msgstr "Benachrichtigungen"
#: user/views/views.py:64
msgid "Notifications edited" msgid "Notifications edited"
msgstr "Benachrichtigungen bearbeitet" msgstr "Benachrichtigungen bearbeitet"
#: user/views/views.py:152 #: user/views/views.py:71
msgid "User notifications"
msgstr "Benachrichtigungen"
#: user/views/views.py:147
msgid "New team added" msgid "New team added"
msgstr "Neues Team hinzugefügt" msgstr "Neues Team hinzugefügt"
#: user/views/views.py:167 #: user/views/views.py:162
msgid "Team edited" msgid "Team edited"
msgstr "Team bearbeitet" msgstr "Team bearbeitet"
#: user/views/views.py:182 #: user/views/views.py:177
msgid "Team removed" msgid "Team removed"
msgstr "Team gelöscht" msgstr "Team gelöscht"
#: user/views/views.py:197 #: user/views/views.py:192
msgid "You are not a member of this team" msgid "You are not a member of this team"
msgstr "Sie sind kein Mitglied dieses Teams" msgstr "Sie sind kein Mitglied dieses Teams"
#: user/views/views.py:204 #: user/views/views.py:199
msgid "Left Team" msgid "Left Team"
msgstr "Team verlassen" msgstr "Team verlassen"
#~ msgid "EMA {} added"
#~ msgstr "EMA {} hinzugefügt"
#~ msgid "EMA {} edited"
#~ msgstr "EMA {} bearbeitet"

View File

@ -58,7 +58,7 @@
[ [
{ "id": "webatlas_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "WebatlasRP farbig", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_WebAtlasRP/MapServer/WmsServer?", "name": "RP_WebAtlasRP", "active": true}, { "id": "webatlas_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "WebatlasRP farbig", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_WebAtlasRP/MapServer/WmsServer?", "name": "RP_WebAtlasRP", "active": true},
{ "id": "webatlas_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "WebatlasRP grau", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_ETRS_Gt/MapServer/WmsServer?", "name": "0", "active": false }, { "id": "webatlas_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "WebatlasRP grau", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_ETRS_Gt/MapServer/WmsServer?", "name": "0", "active": false },
{ "id": "luftbilder", "folder": "bg", "type": "WMS", "order": -1, "title": "Luftbilder", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dop_basis.fcgi?", "name": "rp_dop", "active": false }, { "id": "luftbilder", "folder": "bg", "type": "WMS", "order": -1, "title": "Luftbilder", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/rp_dop20.fcgi?", "name": "rp_dop20", "active": false },
{ "id": "basemap_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE farbig", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_farbe", "active": false }, { "id": "basemap_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE farbig", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_farbe", "active": false },
{ "id": "basemap_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE grau", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_grau", "active": false }, { "id": "basemap_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE grau", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_grau", "active": false },
{ "id": "dtk_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "DTK5 farbig", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dtk5_rp.fcgi?", "name": "rp_dtk5", "active": false }, { "id": "dtk_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "DTK5 farbig", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dtk5_rp.fcgi?", "name": "rp_dtk5", "active": false },

File diff suppressed because it is too large Load Diff

View File

@ -56,7 +56,7 @@
{% if user.is_staff or user.is_superuser %} {% if user.is_staff or user.is_superuser %}
<a class="dropdown-item" target="_blank" href="{% url 'admin:index' %}">{% fa5_icon 'tools' %} {% trans 'Admin' %}</a> <a class="dropdown-item" target="_blank" href="{% url 'admin:index' %}">{% fa5_icon 'tools' %} {% trans 'Admin' %}</a>
{% endif %} {% endif %}
<a class="dropdown-item" href="{% url 'user:detail' %}">{% fa5_icon 'cogs' %} {% trans 'Settings' %}</a> <a class="dropdown-item" href="{% url 'user:index' %}">{% fa5_icon 'cogs' %} {% trans 'Settings' %}</a>
<a class="dropdown-item" href="{% url 'logout' %}">{% fa5_icon 'sign-out-alt' %} {% trans 'Logout' %}</a> <a class="dropdown-item" href="{% url 'logout' %}">{% fa5_icon 'sign-out-alt' %} {% trans 'Logout' %}</a>
</div> </div>
</li> </li>

View File

@ -22,7 +22,6 @@ class NewAPITokenModalForm(BaseModalForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.template = "modal/modal_form.html" self.template = "modal/modal_form.html"
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.instance = self.user
self.form_title = _("Generate API Token") self.form_title = _("Generate API Token")
self.form_caption = "" self.form_caption = ""

View File

@ -38,7 +38,7 @@ class UserNotificationForm(BaseForm):
self.form_title = _("Edit notifications") self.form_title = _("Edit notifications")
self.form_caption = _("") self.form_caption = _("")
self.action_url = reverse("user:notifications") self.action_url = reverse("user:notifications")
self.cancel_redirect = reverse("user:detail") self.cancel_redirect = reverse("user:index")
# Insert all notifications into form field by creating choices as tuples # Insert all notifications into form field by creating choices as tuples
notifications = UserNotification.objects.filter( notifications = UserNotification.objects.filter(

View File

@ -26,7 +26,7 @@ class UserViewTestCase(BaseViewTestCase):
self.team.users.add(self.superuser) self.team.users.add(self.superuser)
self.team.admins.add(self.superuser) self.team.admins.add(self.superuser)
# Prepare urls # Prepare urls
self.index_url = reverse("user:detail", args=()) self.index_url = reverse("user:index", args=())
self.notification_url = reverse("user:notifications", args=()) self.notification_url = reverse("user:notifications", args=())
self.api_token_url = reverse("user:api-token", args=()) self.api_token_url = reverse("user:api-token", args=())
self.contact_url = reverse("user:contact", args=(self.superuser.id,)) self.contact_url = reverse("user:contact", args=(self.superuser.id,))

View File

@ -233,7 +233,7 @@ class UserNotificationFormTestCase(BaseTestCase):
self.assertEqual(form.form_title, str(_("Edit notifications"))) self.assertEqual(form.form_title, str(_("Edit notifications")))
self.assertEqual(form.form_caption, "") self.assertEqual(form.form_caption, "")
self.assertEqual(form.action_url, reverse("user:notifications")) self.assertEqual(form.action_url, reverse("user:notifications"))
self.assertEqual(form.cancel_redirect, reverse("user:detail")) self.assertEqual(form.cancel_redirect, reverse("user:index"))
def test_save(self): def test_save(self):
selected_notification = UserNotification.objects.first() selected_notification = UserNotification.objects.first()
@ -260,7 +260,7 @@ class ApiTokenFormTestCase(BaseTestCase):
} }
self.assertIsNone(self.user.api_token) self.assertIsNone(self.user.api_token)
form = NewAPITokenModalForm(request.POST, request=request) form = NewAPITokenModalForm(request.POST, instance=self.user)
form.save() form.save()
self.user.refresh_from_db() self.user.refresh_from_db()
token = self.user.api_token token = self.user.api_token

View File

@ -9,26 +9,24 @@ from django.urls import path
from user.autocomplete.share import ShareUserAutocomplete, ShareTeamAutocomplete from user.autocomplete.share import ShareUserAutocomplete, ShareTeamAutocomplete
from user.autocomplete.team import TeamAdminAutocomplete from user.autocomplete.team import TeamAdminAutocomplete
from user.views.api_token import APITokenView, NewAPITokenView from user.views.api_token import APITokenView, new_api_token_view
from user.views.propagate import PropagateUserView from user.views.propagate import PropagateUserView
from user.views.teams import TeamIndexView, NewTeamView, TeamDetailModalView, EditTeamView, RemoveTeamView, \ from user.views.views import *
LeaveTeamView
from user.views.users import UserDetailView, NotificationsView, ContactView
app_name = "user" app_name = "user"
urlpatterns = [ urlpatterns = [
path("", UserDetailView.as_view(), name="detail"), path("", index_view, name="index"),
path("propagate/", PropagateUserView.as_view(), name="propagate"), path("propagate/", PropagateUserView.as_view(), name="propagate"),
path("notifications/", NotificationsView.as_view(), name="notifications"), path("notifications/", notifications_view, name="notifications"),
path("token/api", APITokenView.as_view(), name="api-token"), path("token/api", APITokenView.as_view(), name="api-token"),
path("token/api/new", NewAPITokenView.as_view(), name="api-token-new"), path("token/api/new", new_api_token_view, name="api-token-new"),
path("contact/<id>", ContactView.as_view(), name="contact"), path("contact/<id>", contact_view, name="contact"),
path("team/", TeamIndexView.as_view(), name="team-index"), path("team/", index_team_view, name="team-index"),
path("team/new", NewTeamView.as_view(), name="team-new"), path("team/new", new_team_view, name="team-new"),
path("team/<id>", TeamDetailModalView.as_view(), name="team-data"), path("team/<id>", data_team_view, name="team-data"),
path("team/<id>/edit", EditTeamView.as_view(), name="team-edit"), path("team/<id>/edit", edit_team_view, name="team-edit"),
path("team/<id>/remove", RemoveTeamView.as_view(), name="team-remove"), path("team/<id>/remove", remove_team_view, name="team-remove"),
path("team/<id>/leave", LeaveTeamView.as_view(), name="team-leave"), path("team/<id>/leave", leave_team_view, name="team-leave"),
# Autocomplete urls # Autocomplete urls
path("atcmplt/share/u", ShareUserAutocomplete.as_view(), name="share-user-autocomplete"), path("atcmplt/share/u", ShareUserAutocomplete.as_view(), name="share-user-autocomplete"),

Some files were not shown because too many files have changed in this diff Show More