Compare commits

..

77 Commits

Author SHA1 Message Date
b5cf5c4446 Merge branch 'master' into 490_View_refactoring 2025-12-12 14:14:34 +01:00
e49eed21da # Renaming
* renames certain classes to match their content
* splits larger files into smaller ones
2025-12-12 14:13:24 +01:00
c4cd40913d Merge remote-tracking branch 'origin/490_View_refactoring' into 490_View_refactoring
# Conflicts:
#	konova/static/css/konova.css
2025-11-28 11:54:40 +01:00
a70434e2cc # Refactoring eiv-kom remove view
* refactors removing compensation from intervention view
* drops unused view on api app
2025-11-28 11:53:43 +01:00
1ac73e4bbb # Refactoring APITokenView
* refactors API Token view
* updates tests
2025-11-28 11:53:43 +01:00
a16fc2eb91 # Refactoring team views
* refactors team views
* split views.py into users.py and teams.py in users app
* refactors method headers for _user_has_permission()
* adds method and class comments and documentation to base view classes
2025-11-28 11:53:43 +01:00
644aa2e3cd # Refactoring payment view
* refactors views for adding, editing and removing payments
2025-11-28 11:53:43 +01:00
c14aff771e # Refactoring revocation views
* refactors views for adding, editing and removing revocations
* refactors view for getting the document of a revocation
* updates tests
2025-11-28 11:53:43 +01:00
e239736a72 # Compensation State view refactoring
* refactors compensation state views for kom, ema, oek
* updates tests
* refactors before-after state toggling into initialization of NewCompensationStateModalForm
2025-11-28 11:53:43 +01:00
21af4f2c57 # Remove View refactoring
* refactors remove view for kom, eiv, oek and ema
* introduces BaseRemoveModalFormView
* moves html blocking logic from BaseModalForm into BaseModalFormView
2025-11-28 11:53:43 +01:00
765356d064 # Test update
* fixes bug for sharing via token where permission was too tight
2025-11-28 11:53:43 +01:00
00bf03f58d # Share view refactoring
* refactors share views for eiv, oek, ema (kom does not have any)
2025-11-28 11:53:43 +01:00
3de97e2f6a # Resubmission view refactoring
* refactors resubmission view for eiv, kom, oek, ema
* removes unused attributes on BaseModalFormView
2025-11-28 11:53:43 +01:00
00109b6bfd # Log view refactoring
* refactors log views to inherit from BaseView
2025-11-28 11:53:43 +01:00
971e3f20c8 # Parcel view refactoring
* refactors parcel view to inherit from BaseView
2025-11-28 11:53:43 +01:00
efb76278b4 # Document views refactoring
* refactors new, edit, get and delete views for eiv, kom, oek and ema
* introduces
2025-11-28 11:53:43 +01:00
e0b8922494 # Deadline tests refactored
* refactors tests for deadline views to check whether they work properly
2025-11-28 11:53:43 +01:00
bfdf82ac46 # Deadline views refactored
* refactors AbstractDeadlineViews (new, edit, remove) to inherit from BaseModalFormView
* refactors KOM, OEK, EMA views for deadline views
2025-11-28 11:53:43 +01:00
0ad0b02f95 # CompensationAction views refactored
* refactors AbstractCompensationActionViews (new, edit, remove) to inherit from BaseModalFormView
* refactors KOM, OEK, EMA views for compensation actions
* moves message template strings into message_templates.py
2025-11-28 11:53:43 +01:00
a407c86dfb # RecordModalForm refactored
* refactors AbstractRecordModalForm
* refactors recording view for ema, intervention and eco account
2025-11-28 11:53:42 +01:00
abfc48d79b # BaseModalFormView refactoring
* extends BaseModalFormView to hold general logic for processing GET and POST requests for BaseModalForm endpoints
* refactors uuid check to use a specific parameter instead of kwargs
* fixes css bug where modal form input elements would not be visible
* refactors check view for intervention from function to class
* refactors DeductionViews to inherit from extended BaseModalFormView
2025-11-28 11:53:40 +01:00
43846e8d2f # Bugfix NewCompensationForm
* fixes bug where a form error would trigger a wrong error warning
2025-11-28 11:53:31 +01:00
5e01d7ccda # NewEcoAccount EditEcoAccount view
* refactors new and edit eco account views from function to class based
* removes info message if checked intervention is altered and loses the current checked state
* updates comments/documentation
* removes code duplicates
* fixes display error where modal form was hidden behind menu bar of map client
* fixes bug where compensation could not be created directly from intervention
2025-11-28 11:53:15 +01:00
5b1af04d66 # NewCompensation EditCompensation view
* refactors new and edit compensation views from function to class based
* adds checked property to compensation to return parent-intervention's checked info
* fixes bug where compensation could be added to recorded intervention
* updates translations
2025-11-28 11:52:14 +01:00
0d939c9e19 # NewEma EditEma views
* refactors views for new ema and edit ema from function to class based
* moves shared access check to base edit form view to be checked for every inheriting class
* fixes bug where private variables changed on singleton objects
* updates translations
2025-11-28 11:52:14 +01:00
7d80875727 # EditIntervention view
* refactors edit intervention view from function to class
2025-11-28 11:52:14 +01:00
53d5e03a3f # NewIntervention view
* introduces BaseFormView and BaseNewSpatialLocatedObjectFormView
* refactors new intervention view from function to class
2025-11-28 11:52:14 +01:00
34a8ea4aec # Detail View
* introduces BaseDetailView
* refactors detail views for EIV, KOM, OEK, EMA from function based to class based
* refactors already class based HomeView to inherit from new BaseView
2025-11-28 11:52:14 +01:00
478d630d76 # ClientProxyView
* refactors login required from method decorator to mixin inheritance
2025-11-28 11:52:14 +01:00
00e2178f3c # Report view refactoring
* refactors function based report views into class based for EIV, OEK, EMA, KOM
* introduces BaseReportView for proper inheritance of shared logic
* refactors generating of qr codes into proper class
2025-11-28 11:52:14 +01:00
8a984d0169 # Deduction views
* refactors deduction views on interventions and eco accounts from function to class based
* introduces basic checks on shared access and permission on BaseView on dispatching --> checks shall be overwritten on inheriting classes
2025-11-28 11:52:14 +01:00
f4e97db9ac # User view refactoring
* refactors majority of user views into class based views
* introduces BaseModalFormView and BaseView for even more generic usage
* renames url identifier user:index into user:detail for more clarity
2025-11-28 11:52:14 +01:00
95510cef36 # Identifier Generator View EcoAccount refactoring
* refactors identifier generator view for ecoaccount
* simplifies base identifier generator view even further
2025-11-28 11:52:14 +01:00
225d4e8ce1 # Identifier Generator View Compensation refactoring
* refactors identifier generator view for compensation
2025-11-28 11:52:14 +01:00
ca7411b6c3 # Identifier Generator View refactoring
* refactors identifier generator view for interventions
* simplifies same view for ema
2025-11-28 11:52:14 +01:00
43f313a71e # NewId Generator Ema refactoring
* introduces BaseNewIdentifierGeneratorView class
* refactors new identifier generator view for ema
2025-11-28 11:52:14 +01:00
241db2f51d # Index Ema refactoring
* refactors index view for ema
2025-11-28 11:52:14 +01:00
dce61c4d7e # Index EcoAccount refactoring
* refactors index view for eco account
2025-11-28 11:52:14 +01:00
17d817bbe1 # Index Compensation refactoring
* refactors index view for compensations
2025-11-28 11:52:14 +01:00
aec10ee0af # Index Intervention refactoring
* introduces BaseIndexView class
* refactors index view for interventions
2025-11-28 11:52:14 +01:00
1b6eea2c9e # Refactoring eiv-kom remove view
* refactors removing compensation from intervention view
* drops unused view on api app
2025-11-08 13:05:14 +01:00
cf6f188ef3 # Refactoring APITokenView
* refactors API Token view
* updates tests
2025-11-05 10:37:27 +01:00
f122778232 # Refactoring team views
* refactors team views
* split views.py into users.py and teams.py in users app
* refactors method headers for _user_has_permission()
* adds method and class comments and documentation to base view classes
2025-11-05 10:12:49 +01:00
bc2e901ca9 # Refactoring payment view
* refactors views for adding, editing and removing payments
2025-11-04 09:09:05 +01:00
4a16727da1 # Refactoring revocation views
* refactors views for adding, editing and removing revocations
* refactors view for getting the document of a revocation
* updates tests
2025-11-04 08:58:47 +01:00
d85ebccec8 # Compensation State view refactoring
* refactors compensation state views for kom, ema, oek
* updates tests
* refactors before-after state toggling into initialization of NewCompensationStateModalForm
2025-10-21 21:05:45 +02:00
837c1de938 # Remove View refactoring
* refactors remove view for kom, eiv, oek and ema
* introduces BaseRemoveModalFormView
* moves html blocking logic from BaseModalForm into BaseModalFormView
2025-10-21 20:28:43 +02:00
fe2ac3d97d # Test update
* fixes bug for sharing via token where permission was too tight
2025-10-21 19:37:34 +02:00
554ade6794 # Share view refactoring
* refactors share views for eiv, oek, ema (kom does not have any)
2025-10-21 19:15:17 +02:00
3a9c4e13f6 # Resubmission view refactoring
* refactors resubmission view for eiv, kom, oek, ema
* removes unused attributes on BaseModalFormView
2025-10-21 17:03:12 +02:00
1fc1b533cd # Log view refactoring
* refactors log views to inherit from BaseView
2025-10-21 14:56:26 +02:00
1175fe3b37 # Parcel view refactoring
* refactors parcel view to inherit from BaseView
2025-10-21 14:06:11 +02:00
d2a57df080 # Document views refactoring
* refactors new, edit, get and delete views for eiv, kom, oek and ema
* introduces
2025-10-21 13:48:16 +02:00
d5accb2143 # Deadline tests refactored
* refactors tests for deadline views to check whether they work properly
2025-10-21 09:14:46 +02:00
6056a8882d # Deadline views refactored
* refactors AbstractDeadlineViews (new, edit, remove) to inherit from BaseModalFormView
* refactors KOM, OEK, EMA views for deadline views
2025-10-21 09:04:57 +02:00
c7a4c309bf # CompensationAction views refactored
* refactors AbstractCompensationActionViews (new, edit, remove) to inherit from BaseModalFormView
* refactors KOM, OEK, EMA views for compensation actions
* moves message template strings into message_templates.py
2025-10-21 08:48:30 +02:00
a7b23935a1 # RecordModalForm refactored
* refactors AbstractRecordModalForm
* refactors recording view for ema, intervention and eco account
2025-10-20 16:29:50 +02:00
97fbe02742 # BaseModalFormView refactoring
* extends BaseModalFormView to hold general logic for processing GET and POST requests for BaseModalForm endpoints
* refactors uuid check to use a specific parameter instead of kwargs
* fixes css bug where modal form input elements would not be visible
* refactors check view for intervention from function to class
* refactors DeductionViews to inherit from extended BaseModalFormView
2025-10-20 16:13:58 +02:00
ed5d571704 # Bugfix NewCompensationForm
* fixes bug where a form error would trigger a wrong error warning
2025-10-20 13:52:15 +02:00
a86d86b731 # NewEcoAccount EditEcoAccount view
* refactors new and edit eco account views from function to class based
* removes info message if checked intervention is altered and loses the current checked state
* updates comments/documentation
* removes code duplicates
* fixes display error where modal form was hidden behind menu bar of map client
* fixes bug where compensation could not be created directly from intervention
2025-10-20 09:49:18 +02:00
73178b3fd2 # NewCompensation EditCompensation view
* refactors new and edit compensation views from function to class based
* adds checked property to compensation to return parent-intervention's checked info
* fixes bug where compensation could be added to recorded intervention
* updates translations
2025-10-19 14:06:14 +02:00
278a951e92 # NewEma EditEma views
* refactors views for new ema and edit ema from function to class based
* moves shared access check to base edit form view to be checked for every inheriting class
* fixes bug where private variables changed on singleton objects
* updates translations
2025-10-19 13:10:22 +02:00
9e4a78ec60 # EditIntervention view
* refactors edit intervention view from function to class
2025-10-19 12:50:35 +02:00
d03b714fb5 # NewIntervention view
* introduces BaseFormView and BaseNewSpatialLocatedObjectFormView
* refactors new intervention view from function to class
2025-10-19 12:37:13 +02:00
a9b402862b # Detail View
* introduces BaseDetailView
* refactors detail views for EIV, KOM, OEK, EMA from function based to class based
* refactors already class based HomeView to inherit from new BaseView
2025-10-17 15:40:45 +02:00
61ec9c8c9b # ClientProxyView
* refactors login required from method decorator to mixin inheritance
2025-10-17 14:42:44 +02:00
f2baa054bf # Report view refactoring
* refactors function based report views into class based for EIV, OEK, EMA, KOM
* introduces BaseReportView for proper inheritance of shared logic
* refactors generating of qr codes into proper class
2025-10-17 11:07:16 +02:00
242730435e # Deduction views
* refactors deduction views on interventions and eco accounts from function to class based
* introduces basic checks on shared access and permission on BaseView on dispatching --> checks shall be overwritten on inheriting classes
2025-10-16 15:36:57 +02:00
afbdf221c3 # User view refactoring
* refactors majority of user views into class based views
* introduces BaseModalFormView and BaseView for even more generic usage
* renames url identifier user:index into user:detail for more clarity
2025-10-15 17:09:40 +02:00
be9f6f1b7e # Identifier Generator View EcoAccount refactoring
* refactors identifier generator view for ecoaccount
* simplifies base identifier generator view even further
2025-10-15 16:46:07 +02:00
80e8925a63 # Identifier Generator View Compensation refactoring
* refactors identifier generator view for compensation
2025-10-15 16:42:42 +02:00
c597e1934b # Identifier Generator View refactoring
* refactors identifier generator view for interventions
* simplifies same view for ema
2025-10-15 16:40:35 +02:00
a44d8658d4 # NewId Generator Ema refactoring
* introduces BaseNewIdentifierGeneratorView class
* refactors new identifier generator view for ema
2025-10-15 16:29:05 +02:00
bb71c0fcc8 # Index Ema refactoring
* refactors index view for ema
2025-10-15 16:14:03 +02:00
67acddf701 # Index EcoAccount refactoring
* refactors index view for eco account
2025-10-15 16:12:21 +02:00
21bb988d86 # Index Compensation refactoring
* refactors index view for compensations
2025-10-15 16:03:53 +02:00
1ceffccd40 # Index Intervention refactoring
* introduces BaseIndexView class
* refactors index view for interventions
2025-10-15 16:00:51 +02:00
123 changed files with 2806 additions and 3721 deletions

