Merge pull request '7_New_forms' (#28) from 7_New_forms into master
Reviewed-on: SGD-Nord/konova#28
This commit is contained in:
commit
0e7c378d4a
@ -13,7 +13,7 @@ from codelist.models import KonovaCode, KonovaCodeList
|
||||
from codelist.settings import CODELIST_INTERVENTION_HANDLER_ID, CODELIST_CONSERVATION_OFFICE_ID, \
|
||||
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, CODELIST_COMPENSATION_HANDLER_ID, \
|
||||
CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_CLASS_ID, CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID, \
|
||||
CODELIST_COMPENSATION_COMBINATION_ID, CODELIST_BASE_URL, CODELIST_PROCESS_TYPE_ID
|
||||
CODELIST_COMPENSATION_FUNDING_ID, CODELIST_BASE_URL, CODELIST_PROCESS_TYPE_ID
|
||||
|
||||
bool_map = {
|
||||
"true": True,
|
||||
@ -35,7 +35,7 @@ class Command(BaseCommand):
|
||||
CODELIST_COMPENSATION_ACTION_ID,
|
||||
CODELIST_COMPENSATION_ACTION_CLASS_ID,
|
||||
CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID,
|
||||
CODELIST_COMPENSATION_COMBINATION_ID,
|
||||
CODELIST_COMPENSATION_FUNDING_ID,
|
||||
CODELIST_PROCESS_TYPE_ID,
|
||||
]
|
||||
self._write_warning("Fetching codes...")
|
||||
|
@ -53,8 +53,9 @@ class KonovaCode(models.Model):
|
||||
if self.parent:
|
||||
ret_val += self.parent.long_name + " > "
|
||||
ret_val += self.long_name
|
||||
if self.short_name:
|
||||
ret_val += " ({})".format(self.short_name)
|
||||
if self.short_name and self.short_name != self.long_name:
|
||||
# Only add short name, if we won't have stupid repition like 'thing a (thing a)' due to misused long-short names
|
||||
ret_val += f" ({self.short_name})"
|
||||
return ret_val
|
||||
|
||||
@property
|
||||
|
@ -21,4 +21,4 @@ CODELIST_COMPENSATION_HANDLER_ID = 1052 # CLEingreifer
|
||||
CODELIST_COMPENSATION_ACTION_ID = 1026 # CLMassnahmedetail
|
||||
CODELIST_COMPENSATION_ACTION_CLASS_ID = 1034 # CLMassnahmeklasse
|
||||
CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID = 1028 # CLMassnahmetyp, CEF and stuff
|
||||
CODELIST_COMPENSATION_COMBINATION_ID = 1049 # CLKombimassnahme
|
||||
CODELIST_COMPENSATION_FUNDING_ID = 1049 # CLKombimassnahme
|
||||
|
@ -11,6 +11,7 @@ from compensation.views.eco_account_views import *
|
||||
urlpatterns = [
|
||||
path("", index_view, name="acc-index"),
|
||||
path('new/', new_view, name='acc-new'),
|
||||
path('new/id', new_id_view, name='acc-new-id'),
|
||||
path('<id>', open_view, name='acc-open'),
|
||||
path('<id>/log', log_view, name='acc-log'),
|
||||
path('<id>/record', record_view, name='acc-record'),
|
||||
|
@ -11,6 +11,8 @@ from compensation.views.compensation_views import *
|
||||
urlpatterns = [
|
||||
# Main compensation
|
||||
path("", index_view, name="index"),
|
||||
path('new/id', new_id_view, name='new-id'),
|
||||
path('new/<intervention_id>', new_view, name='new'),
|
||||
path('new', new_view, name='new'),
|
||||
path('<id>', open_view, name='open'),
|
||||
path('<id>/log', log_view, name='log'),
|
||||
|
444
compensation/forms/forms.py
Normal file
444
compensation/forms/forms.py
Normal file
@ -0,0 +1,444 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 04.12.20
|
||||
|
||||
"""
|
||||
from dal import autocomplete
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import transaction
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django import forms
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_COMPENSATION_FUNDING_ID, CODELIST_CONSERVATION_OFFICE_ID
|
||||
from compensation.models import Compensation, EcoAccount
|
||||
from intervention.inputs import GenerateInput
|
||||
from intervention.models import Intervention, ResponsibilityData
|
||||
from konova.forms import BaseForm, SimpleGeomForm
|
||||
from user.models import UserActionLogEntry, UserAction
|
||||
|
||||
|
||||
class AbstractCompensationForm(BaseForm):
|
||||
""" Abstract form for compensations
|
||||
|
||||
Holds all important form fields, which are used in compensation and eco account forms
|
||||
|
||||
"""
|
||||
identifier = forms.CharField(
|
||||
label=_("Identifier"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Generated automatically"),
|
||||
widget=GenerateInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"url": None, # Needs to be set in inheriting constructors
|
||||
}
|
||||
)
|
||||
)
|
||||
title = forms.CharField(
|
||||
label=_("Title"),
|
||||
label_suffix="",
|
||||
help_text=_("An explanatory name"),
|
||||
max_length=255,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Compensation XY; Location ABC"),
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
fundings = forms.ModelMultipleChoiceField(
|
||||
label=_("Fundings"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
help_text=_("Select fundings for this compensation"),
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_COMPENSATION_FUNDING_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2Multiple(
|
||||
url="codes-compensation-funding-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
),
|
||||
)
|
||||
comment = forms.CharField(
|
||||
label_suffix="",
|
||||
label=_("Comment"),
|
||||
required=False,
|
||||
help_text=_("Additional comment"),
|
||||
widget=forms.Textarea(
|
||||
attrs={
|
||||
"rows": 5,
|
||||
"class": "form-control"
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class CompensationResponsibleFormMixin(forms.Form):
|
||||
""" Encapsulates form fields used in different compensation related models like EcoAccount or EMA
|
||||
|
||||
"""
|
||||
conservation_office = forms.ModelChoiceField(
|
||||
label=_("Conservation office"),
|
||||
label_suffix="",
|
||||
help_text=_("Select the responsible office"),
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_CONSERVATION_OFFICE_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-conservation-office-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection")
|
||||
}
|
||||
),
|
||||
)
|
||||
conservation_file_number = forms.CharField(
|
||||
label=_("Conservation office file number"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("ETS-123/ABC.456"),
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
handler = forms.CharField(
|
||||
label=_("Eco-account handler"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
required=False,
|
||||
help_text=_("Who handles the eco-account"),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Company Mustermann"),
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class NewCompensationForm(AbstractCompensationForm):
|
||||
""" Form for creating new compensations.
|
||||
|
||||
Can be initialized with an intervention id for preselecting the related intervention.
|
||||
form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
|
||||
...
|
||||
The intervention id will not be resolved into the intervention ORM object but instead will be used to initialize
|
||||
the related form field.
|
||||
|
||||
"""
|
||||
intervention = forms.ModelChoiceField(
|
||||
label=_("compensates intervention"),
|
||||
label_suffix="",
|
||||
help_text=_("Select the intervention for which this compensation compensates"),
|
||||
queryset=Intervention.objects.filter(
|
||||
deleted=None,
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="interventions-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
"data-minimum-input-length": 3,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
# Define a field order for a nicer layout instead of running with the inheritance result
|
||||
field_order = [
|
||||
"identifier",
|
||||
"title",
|
||||
"intervention",
|
||||
"fundings",
|
||||
"comment",
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
intervention_id = kwargs.pop("intervention_id", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("New compensation")
|
||||
|
||||
# If the compensation shall directly be initialized from an intervention, we need to fill in the intervention id
|
||||
# and disable the form field.
|
||||
# Furthermore the action_url needs to be set accordingly.
|
||||
if intervention_id is not None:
|
||||
self.initialize_form_field("intervention", intervention_id)
|
||||
self.disable_form_field("intervention")
|
||||
self.action_url = reverse("compensation:new", args=(intervention_id,))
|
||||
self.cancel_redirect = reverse("intervention:open", args=(intervention_id,))
|
||||
else:
|
||||
self.action_url = reverse("compensation:new")
|
||||
self.cancel_redirect = reverse("compensation:index")
|
||||
|
||||
tmp = Compensation()
|
||||
identifier = tmp.generate_new_identifier()
|
||||
self.initialize_form_field("identifier", identifier)
|
||||
self.fields["identifier"].widget.attrs["url"] = reverse_lazy("compensation:new-id")
|
||||
|
||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||
with transaction.atomic():
|
||||
# Fetch data from cleaned POST values
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
fundings = self.cleaned_data.get("fundings", None)
|
||||
intervention = self.cleaned_data.get("intervention", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.objects.create(
|
||||
user=user,
|
||||
action=UserAction.CREATED,
|
||||
)
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Finally create main object
|
||||
comp = Compensation.objects.create(
|
||||
identifier=identifier,
|
||||
title=title,
|
||||
intervention=intervention,
|
||||
created=action,
|
||||
geometry=geometry,
|
||||
comment=comment,
|
||||
)
|
||||
comp.fundings.set(fundings)
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
comp.log.add(action)
|
||||
return comp
|
||||
|
||||
|
||||
class EditCompensationForm(NewCompensationForm):
|
||||
""" Form for editing compensations
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Edit compensation")
|
||||
self.action_url = reverse("compensation:edit", args=(self.instance.id,))
|
||||
self.cancel_redirect = reverse("compensation:open", args=(self.instance.id,))
|
||||
|
||||
# Initialize form data
|
||||
form_data = {
|
||||
"identifier": self.instance.identifier,
|
||||
"title": self.instance.title,
|
||||
"intervention": self.instance.intervention,
|
||||
"fundings": self.instance.fundings.all(),
|
||||
"comment": self.instance.comment,
|
||||
}
|
||||
disabled_fields = []
|
||||
self.load_initial_data(
|
||||
form_data,
|
||||
disabled_fields
|
||||
)
|
||||
|
||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||
with transaction.atomic():
|
||||
# Fetch data from cleaned POST values
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
fundings = self.cleaned_data.get("fundings", None)
|
||||
intervention = self.cleaned_data.get("intervention", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.objects.create(
|
||||
user=user,
|
||||
action=UserAction.EDITED,
|
||||
)
|
||||
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Finally create main object
|
||||
self.instance.identifier = identifier
|
||||
self.instance.title = title
|
||||
self.instance.intervention = intervention
|
||||
self.instance.geometry = geometry
|
||||
self.instance.comment = comment
|
||||
self.instance.modified = action
|
||||
self.instance.fundings.set(fundings)
|
||||
self.instance.save()
|
||||
|
||||
self.instance.log.add(action)
|
||||
return self.instance
|
||||
|
||||
|
||||
class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
|
||||
""" Form for creating eco accounts
|
||||
|
||||
Inherits from basic AbstractCompensationForm and further form fields from CompensationResponsibleFormMixin
|
||||
|
||||
"""
|
||||
field_order = [
|
||||
"identifier",
|
||||
"title",
|
||||
"conservation_office",
|
||||
"conservation_file_number",
|
||||
"handler",
|
||||
"fundings",
|
||||
"comment",
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("New Eco-Account")
|
||||
|
||||
self.action_url = reverse("compensation:acc-new")
|
||||
self.cancel_redirect = reverse("compensation:acc-index")
|
||||
|
||||
tmp = EcoAccount()
|
||||
identifier = tmp.generate_new_identifier()
|
||||
self.initialize_form_field("identifier", identifier)
|
||||
self.fields["identifier"].widget.attrs["url"] = reverse_lazy("compensation:acc-new-id")
|
||||
self.fields["title"].widget.attrs["placeholder"] = _("Eco-Account XY; Location ABC")
|
||||
|
||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||
with transaction.atomic():
|
||||
# Fetch data from cleaned POST values
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
fundings = self.cleaned_data.get("fundings", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.objects.create(
|
||||
user=user,
|
||||
action=UserAction.CREATED,
|
||||
)
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
responsible = ResponsibilityData.objects.create(
|
||||
handler=handler,
|
||||
conservation_file_number=conservation_file_number,
|
||||
conservation_office=conservation_office,
|
||||
)
|
||||
|
||||
# Finally create main object
|
||||
acc = EcoAccount.objects.create(
|
||||
identifier=identifier,
|
||||
title=title,
|
||||
responsible=responsible,
|
||||
deductable_surface=0.00,
|
||||
created=action,
|
||||
geometry=geometry,
|
||||
comment=comment,
|
||||
)
|
||||
acc.fundings.set(fundings)
|
||||
acc.users.add(user)
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
acc.log.add(action)
|
||||
return acc
|
||||
|
||||
|
||||
class EditEcoAccountForm(NewEcoAccountForm):
|
||||
""" Form for editing eco accounts
|
||||
|
||||
"""
|
||||
surface = forms.DecimalField(
|
||||
min_value=0.00,
|
||||
decimal_places=2,
|
||||
label=_("Available Surface"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
help_text=_("The amount that can be used for deductions"),
|
||||
widget=forms.NumberInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "0,00"
|
||||
}
|
||||
)
|
||||
)
|
||||
field_order = [
|
||||
"identifier",
|
||||
"title",
|
||||
"conservation_office",
|
||||
"surface",
|
||||
"conservation_file_number",
|
||||
"handler",
|
||||
"fundings",
|
||||
"comment",
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Edit Eco-Account")
|
||||
|
||||
self.action_url = reverse("compensation:acc-edit", args=(self.instance.id,))
|
||||
self.cancel_redirect = reverse("compensation:acc-open", args=(self.instance.id,))
|
||||
|
||||
# Initialize form data
|
||||
form_data = {
|
||||
"identifier": self.instance.identifier,
|
||||
"title": self.instance.title,
|
||||
"surface": self.instance.deductable_surface,
|
||||
"handler": self.instance.responsible.handler,
|
||||
"conservation_office": self.instance.responsible.conservation_office,
|
||||
"conservation_file_number": self.instance.responsible.conservation_file_number,
|
||||
"fundings": self.instance.fundings.all(),
|
||||
"comment": self.instance.comment,
|
||||
}
|
||||
disabled_fields = []
|
||||
self.load_initial_data(
|
||||
form_data,
|
||||
disabled_fields
|
||||
)
|
||||
|
||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||
with transaction.atomic():
|
||||
# Fetch data from cleaned POST values
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
fundings = self.cleaned_data.get("fundings", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
surface = self.cleaned_data.get("surface", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.objects.create(
|
||||
user=user,
|
||||
action=UserAction.EDITED,
|
||||
)
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Update responsible data
|
||||
self.instance.responsible.handler = handler
|
||||
self.instance.responsible.conservation_office = conservation_office
|
||||
self.instance.responsible.conservation_file_number = conservation_file_number
|
||||
self.instance.responsible.save()
|
||||
|
||||
# Update main oject data
|
||||
self.instance.identifier = identifier
|
||||
self.instance.title = title
|
||||
self.instance.deductable_surface = surface
|
||||
self.instance.geometry = geometry
|
||||
self.instance.comment = comment
|
||||
self.instance.modified = action
|
||||
self.instance.save()
|
||||
self.instance.fundings.set(fundings)
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
self.instance.log.add(action)
|
||||
return self.instance
|
@ -2,7 +2,7 @@
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 04.12.20
|
||||
Created on: 04.10.21
|
||||
|
||||
"""
|
||||
from bootstrap_modal_forms.utils import is_ajax
|
||||
@ -12,40 +12,34 @@ from django.contrib import messages
|
||||
from django.db import transaction
|
||||
from django.http import HttpRequest, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import pgettext_lazy as _con
|
||||
from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION_ID
|
||||
from compensation.models import Payment, CompensationState, CompensationAction, UnitChoices
|
||||
from compensation.models import Payment, CompensationState, UnitChoices, CompensationAction
|
||||
from konova.contexts import BaseContext
|
||||
from konova.forms import BaseForm, BaseModalForm
|
||||
from konova.models import Deadline, DeadlineType
|
||||
from konova.forms import BaseModalForm
|
||||
from konova.models import DeadlineType, Deadline
|
||||
from konova.utils.message_templates import FORM_INVALID
|
||||
from user.models import UserActionLogEntry, UserAction
|
||||
|
||||
|
||||
class NewCompensationForm(BaseForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def save(self):
|
||||
with transaction.atomic():
|
||||
user_action = UserActionLogEntry.objects.create(
|
||||
user=self.user,
|
||||
action=UserAction.CREATED
|
||||
)
|
||||
# Save action to log
|
||||
|
||||
|
||||
class NewPaymentForm(BaseModalForm):
|
||||
""" Form handling payment related input
|
||||
|
||||
"""
|
||||
amount = forms.DecimalField(
|
||||
min_value=0.00,
|
||||
decimal_places=2,
|
||||
label=_con("money", "Amount"), # contextual translation
|
||||
label_suffix=_(""),
|
||||
help_text=_("in Euro"),
|
||||
widget=forms.NumberInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "0,00",
|
||||
}
|
||||
)
|
||||
)
|
||||
due = forms.DateField(
|
||||
label=_("Due on"),
|
||||
@ -56,6 +50,7 @@ class NewPaymentForm(BaseModalForm):
|
||||
attrs={
|
||||
"type": "date",
|
||||
"data-provide": "datepicker",
|
||||
"class": "form-control",
|
||||
},
|
||||
format="%d.%m.%Y"
|
||||
)
|
||||
@ -69,7 +64,7 @@ class NewPaymentForm(BaseModalForm):
|
||||
widget=forms.Textarea(
|
||||
attrs={
|
||||
"rows": 5,
|
||||
"class": "w-100"
|
||||
"class": "form-control"
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -79,7 +74,6 @@ class NewPaymentForm(BaseModalForm):
|
||||
self.intervention = self.instance
|
||||
self.form_title = _("Payment")
|
||||
self.form_caption = _("Add a payment for intervention '{}'").format(self.intervention.title)
|
||||
self.add_placeholder_for_field("amount", "0,00")
|
||||
|
||||
def is_valid(self):
|
||||
"""
|
||||
@ -129,6 +123,12 @@ class NewPaymentForm(BaseModalForm):
|
||||
|
||||
|
||||
class NewStateModalForm(BaseModalForm):
|
||||
""" Form handling state related input
|
||||
|
||||
Compensation states refer to 'before' and 'after' states of a compensated surface. Basically it means:
|
||||
What has been on this area before changes/compensations have been applied and what will be the result ('after')?
|
||||
|
||||
"""
|
||||
biotope_type = forms.ModelChoiceField(
|
||||
label=_("Biotope Type"),
|
||||
label_suffix="",
|
||||
@ -152,14 +152,19 @@ class NewStateModalForm(BaseModalForm):
|
||||
label=_("Surface"),
|
||||
label_suffix="",
|
||||
required=True,
|
||||
help_text=_("in m²")
|
||||
help_text=_("in m²"),
|
||||
widget=forms.NumberInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "0,00"
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("New state")
|
||||
self.form_caption = _("Insert data for the new state")
|
||||
self.add_placeholder_for_field("surface", "0,00")
|
||||
|
||||
def save(self, is_before_state: bool = False):
|
||||
with transaction.atomic():
|
||||
@ -232,6 +237,9 @@ class NewStateModalForm(BaseModalForm):
|
||||
|
||||
|
||||
class NewDeadlineModalForm(BaseModalForm):
|
||||
""" Form handling deadline related input
|
||||
|
||||
"""
|
||||
type = forms.ChoiceField(
|
||||
label=_("Deadline Type"),
|
||||
label_suffix="",
|
||||
@ -240,7 +248,7 @@ class NewDeadlineModalForm(BaseModalForm):
|
||||
choices=DeadlineType.choices,
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
"class": "custom-select"
|
||||
"class": "form-control"
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -253,6 +261,7 @@ class NewDeadlineModalForm(BaseModalForm):
|
||||
attrs={
|
||||
"type": "date",
|
||||
"data-provide": "datepicker",
|
||||
"class": "form-control",
|
||||
},
|
||||
format="%d.%m.%Y"
|
||||
)
|
||||
@ -267,6 +276,7 @@ class NewDeadlineModalForm(BaseModalForm):
|
||||
attrs={
|
||||
"cols": 30,
|
||||
"rows": 5,
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -301,6 +311,13 @@ class NewDeadlineModalForm(BaseModalForm):
|
||||
|
||||
|
||||
class NewActionModalForm(BaseModalForm):
|
||||
""" Form handling action related input
|
||||
|
||||
Compensation actions are the actions performed on the area, which shall be compensated. Actions will change the
|
||||
surface of the area, the biotopes, and have an environmental impact. With actions the before-after states can change
|
||||
(not in the process logic in Konova, but in the real world).
|
||||
|
||||
"""
|
||||
action_type = forms.ModelChoiceField(
|
||||
label=_("Action Type"),
|
||||
label_suffix="",
|
||||
@ -326,7 +343,7 @@ class NewActionModalForm(BaseModalForm):
|
||||
choices=UnitChoices.choices,
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
"class": "custom-select"
|
||||
"class": "form-control"
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -337,6 +354,12 @@ class NewActionModalForm(BaseModalForm):
|
||||
help_text=_("Insert the amount"),
|
||||
decimal_places=2,
|
||||
min_value=0.00,
|
||||
widget=forms.NumberInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "0,00",
|
||||
}
|
||||
)
|
||||
)
|
||||
comment = forms.CharField(
|
||||
required=False,
|
||||
@ -347,7 +370,7 @@ class NewActionModalForm(BaseModalForm):
|
||||
widget=forms.Textarea(
|
||||
attrs={
|
||||
"rows": 5,
|
||||
"class": "w-100"
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -356,7 +379,6 @@ class NewActionModalForm(BaseModalForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("New action")
|
||||
self.form_caption = _("Insert data for the new action")
|
||||
self.add_placeholder_for_field("amount", "0,00")
|
||||
|
||||
def save(self):
|
||||
with transaction.atomic():
|
||||
@ -380,5 +402,4 @@ class NewActionModalForm(BaseModalForm):
|
||||
self.instance.save()
|
||||
self.instance.log.add(edited_action)
|
||||
self.instance.actions.add(comp_action)
|
||||
return comp_action
|
||||
|
||||
return comp_action
|
@ -16,7 +16,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, \
|
||||
CODELIST_COMPENSATION_COMBINATION_ID
|
||||
CODELIST_COMPENSATION_FUNDING_ID
|
||||
from intervention.models import Intervention, ResponsibilityData
|
||||
from konova.models import BaseObject, BaseResource, Geometry, UuidModel, AbstractDocument, \
|
||||
generate_document_file_upload_path
|
||||
@ -144,7 +144,7 @@ class AbstractCompensation(BaseObject):
|
||||
null=True,
|
||||
blank=True,
|
||||
limit_choices_to={
|
||||
"code_lists__in": [CODELIST_COMPENSATION_COMBINATION_ID],
|
||||
"code_lists__in": [CODELIST_COMPENSATION_FUNDING_ID],
|
||||
"is_selectable": True,
|
||||
"is_archived": False,
|
||||
},
|
||||
@ -185,9 +185,9 @@ class Compensation(AbstractCompensation):
|
||||
def save(self, *args, **kwargs):
|
||||
if self.identifier is None or len(self.identifier) == 0:
|
||||
# Create new identifier
|
||||
new_id = self._generate_new_identifier()
|
||||
new_id = self.generate_new_identifier()
|
||||
while Compensation.objects.filter(identifier=new_id).exists():
|
||||
new_id = self._generate_new_identifier()
|
||||
new_id = self.generate_new_identifier()
|
||||
self.identifier = new_id
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@ -322,9 +322,9 @@ class EcoAccount(AbstractCompensation):
|
||||
def save(self, *args, **kwargs):
|
||||
if self.identifier is None or len(self.identifier) == 0:
|
||||
# Create new identifier
|
||||
new_id = self._generate_new_identifier()
|
||||
new_id = self.generate_new_identifier()
|
||||
while EcoAccount.objects.filter(identifier=new_id).exists():
|
||||
new_id = self._generate_new_identifier()
|
||||
new_id = self.generate_new_identifier()
|
||||
self.identifier = new_id
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@ -355,28 +355,28 @@ class EcoAccount(AbstractCompensation):
|
||||
"""
|
||||
return self.after_states.all().aggregate(Sum("surface"))["surface__sum"] or 0
|
||||
|
||||
def get_available_rest(self, as_percentage: bool = False) -> float:
|
||||
def get_available_rest(self) -> (float, float):
|
||||
""" Calculates available rest surface of the eco account
|
||||
|
||||
Args:
|
||||
as_percentage (bool): Whether to return the result as m² or %
|
||||
|
||||
Returns:
|
||||
|
||||
ret_val_total (float): Total amount
|
||||
ret_val_relative (float): Amount as percentage (0-100)
|
||||
"""
|
||||
deductions = self.deductions.filter(
|
||||
intervention__deleted=None,
|
||||
)
|
||||
deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0
|
||||
available_surfaces = self.deductable_surface or deductions_surfaces ## no division by zero
|
||||
ret_val = available_surfaces - deductions_surfaces
|
||||
ret_val_total = available_surfaces - deductions_surfaces
|
||||
|
||||
if as_percentage:
|
||||
if available_surfaces > 0:
|
||||
ret_val = int((ret_val / available_surfaces) * 100)
|
||||
else:
|
||||
ret_val = 0
|
||||
return ret_val
|
||||
if available_surfaces > 0:
|
||||
ret_val_relative = int((ret_val_total / available_surfaces) * 100)
|
||||
else:
|
||||
ret_val_relative = 0
|
||||
|
||||
return ret_val_total, ret_val_relative
|
||||
|
||||
def get_LANIS_link(self) -> str:
|
||||
""" Generates a link for LANIS depending on the geometry
|
||||
|
@ -5,8 +5,8 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 18.12.20
|
||||
|
||||
"""
|
||||
COMPENSATION_IDENTIFIER_LENGTH = 10
|
||||
COMPENSATION_IDENTIFIER_LENGTH = 6
|
||||
COMPENSATION_IDENTIFIER_TEMPLATE = "KOM-{}"
|
||||
|
||||
ECO_ACCOUNT_IDENTIFIER_LENGTH = 10
|
||||
ECO_ACCOUNT_IDENTIFIER_LENGTH = 6
|
||||
ECO_ACCOUNT_IDENTIFIER_TEMPLATE = "OEK-{}"
|
@ -5,6 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 01.12.20
|
||||
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import HttpRequest
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
@ -148,6 +149,8 @@ class CompensationTable(BaseTable):
|
||||
|
||||
"""
|
||||
html = ""
|
||||
if value is None:
|
||||
value = User.objects.none()
|
||||
has_access = value.filter(
|
||||
username=self.user.username
|
||||
).exists()
|
||||
@ -236,8 +239,8 @@ class EcoAccountTable(BaseTable):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
value = record.get_available_rest(as_percentage=True)
|
||||
html = render_to_string("konova/custom_widgets/progressbar.html", {"value": value})
|
||||
value_total, value_relative = record.get_available_rest()
|
||||
html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
|
||||
return format_html(html)
|
||||
|
||||
def render_r(self, value, record: EcoAccount):
|
||||
|
@ -0,0 +1,23 @@
|
||||
{% load i18n fontawesome_5 %}
|
||||
|
||||
{% if obj.comment %}
|
||||
<div class="w-100">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header rlp-gd">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<h5 class="card-title">
|
||||
{% fa5_icon 'info-circle' %}
|
||||
{% trans 'Comment' %}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-text font-italic">
|
||||
{{obj.comment}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
@ -13,7 +13,7 @@
|
||||
</a>
|
||||
{% if has_access %}
|
||||
{% if is_default_member %}
|
||||
<a href="{% url 'home' %}" class="mr-2">
|
||||
<a href="{% url 'compensation:edit' obj.id %}" class="mr-2">
|
||||
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
||||
{% fa5_icon 'edit' %}
|
||||
</button>
|
||||
|
@ -75,7 +75,7 @@
|
||||
</div>
|
||||
<br>
|
||||
{% empty %}
|
||||
None
|
||||
{% trans 'None' %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -99,7 +99,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
{% include 'map/geom_form.html' %}
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'compensation/detail/compensation/includes/comment.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
@ -0,0 +1,23 @@
|
||||
{% load i18n fontawesome_5 %}
|
||||
|
||||
{% if obj.comment %}
|
||||
<div class="w-100">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header rlp-gd">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<h5 class="card-title">
|
||||
{% fa5_icon 'info-circle' %}
|
||||
{% trans 'Comment' %}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-text font-italic">
|
||||
{{obj.comment}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
@ -24,7 +24,7 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if is_default_member %}
|
||||
<a href="{% url 'home' %}" class="mr-2">
|
||||
<a href="{% url 'compensation:acc-edit' obj.id %}" class="mr-2">
|
||||
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
||||
{% fa5_icon 'edit' %}
|
||||
</button>
|
||||
|
@ -30,12 +30,12 @@
|
||||
<th class="w-25" scope="row">{% trans 'Title' %}</th>
|
||||
<td class="align-middle">{{obj.title}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr {% if not obj.deductable_surface %}class="alert alert-danger" title="{% trans 'No surface deductable' %}" {% endif %}>
|
||||
<th scope="row">{% trans 'Available' %}</th>
|
||||
<td class="align-middle">
|
||||
{{obj.deductions_surface_sum|floatformat:2}} / {{obj.deductable_surface|floatformat:2}} m²
|
||||
{{available_total|floatformat:2}} / {{obj.deductable_surface|default_if_none:0.00|floatformat:2}} m²
|
||||
{% with available as value %}
|
||||
{% include 'konova/custom_widgets/progressbar.html' %}
|
||||
{% include 'konova/widgets/progressbar.html' %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -58,7 +58,7 @@
|
||||
<td class="align-middle">{{obj.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr {% if not obj.responsible.conservation_file_number %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
||||
<th scope="row">{% trans 'Conversation office file number' %}</th>
|
||||
<th scope="row">{% trans 'Conservation office file number' %}</th>
|
||||
<td class="align-middle">{{obj.responsible.conservation_file_number|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr {% if not obj.responsible.handler %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
||||
@ -98,7 +98,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
{% include 'map/geom_form.html' %}
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'compensation/detail/compensation/includes/comment.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
6
compensation/templates/compensation/form/view.html
Normal file
6
compensation/templates/compensation/form/view.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n l10n %}
|
||||
|
||||
{% block body %}
|
||||
{% include 'form/collapsable/form.html' %}
|
||||
{% endblock %}
|
@ -1,16 +1,19 @@
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Sum
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.http import HttpRequest, JsonResponse
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.forms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm
|
||||
from compensation.forms.forms import NewCompensationForm, EditCompensationForm
|
||||
from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm
|
||||
from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument
|
||||
from compensation.tables import CompensationTable
|
||||
from intervention.models import Intervention
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import *
|
||||
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm
|
||||
from konova.utils.documents import get_document, remove_document
|
||||
from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED
|
||||
from konova.utils.user_checks import in_group
|
||||
|
||||
|
||||
@ -44,16 +47,100 @@ def index_view(request: HttpRequest):
|
||||
|
||||
@login_required
|
||||
@default_group_required
|
||||
def new_view(request: HttpRequest):
|
||||
# ToDo
|
||||
pass
|
||||
@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
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "compensation/form/view.html"
|
||||
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
|
||||
)
|
||||
)
|
||||
messages.success(request, _("Compensation {} added").format(comp.identifier))
|
||||
return redirect("compensation:open", id=comp.id)
|
||||
else:
|
||||
messages.error(request, FORM_INVALID)
|
||||
else:
|
||||
# For clarification: nothing in this case
|
||||
pass
|
||||
context = {
|
||||
"form": data_form,
|
||||
"geom_form": geom_form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def new_id_view(request: HttpRequest):
|
||||
""" JSON endpoint
|
||||
|
||||
Provides fetching of free identifiers for e.g. AJAX calls
|
||||
|
||||
"""
|
||||
tmp = Compensation()
|
||||
identifier = tmp.generate_new_identifier()
|
||||
while Compensation.objects.filter(identifier=identifier).exists():
|
||||
identifier = tmp.generate_new_identifier()
|
||||
return JsonResponse(
|
||||
data={
|
||||
"identifier": identifier
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@default_group_required
|
||||
def edit_view(request: HttpRequest, id: str):
|
||||
# ToDo
|
||||
pass
|
||||
"""
|
||||
Renders a view for editing compensations
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "compensation/form/view.html"
|
||||
# Get object from db
|
||||
comp = get_object_or_404(Compensation, id=id)
|
||||
# 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():
|
||||
# The data form takes the geom form for processing, as well as the performing user
|
||||
comp = data_form.save(request.user, geom_form)
|
||||
messages.success(request, _("Compensation {} edited").format(comp.identifier))
|
||||
return redirect("compensation:open", id=comp.id)
|
||||
else:
|
||||
messages.error(request, FORM_INVALID)
|
||||
else:
|
||||
# For clarification: nothing in this case
|
||||
pass
|
||||
context = {
|
||||
"form": data_form,
|
||||
"geom_form": geom_form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -5,23 +5,26 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 09.08.21
|
||||
|
||||
"""
|
||||
from django.contrib import messages
|
||||
from django.db.models import Sum
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.http import HttpRequest, Http404, JsonResponse
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
|
||||
from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
|
||||
from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm
|
||||
from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
|
||||
from compensation.models import EcoAccount, EcoAccountDocument
|
||||
from compensation.tables import EcoAccountTable
|
||||
from intervention.forms import NewDeductionForm
|
||||
from intervention.forms.modalForms import NewDeductionModalForm
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required
|
||||
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm
|
||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||
from konova.utils.documents import get_document, remove_document
|
||||
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID
|
||||
from konova.utils.user_checks import in_group
|
||||
|
||||
|
||||
@ -56,15 +59,98 @@ def index_view(request: HttpRequest):
|
||||
@login_required
|
||||
@default_group_required
|
||||
def new_view(request: HttpRequest):
|
||||
# ToDo
|
||||
pass
|
||||
"""
|
||||
Renders a view for a new eco account creation
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
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))
|
||||
return redirect("compensation:acc-open", id=acc.id)
|
||||
else:
|
||||
messages.error(request, FORM_INVALID)
|
||||
else:
|
||||
# For clarification: nothing in this case
|
||||
pass
|
||||
context = {
|
||||
"form": data_form,
|
||||
"geom_form": geom_form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def new_id_view(request: HttpRequest):
|
||||
""" JSON endpoint
|
||||
|
||||
Provides fetching of free identifiers for e.g. AJAX calls
|
||||
|
||||
"""
|
||||
tmp = EcoAccount()
|
||||
identifier = tmp.generate_new_identifier()
|
||||
while EcoAccount.objects.filter(identifier=identifier).exists():
|
||||
identifier = tmp.generate_new_identifier()
|
||||
return JsonResponse(
|
||||
data={
|
||||
"identifier": identifier
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@default_group_required
|
||||
def edit_view(request: HttpRequest, id: str):
|
||||
# ToDo
|
||||
pass
|
||||
"""
|
||||
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)
|
||||
# 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":
|
||||
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
|
||||
acc = data_form.save(request.user, geom_form)
|
||||
messages.success(request, _("Eco-Account {} edited").format(acc.identifier))
|
||||
return redirect("compensation:acc-open", id=acc.id)
|
||||
else:
|
||||
messages.error(request, FORM_INVALID)
|
||||
else:
|
||||
# For clarification: nothing in this case
|
||||
pass
|
||||
context = {
|
||||
"form": data_form,
|
||||
"geom_form": geom_form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -96,7 +182,7 @@ def open_view(request: HttpRequest, id: str):
|
||||
diff_states = abs(sum_before_states - sum_after_states)
|
||||
|
||||
# Calculate rest of available surface for deductions
|
||||
available = acc.get_available_rest(as_percentage=True)
|
||||
available_total, available_relative = acc.get_available_rest()
|
||||
|
||||
deductions = acc.deductions.filter(
|
||||
intervention__deleted=None,
|
||||
@ -111,7 +197,8 @@ def open_view(request: HttpRequest, id: str):
|
||||
"sum_before_states": sum_before_states,
|
||||
"sum_after_states": sum_after_states,
|
||||
"diff_states": diff_states,
|
||||
"available": available,
|
||||
"available": available_relative,
|
||||
"available_total": available_total,
|
||||
"is_default_member": in_group(_user, DEFAULT_GROUP),
|
||||
"is_zb_member": in_group(_user, ZB_GROUP),
|
||||
"is_ets_member": in_group(_user, ETS_GROUP),
|
||||
@ -340,7 +427,7 @@ def new_deduction_view(request: HttpRequest, id: str):
|
||||
|
||||
"""
|
||||
acc = get_object_or_404(EcoAccount, id=id)
|
||||
form = NewDeductionForm(request.POST or None, instance=acc, user=request.user)
|
||||
form = NewDeductionModalForm(request.POST or None, instance=acc, user=request.user)
|
||||
return form.process_request(
|
||||
request,
|
||||
msg_success=_("Deduction added")
|
||||
|
@ -10,7 +10,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from compensation.forms import NewPaymentForm
|
||||
from compensation.forms.modalForms import NewPaymentForm
|
||||
from compensation.models import Payment
|
||||
from intervention.models import Intervention
|
||||
from konova.decorators import default_group_required
|
||||
|
159
ema/forms.py
Normal file
159
ema/forms.py
Normal file
@ -0,0 +1,159 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 06.10.21
|
||||
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import transaction
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from compensation.forms.forms import AbstractCompensationForm, CompensationResponsibleFormMixin
|
||||
from ema.models import Ema
|
||||
from intervention.models import ResponsibilityData
|
||||
from konova.forms import SimpleGeomForm
|
||||
from user.models import UserActionLogEntry, UserAction
|
||||
|
||||
|
||||
class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
|
||||
""" Form for creating new EMA objects.
|
||||
|
||||
Inherits basic form fields from AbstractCompensationForm and additional from CompensationResponsibleFormMixin.
|
||||
Second holds self.instance.response related fields
|
||||
|
||||
"""
|
||||
field_order = [
|
||||
"identifier",
|
||||
"title",
|
||||
"conservation_office",
|
||||
"conservation_file_number",
|
||||
"handler",
|
||||
"fundings",
|
||||
"comment",
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("New EMA")
|
||||
|
||||
self.action_url = reverse("ema:new")
|
||||
self.cancel_redirect = reverse("ema:index")
|
||||
|
||||
tmp = Ema()
|
||||
identifier = tmp.generate_new_identifier()
|
||||
self.initialize_form_field("identifier", identifier)
|
||||
self.fields["identifier"].widget.attrs["url"] = reverse_lazy("ema:new-id")
|
||||
self.fields["title"].widget.attrs["placeholder"] = _("Compensation XY; Location ABC")
|
||||
|
||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||
with transaction.atomic():
|
||||
# Fetch data from cleaned POST values
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
fundings = self.cleaned_data.get("fundings", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.objects.create(
|
||||
user=user,
|
||||
action=UserAction.CREATED,
|
||||
)
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
responsible = ResponsibilityData.objects.create(
|
||||
handler=handler,
|
||||
conservation_file_number=conservation_file_number,
|
||||
conservation_office=conservation_office,
|
||||
)
|
||||
|
||||
# Finally create main object
|
||||
acc = Ema.objects.create(
|
||||
identifier=identifier,
|
||||
title=title,
|
||||
responsible=responsible,
|
||||
created=action,
|
||||
geometry=geometry,
|
||||
comment=comment,
|
||||
)
|
||||
acc.fundings.set(fundings)
|
||||
|
||||
# Add the creating user to the list of shared users
|
||||
acc.users.add(user)
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
acc.log.add(action)
|
||||
return acc
|
||||
|
||||
|
||||
class EditEmaForm(NewEmaForm):
|
||||
""" Form for editing EMAs
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Edit EMA")
|
||||
|
||||
self.action_url = reverse("ema:edit", args=(self.instance.id,))
|
||||
self.cancel_redirect = reverse("ema:open", args=(self.instance.id,))
|
||||
|
||||
self.fields["identifier"].widget.attrs["url"] = reverse_lazy("ema:new-id")
|
||||
self.fields["title"].widget.attrs["placeholder"] = _("Compensation XY; Location ABC")
|
||||
|
||||
# Initialize form data
|
||||
form_data = {
|
||||
"identifier": self.instance.identifier,
|
||||
"title": self.instance.title,
|
||||
"handler": self.instance.responsible.handler,
|
||||
"conservation_office": self.instance.responsible.conservation_office,
|
||||
"conservation_file_number": self.instance.responsible.conservation_file_number,
|
||||
"fundings": self.instance.fundings.all(),
|
||||
"comment": self.instance.comment,
|
||||
}
|
||||
disabled_fields = []
|
||||
self.load_initial_data(
|
||||
form_data,
|
||||
disabled_fields
|
||||
)
|
||||
|
||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||
with transaction.atomic():
|
||||
# Fetch data from cleaned POST values
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
fundings = self.cleaned_data.get("fundings", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.objects.create(
|
||||
user=user,
|
||||
action=UserAction.EDITED,
|
||||
)
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Update responsible data
|
||||
self.instance.responsible.handler = handler
|
||||
self.instance.responsible.conservation_office = conservation_office
|
||||
self.instance.responsible.conservation_file_number = conservation_file_number
|
||||
self.instance.responsible.save()
|
||||
|
||||
# Update main oject data
|
||||
self.instance.identifier = identifier
|
||||
self.instance.title = title
|
||||
self.instance.geometry = geometry
|
||||
self.instance.comment = comment
|
||||
self.instance.modified = action
|
||||
self.instance.save()
|
||||
self.instance.fundings.set(fundings)
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
self.instance.log.add(action)
|
||||
return self.instance
|
@ -49,9 +49,9 @@ class Ema(AbstractCompensation):
|
||||
def save(self, *args, **kwargs):
|
||||
if self.identifier is None or len(self.identifier) == 0:
|
||||
# Create new identifier
|
||||
new_id = self._generate_new_identifier()
|
||||
new_id = self.generate_new_identifier()
|
||||
while Ema.objects.filter(identifier=new_id).exists():
|
||||
new_id = self._generate_new_identifier()
|
||||
new_id = self.generate_new_identifier()
|
||||
self.identifier = new_id
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
@ -6,5 +6,5 @@ Created on: 19.08.21
|
||||
|
||||
"""
|
||||
|
||||
EMA_ACCOUNT_IDENTIFIER_LENGTH = 10
|
||||
EMA_ACCOUNT_IDENTIFIER_LENGTH = 6
|
||||
EMA_ACCOUNT_IDENTIFIER_TEMPLATE = "EMA-{}"
|
@ -24,7 +24,7 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if is_default_member %}
|
||||
<a href="{% url 'home' %}" class="mr-2">
|
||||
<a href="{% url 'ema:edit' obj.id %}" class="mr-2">
|
||||
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
||||
{% fa5_icon 'edit' %}
|
||||
</button>
|
||||
|
@ -43,7 +43,7 @@
|
||||
<td class="align-middle">{{obj.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr {% if not obj.responsible.conservation_file_number %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
||||
<th scope="row">{% trans 'Conversation office file number' %}</th>
|
||||
<th scope="row">{% trans 'Conservation office file number' %}</th>
|
||||
<td class="align-middle">{{obj.responsible.conservation_file_number|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr {% if not obj.responsible.handler %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
||||
|
6
ema/templates/ema/form/view.html
Normal file
6
ema/templates/ema/form/view.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n l10n %}
|
||||
|
||||
{% block body %}
|
||||
{% include 'form/collapsable/form.html' %}
|
||||
{% endblock %}
|
@ -12,6 +12,7 @@ app_name = "ema"
|
||||
urlpatterns = [
|
||||
path("", index_view, name="index"),
|
||||
path("new/", new_view, name="new"),
|
||||
path("new/id", new_id_view, name="new-id"),
|
||||
path("<id>", open_view, name="open"),
|
||||
path('<id>/log', log_view, name='log'),
|
||||
path('<id>/edit', edit_view, name='edit'),
|
||||
|
91
ema/views.py
91
ema/views.py
@ -1,12 +1,14 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Sum
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.http import HttpRequest, JsonResponse
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import compensation
|
||||
from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
|
||||
from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
|
||||
from ema.forms import NewEmaForm, EditEmaForm
|
||||
from ema.tables import EmaTable
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import conservation_office_group_required
|
||||
@ -14,6 +16,7 @@ from ema.models import Ema, EmaDocument
|
||||
from konova.forms import RemoveModalForm, NewDocumentForm, SimpleGeomForm, RecordModalForm
|
||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||
from konova.utils.documents import get_document, remove_document
|
||||
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID
|
||||
from konova.utils.user_checks import in_group
|
||||
|
||||
|
||||
@ -47,7 +50,8 @@ def index_view(request: HttpRequest):
|
||||
@login_required
|
||||
@conservation_office_group_required
|
||||
def new_view(request: HttpRequest):
|
||||
""" Renders the form for a new EMA
|
||||
"""
|
||||
Renders a view for a new eco account creation
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
@ -55,12 +59,54 @@ def new_view(request: HttpRequest):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "generic_index.html"
|
||||
context = {}
|
||||
template = "ema/form/view.html"
|
||||
data_form = NewEmaForm(request.POST or None)
|
||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||
if request.method == "POST":
|
||||
if data_form.is_valid() and geom_form.is_valid():
|
||||
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
||||
ema = data_form.save(request.user, geom_form)
|
||||
if generated_identifier != ema.identifier:
|
||||
messages.info(
|
||||
request,
|
||||
IDENTIFIER_REPLACED.format(
|
||||
generated_identifier,
|
||||
ema.identifier
|
||||
)
|
||||
)
|
||||
messages.success(request, _("EMA {} added").format(ema.identifier))
|
||||
return redirect("ema:open", id=ema.id)
|
||||
else:
|
||||
messages.error(request, FORM_INVALID)
|
||||
else:
|
||||
# For clarification: nothing in this case
|
||||
pass
|
||||
context = {
|
||||
"form": data_form,
|
||||
"geom_form": geom_form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def new_id_view(request: HttpRequest):
|
||||
""" JSON endpoint
|
||||
|
||||
Provides fetching of free identifiers for e.g. AJAX calls
|
||||
|
||||
"""
|
||||
tmp = Ema()
|
||||
identifier = tmp.generate_new_identifier()
|
||||
while Ema.objects.filter(identifier=identifier).exists():
|
||||
identifier = tmp.generate_new_identifier()
|
||||
return JsonResponse(
|
||||
data={
|
||||
"identifier": identifier
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def open_view(request: HttpRequest, id: str):
|
||||
""" Renders the detail view of an EMA
|
||||
@ -133,7 +179,38 @@ def log_view(request: HttpRequest, id: str):
|
||||
|
||||
@login_required
|
||||
def edit_view(request: HttpRequest, id: str):
|
||||
get_object_or_404(Ema, id=id)
|
||||
"""
|
||||
Renders a view for editing compensations
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "compensation/form/view.html"
|
||||
# Get object from db
|
||||
ema = get_object_or_404(Ema, id=id)
|
||||
# Create forms, initialize with values from db/from POST request
|
||||
data_form = EditEmaForm(request.POST or None, instance=ema)
|
||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema)
|
||||
if request.method == "POST":
|
||||
if data_form.is_valid() and geom_form.is_valid():
|
||||
# The data form takes the geom form for processing, as well as the performing user
|
||||
ema = data_form.save(request.user, geom_form)
|
||||
messages.success(request, _("EMA {} edited").format(ema.identifier))
|
||||
return redirect("ema:open", id=ema.id)
|
||||
else:
|
||||
messages.error(request, FORM_INVALID)
|
||||
else:
|
||||
# For clarification: nothing in this case
|
||||
pass
|
||||
context = {
|
||||
"form": data_form,
|
||||
"geom_form": geom_form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -12,7 +12,7 @@ from django.db.models import QuerySet, Q
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from intervention.forms import DummyFilterInput
|
||||
from intervention.inputs import DummyFilterInput
|
||||
from intervention.models import Intervention
|
||||
|
||||
|
||||
|
359
intervention/forms/forms.py
Normal file
359
intervention/forms/forms.py
Normal file
@ -0,0 +1,359 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 02.12.20
|
||||
|
||||
"""
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import transaction
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_PROCESS_TYPE_ID, CODELIST_LAW_ID, \
|
||||
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID
|
||||
from intervention.inputs import GenerateInput
|
||||
from intervention.models import Intervention, LegalData, ResponsibilityData
|
||||
from konova.forms import BaseForm, SimpleGeomForm
|
||||
from user.models import UserActionLogEntry, UserAction
|
||||
|
||||
|
||||
class NewInterventionForm(BaseForm):
|
||||
identifier = forms.CharField(
|
||||
label=_("Identifier"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Generated automatically"),
|
||||
widget=GenerateInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"url": reverse_lazy("intervention:new-id"),
|
||||
}
|
||||
)
|
||||
)
|
||||
title = forms.CharField(
|
||||
label=_("Title"),
|
||||
label_suffix="",
|
||||
help_text=_("An explanatory name"),
|
||||
max_length=255,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Construction XY; Location ABC"),
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
type = forms.ModelChoiceField(
|
||||
label=_("Process type"),
|
||||
label_suffix="",
|
||||
help_text=_(""),
|
||||
required=False,
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_PROCESS_TYPE_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-process-type-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
),
|
||||
)
|
||||
laws = forms.ModelMultipleChoiceField(
|
||||
label=_("Law"),
|
||||
label_suffix="",
|
||||
help_text=_("Multiple selection possible"),
|
||||
required=False,
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_LAW_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2Multiple(
|
||||
url="codes-law-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
),
|
||||
)
|
||||
registration_office = forms.ModelChoiceField(
|
||||
label=_("Registration office"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_REGISTRATION_OFFICE_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-registration-office-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
),
|
||||
)
|
||||
conservation_office = forms.ModelChoiceField(
|
||||
label=_("Conservation office"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_CONSERVATION_OFFICE_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-conservation-office-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
),
|
||||
)
|
||||
registration_file_number = forms.CharField(
|
||||
label=_("Registration office file number"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("ZB-123/ABC.456"),
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
conservation_file_number = forms.CharField(
|
||||
label=_("Conservation office file number"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("ETS-123/ABC.456"),
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
handler = forms.CharField(
|
||||
label=_("Intervention handler"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
required=False,
|
||||
help_text=_("Who performs the intervention"),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Company Mustermann"),
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
registration_date = forms.DateField(
|
||||
label=_("Registration date"),
|
||||
label_suffix=_(""),
|
||||
required=False,
|
||||
widget=forms.DateInput(
|
||||
attrs={
|
||||
"type": "date",
|
||||
"class": "form-control",
|
||||
},
|
||||
format="%d.%m.%Y"
|
||||
)
|
||||
)
|
||||
binding_date = forms.DateField(
|
||||
label=_("Binding on"),
|
||||
label_suffix=_(""),
|
||||
required=False,
|
||||
widget=forms.DateInput(
|
||||
attrs={
|
||||
"type": "date",
|
||||
"class": "form-control",
|
||||
},
|
||||
format="%d.%m.%Y"
|
||||
)
|
||||
)
|
||||
comment = forms.CharField(
|
||||
label_suffix="",
|
||||
label=_("Comment"),
|
||||
required=False,
|
||||
help_text=_("Additional comment"),
|
||||
widget=forms.Textarea(
|
||||
attrs={
|
||||
"rows": 5,
|
||||
"class": "form-control"
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("New intervention")
|
||||
self.action_url = reverse("intervention:new")
|
||||
self.cancel_redirect = reverse("intervention:index")
|
||||
|
||||
tmp_intervention = Intervention()
|
||||
identifier = tmp_intervention.generate_new_identifier()
|
||||
self.initialize_form_field("identifier", identifier)
|
||||
|
||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||
with transaction.atomic():
|
||||
# Fetch data from cleaned POST values
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
_type = self.cleaned_data.get("type", None)
|
||||
laws = self.cleaned_data.get("laws", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
registration_office = self.cleaned_data.get("registration_office", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
|
||||
registration_file_number = self.cleaned_data.get("registration_file_number", None)
|
||||
binding_date = self.cleaned_data.get("binding_date", None)
|
||||
registration_date = self.cleaned_data.get("registration_date", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.objects.create(
|
||||
user=user,
|
||||
action=UserAction.CREATED,
|
||||
)
|
||||
|
||||
# Create legal data object (without M2M laws first)
|
||||
legal_data = LegalData.objects.create(
|
||||
registration_date=registration_date,
|
||||
binding_date=binding_date,
|
||||
process_type=_type,
|
||||
)
|
||||
# Then add the M2M laws to the object
|
||||
legal_data.laws.set(laws)
|
||||
|
||||
# Create responsible data object
|
||||
responsibility_data = ResponsibilityData.objects.create(
|
||||
registration_office=registration_office,
|
||||
conservation_office=conservation_office,
|
||||
registration_file_number=registration_file_number,
|
||||
conservation_file_number=conservation_file_number,
|
||||
handler=handler,
|
||||
)
|
||||
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Finally create main object, holding the other objects
|
||||
intervention = Intervention.objects.create(
|
||||
identifier=identifier,
|
||||
title=title,
|
||||
responsible=responsibility_data,
|
||||
legal=legal_data,
|
||||
created=action,
|
||||
geometry=geometry,
|
||||
comment=comment,
|
||||
)
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
intervention.log.add(action)
|
||||
|
||||
# Add the performing user as the first user having access to the data
|
||||
intervention.users.add(user)
|
||||
return intervention
|
||||
|
||||
|
||||
class EditInterventionForm(NewInterventionForm):
|
||||
""" Subclasses NewInterventionForm
|
||||
|
||||
Simply adds initializing of a provided self.instance object into the form fields
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.instance is not None:
|
||||
self.action_url = reverse("intervention:edit", args=(self.instance.id,))
|
||||
self.cancel_redirect = reverse("intervention:open", args=(self.instance.id,))
|
||||
self.form_title = _("Edit intervention")
|
||||
self.form_caption = ""
|
||||
|
||||
reg_date = self.instance.legal.registration_date
|
||||
bind_date = self.instance.legal.binding_date
|
||||
if reg_date is not None:
|
||||
reg_date = reg_date.isoformat()
|
||||
if bind_date is not None:
|
||||
bind_date = bind_date.isoformat()
|
||||
|
||||
# Initialize form data
|
||||
form_data = {
|
||||
"identifier": self.instance.identifier,
|
||||
"title": self.instance.title,
|
||||
"type": self.instance.legal.process_type,
|
||||
"laws": list(self.instance.legal.laws.values_list("id", flat=True)),
|
||||
"handler": self.instance.responsible.handler,
|
||||
"registration_office": self.instance.responsible.registration_office,
|
||||
"registration_file_number": self.instance.responsible.registration_file_number,
|
||||
"conservation_office": self.instance.responsible.conservation_office,
|
||||
"conservation_file_number": self.instance.responsible.conservation_file_number,
|
||||
"registration_date": reg_date,
|
||||
"binding_date": bind_date,
|
||||
"comment": self.instance.comment,
|
||||
}
|
||||
disabled_fields = []
|
||||
self.load_initial_data(
|
||||
form_data,
|
||||
disabled_fields
|
||||
)
|
||||
|
||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||
""" Overwrite instance with new form data
|
||||
|
||||
Args:
|
||||
user ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
with transaction.atomic():
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
process_type = self.cleaned_data.get("type", None)
|
||||
laws = self.cleaned_data.get("laws", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
registration_office = self.cleaned_data.get("registration_office", None)
|
||||
registration_file_number = self.cleaned_data.get("registration_file_number", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
|
||||
registration_date = self.cleaned_data.get("registration_date", None)
|
||||
binding_date = self.cleaned_data.get("binding_date", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
self.instance.legal.process_type = process_type
|
||||
self.instance.legal.registration_date = registration_date
|
||||
self.instance.legal.binding_date = binding_date
|
||||
self.instance.legal.laws.set(laws)
|
||||
self.instance.legal.save()
|
||||
|
||||
self.instance.responsible.handler = handler
|
||||
self.instance.responsible.registration_office = registration_office
|
||||
self.instance.responsible.registration_file_number = registration_file_number
|
||||
self.instance.responsible.conservation_office = conservation_office
|
||||
self.instance.responsible.conservation_file_number = conservation_file_number
|
||||
self.instance.responsible.save()
|
||||
|
||||
user_action = UserActionLogEntry.objects.create(
|
||||
user=user,
|
||||
timestamp=timezone.now(),
|
||||
action=UserAction.EDITED,
|
||||
)
|
||||
|
||||
geometry = geom_form.save(user_action)
|
||||
self.instance.geometry = geometry
|
||||
self.instance.geometry.save()
|
||||
|
||||
self.instance.log.add(user_action)
|
||||
|
||||
self.instance.identifier = identifier
|
||||
self.instance.title = title
|
||||
self.instance.comment = comment
|
||||
self.instance.modified = user_action
|
||||
self.instance.save()
|
||||
|
||||
return self.instance
|
||||
|
@ -2,244 +2,28 @@
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 02.12.20
|
||||
Created on: 27.09.21
|
||||
|
||||
"""
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.gis import forms as gis_forms
|
||||
from django.contrib.gis.geos import Polygon
|
||||
from django.db import transaction
|
||||
from django import forms
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.models import EcoAccountDeduction, EcoAccount
|
||||
from intervention.models import Intervention, Revocation, RevocationDocument
|
||||
from konova.forms import BaseForm, BaseModalForm
|
||||
from konova.settings import DEFAULT_LAT, DEFAULT_LON, DEFAULT_ZOOM, ZB_GROUP, ETS_GROUP
|
||||
from compensation.models import EcoAccount, EcoAccountDeduction
|
||||
from intervention.inputs import TextToClipboardInput
|
||||
from intervention.models import Revocation, RevocationDocument, Intervention
|
||||
from konova.forms import BaseModalForm
|
||||
from konova.settings import ZB_GROUP, ETS_GROUP
|
||||
from konova.utils.general import format_german_float
|
||||
from konova.utils.messenger import Messenger
|
||||
from konova.utils.user_checks import in_group
|
||||
from organisation.models import Organisation
|
||||
from user.models import UserActionLogEntry, UserAction
|
||||
|
||||
|
||||
class NewInterventionForm(BaseForm):
|
||||
identifier = forms.CharField(
|
||||
label=_("Identifier"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Generated automatically if none was given"),
|
||||
required=False,
|
||||
)
|
||||
title = forms.CharField(
|
||||
label=_("Title"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
)
|
||||
type = forms.CharField(
|
||||
label=_("Type"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Which intervention type is this"),
|
||||
)
|
||||
law = forms.CharField(
|
||||
label=_("Law"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Based on which law"),
|
||||
)
|
||||
handler = forms.CharField(
|
||||
label=_("Intervention handler"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Who performs the intervention"),
|
||||
)
|
||||
data_provider = forms.ModelChoiceField(
|
||||
label=_("Data provider"),
|
||||
label_suffix="",
|
||||
help_text=_("Who provides the data for the intervention"),
|
||||
queryset=Organisation.objects.all(),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="other-orgs-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Organization"),
|
||||
"data-minimum-input-length": 3,
|
||||
}
|
||||
),
|
||||
)
|
||||
data_provider_detail = forms.CharField(
|
||||
label=_("Data provider details"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
help_text=_("Further details"),
|
||||
required=False,
|
||||
)
|
||||
geometry = gis_forms.MultiPolygonField(
|
||||
widget=gis_forms.OSMWidget(
|
||||
attrs={
|
||||
"default_lat": DEFAULT_LAT,
|
||||
"default_lon": DEFAULT_LON,
|
||||
"default_zoom": DEFAULT_ZOOM,
|
||||
'map_width': 800,
|
||||
'map_height': 500
|
||||
},
|
||||
),
|
||||
label=_("Map"),
|
||||
label_suffix="",
|
||||
help_text=_("Where does the intervention take place")
|
||||
)
|
||||
documents = forms.FileField(
|
||||
widget=forms.ClearableFileInput(
|
||||
attrs={
|
||||
"multiple": True,
|
||||
}
|
||||
),
|
||||
label=_("Files"),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("New intervention")
|
||||
self.action_url = reverse("intervention:new")
|
||||
self.cancel_redirect = reverse("intervention:index")
|
||||
|
||||
def save(self, user: User):
|
||||
with transaction.atomic():
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
_type = self.cleaned_data.get("type", None)
|
||||
law = self.cleaned_data.get("law", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
data_provider = self.cleaned_data.get("data_provider", None)
|
||||
data_provider_detail = self.cleaned_data.get("data_provider_detail", None)
|
||||
geometry = self.cleaned_data.get("geometry", Polygon())
|
||||
documents = self.cleaned_data.get("documents", []) or []
|
||||
|
||||
action = UserActionLogEntry.objects.create(
|
||||
user=user,
|
||||
action=UserAction.CREATED,
|
||||
)
|
||||
intervention = Intervention(
|
||||
identifier=identifier,
|
||||
title=title,
|
||||
type=_type,
|
||||
law=law,
|
||||
handler=handler,
|
||||
data_provider=data_provider,
|
||||
data_provider_detail=data_provider_detail,
|
||||
geometry=geometry,
|
||||
created=action,
|
||||
)
|
||||
intervention.save()
|
||||
intervention.log.add(action)
|
||||
return intervention
|
||||
|
||||
|
||||
class EditInterventionForm(NewInterventionForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.instance is not None:
|
||||
self.action_url = reverse("intervention:edit", args=(self.instance.id,))
|
||||
self.cancel_redirect = reverse("intervention:index")
|
||||
self.form_title = _("Edit intervention")
|
||||
self.form_caption = ""
|
||||
|
||||
# Initialize form data
|
||||
form_data = {
|
||||
"identifier": self.instance.identifier,
|
||||
"title": self.instance.title,
|
||||
"type": self.instance.type,
|
||||
"law": self.instance.law,
|
||||
"handler": self.instance.handler,
|
||||
"data_provider": self.instance.data_provider,
|
||||
"data_provider_detail": self.instance.data_provider_detail,
|
||||
"geometry": self.instance.geometry,
|
||||
"documents": self.instance.documents.all(),
|
||||
}
|
||||
disabled_fields = [
|
||||
"identifier",
|
||||
]
|
||||
self.load_initial_data(
|
||||
form_data,
|
||||
disabled_fields,
|
||||
)
|
||||
|
||||
def save(self, user: User):
|
||||
""" Overwrite instance with new form data
|
||||
|
||||
Args:
|
||||
user ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
with transaction.atomic():
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
_type = self.cleaned_data.get("type", None)
|
||||
law = self.cleaned_data.get("law", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
data_provider = self.cleaned_data.get("data_provider", None)
|
||||
data_provider_detail = self.cleaned_data.get("data_provider_detail", None)
|
||||
geometry = self.cleaned_data.get("geometry", Polygon())
|
||||
documents = self.cleaned_data.get("documents", []) or []
|
||||
|
||||
self.instance.identifier = identifier
|
||||
self.instance.title = title
|
||||
self.instance.type = _type
|
||||
self.instance.law = law
|
||||
self.instance.handler = handler
|
||||
self.instance.data_provider = data_provider
|
||||
self.instance.data_provider_detail = data_provider_detail
|
||||
self.instance.geometry = geometry
|
||||
self.instance.save()
|
||||
|
||||
user_action = UserActionLogEntry.objects.create(
|
||||
user=self.user,
|
||||
action=UserAction.EDITED
|
||||
)
|
||||
self.instance.log.add(user_action)
|
||||
self.instance.modified = user_action
|
||||
self.instance.save()
|
||||
|
||||
return self.instance
|
||||
|
||||
|
||||
class OpenInterventionForm(EditInterventionForm):
|
||||
"""
|
||||
This form is not intended to be used as data-input form. It's used to simplify the rendering of intervention:open
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Resize map
|
||||
self.fields["geometry"].widget.attrs["map_width"] = 500
|
||||
self.fields["geometry"].widget.attrs["map_height"] = 300
|
||||
|
||||
# Disable all form fields
|
||||
for field in self.fields:
|
||||
self.disable_form_field(field)
|
||||
|
||||
|
||||
class DummyFilterInput(forms.HiddenInput):
|
||||
""" A dummy input widget
|
||||
|
||||
Does not render anything. Can be used to keep filter logic using django_filter without having a pre defined
|
||||
filter widget being rendered to the template.
|
||||
|
||||
"""
|
||||
template_name = "konova/custom_widgets/dummy-filter-input.html"
|
||||
|
||||
|
||||
class TextToClipboardInput(forms.TextInput):
|
||||
template_name = "konova/custom_widgets/text-to-clipboard-input.html"
|
||||
|
||||
|
||||
class ShareInterventionForm(BaseModalForm):
|
||||
class ShareInterventionModalForm(BaseModalForm):
|
||||
url = forms.CharField(
|
||||
label=_("Share link"),
|
||||
label_suffix="",
|
||||
@ -247,7 +31,8 @@ class ShareInterventionForm(BaseModalForm):
|
||||
required=False,
|
||||
widget=TextToClipboardInput(
|
||||
attrs={
|
||||
"readonly": True
|
||||
"readonly": True,
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -316,7 +101,7 @@ class ShareInterventionForm(BaseModalForm):
|
||||
self.instance.users.set(accessing_users)
|
||||
|
||||
|
||||
class NewRevocationForm(BaseModalForm):
|
||||
class NewRevocationModalForm(BaseModalForm):
|
||||
date = forms.DateField(
|
||||
label=_("Date"),
|
||||
label_suffix=_(""),
|
||||
@ -325,6 +110,7 @@ class NewRevocationForm(BaseModalForm):
|
||||
attrs={
|
||||
"type": "date",
|
||||
"data-provide": "datepicker",
|
||||
"class": "form-control",
|
||||
},
|
||||
format="%d.%m.%Y"
|
||||
)
|
||||
@ -336,7 +122,7 @@ class NewRevocationForm(BaseModalForm):
|
||||
help_text=_("Must be smaller than 15 Mb"),
|
||||
widget=forms.FileInput(
|
||||
attrs={
|
||||
"class": "w-75"
|
||||
"class": "form-control-file"
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -350,6 +136,7 @@ class NewRevocationForm(BaseModalForm):
|
||||
attrs={
|
||||
"cols": 30,
|
||||
"rows": 5,
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -394,7 +181,7 @@ class NewRevocationForm(BaseModalForm):
|
||||
return revocation
|
||||
|
||||
|
||||
class RunCheckForm(BaseModalForm):
|
||||
class RunCheckModalForm(BaseModalForm):
|
||||
checked_intervention = forms.BooleanField(
|
||||
label=_("Checked intervention data"),
|
||||
label_suffix="",
|
||||
@ -458,7 +245,7 @@ class RunCheckForm(BaseModalForm):
|
||||
)
|
||||
|
||||
|
||||
class NewDeductionForm(BaseModalForm):
|
||||
class NewDeductionModalForm(BaseModalForm):
|
||||
""" Form for creating new deduction
|
||||
|
||||
Can be used for Intervention view as well as for EcoAccount views.
|
||||
@ -487,6 +274,12 @@ class NewDeductionForm(BaseModalForm):
|
||||
label=_("Surface"),
|
||||
label_suffix="",
|
||||
help_text=_("in m²"),
|
||||
widget=forms.NumberInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "0,00",
|
||||
}
|
||||
)
|
||||
)
|
||||
intervention = forms.ModelChoiceField(
|
||||
label=_("Intervention"),
|
||||
@ -508,9 +301,6 @@ class NewDeductionForm(BaseModalForm):
|
||||
self.form_caption = _("Enter the information for a new deduction from a chosen eco-account")
|
||||
self.is_intervention_initially = False
|
||||
|
||||
# Add a placeholder for field 'surface' without having to define the whole widget above
|
||||
self.add_placeholder_for_field("surface", "0,00")
|
||||
|
||||
# Check for Intervention or EcoAccount
|
||||
if isinstance(self.instance, Intervention):
|
||||
# Form has been called with a given intervention
|
32
intervention/inputs.py
Normal file
32
intervention/inputs.py
Normal file
@ -0,0 +1,32 @@
|
||||
from django import forms
|
||||
|
||||
|
||||
class DummyFilterInput(forms.HiddenInput):
|
||||
""" A dummy input widget
|
||||
|
||||
Does not render anything. Can be used to keep filter logic using django_filter without having a pre defined
|
||||
filter widget being rendered to the template.
|
||||
|
||||
"""
|
||||
template_name = "konova/widgets/empty-dummy-input.html"
|
||||
|
||||
|
||||
class TextToClipboardInput(forms.TextInput):
|
||||
template_name = "konova/widgets/text-to-clipboard-input.html"
|
||||
|
||||
|
||||
class GenerateInput(forms.TextInput):
|
||||
"""
|
||||
|
||||
Provides a form group with a button at the end, which generates new content for the input.
|
||||
The url used to fetch new content can be added using the attrs like
|
||||
|
||||
widget=GenerateInput(
|
||||
attrs={
|
||||
"url": reverse_lazy("app_name:view_name")
|
||||
...
|
||||
}
|
||||
)
|
||||
|
||||
"""
|
||||
template_name = "konova/widgets/generate-content-input.html"
|
@ -260,14 +260,41 @@ class Intervention(BaseObject):
|
||||
self.save()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
""" Custom save functionality
|
||||
|
||||
Performs some pre-save checks:
|
||||
1. Checking for existing identifiers
|
||||
|
||||
Args:
|
||||
*args ():
|
||||
**kwargs ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if self.identifier is None or len(self.identifier) == 0:
|
||||
# Create new identifier
|
||||
new_id = self._generate_new_identifier()
|
||||
while Intervention.objects.filter(identifier=new_id).exists():
|
||||
new_id = self._generate_new_identifier()
|
||||
self.identifier = new_id
|
||||
# No identifier given
|
||||
self.identifier = self.generate_new_identifier()
|
||||
|
||||
# Before saving, make sure the set identifier is not used, yet
|
||||
while Intervention.objects.filter(identifier=self.identifier).exists():
|
||||
self.identifier = self.generate_new_identifier()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
to_delete = [
|
||||
self.legal,
|
||||
self.responsible,
|
||||
self.geometry,
|
||||
self.log.all()
|
||||
]
|
||||
for entry in to_delete:
|
||||
try:
|
||||
entry.delete()
|
||||
except AttributeError:
|
||||
pass
|
||||
super().delete(using, keep_parents)
|
||||
|
||||
def quality_check(self) -> list:
|
||||
""" Quality check
|
||||
|
||||
@ -298,7 +325,7 @@ class Intervention(BaseObject):
|
||||
ret_msgs.append(_("Registration office file number missing"))
|
||||
|
||||
if not self.responsible.conservation_file_number or len(self.responsible.conservation_file_number) == 0:
|
||||
ret_msgs.append(_("Conversation office file number missing"))
|
||||
ret_msgs.append(_("Conservation office file number missing"))
|
||||
except AttributeError:
|
||||
# responsible data not found
|
||||
ret_msgs.append(_("Responsible data missing"))
|
||||
|
@ -5,5 +5,5 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 30.11.20
|
||||
|
||||
"""
|
||||
INTERVENTION_IDENTIFIER_LENGTH = 10
|
||||
INTERVENTION_IDENTIFIER_LENGTH = 6
|
||||
INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}"
|
@ -0,0 +1,23 @@
|
||||
{% load i18n fontawesome_5 %}
|
||||
|
||||
{% if intervention.comment %}
|
||||
<div class="w-100">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header rlp-gd">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<h5 class="card-title">
|
||||
{% fa5_icon 'info-circle' %}
|
||||
{% trans 'Comment' %}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-text font-italic">
|
||||
{{intervention.comment}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
@ -11,7 +11,7 @@
|
||||
<div class="col-sm-6">
|
||||
<div class="d-flex justify-content-end">
|
||||
{% if is_default_member and has_access %}
|
||||
<a href="{% url 'compensation:new' %}" title="{% trans 'Add new compensation' %}">
|
||||
<a href="{% url 'compensation:new' intervention.id %}" title="{% trans 'Add new compensation' %}">
|
||||
<button class="btn btn-outline-default">
|
||||
{% fa5_icon 'plus' %}
|
||||
{% fa5_icon 'leaf' %}
|
||||
|
@ -32,7 +32,7 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if is_default_member %}
|
||||
<a href="{% url 'home' %}" class="mr-2">
|
||||
<a href="{% url 'intervention:edit' intervention.id %}" class="mr-2">
|
||||
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
||||
{% fa5_icon 'edit' %}
|
||||
</button>
|
||||
|
@ -56,7 +56,7 @@
|
||||
<td class="align-middle">{{intervention.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr {% if not intervention.responsible.conservation_file_number %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
||||
<th scope="row">{% trans 'Conversation office file number' %}</th>
|
||||
<th scope="row">{% trans 'Conservation office file number' %}</th>
|
||||
<td class="align-middle">{{intervention.responsible.conservation_file_number|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr {% if not intervention.responsible.handler %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
||||
@ -123,7 +123,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
{% include 'map/geom_form.html' %}
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'intervention/detail/includes/comment.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
6
intervention/templates/intervention/form/view.html
Normal file
6
intervention/templates/intervention/form/view.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n l10n %}
|
||||
|
||||
{% block body %}
|
||||
{% include 'form/collapsable/form.html' %}
|
||||
{% endblock %}
|
@ -9,12 +9,13 @@ from django.urls import path
|
||||
|
||||
from intervention.views import index_view, new_view, open_view, edit_view, remove_view, new_document_view, share_view, \
|
||||
create_share_view, remove_revocation_view, new_revocation_view, run_check_view, log_view, new_deduction_view, \
|
||||
record_view, remove_document_view, get_document_view, get_revocation_view
|
||||
record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view
|
||||
|
||||
app_name = "intervention"
|
||||
urlpatterns = [
|
||||
path("", index_view, name="index"),
|
||||
path('new/', new_view, name='new'),
|
||||
path('new/id', new_id_view, name='new-id'),
|
||||
path('<id>', open_view, name='open'),
|
||||
path('<id>/log', log_view, name='log'),
|
||||
path('<id>/edit', edit_view, name='edit'),
|
||||
|
@ -1,11 +1,11 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.http import HttpRequest
|
||||
from django.http import HttpRequest, JsonResponse
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
|
||||
from intervention.forms import NewInterventionForm, EditInterventionForm, ShareInterventionForm, NewRevocationForm, \
|
||||
RunCheckForm, NewDeductionForm
|
||||
from intervention.forms.forms import NewInterventionForm, EditInterventionForm
|
||||
from intervention.forms.modalForms import ShareInterventionModalForm, NewRevocationModalForm, \
|
||||
RunCheckModalForm, NewDeductionModalForm
|
||||
from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
|
||||
from intervention.tables import InterventionTable
|
||||
from konova.contexts import BaseContext
|
||||
@ -13,7 +13,7 @@ from konova.decorators import *
|
||||
from konova.forms import SimpleGeomForm, NewDocumentForm, RemoveModalForm, RecordModalForm
|
||||
from konova.sub_settings.django_settings import DEFAULT_DATE_FORMAT
|
||||
from konova.utils.documents import remove_document, get_document
|
||||
from konova.utils.message_templates import FORM_INVALID, INTERVENTION_INVALID
|
||||
from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED
|
||||
from konova.utils.user_checks import in_group
|
||||
|
||||
|
||||
@ -58,25 +58,54 @@ def new_view(request: HttpRequest):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "konova/form.html"
|
||||
form = NewInterventionForm(request.POST or None)
|
||||
template = "intervention/form/view.html"
|
||||
data_form = NewInterventionForm(request.POST or None)
|
||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||
if request.method == "POST":
|
||||
if form.is_valid():
|
||||
intervention = form.save(request.user)
|
||||
messages.success(request, _("Intervention {} added").format(intervention.title))
|
||||
return redirect("intervention:index")
|
||||
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))
|
||||
return redirect("intervention:open", id=intervention.id)
|
||||
else:
|
||||
messages.error(request, _("Invalid input"))
|
||||
messages.error(request, FORM_INVALID)
|
||||
else:
|
||||
# For clarification: nothing in this case
|
||||
pass
|
||||
context = {
|
||||
"form": form,
|
||||
"form": data_form,
|
||||
"geom_form": geom_form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def new_id_view(request: HttpRequest):
|
||||
""" JSON endpoint
|
||||
|
||||
Provides fetching of free identifiers for e.g. AJAX calls
|
||||
|
||||
"""
|
||||
tmp_intervention = Intervention()
|
||||
identifier = tmp_intervention.generate_new_identifier()
|
||||
while Intervention.objects.filter(identifier=identifier).exists():
|
||||
identifier = tmp_intervention.generate_new_identifier()
|
||||
return JsonResponse(
|
||||
data={
|
||||
"identifier": identifier
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def new_document_view(request: HttpRequest, id: str):
|
||||
""" Renders a form for uploading new documents
|
||||
@ -212,19 +241,26 @@ def edit_view(request: HttpRequest, id: str):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
template = "konova/form.html"
|
||||
template = "intervention/form/view.html"
|
||||
# Get object from db
|
||||
intervention = get_object_or_404(Intervention, 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 request.method == "POST":
|
||||
form = EditInterventionForm(request.POST or None, instance=intervention)
|
||||
if form.is_valid():
|
||||
intervention = form.save(request.user)
|
||||
messages.success(request, _("{} edited").format(intervention))
|
||||
return redirect("intervention:index")
|
||||
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
|
||||
intervention = data_form.save(request.user, geom_form)
|
||||
messages.success(request, _("Intervention {} edited").format(intervention.identifier))
|
||||
return redirect("intervention:open", id=intervention.id)
|
||||
else:
|
||||
messages.error(request, _("Invalid input"))
|
||||
form = EditInterventionForm(instance=intervention)
|
||||
messages.error(request, FORM_INVALID)
|
||||
else:
|
||||
# For clarification: nothing in this case
|
||||
pass
|
||||
context = {
|
||||
"form": form,
|
||||
"form": data_form,
|
||||
"geom_form": geom_form,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
@ -324,7 +360,7 @@ def create_share_view(request: HttpRequest, id: str):
|
||||
|
||||
"""
|
||||
intervention = get_object_or_404(Intervention, id=id)
|
||||
form = ShareInterventionForm(request.POST or None, instance=intervention, request=request)
|
||||
form = ShareInterventionModalForm(request.POST or None, instance=intervention, request=request)
|
||||
return form.process_request(
|
||||
request,
|
||||
msg_success=_("Share settings updated")
|
||||
@ -343,7 +379,7 @@ def run_check_view(request: HttpRequest, id: str):
|
||||
|
||||
"""
|
||||
intervention = get_object_or_404(Intervention, id=id)
|
||||
form = RunCheckForm(request.POST or None, instance=intervention, user=request.user)
|
||||
form = RunCheckModalForm(request.POST or None, instance=intervention, user=request.user)
|
||||
return form.process_request(
|
||||
request,
|
||||
msg_success=_("Check performed"),
|
||||
@ -363,7 +399,7 @@ def new_revocation_view(request: HttpRequest, id: str):
|
||||
|
||||
"""
|
||||
intervention = get_object_or_404(Intervention, id=id)
|
||||
form = NewRevocationForm(request.POST or None, request.FILES or None, instance=intervention, user=request.user)
|
||||
form = NewRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, user=request.user)
|
||||
return form.process_request(
|
||||
request,
|
||||
msg_success=_("Revocation added")
|
||||
@ -407,7 +443,7 @@ def new_deduction_view(request: HttpRequest, id: str):
|
||||
|
||||
"""
|
||||
intervention = get_object_or_404(Intervention, id=id)
|
||||
form = NewDeductionForm(request.POST or None, instance=intervention, user=request.user)
|
||||
form = NewDeductionModalForm(request.POST or None, instance=intervention, user=request.user)
|
||||
return form.process_request(
|
||||
request,
|
||||
msg_success=_("Deduction added")
|
||||
|
@ -10,7 +10,8 @@ from django.db.models import Q
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, \
|
||||
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_PROCESS_TYPE_ID
|
||||
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_PROCESS_TYPE_ID, \
|
||||
CODELIST_COMPENSATION_FUNDING_ID
|
||||
from compensation.models import EcoAccount
|
||||
from intervention.models import Intervention
|
||||
from organisation.models import Organisation
|
||||
@ -45,12 +46,18 @@ class NonOfficialOrganisationAutocomplete(Select2QuerySetView):
|
||||
|
||||
|
||||
class EcoAccountAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for ecoAccount entries
|
||||
|
||||
Only returns entries that are accessible for the requesting user and already are recorded
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return EcoAccount.objects.none()
|
||||
qs = EcoAccount.objects.filter(
|
||||
deleted=None,
|
||||
recorded__isnull=False,
|
||||
users__in=[self.request.user],
|
||||
)
|
||||
if self.q:
|
||||
qs = qs.filter(
|
||||
@ -63,6 +70,11 @@ class EcoAccountAutocomplete(Select2QuerySetView):
|
||||
|
||||
|
||||
class InterventionAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for intervention entries
|
||||
|
||||
Only returns entries that are accessible for the requesting user
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return Intervention.objects.none()
|
||||
@ -104,10 +116,18 @@ class KonovaCodeAutocomplete(Select2QuerySetView):
|
||||
code_lists__in=[self.c]
|
||||
)
|
||||
if self.q:
|
||||
q_or = Q()
|
||||
q_or |= Q(long_name__icontains=self.q)
|
||||
q_or |= Q(short_name__icontains=self.q)
|
||||
qs = qs.filter(q_or)
|
||||
# Remove whitespaces from self.q and split input in all keywords (if multiple given)
|
||||
q = dict.fromkeys(self.q.strip().split(" "))
|
||||
# Create one filter looking up for all keys where all keywords can be found in the same result
|
||||
_filter = Q()
|
||||
for keyword in q:
|
||||
q_or = Q()
|
||||
q_or |= Q(long_name__icontains=keyword)
|
||||
q_or |= Q(short_name__icontains=keyword)
|
||||
q_or |= Q(parent__long_name__icontains=keyword)
|
||||
q_or |= Q(parent__short_name__icontains=keyword)
|
||||
_filter.add(q_or, Q.AND)
|
||||
qs = qs.filter(_filter).distinct()
|
||||
return qs
|
||||
|
||||
|
||||
@ -120,6 +140,15 @@ class CompensationActionCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class CompensationFundingCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_COMPENSATION_FUNDING_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class BiotopeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
|
@ -9,11 +9,12 @@ Created on: 16.11.20
|
||||
from functools import wraps
|
||||
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from konova.settings import DEFAULT_GROUP, ETS_GROUP, ZB_GROUP
|
||||
from konova.utils.message_templates import MISSING_GROUP_PERMISSION, DATA_UNSHARED
|
||||
|
||||
|
||||
def staff_required(function):
|
||||
@ -80,7 +81,7 @@ def default_group_required(function):
|
||||
if has_group:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
messages.info(request, _("You need to be part of another user group."))
|
||||
messages.info(request, MISSING_GROUP_PERMISSION)
|
||||
return redirect(request.META.get("HTTP_REFERER", reverse("home")))
|
||||
return wrap
|
||||
|
||||
@ -100,7 +101,7 @@ def registration_office_group_required(function):
|
||||
if has_group:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
messages.info(request, _("You need to be part of another user group."))
|
||||
messages.info(request, MISSING_GROUP_PERMISSION)
|
||||
return redirect(request.META.get("HTTP_REFERER", reverse("home")))
|
||||
return wrap
|
||||
|
||||
@ -120,6 +121,35 @@ def conservation_office_group_required(function):
|
||||
if has_group:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
messages.info(request, _("You need to be part of another user group."))
|
||||
messages.info(request, MISSING_GROUP_PERMISSION)
|
||||
return redirect(request.META.get("HTTP_REFERER", reverse("home")))
|
||||
return wrap
|
||||
return wrap
|
||||
|
||||
|
||||
def shared_access_required(obj_class, id_key):
|
||||
""" Checks whether the data is shared with the requesting user
|
||||
|
||||
Args:
|
||||
obj_class (Model): The object/model class
|
||||
id_key (str): The name of the identifier attribute in **kwargs
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
def decorator(function):
|
||||
@wraps(function)
|
||||
def wrap(request, *args, **kwargs):
|
||||
user = request.user
|
||||
_id = kwargs.get(id_key, None)
|
||||
if _id is not None:
|
||||
obj = get_object_or_404(obj_class, id=_id)
|
||||
is_shared = obj.is_shared_with(user)
|
||||
if not is_shared:
|
||||
messages.info(
|
||||
request,
|
||||
DATA_UNSHARED
|
||||
)
|
||||
return redirect("home")
|
||||
return function(request, *args, **kwargs)
|
||||
return wrap
|
||||
return decorator
|
@ -13,8 +13,8 @@ from bootstrap_modal_forms.utils import is_ajax
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.gis.forms import GeometryField, OSMWidget
|
||||
from django.contrib.gis.geos import Polygon
|
||||
from django.contrib.gis.forms import OSMWidget, MultiPolygonField
|
||||
from django.contrib.gis.geos import Polygon, MultiPolygon
|
||||
from django.db import transaction
|
||||
from django.http import HttpRequest, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
@ -25,7 +25,8 @@ from compensation.models import EcoAccount, Compensation, EcoAccountDocument, Co
|
||||
from ema.models import Ema, EmaDocument
|
||||
from intervention.models import Intervention, Revocation, RevocationDocument, InterventionDocument
|
||||
from konova.contexts import BaseContext
|
||||
from konova.models import BaseObject
|
||||
from konova.models import BaseObject, Geometry
|
||||
from konova.settings import DEFAULT_SRID
|
||||
from konova.utils.message_templates import FORM_INVALID
|
||||
from user.models import UserActionLogEntry, UserAction
|
||||
|
||||
@ -75,7 +76,7 @@ class BaseForm(forms.Form):
|
||||
|
||||
def add_placeholder_for_field(self, field: str, val):
|
||||
"""
|
||||
Adds a placeholder to a field after initialization
|
||||
Adds a placeholder to a field after initialization without the need to redefine the form widget
|
||||
|
||||
Args:
|
||||
field (str): Field name
|
||||
@ -184,16 +185,10 @@ class BaseModalForm(BaseForm, BSModalForm):
|
||||
"""
|
||||
is_modal_form = True
|
||||
render_submit = True
|
||||
full_width_fields = False
|
||||
template = "modal/modal_form.html"
|
||||
|
||||
def __init__(self, full_width_fields: bool = True, *args, **kwargs):
|
||||
self.full_width_fields = full_width_fields
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.full_width_fields:
|
||||
# Automatically add bootstrap w-100 class for maximum width of form fields in modals
|
||||
for key, val in self.fields.items():
|
||||
self.add_widget_html_class(key, "w-100")
|
||||
|
||||
def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
|
||||
""" Generic processing of request
|
||||
@ -243,32 +238,65 @@ class SimpleGeomForm(BaseForm):
|
||||
""" A geometry form for rendering geometry read-only using a widget
|
||||
|
||||
"""
|
||||
geom = GeometryField(
|
||||
geom = MultiPolygonField(
|
||||
srid=DEFAULT_SRID,
|
||||
label=_("Geometry"),
|
||||
help_text=_(""),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
disabled=True,
|
||||
disabled=False,
|
||||
widget=OSMWidget(
|
||||
attrs={
|
||||
"map_width": 600,
|
||||
"map_height": 400,
|
||||
# default_zoom defines the nearest possible zoom level from which the JS automatically
|
||||
# zooms out if geometry requires a larger view port. So define a larger range for smaller geometries
|
||||
"default_zoom": 25,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
read_only = kwargs.pop("read_only", True)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Initialize geometry
|
||||
try:
|
||||
geom = self.instance.geometry.geom
|
||||
if geom is None:
|
||||
raise AttributeError
|
||||
self.empty = geom.empty
|
||||
except AttributeError:
|
||||
# catches if no geometry has been added, yet. Replace with empty placeholder polygon.
|
||||
geom = Polygon.from_bbox([0, 0, 0, 0])
|
||||
# Zoom out to a very high level, so the user can see directly that there is no geometry for this entry
|
||||
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
||||
geom = None
|
||||
self.empty = True
|
||||
self.fields["geom"].widget.attrs["default_zoom"] = 1
|
||||
|
||||
self.initialize_form_field("geom", geom)
|
||||
self.area = geom.area
|
||||
if read_only:
|
||||
self.fields["geom"].disabled = True
|
||||
|
||||
def save(self, action: UserActionLogEntry):
|
||||
""" Saves the form's geometry
|
||||
|
||||
Creates a new geometry entry if none is set, yet
|
||||
|
||||
Args:
|
||||
action ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
try:
|
||||
geometry = self.instance.geometry
|
||||
geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID))
|
||||
geometry.modified = action
|
||||
geometry.save()
|
||||
except (AttributeError) as e:
|
||||
# No geometry or linked instance holding a geometry exist --> create a new one!
|
||||
geometry = Geometry.objects.create(
|
||||
geom=self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID)),
|
||||
created=action,
|
||||
)
|
||||
return geometry
|
||||
|
||||
|
||||
class RemoveModalForm(BaseModalForm):
|
||||
@ -294,15 +322,7 @@ class RemoveModalForm(BaseModalForm):
|
||||
|
||||
def save(self):
|
||||
if isinstance(self.instance, BaseObject):
|
||||
with transaction.atomic():
|
||||
action = UserActionLogEntry.objects.create(
|
||||
user=self.user,
|
||||
timestamp=timezone.now(),
|
||||
action=UserAction.DELETED,
|
||||
)
|
||||
self.instance.deleted = action
|
||||
self.instance.log.add(action)
|
||||
self.instance.save()
|
||||
self.instance.mark_as_deleted(self.user)
|
||||
else:
|
||||
# If the class does not provide restorable delete functionality, we must delete the entry finally
|
||||
self.instance.delete()
|
||||
@ -316,6 +336,11 @@ class NewDocumentForm(BaseModalForm):
|
||||
label=_("Title"),
|
||||
label_suffix=_(""),
|
||||
max_length=500,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
creation_date = forms.DateField(
|
||||
label=_("Created on"),
|
||||
@ -325,6 +350,7 @@ class NewDocumentForm(BaseModalForm):
|
||||
attrs={
|
||||
"type": "date",
|
||||
"data-provide": "datepicker",
|
||||
"class": "form-control",
|
||||
},
|
||||
format="%d.%m.%Y"
|
||||
)
|
||||
@ -335,7 +361,7 @@ class NewDocumentForm(BaseModalForm):
|
||||
help_text=_("Must be smaller than 15 Mb"),
|
||||
widget=forms.FileInput(
|
||||
attrs={
|
||||
"class": "w-75"
|
||||
"class": "form-control-file",
|
||||
}
|
||||
),
|
||||
)
|
||||
@ -349,6 +375,7 @@ class NewDocumentForm(BaseModalForm):
|
||||
attrs={
|
||||
"cols": 30,
|
||||
"rows": 5,
|
||||
"class": "form-control",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -21,7 +21,7 @@ class Command(BaseCommand):
|
||||
len_ids = len(identifiers)
|
||||
while len_ids < max_iterations:
|
||||
tmp_intervention = Intervention()
|
||||
_id = tmp_intervention._generate_new_identifier()
|
||||
_id = tmp_intervention.generate_new_identifier()
|
||||
len_ids = len(identifiers)
|
||||
if _id not in identifiers:
|
||||
if len_ids % (max_iterations/5) == 0:
|
||||
|
@ -9,6 +9,8 @@ import os
|
||||
import uuid
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils import timezone
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.gis.db.models import MultiPolygonField
|
||||
@ -61,8 +63,20 @@ class BaseResource(UuidModel):
|
||||
abstract = True
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.created:
|
||||
""" Base deleting of a resource
|
||||
|
||||
Args:
|
||||
using ():
|
||||
keep_parents ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
try:
|
||||
self.created.delete()
|
||||
except (ObjectDoesNotExist, AttributeError) as e:
|
||||
# Object does not exist anymore - we can skip this
|
||||
pass
|
||||
super().delete()
|
||||
|
||||
|
||||
@ -81,14 +95,13 @@ class BaseObject(BaseResource):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
""" Custom delete functionality
|
||||
def mark_as_deleted(self, user: User):
|
||||
""" Mark an entry as deleted
|
||||
|
||||
Does not delete from database but sets a timestamp for being deleted on and which user deleted the object
|
||||
|
||||
Args:
|
||||
*args ():
|
||||
**kwargs ():
|
||||
user (User): The performing user
|
||||
|
||||
Returns:
|
||||
|
||||
@ -97,13 +110,14 @@ class BaseObject(BaseResource):
|
||||
# Nothing to do here
|
||||
return
|
||||
|
||||
_user = kwargs.get("user", None)
|
||||
with transaction.atomic():
|
||||
action = UserActionLogEntry.objects.create(
|
||||
user=_user,
|
||||
action=UserAction.DELETED
|
||||
user=user,
|
||||
action=UserAction.DELETED,
|
||||
timestamp=timezone.now()
|
||||
)
|
||||
self.deleted = action
|
||||
self.log.add(action)
|
||||
self.save()
|
||||
|
||||
def add_log_entry(self, action: UserAction, user: User, comment: str):
|
||||
@ -139,7 +153,7 @@ class BaseObject(BaseResource):
|
||||
else:
|
||||
return User.objects.none()
|
||||
|
||||
def _generate_new_identifier(self) -> str:
|
||||
def generate_new_identifier(self) -> str:
|
||||
""" Generates a new identifier for the intervention object
|
||||
|
||||
Returns:
|
||||
@ -177,7 +191,9 @@ class BaseObject(BaseResource):
|
||||
curr_year = str(_now.year)
|
||||
rand_str = generate_random_string(
|
||||
length=definitions[self.__class__]["length"],
|
||||
only_numbers=True,
|
||||
use_numbers=True,
|
||||
use_letters_lc=False,
|
||||
use_letters_uc=True,
|
||||
)
|
||||
_str = "{}{}-{}".format(curr_month, curr_year, rand_str)
|
||||
return definitions[self.__class__]["template"].format(_str)
|
||||
|
@ -60,6 +60,14 @@ a {
|
||||
color: var(--rlp-red);
|
||||
}
|
||||
|
||||
.form-control:focus{
|
||||
outline: none;
|
||||
border-color: var(--rlp-red);
|
||||
box-shadow: 0 0 3px var(--rlp-red);
|
||||
-moz-box-shadow: 0 0 3px var(--rlp-red);
|
||||
-webkit-box-shadow: 0 0 3px var(--rlp-red);
|
||||
}
|
||||
|
||||
.body-content{
|
||||
margin: 1rem 0rem 0 0rem;
|
||||
}
|
||||
@ -133,6 +141,13 @@ a {
|
||||
height: 8rem;
|
||||
}
|
||||
|
||||
/**
|
||||
Overwrites bootstrap .btn:focus box shadow color
|
||||
*/
|
||||
.btn:focus{
|
||||
box-shadow: 0 0 5px .2rem var(--rlp-gray-light);
|
||||
}
|
||||
|
||||
.btn-default{
|
||||
color: white;
|
||||
background-color: var(--rlp-red);
|
||||
@ -171,13 +186,6 @@ a {
|
||||
background-color: var(--rlp-gray-light);
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus{
|
||||
border-color: var(--rlp-red) !important;
|
||||
box-shadow: 0 0 3px var(--rlp-red) !important;
|
||||
-moz-box-shadow: 0 0 3px var(--rlp-red) !important;
|
||||
-webkit-box-shadow: 0 0 3px var(--rlp-red) !important;
|
||||
}
|
||||
|
||||
.check-star{
|
||||
color: goldenrod;
|
||||
}
|
||||
@ -215,4 +223,7 @@ No other approach worked to get the autocomplete fields to full width of parent
|
||||
*/
|
||||
.select2-container{
|
||||
width: 100% !important;
|
||||
}
|
||||
.select2-results__option--highlighted{
|
||||
background-color: var(--rlp-red) !important;
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
|
||||
<form action="{{ form.action_url }}" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
</form>
|
@ -3,6 +3,6 @@
|
||||
|
||||
{% block body %}
|
||||
<div class="column">
|
||||
{% include 'form/generic_table_form.html' %}
|
||||
{% include 'form/table/generic_table_form.html' %}
|
||||
</div>
|
||||
{% endblock %}
|
22
konova/templates/konova/widgets/generate-content-input.html
Normal file
22
konova/templates/konova/widgets/generate-content-input.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% load i18n fontawesome_5 %}
|
||||
|
||||
<div class="input-group w-100" title="{{ widget.value|stringformat:'s' }}">
|
||||
<input id="gen-id-input" aria-describedby="gen-id-btn" type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
|
||||
<div class="input-group-append" onclick="fetchNewIdentifier()">
|
||||
<span id="gen-id-btn" class="btn btn-default" value="{% trans 'Generate new' %}" title="{% trans 'Generate new' %}">{% fa5_icon 'dice' %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function fetchNewIdentifier() {
|
||||
fetch("{{ widget.attrs.url }}")
|
||||
.then(function(response){
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data){
|
||||
document.getElementById("gen-id-input").value = data["identifier"];
|
||||
})
|
||||
.catch(function(error){
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
</script>
|
@ -19,7 +19,8 @@ from django.urls import path, include
|
||||
|
||||
from konova.autocompletes import OrganisationAutocomplete, NonOfficialOrganisationAutocomplete, EcoAccountAutocomplete, \
|
||||
InterventionAutocomplete, CompensationActionCodeAutocomplete, BiotopeCodeAutocomplete, LawCodeAutocomplete, \
|
||||
RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete
|
||||
RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete, ProcessTypeCodeAutocomplete, \
|
||||
CompensationFundingCodeAutocomplete
|
||||
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
||||
from konova.sso.sso import KonovaSSOClient
|
||||
from konova.views import logout_view, home_view, remove_deadline_view
|
||||
@ -46,9 +47,11 @@ urlpatterns = [
|
||||
path("atcmplt/orgs/other", NonOfficialOrganisationAutocomplete.as_view(), name="other-orgs-autocomplete"),
|
||||
path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"),
|
||||
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="interventions-autocomplete"),
|
||||
path("atcmplt/codes/compensation-action", CompensationActionCodeAutocomplete.as_view(), name="codes-compensation-action-autocomplete"),
|
||||
path("atcmplt/codes/comp/action", CompensationActionCodeAutocomplete.as_view(), name="codes-compensation-action-autocomplete"),
|
||||
path("atcmplt/codes/comp/funding", CompensationFundingCodeAutocomplete.as_view(), name="codes-compensation-funding-autocomplete"),
|
||||
path("atcmplt/codes/biotope", BiotopeCodeAutocomplete.as_view(), name="codes-biotope-autocomplete"),
|
||||
path("atcmplt/codes/law", LawCodeAutocomplete.as_view(), name="codes-law-autocomplete"),
|
||||
path("atcmplt/codes/prc-type", ProcessTypeCodeAutocomplete.as_view(), name="codes-process-type-autocomplete"),
|
||||
path("atcmplt/codes/reg-off", RegistrationOfficeCodeAutocomplete.as_view(), name="codes-registration-office-autocomplete"),
|
||||
path("atcmplt/codes/cons-off", ConservationOfficeCodeAutocomplete.as_view(), name="codes-conservation-office-autocomplete"),
|
||||
]
|
||||
|
@ -9,14 +9,18 @@ import random
|
||||
import string
|
||||
|
||||
|
||||
def generate_random_string(length: int, only_numbers: bool = False) -> str:
|
||||
def generate_random_string(length: int, use_numbers: bool = False, use_letters_lc: bool = False, use_letters_uc: bool = False) -> str:
|
||||
"""
|
||||
Generates a random string of variable length
|
||||
"""
|
||||
if only_numbers:
|
||||
elements = string.digits
|
||||
else:
|
||||
elements = string.ascii_letters
|
||||
elements = []
|
||||
if use_numbers:
|
||||
elements.append(string.digits)
|
||||
if use_letters_lc:
|
||||
elements.append(string.ascii_lowercase)
|
||||
if use_letters_uc:
|
||||
elements.append(string.ascii_uppercase)
|
||||
|
||||
elements = "".join(elements)
|
||||
ret_val = "".join(random.choice(elements) for i in range(length))
|
||||
return ret_val
|
||||
|
@ -10,3 +10,6 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
FORM_INVALID = _("There was an error on this form.")
|
||||
INTERVENTION_INVALID = _("There are errors in this intervention.")
|
||||
IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since another entry has been added in the meanwhile, which uses this identifier")
|
||||
DATA_UNSHARED = _("This data is not shared with you")
|
||||
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -13,8 +13,8 @@ django-simple-sso==1.1.0
|
||||
django-tables2==2.3.4
|
||||
idna==2.10
|
||||
importlib-metadata==2.1.1
|
||||
pkg-resources==0.0.0
|
||||
psycopg2==2.8.6
|
||||
itsdangerous
|
||||
psycopg2-binary
|
||||
pytz==2020.4
|
||||
requests==2.25.0
|
||||
six==1.15.0
|
||||
|
65
templates/form/collapsable/form.html
Normal file
65
templates/form/collapsable/form.html
Normal file
@ -0,0 +1,65 @@
|
||||
{% load i18n l10n fontawesome_5 %}
|
||||
<form method="post" action="{{ form.action_url }}" {% for attr_key, attr_val in form.form_attrs.items %} {{attr_key}}="{{attr_val}}"{% endfor %}>
|
||||
{% csrf_token %}
|
||||
<h2>{{form.form_title}}</h2>
|
||||
<div id="help" class="col">
|
||||
<div class="row rlp-gd-outline p-2">
|
||||
<div class="col-lg-1 rlp-r-inv">
|
||||
<span class="d-flex justify-content-center align-items-center h-100">
|
||||
{% fa5_icon 'question-circle' 'far' %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-lg-11">
|
||||
<small>
|
||||
{% blocktrans %}
|
||||
First enter the most basic data. Of course you can change everything later.
|
||||
All further data, like documents or further details, can be added in the detail view after saving
|
||||
your new entry.
|
||||
{% endblocktrans %}
|
||||
<br>
|
||||
{% trans 'Open the input topic with a simple click.' %}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<div class="card">
|
||||
<div id="dataCardHeader" class="card-header cursor-pointer rlp-r" data-toggle="collapse" data-target="#dataCard" aria-expanded="true" aria-controls="dataCard">
|
||||
<h5>
|
||||
{% fa5_icon 'list' %}
|
||||
{% trans 'General data' %}
|
||||
</h5>
|
||||
</div>
|
||||
<div id="dataCard" class="collapse" aria-labelledby="dataCardHeader">
|
||||
<div class="card-body">
|
||||
{% include 'form/table/generic_table_form_body.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="card">
|
||||
<div id="geometryCardHeader" class="card-header cursor-pointer rlp-r" data-toggle="collapse" data-target="#geometryCard" aria-expanded="true" aria-controls="geometryCard">
|
||||
<h5>
|
||||
{% fa5_icon 'map-marked-alt' %}
|
||||
{% trans 'Geometry' %}
|
||||
</h5>
|
||||
</div>
|
||||
<div id="geometryCard" class="collapse show" aria-labelledby="geometryCardHeader">
|
||||
<div class="card-body">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<a href="{{ form.cancel_redirect }}">
|
||||
<button class="btn btn-default" type="button" title="{% trans 'Cancel' %}">{% trans 'Cancel' %}</button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 d-flex justify-content-end">
|
||||
<button class="btn btn-default" type="submit" title="{% trans 'Save' %}">{% trans 'Save' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -16,14 +16,14 @@
|
||||
{% endif %}
|
||||
<form method="post" action="{{ form.action_url }}" {% for attr_key, attr_val in form.form_attrs.items %} {{attr_key}}="{{attr_val}}"{% endfor %}>
|
||||
{% csrf_token %}
|
||||
{% include 'form/generic_table_form_body.html' %}
|
||||
{% include 'form/table/generic_table_form_body.html' %}
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="col-6">
|
||||
<a href="{{ form.cancel_redirect }}">
|
||||
<button class="btn btn-default" type="button" title="{% trans 'Cancel' %}">{% trans 'Cancel' %}</button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md d-flex justify-content-end">
|
||||
<div class="col-6 d-flex justify-content-end">
|
||||
<button class="btn btn-default" type="submit" title="{% trans 'Save' %}">{% trans 'Save' %}</button>
|
||||
</div>
|
||||
</div>
|
@ -6,6 +6,7 @@
|
||||
<tr title="{{ field.help_text }}" class="{% if field.errors %}alert-danger{% endif %}">
|
||||
<th scope="row" class="col-sm-3">
|
||||
<label for="id_{{ field.name }}">{{ field.label }}<span class="label-required">{% if field.field.required %}*{% endif %}</span></label>
|
||||
<br>
|
||||
<small>{{ field.help_text }}</small>
|
||||
</th>
|
||||
<td class="col-sm-9">
|
@ -4,9 +4,10 @@
|
||||
Encapsules the rendering and initializing of a geometry view component, e.g. used in the detail views.
|
||||
{% endcomment %}
|
||||
|
||||
|
||||
{% if geom_form.area == 0 %}
|
||||
<div class="alert alert-info">{% trans 'No geometry added, yet.' %}</div>
|
||||
{% if geom_form.empty %}
|
||||
<div class="w-100">
|
||||
<div class="alert alert-info">{% trans 'No geometry added, yet.' %}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{geom_form.media}}
|
||||
{{geom_form.geom}}
|
@ -18,7 +18,7 @@
|
||||
<article>
|
||||
{{ form.form_caption }}
|
||||
</article>
|
||||
{% include 'form/generic_table_form_body.html' %}
|
||||
{% include 'form/table/generic_table_form_body.html' %}
|
||||
</div>
|
||||
{% if form.render_submit %}
|
||||
<div class="modal-footer">
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block body %}
|
||||
{% include 'form/generic_table_form.html' %}
|
||||
{% include 'form/table/generic_table_form.html' %}
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user