View File

@@ -7,11 +7,8 @@ Created on: 21.01.22
"""
from django.urls import path, include
from api.views.method_views import generate_new_token_view
app_name = "api"
urlpatterns = [
path("v1/", include("api.urls.v1.urls", namespace="v1")),
path("token/generate", generate_new_token_view, name="generate-new-token"),
]

View File

@@ -1,35 +0,0 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 27.01.22
"""
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest, JsonResponse
from api.models import APIUserToken
@login_required
def generate_new_token_view(request: HttpRequest):
""" Handles request for fetching
Args:
request (HttpRequest): The incoming request
Returns:
"""
if request.method == "GET":
token = APIUserToken()
while APIUserToken.objects.filter(token=token.token).exists():
token = APIUserToken()
return JsonResponse(
data={
"gen_data": token.token
}
)
else:
raise NotImplementedError

View File

View File

@@ -45,14 +45,6 @@ class AbstractCompensationAdmin(BaseObjectAdmin):
states = "\n".join(states)
return states
def get_actions(self, request):
DELETE_ACTION_IDENTIFIER = "delete_selected"
actions = super().get_actions(request)
if DELETE_ACTION_IDENTIFIER in actions:
del actions[DELETE_ACTION_IDENTIFIER]
return actions
class CompensationAdmin(AbstractCompensationAdmin):
autocomplete_fields = [

View File

@@ -168,6 +168,17 @@ class NewCompensationForm(AbstractCompensationForm,
comp.log.add(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):
with transaction.atomic():
comp, action = self.__create_comp(user)

View File

@@ -15,7 +15,6 @@ from compensation.models import EcoAccount
from intervention.models import Handler, Responsibility, Legal
from konova.forms import SimpleGeomForm
from konova.forms.modals import RemoveModalForm
from konova.settings import ETS_GROUP
from konova.utils import validators
from user.models import User, UserActionLogEntry
@@ -238,7 +237,11 @@ class EditEcoAccountForm(NewEcoAccountForm):
class RemoveEcoAccountModalForm(RemoveModalForm):
""" Form class
Provides a form for deleting eco accounts
"""
def is_valid(self):
super_valid = super().is_valid()
has_deductions = self.instance.deductions.exists()
@@ -247,13 +250,4 @@ class RemoveEcoAccountModalForm(RemoveModalForm):
"confirm",
_("The account can not be removed, since there are still deductions.")
)
# If there are deductions but the performing user is not part of an ETS group, we assume this poor
# fella does not know what he/she does -> give a hint that they should contact someone in charge...
user_is_ets_user = self.user.in_group(ETS_GROUP)
if not user_is_ets_user:
self.add_error(
"confirm",
_("Please contact the responsible conservation office to find a solution!")
)
return super_valid and not has_deductions

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,15 @@
"""
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,21 +5,17 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 18.08.22
"""
from bootstrap_modal_forms.mixins import is_ajax
from dal import autocomplete
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 codelist.models import KonovaCode
from codelist.settings import CODELIST_BIOTOPES_ID, \
CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID
from compensation.models import CompensationState
from intervention.inputs import CompensationStateTreeRadioSelect
from konova.contexts import BaseContext
from konova.forms.modals import RemoveModalForm, BaseModalForm
from konova.utils.message_templates import COMPENSATION_STATE_EDITED, FORM_INVALID, ADDED_COMPENSATION_STATE
from konova.utils.message_templates import COMPENSATION_STATE_EDITED, ADDED_COMPENSATION_STATE
class NewCompensationStateModalForm(BaseModalForm):
@@ -68,10 +64,13 @@ class NewCompensationStateModalForm(BaseModalForm):
)
)
_is_before_state: bool = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("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(
code_lists__in=[CODELIST_BIOTOPES_ID],
is_archived=False,
@@ -83,65 +82,19 @@ class NewCompensationStateModalForm(BaseModalForm):
]
self.fields["biotope_type"].choices = choices
def save(self, is_before_state: bool = False):
state = self.instance.add_state(self, is_before_state)
def save(self):
state = self.instance.add_state(self, self._is_before_state)
self.instance.mark_as_edited(self.user, self.request, ADDED_COMPENSATION_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):
state = None
def __init__(self, *args, **kwargs):
self.state = kwargs.pop("state", None)
state_id = kwargs.pop("state_id", None)
self.state = CompensationState.objects.get(id=state_id)
super().__init__(*args, **kwargs)
self.form_title = _("Edit state")
biotope_type_id = self.state.biotope_type.id if self.state.biotope_type else None
@@ -172,8 +125,8 @@ class RemoveCompensationStateModalForm(RemoveModalForm):
state = None
def __init__(self, *args, **kwargs):
state = kwargs.pop("state", None)
self.state = state
state_id = kwargs.pop("state_id", None)
self.state = CompensationState.objects.get(id=state_id)
super().__init__(*args, **kwargs)
def save(self):

View File

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

View File

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

View File

@@ -7,31 +7,30 @@ Created on: 24.08.21
"""
from django.urls import path
from compensation.views.compensation.detail import DetailCompensationView
from compensation.views.compensation.document import EditCompensationDocumentView, NewCompensationDocumentView, \
GetCompensationDocumentView, RemoveCompensationDocumentView
from compensation.views.compensation.remove import RemoveCompensationView
from compensation.views.compensation.resubmission import CompensationResubmissionView
from compensation.views.compensation.report import CompensationPublicReportView
from compensation.views.compensation.report import CompensationReportView
from compensation.views.compensation.deadline import NewCompensationDeadlineView, EditCompensationDeadlineView, \
RemoveCompensationDeadlineView
from compensation.views.compensation.action import NewCompensationActionView, EditCompensationActionView, \
RemoveCompensationActionView
from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \
RemoveCompensationStateView
from compensation.views.compensation.compensation import new_view, edit_view, \
IndexCompensationView, CompensationIdentifierGeneratorView
from compensation.views.compensation.compensation import \
CompensationIndexView, CompensationIdentifierGeneratorView, CompensationDetailView, \
NewCompensationFormView, EditCompensationFormView, RemoveCompensationView
from compensation.views.compensation.log import CompensationLogView
urlpatterns = [
# Main compensation
path("", IndexCompensationView.as_view(), name="index"),
path("", CompensationIndexView.as_view(), name="index"),
path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'),
path('new/<intervention_id>', new_view, name='new'),
path('new', new_view, name='new'),
path('<id>', DetailCompensationView.as_view(), name='detail'),
path('new/<intervention_id>', NewCompensationFormView.as_view(), name='new'),
path('new', NewCompensationFormView.as_view(), name='new'),
path('<id>', CompensationDetailView.as_view(), name='detail'),
path('<id>/log', CompensationLogView.as_view(), name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/edit', EditCompensationFormView.as_view(), name='edit'),
path('<id>/remove', RemoveCompensationView.as_view(), name='remove'),
path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'),
@@ -45,7 +44,7 @@ urlpatterns = [
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>/remove', RemoveCompensationDeadlineView.as_view(), name='deadline-remove'),
path('<id>/report', CompensationPublicReportView.as_view(), name='report'),
path('<id>/report', CompensationReportView.as_view(), name='report'),
path('<id>/resub', CompensationResubmissionView.as_view(), name='resubmission-create'),
# Documents

View File

@@ -8,13 +8,11 @@ Created on: 24.08.21
from django.urls import path
from compensation.autocomplete.eco_account import EcoAccountAutocomplete
from compensation.views.eco_account.detail import DetailEcoAccountView
from compensation.views.eco_account.eco_account import new_view, edit_view, \
IndexEcoAccountView, EcoAccountIdentifierGeneratorView
from compensation.views.eco_account.eco_account import EcoAccountIndexView, EcoAccountIdentifierGeneratorView, \
EcoAccountDetailView, NewEcoAccountFormView, EditEcoAccountFormView, RemoveEcoAccountView
from compensation.views.eco_account.log import EcoAccountLogView
from compensation.views.eco_account.record import EcoAccountRecordView
from compensation.views.eco_account.remove import RemoveEcoAccountView
from compensation.views.eco_account.report import EcoAccountPublicReportView
from compensation.views.eco_account.report import EcoAccountReportView
from compensation.views.eco_account.resubmission import EcoAccountResubmissionView
from compensation.views.eco_account.state import NewEcoAccountStateView, EditEcoAccountStateView, \
RemoveEcoAccountStateView
@@ -30,14 +28,14 @@ from compensation.views.eco_account.deduction import NewEcoAccountDeductionView,
app_name = "acc"
urlpatterns = [
path("", IndexEcoAccountView.as_view(), name="index"),
path('new/', new_view, name='new'),
path("", EcoAccountIndexView.as_view(), name="index"),
path('new/', NewEcoAccountFormView.as_view(), name='new'),
path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'),
path('<id>', DetailEcoAccountView.as_view(), name='detail'),
path('<id>', EcoAccountDetailView.as_view(), name='detail'),
path('<id>/log', EcoAccountLogView.as_view(), name='log'),
path('<id>/record', EcoAccountRecordView.as_view(), name='record'),
path('<id>/report', EcoAccountPublicReportView.as_view(), name='report'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/report', EcoAccountReportView.as_view(), name='report'),
path('<id>/edit', EditEcoAccountFormView.as_view(), name='edit'),
path('<id>/remove', RemoveEcoAccountView.as_view(), name='remove'),
path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'),

View File

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

View File

@@ -5,53 +5,23 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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.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 compensation.models import Compensation
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView
_COMPENSATION_DETAIL_URL_NAME = "compensation:detail"
class NewCompensationActionView(AbstractNewCompensationActionView):
model = Compensation
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)
_MODEL_CLS = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME
class EditCompensationActionView(AbstractEditCompensationActionView):
model = Compensation
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)
_MODEL_CLS = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME
class RemoveCompensationActionView(AbstractRemoveCompensationActionView):
model = Compensation
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)
_MODEL_CLS = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME

View File

@@ -6,188 +6,166 @@ Created on: 19.08.22
"""
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render, redirect
from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import gettext_lazy as _
from compensation.forms.compensation import EditCompensationForm, NewCompensationForm
from compensation.models import Compensation
from compensation.tables.compensation import CompensationTable
from intervention.models import Intervention
from konova.contexts import BaseContext
from konova.decorators import shared_access_required, default_group_required
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, \
IDENTIFIER_REPLACED, COMPENSATION_ADDED_TEMPLATE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
from konova.forms.modals import RemoveModalForm
from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, \
RECORDED_BLOCKS_EDIT, PARAMS_INVALID
from konova.views.identifier import AbstractIdentifierGeneratorView
from konova.views.form import AbstractNewGeometryFormView, AbstractEditGeometryFormView
from konova.views.index import AbstractIndexView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class IndexCompensationView(AbstractIndexView):
def get(self, request, *args, **kwargs) -> HttpResponse:
"""
Renders the index view for compensation
class CompensationIndexView(LoginRequiredMixin, AbstractIndexView):
_TAB_TITLE = _("Compensations - Overview")
_INDEX_TABLE_CLS = CompensationTable
Args:
request (HttpRequest): The incoming request
Returns:
A rendered view
"""
compensations = Compensation.objects.filter(
def _get_queryset(self):
qs = 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, self._TEMPLATE, context)
return qs
@login_required
@default_group_required
@shared_access_required(Intervention, "intervention_id")
def new_view(request: HttpRequest, intervention_id: str = None):
"""
Renders a view for a new compensation creation
Args:
request (HttpRequest): The incoming request
class NewCompensationFormView(AbstractNewGeometryFormView):
_FORM_CLS = NewCompensationForm
_MODEL_CLS = Compensation
_TEMPLATE = "compensation/form/view.html"
_TAB_TITLE = _("New Compensation")
_REDIRECT_URL = "compensation:detail"
Returns:
def _user_has_shared_access(self, user, **kwargs):
# On a new compensation make sure the intervention (if call came directly through an intervention's detail
# 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)
"""
template = "compensation/form/view.html"
if intervention_id is not None:
try:
intervention = Intervention.objects.get(id=intervention_id)
except ObjectDoesNotExist:
messages.error(request, PARAMS_INVALID)
return redirect("home")
if intervention.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("intervention:detail", id=intervention_id)
def _user_has_permission(self, user, **kwargs):
# User has to be an ets user
return user.is_default_user()
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
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)
comp = data_form.save(request.user, geom_form)
if generated_identifier != comp.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
comp.identifier
def dispatch(self, request, *args, **kwargs):
# Make sure there is an existing intervention based on the given id
# Compensations can not exist without an intervention
intervention_id = kwargs.get("intervention_id", None)
if intervention_id:
try:
intervention = Intervention.objects.get(id=intervention_id)
if intervention.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
)
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)
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 CompensationIdentifierGeneratorView(AbstractIdentifierGeneratorView):
_MODEL = Compensation
class EditCompensationFormView(AbstractEditGeometryFormView):
_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()
@login_required
@default_group_required
@shared_access_required(Compensation, "id")
def edit_view(request: HttpRequest, id: str):
"""
Renders a view for editing compensations
class CompensationIdentifierGeneratorView(LoginRequiredMixin, AbstractIdentifierGeneratorView):
_MODEL_CLS = Compensation
_REDIRECT_URL = "compensation:index"
Args:
request (HttpRequest): The incoming request
Returns:
class CompensationDetailView(BaseDetailView):
_MODEL_CLS = Compensation
_TEMPLATE = "compensation/detail/compensation/view.html"
"""
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
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 redirect("compensation:detail", id=id)
return comp
# 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
def _get_detail_context(self, obj: Compensation):
""" Generate object specific detail context for view
# 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
)
Args:
obj (): The record
num_ignored_geometries = geom_form.get_num_geometries_ignored()
if num_ignored_geometries > 0:
messages.info(
request,
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
)
Returns:
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)
"""
# Order states according to surface
before_states = obj.before_states.all().prefetch_related("biotope_type").order_by("-surface")
after_states = obj.after_states.all().prefetch_related("biotope_type").order_by("-surface")
actions = obj.actions.all().prefetch_related("action_type")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = obj.get_surface_before_states()
sum_after_states = obj.get_surface_after_states()
diff_states = abs(sum_before_states - sum_after_states)
last_checked = obj.intervention.get_last_checked_action()
last_checked_tooltip = ""
if last_checked:
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(
last_checked.get_timestamp_str_formatted(),
last_checked.user
)
context = {
"last_checked": last_checked,
"last_checked_tooltip": last_checked_tooltip,
"actions": actions,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"has_finished_deadlines": obj.get_finished_deadlines().exists(),
}
return context
class RemoveCompensationView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = Compensation
_FORM_CLS = RemoveModalForm
_REDIRECT_URL = "compensation:index"
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()

View File

@@ -5,45 +5,21 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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 konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.deadline import AbstractRemoveDeadlineView, AbstractEditDeadlineView, AbstractNewDeadlineView
_COMPENSATION_DETAIL_URL_NAME = "compensation:detail"
class NewCompensationDeadlineView(AbstractNewDeadlineView):
model = Compensation
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)
_MODEL_CLS = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME
class EditCompensationDeadlineView(AbstractEditDeadlineView):
model = Compensation
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)
_MODEL_CLS = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME
class RemoveCompensationDeadlineView(AbstractRemoveDeadlineView):
model = Compensation
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)
_MODEL_CLS = Compensation
_REDIRECT_URL = _COMPENSATION_DETAIL_URL_NAME

View File

@@ -1,97 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.contrib import messages
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render, get_object_or_404
from compensation.models import Compensation
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.settings import ETS_GROUP, ZB_GROUP, DEFAULT_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE, DATA_CHECKED_PREVIOUSLY_TEMPLATE
from konova.views.detail import AbstractDetailView
class DetailCompensationView(AbstractDetailView):
_TEMPLATE = "compensation/detail/compensation/view.html"
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" Renders a detail view for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
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, self._TEMPLATE, context)

View File

@@ -5,62 +5,33 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.forms.modals.document import NewCompensationDocumentModalForm
from compensation.forms.modals.document import NewCompensationDocumentModalForm, EditCompensationDocumentModalForm, \
RemoveCompensationDocumentModalForm
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, \
AbstractEditDocumentView
class NewCompensationDocumentView(AbstractNewDocumentView):
model = Compensation
form = NewCompensationDocumentModalForm
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)
_MODEL_CLS = Compensation
_FORM_CLS = NewCompensationDocumentModalForm
_REDIRECT_URL = "compensation:detail"
class GetCompensationDocumentView(AbstractGetDocumentView):
model = Compensation
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)
_MODEL_CLS = Compensation
_DOCUMENT_CLS = CompensationDocument
class RemoveCompensationDocumentView(AbstractRemoveDocumentView):
model = Compensation
document_model = CompensationDocument
@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)
_MODEL_CLS = Compensation
_DOCUMENT_CLS = CompensationDocument
_FORM_CLS = RemoveCompensationDocumentModalForm
_REDIRECT_URL = "compensation:detail"
class EditCompensationDocumentView(AbstractEditDocumentView):
model = Compensation
document_model = CompensationDocument
form = EditDocumentModalForm
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)
_MODEL_CLS = Compensation
_DOCUMENT_CLS = CompensationDocument
_FORM_CLS = EditCompensationDocumentModalForm
_REDIRECT_URL = "compensation:detail"

View File

@@ -5,20 +5,11 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.contrib.auth.mixins import LoginRequiredMixin
from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.log import AbstractLogView
class CompensationLogView(AbstractLogView):
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)
class CompensationLogView(LoginRequiredMixin, AbstractLogView):
_MODEL_CLS = Compensation

View File

@@ -1,20 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.http import HttpRequest, HttpResponse
from django.utils.decorators import method_decorator
from compensation.models import Compensation
from konova.decorators import shared_access_required
from konova.views.remove import AbstractRemoveView
class RemoveCompensationView(AbstractRemoveView):
_MODEL = Compensation
_REDIRECT_URL = "compensation:index"
@method_decorator(shared_access_required(Compensation, "id"))
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
return super().get(request, *args, **kwargs)

View File

@@ -5,81 +5,48 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.models import Compensation
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.sub_settings.django_settings import BASE_URL
from konova.utils.qrcode import QrCode
from konova.views.report import AbstractPublicReportView
from konova.views.report import AbstractReportView
class CompensationPublicReportView(AbstractPublicReportView):
class BaseCompensationReportView(AbstractReportView):
def _get_compensation_report_context(self, obj):
# Order states by surface
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 {
"before_states": before_states,
"after_states": after_states,
"actions": actions,
}
class CompensationReportView(BaseCompensationReportView):
_MODEL = Compensation
_TEMPLATE = "compensation/report/compensation/report.html"
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" Renders the public report view
def _get_report_context(self, obj):
report_url = BASE_URL + reverse("compensation:report", args=(obj.id,))
qrcode_report = QrCode(report_url, 10)
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
Args:
request (HttpRequest): The incoming request
id (str): The id of the intervention
Returns:
"""
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()
qrcode = QrCode(
content=request.build_absolute_uri(reverse("compensation:report", args=(id,))),
size=10
)
qrcode_lanis = QrCode(
content=comp.get_LANIS_link(),
size=7
)
# Order states by surface
before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type")
actions = comp.actions.all().prefetch_related("action_type")
context = {
"obj": comp,
report_context = {
"qrcode": {
"img": qrcode.get_img(),
"url": qrcode.get_content(),
"img": qrcode_report.get_img(),
"url": qrcode_report.get_content(),
},
"qrcode_lanis": {
"img": qrcode_lanis.get_img(),
"url": qrcode_lanis.get_content(),
},
"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, self._TEMPLATE, context)
report_context.update(self._get_compensation_report_context(obj))
return report_context

View File

@@ -5,22 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.forms.modals.resubmission import CompensationResubmissionModalForm
from compensation.models import Compensation
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView
class CompensationResubmissionView(AbstractResubmissionView):
model = Compensation
redirect_url_base = "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)
_MODEL_CLS = Compensation
_FORM_CLS = CompensationResubmissionModalForm
_REDIRECT_URL = "compensation:detail"

View File

@@ -5,46 +5,21 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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 konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \
AbstractRemoveCompensationStateView
class NewCompensationStateView(AbstractNewCompensationStateView):
model = Compensation
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)
_MODEL_CLS = Compensation
_REDIRECT_URL = "compensation:detail"
class EditCompensationStateView(AbstractEditCompensationStateView):
model = Compensation
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)
_MODEL_CLS = Compensation
_REDIRECT_URL = "compensation:detail"
class RemoveCompensationStateView(AbstractRemoveCompensationStateView):
model = Compensation
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)
_MODEL_CLS = Compensation
_REDIRECT_URL = "compensation:detail"

View File

@@ -5,46 +5,22 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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 konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView
_ECO_ACCOUNT_DETAIL_URL_NAME = "compensation:acc:detail"
class NewEcoAccountActionView(AbstractNewCompensationActionView):
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)
_MODEL_CLS = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME
class EditEcoAccountActionView(AbstractEditCompensationActionView):
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)
_MODEL_CLS = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME
class RemoveEcoAccountActionView(AbstractRemoveCompensationActionView):
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)
_MODEL_CLS = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME

View File

@@ -5,45 +5,22 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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 konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.deadline import AbstractNewDeadlineView, AbstractEditDeadlineView, AbstractRemoveDeadlineView
_ECO_ACCOUNT_DETAIL_URL_NAME = "compensation:acc:detail"
class NewEcoAccountDeadlineView(AbstractNewDeadlineView):
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)
_MODEL_CLS = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME
class EditEcoAccountDeadlineView(AbstractEditDeadlineView):
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)
_MODEL_CLS = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME
class RemoveEcoAccountDeadlineView(AbstractRemoveDeadlineView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
_MODEL_CLS = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIL_URL_NAME
@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,54 +5,33 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404
from django.utils.decorators import method_decorator
from compensation.models import EcoAccount
from konova.decorators import default_group_required, login_required_modal
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
_ECO_ACCOUNT_DETAIl_URL_NAME = "compensation:acc:detail"
class NewEcoAccountDeductionView(AbstractNewDeductionView):
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 NewEcoAccountDeductionView(LoginRequiredMixin, AbstractNewDeductionView):
_MODEL_CLS = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME
def _custom_check(self, obj):
# New deductions can only be created if the eco account has been recorded
if not obj.recorded:
raise Http404()
class EditEcoAccountDeductionView(AbstractEditDeductionView):
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)
def _check_for_recorded_instance(self, obj):
# Deductions can be created on recorded as well as on non-recorded entries
return None
class RemoveEcoAccountDeductionView(AbstractRemoveDeductionView):
def _custom_check(self, obj):
pass
class EditEcoAccountDeductionView(LoginRequiredMixin, AbstractEditDeductionView):
_MODEL_CLS = EcoAccount
_REDIRECT_URL = _ECO_ACCOUNT_DETAIl_URL_NAME
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

@@ -1,97 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.contrib import messages
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render, get_object_or_404
from compensation.models import EcoAccount
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.settings import ETS_GROUP, ZB_GROUP, DEFAULT_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE
from konova.views.detail import AbstractDetailView
class DetailEcoAccountView(AbstractDetailView):
_TEMPLATE = "compensation/detail/eco_account/view.html"
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" Renders a detail view for a compensation
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
Returns:
"""
acc = get_object_or_404(
EcoAccount.objects.prefetch_related(
"deadlines",
).select_related(
'geometry',
'responsible',
),
id=id,
deleted=None,
)
geom_form = SimpleGeomForm(instance=acc)
parcels = acc.get_underlying_parcels()
_user = request.user
is_data_shared = acc.is_shared_with(_user)
# Order states according to surface
before_states = acc.before_states.order_by("-surface")
after_states = acc.after_states.order_by("-surface")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = acc.get_surface_before_states()
sum_after_states = acc.get_surface_after_states()
diff_states = abs(sum_before_states - sum_after_states)
# Calculate rest of available surface for deductions
available_total = acc.deductable_rest
available_relative = acc.get_deductable_rest_relative()
# Prefetch related data to decrease the amount of db connections
deductions = acc.deductions.filter(
intervention__deleted=None,
)
actions = acc.actions.all()
request = acc.set_status_messages(request)
requesting_user_is_only_shared_user = acc.is_only_shared_with(_user)
if requesting_user_is_only_shared_user:
messages.info(
request,
DO_NOT_FORGET_TO_SHARE
)
context = {
"obj": acc,
"geom_form": geom_form,
"parcels": parcels,
"is_entry_shared": is_data_shared,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"available": available_relative,
"available_total": available_total,
"is_default_member": _user.in_group(DEFAULT_GROUP),
"is_zb_member": _user.in_group(ZB_GROUP),
"is_ets_member": _user.in_group(ETS_GROUP),
"LANIS_LINK": acc.get_LANIS_link(),
"deductions": deductions,
"actions": actions,
TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}",
"has_finished_deadlines": acc.get_finished_deadlines().exists(),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)

View File

@@ -5,65 +5,33 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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.forms.modals.document import NewEcoAccountDocumentModalForm
from compensation.forms.modals.document import NewEcoAccountDocumentModalForm, RemoveEcoAccountDocumentModalForm, \
EditEcoAccountDocumentModalForm
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, \
AbstractEditDocumentView
class NewEcoAccountDocumentView(AbstractNewDocumentView):
model = EcoAccount
form = NewEcoAccountDocumentModalForm
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)
_MODEL_CLS = EcoAccount
_FORM_CLS = NewEcoAccountDocumentModalForm
_REDIRECT_URL = "compensation:acc:detail"
class GetEcoAccountDocumentView(AbstractGetDocumentView):
model = EcoAccount
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)
_MODEL_CLS = EcoAccount
_DOCUMENT_CLS = EcoAccountDocument
class RemoveEcoAccountDocumentView(AbstractRemoveDocumentView):
model = EcoAccount
document_model = EcoAccountDocument
@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)
_MODEL_CLS = EcoAccount
_DOCUMENT_CLS = EcoAccountDocument
_FORM_CLS = RemoveEcoAccountDocumentModalForm
_REDIRECT_URL = "compensation:acc:detail"
class EditEcoAccountDocumentView(AbstractEditDocumentView):
model = EcoAccount
document_model = EcoAccountDocument
form = EditDocumentModalForm
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)
_MODEL_CLS = EcoAccount
_DOCUMENT_CLS = EcoAccountDocument
_FORM_CLS = EditEcoAccountDocumentModalForm
_REDIRECT_URL = "compensation:acc:detail"

View File

@@ -5,167 +5,159 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm
from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm, RemoveEcoAccountModalForm
from compensation.models import EcoAccount
from compensation.tables.eco_account import EcoAccountTable
from konova.contexts import BaseContext
from konova.decorators import shared_access_required, default_group_required
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, FORM_INVALID, \
IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
from konova.views.identifier import AbstractIdentifierGeneratorView
from konova.views.form import AbstractNewGeometryFormView, AbstractEditGeometryFormView
from konova.views.index import AbstractIndexView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class IndexEcoAccountView(AbstractIndexView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""
Renders the index view for eco accounts
class EcoAccountIndexView(LoginRequiredMixin, AbstractIndexView):
""" View class for indexing eco accounts
Args:
request (HttpRequest): The incoming request
"""
_INDEX_TABLE_CLS = EcoAccountTable
_TAB_TITLE = _("Eco-account - Overview")
Returns:
A rendered view
"""
eco_accounts = EcoAccount.objects.filter(
def _get_queryset(self):
qs = EcoAccount.objects.filter(
deleted=None,
).order_by(
"-modified__timestamp"
)
table = EcoAccountTable(
request=request,
queryset=eco_accounts
return qs
class NewEcoAccountFormView(AbstractNewGeometryFormView):
""" Form view class
Renders a form for new eco accounts
"""
_FORM_CLS = NewEcoAccountForm
_MODEL_CLS = EcoAccount
_TEMPLATE = "compensation/form/view.html"
_TAB_TITLE = _("New Eco-Account")
_REDIRECT_URL = "compensation:acc:detail"
def _user_has_permission(self, user, **kwargs):
# User has to be a default user
return user.is_default_user()
class EditEcoAccountFormView(AbstractEditGeometryFormView):
""" Form view class
Renders a form for editing of eco accounts
"""
_FORM_CLS = EditEcoAccountForm
_MODEL_CLS = EcoAccount
_TEMPLATE = "compensation/form/view.html"
_REDIRECT_URL = "compensation:acc:detail"
def _user_has_permission(self, user, **kwargs):
# User has to be a default user
return user.is_default_user()
class EcoAccountIdentifierGeneratorView(LoginRequiredMixin, AbstractIdentifierGeneratorView):
""" View class for identifier generation on eco accounts
"""
_MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:index"
class EcoAccountDetailView(BaseDetailView):
""" Detail view class
Renders details of an eco account
"""
_MODEL_CLS = EcoAccount
_TEMPLATE = "compensation/detail/eco_account/view.html"
def _get_object(self, id: str):
""" Fetch object for detail view
Args:
id (str): The record's id'
Returns:
"""
acc = get_object_or_404(
EcoAccount.objects.prefetch_related(
"deadlines",
).select_related(
'geometry',
'responsible',
),
id=id,
deleted=None,
)
return acc
def _get_detail_context(self, obj: EcoAccount):
""" Generate object specific detail context for view
Args:
obj (): The record
Returns:
"""
# Order states according to surface
before_states = obj.before_states.order_by("-surface")
after_states = obj.after_states.order_by("-surface")
# Precalculate logical errors between before- and after-states
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
sum_before_states = obj.get_surface_before_states()
sum_after_states = obj.get_surface_after_states()
diff_states = abs(sum_before_states - sum_after_states)
# Calculate rest of available surface for deductions
available_total = obj.deductable_rest
available_relative = obj.get_deductable_rest_relative()
# Prefetch related data to decrease the amount of db connections
deductions = obj.deductions.filter(
intervention__deleted=None,
)
actions = obj.actions.all()
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("Eco-account - Overview"),
"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(),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
return context
@login_required
@default_group_required
def new_view(request: HttpRequest):
"""
Renders a view for a new eco account creation
class RemoveEcoAccountView(LoginRequiredMixin, BaseRemoveModalFormView):
""" Form view class
Args:
request (HttpRequest): The incoming request
Returns:
Renders a form for removing eco accounts
"""
template = "compensation/form/view.html"
data_form = NewEcoAccountForm(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)
acc = data_form.save(request.user, geom_form)
if generated_identifier != acc.identifier:
messages.info(
request,
IDENTIFIER_REPLACED.format(
generated_identifier,
acc.identifier
)
)
messages.success(request, _("Eco-Account {} added").format(acc.identifier))
if geom_form.has_geometry_simplified():
messages.info(
request,
GEOMETRY_SIMPLIFIED
)
_MODEL_CLS = EcoAccount
_FORM_CLS = RemoveEcoAccountModalForm
_REDIRECT_URL = "compensation:acc:index"
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:acc:detail", id=acc.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 Eco-Account"),
}
context = BaseContext(request, context).context
return render(request, template, context)
class EcoAccountIdentifierGeneratorView(AbstractIdentifierGeneratorView):
_MODEL = EcoAccount
@login_required
@default_group_required
@shared_access_required(EcoAccount, "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
acc = get_object_or_404(EcoAccount, id=id)
if acc.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("compensation:acc:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
if request.method == "POST":
data_form_valid = data_form.is_valid()
geom_form_valid = geom_form.is_valid()
if data_form_valid and geom_form_valid:
# The data form takes the geom form for processing, as well as the performing user
acc = data_form.save(request.user, geom_form)
messages.success(request, _("Eco-Account {} edited").format(acc.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:acc:detail", id=acc.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(acc.identifier),
}
context = BaseContext(request, context).context
return render(request, template, context)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()

View File

@@ -5,20 +5,11 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.contrib.auth.mixins import LoginRequiredMixin
from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.log import AbstractLogView
class EcoAccountLogView(AbstractLogView):
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)
class EcoAccountLogView(LoginRequiredMixin, AbstractLogView):
_MODEL_CLS = EcoAccount

View File

@@ -5,20 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.contrib.auth.mixins import LoginRequiredMixin
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
class EcoAccountRecordView(AbstractRecordView):
model = EcoAccount
@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)
class EcoAccountRecordView(LoginRequiredMixin, AbstractRecordView):
_MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:detail"

View File

@@ -1,22 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.http import HttpRequest, HttpResponse
from django.utils.decorators import method_decorator
from compensation.forms.eco_account import RemoveEcoAccountModalForm
from compensation.models import EcoAccount
from konova.decorators import shared_access_required
from konova.views.remove import AbstractRemoveView
class RemoveEcoAccountView(AbstractRemoveView):
_MODEL = EcoAccount
_REDIRECT_URL = "compensation:acc:index"
_FORM = RemoveEcoAccountModalForm
@method_decorator(shared_access_required(EcoAccount, "id"))
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
return super().get(request, *args, **kwargs)

View File

@@ -5,88 +5,41 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from compensation.views.compensation.report import BaseCompensationReportView
from konova.sub_settings.django_settings import BASE_URL
from konova.utils.qrcode import QrCode
from konova.views.report import AbstractPublicReportView
class EcoAccountPublicReportView(AbstractPublicReportView):
class EcoAccountReportView(BaseCompensationReportView):
_MODEL = EcoAccount
_TEMPLATE = "compensation/report/eco_account/report.html"
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" Renders the public report view
Args:
request (HttpRequest): The incoming request
id (str): The id of the intervention
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
tab_title = _("Report {}").format(acc.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not acc.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=acc
)
parcels = acc.get_underlying_parcels()
qrcode = QrCode(
content=request.build_absolute_uri(reverse("compensation:acc:report", args=(id,))),
size=10
)
qrcode_lanis = QrCode(
content=acc.get_LANIS_link(),
size=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")
def _get_report_context(self, obj):
report_url = BASE_URL + reverse("compensation:acc:report", args=(obj.id,))
qrcode_report = QrCode(report_url, 10)
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)
deductions = acc.deductions.all() \
deductions = obj.deductions.all() \
.distinct("intervention") \
.select_related("intervention") \
.values_list("intervention__id", "intervention__identifier", "intervention__title", named=True)
context = {
"obj": acc,
report_context = {
"qrcode": {
"img": qrcode.get_img(),
"url": qrcode.get_content(),
"img": qrcode_report.get_img(),
"url": qrcode_report.get_content(),
},
"qrcode_lanis": {
"img": qrcode_lanis.get_img(),
"url": qrcode_lanis.get_content(),
},
"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, self._TEMPLATE, context)
report_context.update(self._get_compensation_report_context(obj))
return report_context

View File

@@ -5,22 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from compensation.forms.modals.resubmission import EcoAccountResubmissionModalForm
from compensation.models import EcoAccount
from konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView
class EcoAccountResubmissionView(AbstractResubmissionView):
model = EcoAccount
redirect_url_base = "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)
_MODEL_CLS = EcoAccount
_FORM_CLS = EcoAccountResubmissionModalForm
_REDIRECT_URL = "compensation:acc:detail"

View File

@@ -5,29 +5,15 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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 konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.share import AbstractShareByTokenView, AbstractShareFormView
class EcoAccountShareByTokenView(AbstractShareByTokenView):
model = EcoAccount
redirect_url = "compensation:acc:detail"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
_MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:detail"
class EcoAccountShareFormView(AbstractShareFormView):
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)
_MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:detail"

View File

@@ -5,46 +5,21 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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 konova.decorators import shared_access_required, default_group_required, login_required_modal
from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \
AbstractRemoveCompensationStateView
class NewEcoAccountStateView(AbstractNewCompensationStateView):
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)
_MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:detail"
class EditEcoAccountStateView(AbstractEditCompensationStateView):
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)
_MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:detail"
class RemoveEcoAccountStateView(AbstractRemoveCompensationStateView):
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)
_MODEL_CLS = EcoAccount
_REDIRECT_URL = "compensation:acc:detail"

View File

@@ -5,84 +5,38 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 09.08.21
"""
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 django.contrib.auth.mixins import LoginRequiredMixin
from compensation.forms.modals.payment import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
from compensation.models import Payment
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.views.modal import AbstractModalFormView
@login_required
@default_group_required
@shared_access_required(Intervention, "id")
def new_payment_view(request: HttpRequest, id: str):
""" Renders a modal view for adding new payments
class BasePaymentView(LoginRequiredMixin, AbstractModalFormView):
_MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:detail"
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id for which a new payment shall be added
class Meta:
abstract = True
Returns:
def _get_redirect_url(self, *args, **kwargs):
url = super()._get_redirect_url(*args, **kwargs)
return f"{url}#related_data"
"""
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"
)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
@login_required
@default_group_required
@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 NewPaymentView(BasePaymentView):
_FORM_CLS = NewPaymentForm
_MSG_SUCCESS = PAYMENT_ADDED
@login_required
@default_group_required
@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 EditPaymentView(BasePaymentView):
_MSG_SUCCESS = PAYMENT_EDITED
_FORM_CLS = EditPaymentModalForm
class RemovePaymentView(BasePaymentView):
_MSG_SUCCESS = PAYMENT_REMOVED
_FORM_CLS = RemovePaymentModalForm

View File

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

View File

@@ -15,10 +15,10 @@
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
{% fa5_icon 'bell' %}
</button>
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-form' obj.id %}">
{% fa5_icon 'share-alt' %}
</button>
{% if is_ets_member %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-form' obj.id %}">
{% fa5_icon 'share-alt' %}
</button>
{% if obj.recorded %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Unrecord' %}" data-form-url="{% url 'ema:record' obj.id %}">
{% fa5_icon 'bookmark' 'far' %}
@@ -28,21 +28,19 @@
{% fa5_icon 'bookmark' %}
</button>
{% endif %}
<a href="{% url 'ema:edit' obj.id %}" class="mr-2">
<button class="btn btn-default" title="{% trans 'Edit' %}">
{% fa5_icon 'edit' %}
</button>
</a>
{% endif %}
{% if is_default_member %}
<button class="btn btn-default btn-modal mr-2" data-form-url="{% url 'ema:log' obj.id %}" title="{% trans 'Show log' %}">
{% fa5_icon 'history' %}
</button>
{% endif %}
{% if is_ets_member %}
<button class="btn btn-default btn-modal" data-form-url="{% url 'ema:remove' obj.id %}" title="{% trans 'Delete' %}">
{% fa5_icon 'trash' %}
<a href="{% url 'ema:edit' obj.id %}" class="mr-2">
<button class="btn btn-default" title="{% trans 'Edit' %}">
{% fa5_icon 'edit' %}
</button>
</a>
<button class="btn btn-default btn-modal mr-2" data-form-url="{% url 'ema:log' obj.id %}" title="{% trans 'Show log' %}">
{% fa5_icon 'history' %}
</button>
<button class="btn btn-default btn-modal" data-form-url="{% url 'ema:remove' obj.id %}" title="{% trans 'Delete' %}">
{% fa5_icon 'trash' %}
</button>
{% endif %}
{% endif %}
</div>

View File

@@ -118,7 +118,6 @@ class EmaViewTestCase(CompensationViewTestCase):
self.index_url,
self.detail_url,
self.report_url,
self.log_url,
]
fail_urls = [
self.new_url,
@@ -134,6 +133,7 @@ class EmaViewTestCase(CompensationViewTestCase):
self.action_remove_url,
self.action_new_url,
self.new_doc_url,
self.log_url,
self.remove_url,
]
self.assert_url_fail(client, fail_urls)

View File

@@ -9,28 +9,27 @@ from django.urls import path
from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActionView
from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView
from ema.views.detail import DetailEmaView
from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView
from ema.views.ema import IndexEmaView, EmaIdentifierGeneratorView, EditEmaView, NewEmaView
from ema.views.ema import EmaIndexView, EmaIdentifierGeneratorView, EmaDetailView, EditEmaFormView, NewEmaFormView, \
RemoveEmaView
from ema.views.log import EmaLogView
from ema.views.record import EmaRecordView
from ema.views.remove import RemoveEmaView
from ema.views.report import EmaPublicReportView
from ema.views.report import EmaReportView
from ema.views.resubmission import EmaResubmissionView
from ema.views.share import EmaShareFormView, EmaShareByTokenView
from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateView
app_name = "ema"
urlpatterns = [
path("", IndexEmaView.as_view(), name="index"),
path("new/", NewEmaView.as_view(), name="new"),
path("", EmaIndexView.as_view(), name="index"),
path("new/", NewEmaFormView.as_view(), name="new"),
path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"),
path("<id>", DetailEmaView.as_view(), name="detail"),
path("<id>", EmaDetailView.as_view(), name="detail"),
path('<id>/log', EmaLogView.as_view(), name='log'),
path('<id>/edit', EditEmaView.as_view(), name='edit'),
path('<id>/edit', EditEmaFormView.as_view(), name='edit'),
path('<id>/remove', RemoveEmaView.as_view(), name='remove'),
path('<id>/record', EmaRecordView.as_view(), name='record'),
path('<id>/report', EmaPublicReportView.as_view(), name='report'),
path('<id>/report', EmaReportView.as_view(), name='report'),
path('<id>/resub', EmaResubmissionView.as_view(), name='resubmission-create'),
path('<id>/state/new', NewEmaStateView.as_view(), name='new-state'),

View File

@@ -5,46 +5,31 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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 konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.action import AbstractNewCompensationActionView, AbstractEditCompensationActionView, \
AbstractRemoveCompensationActionView
_EMA_ACCOUNT_DETAIL_URL_NAME = "ema:detail"
class NewEmaActionView(AbstractNewCompensationActionView):
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)
_MODEL_CLS = Ema
_REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class EditEmaActionView(AbstractEditCompensationActionView):
model = Ema
redirect_url = "ema:detail"
_MODEL_CLS = Ema
_REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME
@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 RemoveEmaActionView(AbstractRemoveCompensationActionView):
model = Ema
redirect_url = "ema:detail"
_MODEL_CLS = Ema
_REDIRECT_URL = _EMA_ACCOUNT_DETAIL_URL_NAME
@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,46 +5,30 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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 konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal
from konova.views.deadline import AbstractNewDeadlineView, AbstractRemoveDeadlineView, AbstractEditDeadlineView
_EMA_DETAIL_URL_NAME = "ema:detail"
class NewEmaDeadlineView(AbstractNewDeadlineView):
model = Ema
redirect_url = "ema:detail"
_MODEL_CLS = Ema
_REDIRECT_URL = _EMA_DETAIL_URL_NAME
@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 EditEmaDeadlineView(AbstractEditDeadlineView):
model = Ema
redirect_url = "ema:detail"
_MODEL_CLS = Ema
_REDIRECT_URL = _EMA_DETAIL_URL_NAME
@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 RemoveEmaDeadlineView(AbstractRemoveDeadlineView):
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)
_MODEL_CLS = Ema
_REDIRECT_URL = _EMA_DETAIL_URL_NAME
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()

View File

@@ -1,76 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.contrib import messages
from django.http import HttpResponse, HttpRequest
from django.shortcuts import get_object_or_404, render
from ema.models import Ema
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE
from konova.views.detail import AbstractDetailView
class DetailEmaView(AbstractDetailView):
_TEMPLATE = "ema/detail/view.html"
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" Renders the detail view of an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA id
Returns:
"""
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, self._TEMPLATE, context)

View File

@@ -5,62 +5,41 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.forms import NewEmaDocumentModalForm
from ema.forms import NewEmaDocumentModalForm, RemoveEmaDocumentModalForm, EditEmaDocumentModalForm
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, \
AbstractNewDocumentView
class NewEmaDocumentView(AbstractNewDocumentView):
model = Ema
form = NewEmaDocumentModalForm
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)
_MODEL_CLS = Ema
_FORM_CLS = NewEmaDocumentModalForm
_REDIRECT_URL = "ema:detail"
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class GetEmaDocumentView(AbstractGetDocumentView):
model = Ema
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)
_MODEL_CLS = Ema
_DOCUMENT_CLS = EmaDocument
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class RemoveEmaDocumentView(AbstractRemoveDocumentView):
model = Ema
document_model = EmaDocument
@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)
_MODEL_CLS = Ema
_DOCUMENT_CLS = EmaDocument
_FORM_CLS = RemoveEmaDocumentModalForm
_REDIRECT_URL = "ema:detail"
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class EditEmaDocumentView(AbstractEditDocumentView):
model = Ema
document_model = EmaDocument
form = EditDocumentModalForm
redirect_url = "ema:detail"
_MODEL_CLS = Ema
_FORM_CLS = EditEmaDocumentModalForm
_DOCUMENT_CLS = EmaDocument
_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,228 +5,113 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from django.views.generic.base import View
from ema.forms import NewEmaForm, EditEmaForm
from ema.models import Ema
from ema.tables import EmaTable
from konova.contexts import BaseContext
from konova.decorators import shared_access_required, conservation_office_group_required
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \
GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
from konova.views.identifier import AbstractIdentifierGeneratorView
from konova.views.form import AbstractNewGeometryFormView, AbstractEditGeometryFormView
from konova.views.index import AbstractIndexView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class IndexEmaView(AbstractIndexView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
""" Renders the index view for EMAs
class EmaIndexView(LoginRequiredMixin, AbstractIndexView):
_TAB_TITLE = _("EMAs - Overview")
_INDEX_TABLE_CLS = EmaTable
Args:
request (HttpRequest): The incoming request
Returns:
"""
emas = Ema.objects.filter(
def _get_queryset(self):
qs = Ema.objects.filter(
deleted=None,
).order_by(
"-modified__timestamp"
)
return qs
table = EmaTable(
request,
queryset=emas
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("EMAs - Overview"),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
class NewEmaView(LoginRequiredMixin, View):
class NewEmaFormView(AbstractNewGeometryFormView):
_FORM_CLS = NewEmaForm
_MODEL_CLS = Ema
_TEMPLATE = "ema/form/view.html"
_TAB_TITLE = _("New EMA")
_REDIRECT_URL = "ema:detail"
@method_decorator(conservation_office_group_required)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
""" GET endpoint
def _user_has_permission(self, user, **kwargs):
# User has to be an ets user
return user.is_ets_user()
Renders form for new EMA
class EditEmaFormView(AbstractEditGeometryFormView):
_MODEL_CLS = Ema
_FORM_CLS = EditEmaForm
_TEMPLATE = "ema/form/view.html"
_REDIRECT_URL = "ema:detail"
_TAB_TITLE = _("Edit {}")
def _user_has_permission(self, user, **kwargs):
# User has to be an ets user
return user.is_ets_user()
class EmaIdentifierGeneratorView(LoginRequiredMixin, AbstractIdentifierGeneratorView):
_MODEL_CLS = Ema
_REDIRECT_URL = "ema:index"
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class EmaDetailView(BaseDetailView):
_MODEL_CLS = Ema
_TEMPLATE = "ema/detail/view.html"
def _get_object(self, id: str):
""" Fetch object for detail view
Args:
request (HttpRequest): The incoming request
*args ():
**kwargs ():
id (str): The record's id'
Returns:
"""
data_form = NewEmaForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New EMA"),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
ema = get_object_or_404(Ema, id=id, deleted=None)
return ema
@method_decorator(conservation_office_group_required)
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
""" POST endpoint
Processes submitted form
def _get_detail_context(self, obj: Ema):
""" Generate object specific detail context for view
Args:
request (HttpRequest): The incoming request
*args ():
**kwargs ():
obj (): The record
Returns:
"""
data_form = NewEmaForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
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",)
# 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 = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New EMA"),
"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(),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
return context
class EmaIdentifierGeneratorView(AbstractIdentifierGeneratorView):
_MODEL = Ema
class RemoveEmaView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = Ema
_REDIRECT_URL = "ema:index"
@method_decorator(conservation_office_group_required)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
return super().get(request, *args, **kwargs)
class EditEmaView(LoginRequiredMixin, View):
_TEMPLATE = "compensation/form/view.html"
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" GET endpoint
Renders form
Args:
request (HttpRequest): The incoming request
id (str): The ema identifier
*args ():
**kwargs ():
Returns:
"""
# 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(instance=ema)
geom_form = SimpleGeomForm(read_only=False, instance=ema)
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def post(self, request: HttpRequest, id:str, *args, **kwargs) -> HttpResponse:
""" POST endpoint
Process submitted forms
Args:
request (HttpRequest): The incoming request
id (str): The id of the ema
*args ():
**kwargs ():
Returns:
"""
# 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 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", )
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()

View File

@@ -5,19 +5,14 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.contrib.auth.mixins import LoginRequiredMixin
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
class EmaLogView(AbstractLogView):
model = Ema
class EmaLogView(LoginRequiredMixin, AbstractLogView):
_MODEL_CLS = Ema
@method_decorator(login_required_modal)
@method_decorator(login_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,20 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.contrib.auth.mixins import LoginRequiredMixin
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
class EmaRecordView(AbstractRecordView):
model = Ema
@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)
class EmaRecordView(LoginRequiredMixin, AbstractRecordView):
_MODEL_CLS = Ema
_REDIRECT_URL = "ema:detail"

View File

@@ -1,21 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.http import HttpRequest, HttpResponse
from django.utils.decorators import method_decorator
from ema.models import Ema
from konova.decorators import shared_access_required, conservation_office_group_required
from konova.views.remove import AbstractRemoveView
class RemoveEmaView(AbstractRemoveView):
_MODEL = Ema
_REDIRECT_URL = "ema:index"
@method_decorator(conservation_office_group_required)
@method_decorator(shared_access_required(Ema, "id"))
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
return super().get(request, *args, **kwargs)

View File

@@ -5,81 +5,36 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render
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 konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.sub_settings.django_settings import BASE_URL
from konova.utils.qrcode import QrCode
from konova.views.report import AbstractPublicReportView
class EmaPublicReportView(AbstractPublicReportView):
class EmaReportView(BaseCompensationReportView):
_TEMPLATE = "ema/report/report.html"
_MODEL = Ema
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" Renders the public report view
def _get_report_context(self, obj):
report_url = BASE_URL + reverse("ema:report", args=(obj.id,))
qrcode_report = QrCode(report_url, 10)
qrcode_lanis = QrCode(obj.get_LANIS_link(), 7)
Args:
request (HttpRequest): The incoming request
id (str): The id of the intervention
generic_compensation_report_context = self._get_compensation_report_context(obj)
Returns:
"""
ema = get_object_or_404(Ema, id=id)
tab_title = _("Report {}").format(ema.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not ema.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=ema,
)
parcels = ema.get_underlying_parcels()
qrcode = QrCode(
content=request.build_absolute_uri(reverse("ema:report", args=(id,))),
size=10
)
qrcode_lanis = QrCode(
content=ema.get_LANIS_link(),
size=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,
report_context = {
"qrcode": {
"img": qrcode.get_img(),
"url": qrcode.get_content(),
"img": qrcode_report.get_img(),
"url": qrcode_report.get_content(),
},
"qrcode_lanis": {
"img": qrcode_lanis.get_img(),
"url": qrcode_lanis.get_content(),
},
"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, self._TEMPLATE, context)
report_context.update(generic_compensation_report_context)
return report_context

View File

@@ -5,22 +5,16 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from ema.forms import EmaResubmissionModalForm
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
class EmaResubmissionView(AbstractResubmissionView):
model = Ema
redirect_url_base = "ema:detail"
form_action_url_base = "ema:resubmission-create"
_MODEL_CLS = Ema
_FORM_CLS = EmaResubmissionModalForm
_REDIRECT_URL = "ema:detail"
action_url = "ema:resubmission-create"
@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,29 +5,17 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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 konova.decorators import conservation_office_group_required, shared_access_required, login_required_modal
from konova.views.share import AbstractShareByTokenView, AbstractShareFormView
class EmaShareByTokenView(AbstractShareByTokenView):
model = Ema
redirect_url = "ema:detail"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
_MODEL_CLS = Ema
_REDIRECT_URL = "ema:detail"
class EmaShareFormView(AbstractShareFormView):
model = Ema
_MODEL_CLS = 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)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()

View File

@@ -5,46 +5,30 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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 konova.decorators import conservation_office_group_required, shared_access_required, login_required_modal
from konova.views.state import AbstractNewCompensationStateView, AbstractEditCompensationStateView, \
AbstractRemoveCompensationStateView
class NewEmaStateView(AbstractNewCompensationStateView):
model = Ema
redirect_url = "ema:detail"
_MODEL_CLS = 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)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class EditEmaStateView(AbstractEditCompensationStateView):
model = Ema
redirect_url = "ema:detail"
_MODEL_CLS = 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)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()
class RemoveEmaStateView(AbstractRemoveCompensationStateView):
model = Ema
redirect_url = "ema:detail"
_MODEL_CLS = 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)
def _user_has_permission(self, user, **kwargs):
return user.is_ets_user()

View File

@@ -37,14 +37,6 @@ class InterventionAdmin(BaseObjectAdmin):
"geometry",
]
def get_actions(self, request):
DELETE_ACTION_IDENTIFIER = "delete_selected"
actions = super().get_actions(request)
if DELETE_ACTION_IDENTIFIER in actions:
del actions[DELETE_ACTION_IDENTIFIER]
return actions
class InterventionDocumentAdmin(AbstractDocumentAdmin):
pass

View File

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

View File

@@ -6,11 +6,11 @@ Created on: 18.08.22
"""
from intervention.models import InterventionDocument
from konova.forms.modals import NewDocumentModalForm
from konova.forms.modals import NewDocumentModalForm, EditDocumentModalForm, RemoveDocumentModalForm
class NewInterventionDocumentModalForm(NewDocumentModalForm):
document_model = InterventionDocument
_DOCUMENT_CLS = InterventionDocument
def save(self, *args, **kwargs):
""" Extension of regular NewDocumentModalForm
@@ -28,3 +28,31 @@ class NewInterventionDocumentModalForm(NewDocumentModalForm):
self.instance.send_data_to_egon()
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

@@ -0,0 +1,22 @@
"""
Author: Michel Peltriaux
Created on: 08.11.25
"""
from django.shortcuts import get_object_or_404
from compensation.models import Compensation
from konova.forms.modals import RemoveModalForm
class RemoveCompensationFromInterventionModalForm(RemoveModalForm):
""" Specific form for removing a compensation from an intervention
"""
def __init__(self, *args, **kwargs):
# The 'instance' that is pushed into the constructor by the generic base class points to the
# intervention instead of the compensation, which shall be deleted. Therefore we need to fetch the compensation
# and replace the instance parameter with that object
instance = get_object_or_404(Compensation, id=kwargs.pop("comp_id"))
kwargs["instance"] = instance
super().__init__(*args, **kwargs)

View File

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

View File

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

View File

@@ -8,42 +8,40 @@ Created on: 30.11.20
from django.urls import path
from intervention.autocomplete.intervention import InterventionAutocomplete
from intervention.views.check import check_view
from intervention.views.compensation import remove_compensation_view
from intervention.views.check import InterventionCheckView
from intervention.views.compensation import RemoveCompensationFromInterventionView
from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \
RemoveInterventionDeductionView
from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \
RemoveInterventionDocumentView, EditInterventionDocumentView
from intervention.views.intervention import IndexInterventionView, InterventionIdentifierGeneratorView, \
NewInterventionView, EditInterventionView
from intervention.views.remove import RemoveInterventionView
from intervention.views.detail import DetailInterventionView
from intervention.views.intervention import InterventionIndexView, InterventionIdentifierGeneratorView, \
InterventionDetailView, NewInterventionFormView, EditInterventionFormView, RemoveInterventionView
from intervention.views.log import InterventionLogView
from intervention.views.record import InterventionRecordView
from intervention.views.report import InterventionPublicReportView
from intervention.views.report import InterventionReportView
from intervention.views.resubmission import InterventionResubmissionView
from intervention.views.revocation import new_revocation_view, edit_revocation_view, remove_revocation_view, \
get_revocation_view
from intervention.views.revocation import NewRevocationView, GetRevocationDocumentView, EditRevocationView, \
RemoveRevocationView
from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView
app_name = "intervention"
urlpatterns = [
path("", IndexInterventionView.as_view(), name="index"),
path('new/', NewInterventionView.as_view(), name='new'),
path("", InterventionIndexView.as_view(), name="index"),
path('new/', NewInterventionFormView.as_view(), name='new'),
path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'),
path('<id>', DetailInterventionView.as_view(), name='detail'),
path('<id>', InterventionDetailView.as_view(), name='detail'),
path('<id>/log', InterventionLogView.as_view(), name='log'),
path('<id>/edit', EditInterventionView.as_view(), name='edit'),
path('<id>/edit', EditInterventionFormView.as_view(), name='edit'),
path('<id>/remove', RemoveInterventionView.as_view(), name='remove'),
path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'),
path('<id>/share', InterventionShareFormView.as_view(), name='share-form'),
path('<id>/check', check_view, name='check'),
path('<id>/check', InterventionCheckView.as_view(), name='check'),
path('<id>/record', InterventionRecordView.as_view(), name='record'),
path('<id>/report', InterventionPublicReportView.as_view(), name='report'),
path('<id>/report', InterventionReportView.as_view(), name='report'),
path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'),
# Compensations
path('<id>/compensation/<comp_id>/remove', remove_compensation_view, name='remove-compensation'),
path('<id>/compensation/<comp_id>/remove', RemoveCompensationFromInterventionView.as_view(), name='remove-compensation'),
# Documents
path('<id>/document/new/', NewInterventionDocumentView.as_view(), name='new-doc'),
@@ -57,10 +55,10 @@ urlpatterns = [
path('<id>/deduction/<deduction_id>/remove', RemoveInterventionDeductionView.as_view(), name='remove-deduction'),
# Revocation routes
path('<id>/revocation/new', new_revocation_view, name='new-revocation'),
path('<id>/revocation/<revocation_id>/edit', edit_revocation_view, name='edit-revocation'),
path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'),
path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
path('<id>/revocation/new', NewRevocationView.as_view(), name='new-revocation'),
path('<id>/revocation/<revocation_id>/edit', EditRevocationView.as_view(), name='edit-revocation'),
path('<id>/revocation/<revocation_id>/remove', RemoveRevocationView.as_view(), name='remove-revocation'),
path('revocation/<doc_id>', GetRevocationDocumentView.as_view(), name='get-doc-revocation'),
# Autocomplete
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="autocomplete"),

View File

@@ -5,35 +5,24 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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.contrib.auth.mixins import LoginRequiredMixin
from django.utils.translation import gettext_lazy as _
from intervention.forms.modals.check import CheckModalForm
from intervention.models import Intervention
from konova.decorators import registration_office_group_required, shared_access_required
from konova.utils.message_templates import INTERVENTION_INVALID
from konova.views.modal import AbstractModalFormView
@login_required
@registration_office_group_required
@shared_access_required(Intervention, "id")
def check_view(request: HttpRequest, id: str):
""" Renders check form for an intervention
class InterventionCheckView(LoginRequiredMixin, AbstractModalFormView):
_MODEL_CLS = Intervention
_FORM_CLS = CheckModalForm
_MSG_SUCCESS = _("Check performed")
_REDIRECT_URL = "intervention:detail"
Args:
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 _user_has_permission(self, user, **kwargs):
return user.is_zb_user()
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,42 +5,36 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpRequest, Http404
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404
from django.urls import reverse
from compensation.models import Compensation
from intervention.forms.modals.remove import RemoveCompensationFromInterventionModalForm
from intervention.models import Intervention
from konova.decorators import shared_access_required, login_required_modal
from konova.forms.modals import RemoveModalForm
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
from konova.views.remove import BaseRemoveModalFormView
@login_required_modal
@login_required
@shared_access_required(Intervention, "id")
def remove_compensation_view(request: HttpRequest, id: str, comp_id: str):
""" Renders a modal view for removing the compensation
class RemoveCompensationFromInterventionView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = Intervention
_FORM_CLS = RemoveCompensationFromInterventionModalForm
_MSG_SUCCESS = COMPENSATION_REMOVED_TEMPLATE
_REDIRECT_URL = "intervention:detail"
Args:
request (HttpRequest): The incoming request
id (str): The compensation's id
def _user_has_shared_access(self, user, **kwargs):
compensation_id = kwargs.get("comp_id", None)
compensation = get_object_or_404(Compensation, id=compensation_id)
return compensation.is_shared_with(user)
Returns:
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
"""
intervention = get_object_or_404(Intervention, id=id)
try:
comp = intervention.compensations.get(
id=comp_id
)
except ObjectDoesNotExist:
raise Http404("Unknown compensation")
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("intervention:detail", args=(id,)) + "#related_data",
)
def _get_msg_success(self, *args, **kwargs):
compensation_id = kwargs.get("comp_id", None)
compensation = get_object_or_404(Compensation, id=compensation_id)
return self._MSG_SUCCESS.format(compensation.identifier)
def _get_redirect_url(self, *args, **kwargs):
obj = kwargs.get("obj")
return reverse(self._REDIRECT_URL, args=(obj.id,)) + "#related_data"

View File

@@ -5,51 +5,27 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.contrib.auth.mixins import LoginRequiredMixin
from intervention.models import Intervention
from konova.decorators import default_group_required, shared_access_required
from konova.utils.message_templates import DEDUCTION_ADDED, DEDUCTION_EDITED, DEDUCTION_REMOVED
from konova.views.deduction import AbstractNewDeductionView, AbstractEditDeductionView, AbstractRemoveDeductionView
_INTERVENTION_DETAIL_URL_NAME = "intervention:detail"
class NewInterventionDeductionView(AbstractNewDeductionView):
def _custom_check(self, obj):
pass
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 NewInterventionDeductionView(LoginRequiredMixin, AbstractNewDeductionView):
_MODEL_CLS = Intervention
_MSG_SUCCESS = DEDUCTION_ADDED
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
class EditInterventionDeductionView(AbstractEditDeductionView):
def _custom_check(self, obj):
pass
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):
_MODEL_CLS = Intervention
_MSG_SUCCESS = DEDUCTION_EDITED
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME
class RemoveInterventionDeductionView(AbstractRemoveDeductionView):
def _custom_check(self, obj):
pass
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):
_MODEL_CLS = Intervention
_MSG_SUCCESS = DEDUCTION_REMOVED
_REDIRECT_URL = _INTERVENTION_DETAIL_URL_NAME

View File

@@ -1,79 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.contrib import messages
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from intervention.models import Intervention
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, DO_NOT_FORGET_TO_SHARE
from konova.views.detail import AbstractDetailView
class DetailInterventionView(AbstractDetailView):
_TEMPLATE = "intervention/detail/view.html"
def get(self, request, id: str, *args, **kwargs) -> HttpResponse:
# Fetch data, filter out deleted related data
intervention = get_object_or_404(
Intervention.objects.select_related(
"geometry",
"legal",
"responsible",
).prefetch_related(
"legal__revocations",
),
id=id,
deleted=None
)
compensations = intervention.compensations.filter(
deleted=None,
)
_user = request.user
is_data_shared = intervention.is_shared_with(user=_user)
geom_form = SimpleGeomForm(
instance=intervention,
)
last_checked = intervention.get_last_checked_action()
last_checked_tooltip = ""
if last_checked:
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(
last_checked.get_timestamp_str_formatted(),
last_checked.user
)
has_payment_without_document = intervention.payments.exists() and not intervention.get_documents()[1].exists()
requesting_user_is_only_shared_user = intervention.is_only_shared_with(_user)
if requesting_user_is_only_shared_user:
messages.info(
request,
DO_NOT_FORGET_TO_SHARE
)
context = {
"obj": intervention,
"last_checked": last_checked,
"last_checked_tooltip": last_checked_tooltip,
"compensations": compensations,
"is_entry_shared": is_data_shared,
"geom_form": geom_form,
"is_default_member": _user.in_group(DEFAULT_GROUP),
"is_zb_member": _user.in_group(ZB_GROUP),
"is_ets_member": _user.in_group(ETS_GROUP),
"LANIS_LINK": intervention.get_LANIS_link(),
"has_payment_without_document": has_payment_without_document,
TAB_TITLE_IDENTIFIER: f"{intervention.identifier} - {intervention.title}",
}
request = intervention.set_status_messages(request)
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)

View File

@@ -5,59 +5,33 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from intervention.forms.modals.document import NewInterventionDocumentModalForm
from intervention.forms.modals.document import NewInterventionDocumentModalForm, EditInterventionDocumentModalForm, \
RemoveInterventionDocumentModalForm
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, \
AbstractEditDocumentView
class NewInterventionDocumentView(AbstractNewDocumentView):
model = Intervention
form = NewInterventionDocumentModalForm
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)
_MODEL_CLS = Intervention
_DOCUMENT_MODEL = InterventionDocument
_FORM_CLS = NewInterventionDocumentModalForm
_REDIRECT_URL = "intervention:detail"
class GetInterventionDocumentView(AbstractGetDocumentView):
model = Intervention
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)
_MODEL_CLS = Intervention
_DOCUMENT_CLS = InterventionDocument
class RemoveInterventionDocumentView(AbstractRemoveDocumentView):
model = Intervention
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)
_MODEL_CLS = Intervention
_DOCUMENT_CLS = InterventionDocument
_FORM_CLS = RemoveInterventionDocumentModalForm
_REDIRECT_URL = "intervention:detail"
class EditInterventionDocumentView(AbstractEditDocumentView):
model = Intervention
document_model = InterventionDocument
form = EditDocumentModalForm
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)
_MODEL_CLS = Intervention
_DOCUMENT_CLS = InterventionDocument
_FORM_CLS = EditInterventionDocumentModalForm
_REDIRECT_URL = "intervention:detail"

View File

@@ -5,229 +5,111 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render, redirect
from django.utils.decorators import method_decorator
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from django.views import View
from intervention.forms.intervention import EditInterventionForm, NewInterventionForm
from intervention.models import Intervention
from intervention.tables import InterventionTable
from konova.contexts import BaseContext
from konova.decorators import default_group_required, shared_access_required
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, \
CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, \
GEOMETRIES_IGNORED_TEMPLATE
from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE
from konova.views.identifier import AbstractIdentifierGeneratorView
from konova.views.form import AbstractNewGeometryFormView, AbstractEditGeometryFormView
from konova.views.index import AbstractIndexView
from konova.views.detail import BaseDetailView
from konova.views.remove import BaseRemoveModalFormView
class IndexInterventionView(AbstractIndexView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""
Renders the index view for Interventions
class InterventionIndexView(LoginRequiredMixin, AbstractIndexView):
_INDEX_TABLE_CLS = InterventionTable
_TAB_TITLE = _("Interventions - Overview")
Args:
request (HttpRequest): The incoming request
Returns:
A rendered view
"""
# Filtering by user access is performed in table filter inside InterventionTableFilter class
interventions = Intervention.objects.filter(
deleted=None, # not deleted
def _get_queryset(self):
qs = Intervention.objects.filter(
deleted=None,
).select_related(
"legal"
).order_by(
"-modified__timestamp"
)
table = InterventionTable(
request=request,
queryset=interventions
return qs
class NewInterventionFormView(AbstractNewGeometryFormView):
_MODEL_CLS = Intervention
_FORM_CLS = NewInterventionForm
_TEMPLATE = "intervention/form/view.html"
_REDIRECT_URL = "intervention:detail"
_TAB_TITLE = _("New intervention")
class EditInterventionFormView(AbstractEditGeometryFormView):
_MODEL_CLS = Intervention
_FORM_CLS = EditInterventionForm
_TEMPLATE = "intervention/form/view.html"
_REDIRECT_URL = "intervention:detail"
_TAB_TITLE = _("Edit {}")
class InterventionIdentifierGeneratorView(LoginRequiredMixin, AbstractIdentifierGeneratorView):
_MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:index"
class InterventionDetailView(BaseDetailView):
_MODEL_CLS = Intervention
_TEMPLATE = "intervention/detail/view.html"
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
)
context = {
"table": table,
TAB_TITLE_IDENTIFIER: _("Interventions - Overview"),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
return obj
class NewInterventionView(LoginRequiredMixin, View):
_TEMPLATE = "intervention/form/view.html"
@method_decorator(default_group_required)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""
Renders a view for a new intervention creation
def _get_detail_context(self, obj: Intervention):
""" Generate object specific detail context for view
Args:
request (HttpRequest): The incoming request
obj (): The record
Returns:
"""
data_form = NewInterventionForm()
geom_form = SimpleGeomForm(read_only=False)
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New intervention"),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
@method_decorator(default_group_required)
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""
Renders a view for a new intervention creation
Args:
request (HttpRequest): The incoming request
Returns:
"""
data_form = NewInterventionForm(request.POST or None)
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
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", )
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("New intervention"),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
class InterventionIdentifierGeneratorView(AbstractIdentifierGeneratorView):
_MODEL = Intervention
class EditInterventionView(LoginRequiredMixin, View):
_TEMPLATE = "intervention/form/view.html"
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
"""
Renders a view for editing interventions
Args:
request (HttpRequest): The incoming request
id (str): The intervention identifier
Returns:
HttpResponse: The rendered view
"""
# Get object from db
intervention = get_object_or_404(Intervention, id=id)
if intervention.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
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
)
return redirect("intervention:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditInterventionForm(request.POST or None, instance=intervention)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention)
has_payment_without_document = obj.payments.exists() and not obj.get_documents()[1].exists()
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier),
"last_checked": last_checked,
"last_checked_tooltip": last_checked_tooltip,
"compensations": compensations,
"has_payment_without_document": has_payment_without_document,
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
return context
@method_decorator(default_group_required)
@method_decorator(shared_access_required(Intervention, "id"))
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
"""
Process saved form content
Args:
request (HttpRequest): The incoming request
id (str): The intervention id
Returns:
HttpResponse:
"""
# Get object from db
intervention = get_object_or_404(Intervention, id=id)
if intervention.is_recorded:
messages.info(
request,
RECORDED_BLOCKS_EDIT
)
return redirect("intervention:detail", id=id)
# Create forms, initialize with values from db/from POST request
data_form = EditInterventionForm(request.POST or None, instance=intervention)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention)
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
# Save the current state of recorded|checked to inform the user in case of a status reset due to editing
intervention_is_checked = intervention.checked is not None
intervention = data_form.save(request.user, geom_form)
messages.success(request, _("Intervention {} edited").format(intervention.identifier))
if intervention_is_checked:
messages.info(request, CHECK_STATE_RESET)
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)
context = {
"form": data_form,
"geom_form": geom_form,
TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier),
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)
class RemoveInterventionView(LoginRequiredMixin, BaseRemoveModalFormView):
_MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:index"

View File

@@ -5,19 +5,11 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.contrib.auth.mixins import LoginRequiredMixin
from intervention.models import Intervention
from konova.decorators import shared_access_required, default_group_required
from konova.views.log import AbstractLogView
class InterventionLogView(AbstractLogView):
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)
class InterventionLogView(LoginRequiredMixin, AbstractLogView):
_MODEL_CLS = Intervention

View File

@@ -5,19 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.contrib.auth.mixins import LoginRequiredMixin
from intervention.models import Intervention
from konova.decorators import conservation_office_group_required, shared_access_required
from konova.views.record import AbstractRecordView
class InterventionRecordView(AbstractRecordView):
model = Intervention
@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)
class InterventionRecordView(LoginRequiredMixin, AbstractRecordView):
_MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:detail"

View File

@@ -1,20 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
"""
from django.http import HttpRequest, HttpResponse
from django.utils.decorators import method_decorator
from intervention.models import Intervention
from konova.decorators import shared_access_required
from konova.views.remove import AbstractRemoveView
class RemoveInterventionView(AbstractRemoveView):
_MODEL = Intervention
_REDIRECT_URL = "intervention:index"
@method_decorator(shared_access_required(Intervention, "id"))
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
return super().get(request, *args, **kwargs)

View File

@@ -5,78 +5,41 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from intervention.models import Intervention
from konova.contexts import BaseContext
from konova.forms import SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.sub_settings.django_settings import BASE_URL
from konova.utils.qrcode import QrCode
from konova.views.report import AbstractPublicReportView
from konova.views.report import AbstractReportView
class InterventionPublicReportView(AbstractPublicReportView):
_TEMPLATE = "intervention/report/report.html"
class InterventionReportView(AbstractReportView):
_TEMPLATE = 'intervention/report/report.html'
_MODEL = Intervention
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
""" Renders the public report view
def _get_report_context(self, obj: Intervention):
""" Returns the specific context needed for an intervention report
Args:
request (HttpRequest): The incoming request
id (str): The id of the intervention
obj (Intervention): The object for the report
Returns:
dict: The object specific context for rendering the report
"""
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)
tab_title = _("Report {}").format(intervention.identifier)
# If intervention is not recorded (yet or currently) we need to render another template without any data
if not intervention.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=intervention
)
parcels = intervention.get_underlying_parcels()
distinct_deductions = intervention.deductions.all().distinct(
"account"
)
qrcode = QrCode(
content=request.build_absolute_uri(reverse("intervention:report", args=(id,))),
size=10
)
qrcode_lanis = QrCode(
content=intervention.get_LANIS_link(),
size=7
)
context = {
"obj": intervention,
return {
"deductions": distinct_deductions,
"qrcode": {
"img": qrcode.get_img(),
"url": qrcode.get_content(),
"img": qrcode_report.get_img(),
"url": qrcode_report.get_content(),
},
"qrcode_lanis": {
"img": qrcode_lanis.get_img(),
"url": qrcode_lanis.get_content(),
},
"geom_form": geom_form,
"parcels": parcels,
"tables_scrollable": False,
TAB_TITLE_IDENTIFIER: tab_title,
}
context = BaseContext(request, context).context
return render(request, self._TEMPLATE, context)

View File

@@ -5,22 +5,12 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from intervention.forms.modals.resubmission import InterventionResubmissionModalForm
from intervention.models import Intervention
from konova.decorators import default_group_required, shared_access_required, login_required_modal
from konova.views.resubmission import AbstractResubmissionView
class InterventionResubmissionView(AbstractResubmissionView):
model = Intervention
redirect_url_base = "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)
_MODEL_CLS = Intervention
_FORM_CLS = InterventionResubmissionModalForm
_REDIRECT_URL = "intervention:detail"

View File

@@ -6,113 +6,71 @@ Created on: 19.08.22
"""
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from intervention.forms.modals.revocation import NewRevocationModalForm, EditRevocationModalForm, \
RemoveRevocationModalForm
from intervention.models import Intervention, RevocationDocument, Revocation
from konova.decorators import default_group_required, shared_access_required, login_required_modal
from intervention.models import Intervention, RevocationDocument
from konova.utils.documents import get_document
from konova.utils.message_templates import REVOCATION_ADDED, DATA_UNSHARED, REVOCATION_EDITED, REVOCATION_REMOVED
from konova.utils.message_templates import DATA_UNSHARED, REVOCATION_EDITED, REVOCATION_REMOVED, REVOCATION_ADDED
from konova.views.modal import AbstractModalFormView, AbstractBaseView
@login_required
@default_group_required
@shared_access_required(Intervention, "id")
def new_revocation_view(request: HttpRequest, id: str):
""" Renders sharing form for an intervention
class BaseRevocationView(LoginRequiredMixin, AbstractModalFormView):
_MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:detail"
Args:
request (HttpRequest): The incoming request
id (str): Intervention's id
class Meta:
abstract = True
Returns:
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
"""
intervention = get_object_or_404(Intervention, id=id)
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"
)
def _get_redirect_url(self, *args, **kwargs):
url = super()._get_redirect_url(*args, **kwargs)
return f"{url}#related_data"
@login_required
@default_group_required
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 NewRevocationView(BaseRevocationView):
_FORM_CLS = NewRevocationModalForm
_MSG_SUCCESS = REVOCATION_ADDED
@login_required
@default_group_required
@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 EditRevocationView(BaseRevocationView):
_FORM_CLS = EditRevocationModalForm
_MSG_SUCCESS = REVOCATION_EDITED
@login_required_modal
@login_required
@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
class RemoveRevocationView(BaseRevocationView):
_FORM_CLS = RemoveRevocationModalForm
_MSG_SUCCESS = REVOCATION_REMOVED
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id as string
revocation_id (str): The revocation's id as string
Returns:
class GetRevocationDocumentView(LoginRequiredMixin, AbstractBaseView):
_MODEL_CLS = RevocationDocument
_REDIRECT_URL = "intervention:detail"
"""
intervention = get_object_or_404(Intervention, id=id)
revocation = get_object_or_404(Revocation, id=revocation_id)
def get(self, request: HttpRequest, doc_id: str):
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)
form = RemoveRevocationModalForm(request.POST or None, instance=intervention, revocation=revocation, request=request)
return form.process_request(
request,
REVOCATION_REMOVED,
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
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,29 +5,15 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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 konova.decorators import default_group_required, shared_access_required, login_required_modal
from konova.views.share import AbstractShareByTokenView, AbstractShareFormView
class InterventionShareByTokenView(AbstractShareByTokenView):
model = Intervention
redirect_url = "intervention:detail"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
_MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:detail"
class InterventionShareFormView(AbstractShareFormView):
model = Intervention
@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)
_MODEL_CLS = Intervention
_REDIRECT_URL = "intervention:detail"

View File

@@ -10,21 +10,18 @@ from abc import abstractmethod
from django import forms
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount
from konova.models import BaseObject
class BaseForm(forms.Form):
"""
Basic form for that holds attributes needed in all other forms
"""
template = None
action_url = None
action_btn_label = _("Save")
form_title = None
cancel_redirect = None
form_caption = None
instance = None # The data holding model object
user = None # The performing user
request = None
form_attrs = {} # Holds additional attributes, that can be used in the template
has_required_fields = False # Automatically set. Triggers hint rendering in templates
@@ -33,6 +30,7 @@ class BaseForm(forms.Form):
def __init__(self, *args, **kwargs):
self.instance = kwargs.pop("instance", None)
self.user = kwargs.pop("user", None)
super().__init__(*args, **kwargs)
if self.request is not None:
self.user = self.request.user
@@ -42,11 +40,10 @@ class BaseForm(forms.Form):
self.has_required_fields = True
break
self.check_for_recorded_instance()
self.__check_valid_label_input_ratio()
@abstractmethod
def save(self):
def save(self, *arg, **kwargs):
# To be implemented in subclasses!
pass
@@ -136,34 +133,3 @@ class BaseForm(forms.Form):
set_class = self.fields[field].widget.attrs.get("class", "")
set_class = set_class.replace(cls, "")
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
render_submit = True
template = "modal/modal_form.html"
_TEMPLATE = "modal/modal_form.html"
def __init__(self, *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")
template = self.template
template = self._TEMPLATE
if request.method == "POST":
if self.is_valid():
if not is_ajax(request.META):

View File

@@ -8,10 +8,10 @@ Created on: 15.08.22
from django import forms
from django.db import transaction
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 konova.forms.modals.base_form import BaseModalForm
from konova.models import AbstractDocument
from konova.utils import validators
from konova.utils.message_templates import DOCUMENT_EDITED, FILE_SIZE_TOO_LARGE, FILE_TYPE_UNSUPPORTED
from user.models import UserActionLogEntry
@@ -69,7 +69,7 @@ class NewDocumentModalForm(BaseModalForm):
}
)
)
document_model = None
_DOCUMENT_CLS = None
class Meta:
abstract = True
@@ -81,7 +81,7 @@ class NewDocumentModalForm(BaseModalForm):
self.form_attrs = {
"enctype": "multipart/form-data", # important for file upload
}
if not self.document_model:
if not self._DOCUMENT_CLS:
raise NotImplementedError("Unsupported document type for {}".format(self.instance.__class__))
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
return super_valid
mime_type_valid = self.document_model.is_mime_type_valid(_file)
mime_type_valid = self._DOCUMENT_CLS.is_mime_type_valid(_file)
if not mime_type_valid:
self.add_error(
"file",
FILE_TYPE_UNSUPPORTED
)
file_size_valid = self.document_model.is_file_size_valid(_file)
file_size_valid = self._DOCUMENT_CLS.is_file_size_valid(_file)
if not file_size_valid:
self.add_error(
"file",
@@ -115,7 +115,7 @@ class NewDocumentModalForm(BaseModalForm):
action = UserActionLogEntry.get_created_action(self.user)
edited_action = UserActionLogEntry.get_edited_action(self.user, _("Added document"))
doc = self.document_model.objects.create(
doc = self._DOCUMENT_CLS.objects.create(
created=action,
title=self.cleaned_data["title"],
comment=self.cleaned_data["comment"],
@@ -133,10 +133,12 @@ class NewDocumentModalForm(BaseModalForm):
class EditDocumentModalForm(NewDocumentModalForm):
document = None
document_model = AbstractDocument
_DOCUMENT_CLS = None
def __init__(self, *args, **kwargs):
self.document = kwargs.pop("document", None)
doc_id = kwargs.pop("doc_id", None)
self.document = get_object_or_404(self._DOCUMENT_CLS, id=doc_id)
super().__init__(*args, **kwargs)
self.form_title = _("Edit document")
form_data = {

View File

@@ -6,10 +6,11 @@ Created on: 15.08.22
"""
from django import forms
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from konova.forms.modals.base_form import BaseModalForm
from konova.models import BaseObject
from konova.models import BaseObject, Deadline
class RemoveModalForm(BaseModalForm):
@@ -51,9 +52,19 @@ class RemoveDeadlineModalForm(RemoveModalForm):
deadline = None
def __init__(self, *args, **kwargs):
deadline = kwargs.pop("deadline", None)
self.deadline = deadline
deadline_id = kwargs.pop("deadline_id", None)
self.deadline = get_object_or_404(Deadline, id=deadline_id)
super().__init__(*args, **kwargs)
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{
z-index: 1 !important;
z-index: 100 !important;
}

View File

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

View File

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

View File

@@ -7,9 +7,7 @@ Created on: 01.09.21
"""
from django.http import FileResponse, HttpRequest, Http404
from konova.forms.modals import RemoveModalForm
from konova.models import AbstractDocument
from konova.utils.message_templates import DOCUMENT_REMOVED_TEMPLATE
def get_document(doc: AbstractDocument):
@@ -26,28 +24,3 @@ def get_document(doc: AbstractDocument):
return FileResponse(doc.file, as_attachment=True)
except FileNotFoundError:
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,6 +5,11 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 17.09.21
"""
from uuid import UUID
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from django.http import HttpRequest, Http404
def format_german_float(num) -> str:
@@ -19,3 +24,27 @@ def format_german_float(num) -> str:
num (str): The number as german Gleitkommazahl
"""
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,10 +7,6 @@ Created on: 09.11.20
"""
import random
import string
import qrcode
import qrcode.image.svg
from io import BytesIO
def generate_token() -> str:
@@ -42,24 +38,3 @@ def generate_random_string(length: int, use_numbers: bool = False, use_letters_l
ret_val = "".join(random.choice(elements) for i in range(length))
return ret_val
class IdentifierGenerator:
_MODEL = None
def __init__(self, model):
from konova.models import BaseObject
if not issubclass(model, BaseObject):
raise AssertionError("Model must be a subclass of BaseObject!")
self._MODEL = model
def generate_id(self) -> str:
""" Generates a unique identifier
Returns:
"""
unpersisted_object = self._MODEL()
identifier = unpersisted_object.generate_new_identifier()
while self._MODEL.objects.filter(identifier=identifier).exists():
identifier = unpersisted_object.generate_new_identifier()
return identifier

View File

@@ -19,7 +19,20 @@ 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.")
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
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.")
ENTRY_RECORDED = _("{} recorded")
ENTRY_UNRECORDED = _("{} unrecorded")
# SHARE
DATA_UNSHARED = _("This data is not shared with you")
@@ -94,4 +107,7 @@ DATA_CHECKED_PREVIOUSLY_TEMPLATE = _("Data has changed since last check on {} by
DATA_IS_UNCHECKED = _("Current data not checked yet")
# 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,6 +1,6 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
Created on: 17.10.25
"""
from io import BytesIO

View File

@@ -5,104 +5,47 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 22.08.22
"""
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
from compensation.forms.modals.compensation_action import NewCompensationActionModalForm, \
EditCompensationActionModalForm, RemoveCompensationActionModalForm
from compensation.models import CompensationAction
from konova.utils.message_templates import COMPENSATION_STATE_ADDED, COMPENSATION_STATE_EDITED, \
COMPENSATION_STATE_REMOVED
from konova.views.modal import AbstractModalFormView
class AbstractCompensationActionView(View):
model = None
redirect_url = None
class AbstractCompensationActionView(LoginRequiredMixin, AbstractModalFormView):
_MODEL_CLS = None
_REDIRECT_URL = None
class Meta:
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):
_FORM_CLS = NewCompensationActionModalForm
_MSG_SUCCESS = COMPENSATION_STATE_ADDED
class Meta:
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):
_FORM_CLS = EditCompensationActionModalForm
_MSG_SUCCESS = COMPENSATION_STATE_EDITED
class Meta:
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):
_FORM_CLS = RemoveCompensationActionModalForm
_MSG_SUCCESS = COMPENSATION_STATE_REMOVED
class Meta:
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)

115
konova/views/base.py Normal file
View File

@@ -0,0 +1,115 @@
"""
Author: Michel Peltriaux
Created on: 15.10.25
"""
from abc import abstractmethod
from django.contrib import messages
from django.http import HttpRequest
from django.shortcuts import redirect
from django.urls import reverse
from django.views import View
from konova.utils.general import check_user_is_in_any_group
from konova.utils.message_templates import MISSING_GROUP_PERMISSION, DATA_UNSHARED
class AbstractBaseView(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

View File

@@ -5,102 +5,57 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 22.08.22
"""
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
from compensation.forms.modals.deadline import NewDeadlineModalForm, EditDeadlineModalForm
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.views.modal import AbstractModalFormView
class AbstractNewDeadlineView(View):
model = None
redirect_url = None
class AbstractNewDeadlineView(LoginRequiredMixin, AbstractModalFormView):
_MODEL_CLS = None
_FORM_CLS = NewDeadlineModalForm
_REDIRECT_URL = None
_MSG_SUCCESS = DEADLINE_ADDED
class Meta:
abstract = True
def get(self, request, id: str):
""" Renders a form for adding new deadlines
def _get_redirect_url(self, *args, **kwargs):
return super()._get_redirect_url(*args, **kwargs) + "#related_data"
Args:
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)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
class AbstractEditDeadlineView(View):
model = None
redirect_url = None
class AbstractEditDeadlineView(LoginRequiredMixin, AbstractModalFormView):
_MODEL_CLS = None
_FORM_CLS = EditDeadlineModalForm
_REDIRECT_URL = None
_MSG_SUCCESS = DEADLINE_EDITED
class Meta:
abstract = True
def get(self, request, id: str, deadline_id: str):
""" Renders a form for editing deadlines
def _get_redirect_url(self, *args, **kwargs):
return super()._get_redirect_url(*args, **kwargs) + "#related_data"
Args:
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)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
class AbstractRemoveDeadlineView(View):
model = None
redirect_url = None
class AbstractRemoveDeadlineView(LoginRequiredMixin, AbstractModalFormView):
_MODEL_CLS = None
_FORM_CLS = RemoveDeadlineModalForm
_REDIRECT_URL = None
_MSG_SUCCESS = DEADLINE_REMOVED
class Meta:
abstract = True
def get(self, request, id: str, deadline_id: str):
""" Renders a form for removing deadlines
def _get_redirect_url(self, *args, **kwargs):
return super()._get_redirect_url(*args, **kwargs) + "#related_data"
Args:
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)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()

View File

@@ -6,126 +6,88 @@ Created on: 22.08.22
"""
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.views import View
from intervention.forms.modals.deduction import NewEcoAccountDeductionModalForm, EditEcoAccountDeductionModalForm, \
RemoveEcoAccountDeductionModalForm
from konova.utils.message_templates import DEDUCTION_ADDED, DEDUCTION_EDITED, DEDUCTION_REMOVED, DEDUCTION_UNKNOWN
from konova.utils.general import check_id_is_valid_uuid
from konova.views.modal import AbstractModalFormView
class AbstractDeductionView(View):
model = None
redirect_url = None
class AbstractDeductionView(AbstractModalFormView):
_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):
"""
Can be used by inheriting classes to provide custom checks before further processing
"""
raise NotImplementedError("Must be implemented in subclasses")
pass
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):
_FORM_CLS = NewEcoAccountDeductionModalForm
class Meta:
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):
_FORM_CLS = EditEcoAccountDeductionModalForm
def _custom_check(self, obj):
pass
def dispatch(self, request, *args, **kwargs):
check_id_is_valid_uuid(kwargs.get("deduction_id"))
return super().dispatch(request, *args, **kwargs)
class Meta:
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):
_FORM_CLS = RemoveEcoAccountDeductionModalForm
def _custom_check(self, obj):
pass
def dispatch(self, request, *args, **kwargs):
check_id_is_valid_uuid(kwargs.get("deduction_id"))
return super().dispatch(request, *args, **kwargs)
class Meta:
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,25 +1,107 @@
"""
Author: Michel Peltriaux
Created on: 14.12.25
Created on: 17.10.25
"""
from abc import ABC
from abc import abstractmethod
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.http import HttpRequest
from django.shortcuts import render
from konova.decorators import uuid_required, any_group_check
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 AbstractBaseView
class AbstractDetailView(LoginRequiredMixin, View, ABC):
_TEMPLATE = None
class BaseDetailView(LoginRequiredMixin, AbstractBaseView):
_MODEL_CLS = None
class Meta:
abstract = True
@method_decorator(uuid_required)
def dispatch(self, request, *args, **kwargs):
check_id_is_valid_uuid(kwargs.get('id'))
return super().dispatch(request, *args, **kwargs)
@method_decorator(any_group_check)
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
raise NotImplementedError()
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,46 +5,35 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
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.urls import reverse
from django.views import View
from konova.utils.documents import get_document, remove_document
from konova.utils.message_templates import DOCUMENT_ADDED, DOCUMENT_EDITED
from konova.forms.modals import EditDocumentModalForm
from konova.utils.documents import get_document
from konova.utils.message_templates import DOCUMENT_ADDED, DOCUMENT_EDITED, DOCUMENT_REMOVED_TEMPLATE
from konova.views.modal import AbstractModalFormView, AbstractBaseView
class AbstractNewDocumentView(View):
model = None
form = None
redirect_url = None
class AbstractNewDocumentView(LoginRequiredMixin, AbstractModalFormView):
_MODEL_CLS = None
_FORM_CLS = None
_REDIRECT_URL = None
_MSG_SUCCESS = DOCUMENT_ADDED
class Meta:
abstract = True
def get(self, request, id: str):
""" Renders a form for uploading new documents
def _get_redirect_url(self, *args, **kwargs):
return super()._get_redirect_url(*args, **kwargs) + "#related_data"
Args:
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)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
class AbstractGetDocumentView(View):
model = None
document_model = None
class AbstractGetDocumentView(LoginRequiredMixin, AbstractBaseView):
_MODEL_CLS = None
_DOCUMENT_CLS = None
class Meta:
abstract = True
@@ -62,77 +51,57 @@ class AbstractGetDocumentView(View):
Returns:
"""
get_object_or_404(self.model, id=id)
doc = get_object_or_404(self.document_model, id=doc_id)
get_object_or_404(self._MODEL_CLS, id=id)
doc = get_object_or_404(self._DOCUMENT_CLS, id=doc_id)
return get_document(doc)
def post(self, request, id: str, doc_id: str):
return self.get(request, id, doc_id)
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
class AbstractRemoveDocumentView(View):
model = None
document_model = None
def _user_has_shared_access(self, user, **kwargs):
obj = kwargs.get("id", None)
assert obj is not None
obj = get_object_or_404(self._MODEL_CLS, id=obj)
return obj.is_shared_with(user)
class AbstractRemoveDocumentView(LoginRequiredMixin, AbstractModalFormView):
_MODEL_CLS = None
_DOCUMENT_CLS = None
_FORM_CLS = None
_MSG_SUCCESS = DOCUMENT_REMOVED_TEMPLATE
class Meta:
abstract = True
def get(self, request, id: str, doc_id: str):
""" Removes the document from the database and file system
def _get_redirect_url(self, *args, **kwargs):
return super()._get_redirect_url(*args, **kwargs) + "#related_data"
Wraps the generic functionality from konova.utils.
def _user_has_permission(self, user, **kwargs):
return user.is_default_user()
Args:
request (HttpRequest): The incoming request
id (str): The intervention id
doc_id (str): The document id
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)
def _get_msg_success(self, *args, **kwargs):
doc_id = kwargs.get("doc_id", None)
assert doc_id is not None
doc = get_object_or_404(self._DOCUMENT_CLS, id=doc_id)
return self._MSG_SUCCESS.format(doc.title)
class AbstractEditDocumentView(View):
model = None
document_model = None
form = None
redirect_url = None
class AbstractEditDocumentView(LoginRequiredMixin, AbstractModalFormView):
_MODEL_CLS = None
_DOCUMENT_CLS = None
_FORM_CLS = EditDocumentModalForm
_REDIRECT_URL = None
_MSG_SUCCESS = DOCUMENT_EDITED
class Meta:
abstract = True
def get(self, request, id: str, doc_id: str):
""" 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 _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"

282
konova/views/form.py Normal file
View File

@@ -0,0 +1,282 @@
"""
Author: Michel Peltriaux
Created on: 12.12.25
"""
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from konova.contexts import BaseContext
from konova.forms import BaseForm, SimpleGeomForm
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE, \
FORM_INVALID, IDENTIFIER_REPLACED
from konova.views.base import AbstractBaseView
class AbstractFormView(AbstractBaseView):
""" 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 AbstractGeometryFormView(LoginRequiredMixin, AbstractFormView):
""" Abstract base view for processing objects with spatial data
"""
_GEOMETRY_FORM_CLS = SimpleGeomForm
class Meta:
abstract = True
class AbstractNewGeometryFormView(AbstractGeometryFormView):
""" 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 AbstractEditGeometryFormView(AbstractGeometryFormView):
""" 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

@@ -10,15 +10,16 @@ from django.http import HttpResponse, HttpRequest
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
from django.utils import timezone
from django.views import View
from konova.models import Geometry
from konova.settings import GEOM_THRESHOLD_RECALCULATION_SECONDS
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from konova.tasks import celery_update_parcels
from konova.views.base import AbstractBaseView
class GeomParcelsView(View):
class GeomParcelsView(AbstractBaseView):
_TEMPLATE = "konova/includes/parcels/parcel_table_frame.html"
def get(self, request: HttpRequest, id: str):
""" Getter for HTMX
@@ -32,7 +33,6 @@ class GeomParcelsView(View):
Returns:
A rendered piece of HTML
"""
template = "konova/includes/parcels/parcel_table_frame.html"
geom = get_object_or_404(Geometry, id=id)
geos_geom = geom.geom or MultiPolygon(srid=DEFAULT_SRID_RLP)
@@ -85,7 +85,7 @@ class GeomParcelsView(View):
"geom_id": str(id),
"next_page": next_page,
}
html = render_to_string(template, context, request)
html = render_to_string(self._TEMPLATE, context, request)
return HttpResponse(html, status=status_code)
else:
return HttpResponse(None, status=404)
@@ -107,8 +107,15 @@ class GeomParcelsView(View):
waiting_too_long = (pcs_diff >= wait_for_seconds)
return waiting_too_long
def _user_has_shared_access(self, user, **kwargs):
return True
class GeomParcelsContentView(View):
def _user_has_permission(self, user, **kwargs):
return True
class GeomParcelsContentView(AbstractBaseView):
_TEMPLATE = "konova/includes/parcels/parcel_table_content.html"
def get(self, request: HttpRequest, id: str, page: int):
""" Getter for infinite scroll of HTMX
@@ -130,7 +137,6 @@ class GeomParcelsContentView(View):
# HTTP code 286 states that the HTMX should stop polling for updates
# https://htmx.org/docs/#polling
status_code = 286
template = "konova/includes/parcels/parcel_table_content.html"
geom = get_object_or_404(Geometry, id=id)
parcels = geom.get_underlying_parcels()
@@ -148,5 +154,11 @@ class GeomParcelsContentView(View):
"geom_id": str(id),
"next_page": next_page,
}
html = render_to_string(template, context, request)
html = render_to_string(self._TEMPLATE, context, request)
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,21 +9,19 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from django.http import HttpRequest
from django.shortcuts import render
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views import View
from compensation.models import EcoAccount, Compensation
from intervention.models import Intervention
from konova.contexts import BaseContext
from konova.decorators import any_group_check
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.views.base import AbstractBaseView
from news.models import ServerMessage
class HomeView(LoginRequiredMixin, View):
class HomeView(LoginRequiredMixin, AbstractBaseView):
_TEMPLATE = "konova/home.html"
@method_decorator(any_group_check)
def get(self, request: HttpRequest):
"""
Renders the landing page
@@ -34,7 +32,6 @@ class HomeView(LoginRequiredMixin, View):
Returns:
A redirect
"""
template = "konova/home.html"
user = request.user
user_teams = user.shared_teams
@@ -75,5 +72,12 @@ class HomeView(LoginRequiredMixin, View):
TAB_TITLE_IDENTIFIER: _("Home"),
}
context = BaseContext(request, additional_context).context
return render(request, template, context)
return render(request, self._TEMPLATE, context)
def _user_has_permission(self, user, **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

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