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, \
|
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_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_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 = {
|
bool_map = {
|
||||||
"true": True,
|
"true": True,
|
||||||
@ -35,7 +35,7 @@ class Command(BaseCommand):
|
|||||||
CODELIST_COMPENSATION_ACTION_ID,
|
CODELIST_COMPENSATION_ACTION_ID,
|
||||||
CODELIST_COMPENSATION_ACTION_CLASS_ID,
|
CODELIST_COMPENSATION_ACTION_CLASS_ID,
|
||||||
CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID,
|
CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID,
|
||||||
CODELIST_COMPENSATION_COMBINATION_ID,
|
CODELIST_COMPENSATION_FUNDING_ID,
|
||||||
CODELIST_PROCESS_TYPE_ID,
|
CODELIST_PROCESS_TYPE_ID,
|
||||||
]
|
]
|
||||||
self._write_warning("Fetching codes...")
|
self._write_warning("Fetching codes...")
|
||||||
|
@ -53,8 +53,9 @@ class KonovaCode(models.Model):
|
|||||||
if self.parent:
|
if self.parent:
|
||||||
ret_val += self.parent.long_name + " > "
|
ret_val += self.parent.long_name + " > "
|
||||||
ret_val += self.long_name
|
ret_val += self.long_name
|
||||||
if self.short_name:
|
if self.short_name and self.short_name != self.long_name:
|
||||||
ret_val += " ({})".format(self.short_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
|
return ret_val
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -21,4 +21,4 @@ CODELIST_COMPENSATION_HANDLER_ID = 1052 # CLEingreifer
|
|||||||
CODELIST_COMPENSATION_ACTION_ID = 1026 # CLMassnahmedetail
|
CODELIST_COMPENSATION_ACTION_ID = 1026 # CLMassnahmedetail
|
||||||
CODELIST_COMPENSATION_ACTION_CLASS_ID = 1034 # CLMassnahmeklasse
|
CODELIST_COMPENSATION_ACTION_CLASS_ID = 1034 # CLMassnahmeklasse
|
||||||
CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID = 1028 # CLMassnahmetyp, CEF and stuff
|
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 = [
|
urlpatterns = [
|
||||||
path("", index_view, name="acc-index"),
|
path("", index_view, name="acc-index"),
|
||||||
path('new/', new_view, name='acc-new'),
|
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>', open_view, name='acc-open'),
|
||||||
path('<id>/log', log_view, name='acc-log'),
|
path('<id>/log', log_view, name='acc-log'),
|
||||||
path('<id>/record', record_view, name='acc-record'),
|
path('<id>/record', record_view, name='acc-record'),
|
||||||
|
@ -11,6 +11,8 @@ from compensation.views.compensation_views import *
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Main compensation
|
# Main compensation
|
||||||
path("", index_view, name="index"),
|
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('new', new_view, name='new'),
|
||||||
path('<id>', open_view, name='open'),
|
path('<id>', open_view, name='open'),
|
||||||
path('<id>/log', log_view, name='log'),
|
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
|
Author: Michel Peltriaux
|
||||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
Created on: 04.12.20
|
Created on: 04.10.21
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from bootstrap_modal_forms.utils import is_ajax
|
from bootstrap_modal_forms.utils import is_ajax
|
||||||
@ -12,40 +12,34 @@ from django.contrib import messages
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.http import HttpRequest, HttpResponseRedirect
|
from django.http import HttpRequest, HttpResponseRedirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _
|
||||||
from django.utils.translation import pgettext_lazy as _con
|
|
||||||
|
|
||||||
from codelist.models import KonovaCode
|
from codelist.models import KonovaCode
|
||||||
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION_ID
|
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.contexts import BaseContext
|
||||||
from konova.forms import BaseForm, BaseModalForm
|
from konova.forms import BaseModalForm
|
||||||
from konova.models import Deadline, DeadlineType
|
from konova.models import DeadlineType, Deadline
|
||||||
from konova.utils.message_templates import FORM_INVALID
|
from konova.utils.message_templates import FORM_INVALID
|
||||||
from user.models import UserActionLogEntry, UserAction
|
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):
|
class NewPaymentForm(BaseModalForm):
|
||||||
|
""" Form handling payment related input
|
||||||
|
|
||||||
|
"""
|
||||||
amount = forms.DecimalField(
|
amount = forms.DecimalField(
|
||||||
min_value=0.00,
|
min_value=0.00,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
label=_con("money", "Amount"), # contextual translation
|
label=_con("money", "Amount"), # contextual translation
|
||||||
label_suffix=_(""),
|
label_suffix=_(""),
|
||||||
help_text=_("in Euro"),
|
help_text=_("in Euro"),
|
||||||
|
widget=forms.NumberInput(
|
||||||
|
attrs={
|
||||||
|
"class": "form-control",
|
||||||
|
"placeholder": "0,00",
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
due = forms.DateField(
|
due = forms.DateField(
|
||||||
label=_("Due on"),
|
label=_("Due on"),
|
||||||
@ -56,6 +50,7 @@ class NewPaymentForm(BaseModalForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"type": "date",
|
"type": "date",
|
||||||
"data-provide": "datepicker",
|
"data-provide": "datepicker",
|
||||||
|
"class": "form-control",
|
||||||
},
|
},
|
||||||
format="%d.%m.%Y"
|
format="%d.%m.%Y"
|
||||||
)
|
)
|
||||||
@ -69,7 +64,7 @@ class NewPaymentForm(BaseModalForm):
|
|||||||
widget=forms.Textarea(
|
widget=forms.Textarea(
|
||||||
attrs={
|
attrs={
|
||||||
"rows": 5,
|
"rows": 5,
|
||||||
"class": "w-100"
|
"class": "form-control"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -79,7 +74,6 @@ class NewPaymentForm(BaseModalForm):
|
|||||||
self.intervention = self.instance
|
self.intervention = self.instance
|
||||||
self.form_title = _("Payment")
|
self.form_title = _("Payment")
|
||||||
self.form_caption = _("Add a payment for intervention '{}'").format(self.intervention.title)
|
self.form_caption = _("Add a payment for intervention '{}'").format(self.intervention.title)
|
||||||
self.add_placeholder_for_field("amount", "0,00")
|
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
"""
|
"""
|
||||||
@ -129,6 +123,12 @@ class NewPaymentForm(BaseModalForm):
|
|||||||
|
|
||||||
|
|
||||||
class NewStateModalForm(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(
|
biotope_type = forms.ModelChoiceField(
|
||||||
label=_("Biotope Type"),
|
label=_("Biotope Type"),
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
@ -152,14 +152,19 @@ class NewStateModalForm(BaseModalForm):
|
|||||||
label=_("Surface"),
|
label=_("Surface"),
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
required=True,
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.form_title = _("New state")
|
self.form_title = _("New state")
|
||||||
self.form_caption = _("Insert data for the new state")
|
self.form_caption = _("Insert data for the new state")
|
||||||
self.add_placeholder_for_field("surface", "0,00")
|
|
||||||
|
|
||||||
def save(self, is_before_state: bool = False):
|
def save(self, is_before_state: bool = False):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
@ -232,6 +237,9 @@ class NewStateModalForm(BaseModalForm):
|
|||||||
|
|
||||||
|
|
||||||
class NewDeadlineModalForm(BaseModalForm):
|
class NewDeadlineModalForm(BaseModalForm):
|
||||||
|
""" Form handling deadline related input
|
||||||
|
|
||||||
|
"""
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
label=_("Deadline Type"),
|
label=_("Deadline Type"),
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
@ -240,7 +248,7 @@ class NewDeadlineModalForm(BaseModalForm):
|
|||||||
choices=DeadlineType.choices,
|
choices=DeadlineType.choices,
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
attrs={
|
attrs={
|
||||||
"class": "custom-select"
|
"class": "form-control"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -253,6 +261,7 @@ class NewDeadlineModalForm(BaseModalForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"type": "date",
|
"type": "date",
|
||||||
"data-provide": "datepicker",
|
"data-provide": "datepicker",
|
||||||
|
"class": "form-control",
|
||||||
},
|
},
|
||||||
format="%d.%m.%Y"
|
format="%d.%m.%Y"
|
||||||
)
|
)
|
||||||
@ -267,6 +276,7 @@ class NewDeadlineModalForm(BaseModalForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"cols": 30,
|
"cols": 30,
|
||||||
"rows": 5,
|
"rows": 5,
|
||||||
|
"class": "form-control",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -301,6 +311,13 @@ class NewDeadlineModalForm(BaseModalForm):
|
|||||||
|
|
||||||
|
|
||||||
class NewActionModalForm(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(
|
action_type = forms.ModelChoiceField(
|
||||||
label=_("Action Type"),
|
label=_("Action Type"),
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
@ -326,7 +343,7 @@ class NewActionModalForm(BaseModalForm):
|
|||||||
choices=UnitChoices.choices,
|
choices=UnitChoices.choices,
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
attrs={
|
attrs={
|
||||||
"class": "custom-select"
|
"class": "form-control"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -337,6 +354,12 @@ class NewActionModalForm(BaseModalForm):
|
|||||||
help_text=_("Insert the amount"),
|
help_text=_("Insert the amount"),
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
min_value=0.00,
|
min_value=0.00,
|
||||||
|
widget=forms.NumberInput(
|
||||||
|
attrs={
|
||||||
|
"class": "form-control",
|
||||||
|
"placeholder": "0,00",
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
comment = forms.CharField(
|
comment = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -347,7 +370,7 @@ class NewActionModalForm(BaseModalForm):
|
|||||||
widget=forms.Textarea(
|
widget=forms.Textarea(
|
||||||
attrs={
|
attrs={
|
||||||
"rows": 5,
|
"rows": 5,
|
||||||
"class": "w-100"
|
"class": "form-control",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -356,7 +379,6 @@ class NewActionModalForm(BaseModalForm):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.form_title = _("New action")
|
self.form_title = _("New action")
|
||||||
self.form_caption = _("Insert data for the new action")
|
self.form_caption = _("Insert data for the new action")
|
||||||
self.add_placeholder_for_field("amount", "0,00")
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
@ -381,4 +403,3 @@ class NewActionModalForm(BaseModalForm):
|
|||||||
self.instance.log.add(edited_action)
|
self.instance.log.add(edited_action)
|
||||||
self.instance.actions.add(comp_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.models import KonovaCode
|
||||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, \
|
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 intervention.models import Intervention, ResponsibilityData
|
||||||
from konova.models import BaseObject, BaseResource, Geometry, UuidModel, AbstractDocument, \
|
from konova.models import BaseObject, BaseResource, Geometry, UuidModel, AbstractDocument, \
|
||||||
generate_document_file_upload_path
|
generate_document_file_upload_path
|
||||||
@ -144,7 +144,7 @@ class AbstractCompensation(BaseObject):
|
|||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
limit_choices_to={
|
limit_choices_to={
|
||||||
"code_lists__in": [CODELIST_COMPENSATION_COMBINATION_ID],
|
"code_lists__in": [CODELIST_COMPENSATION_FUNDING_ID],
|
||||||
"is_selectable": True,
|
"is_selectable": True,
|
||||||
"is_archived": False,
|
"is_archived": False,
|
||||||
},
|
},
|
||||||
@ -185,9 +185,9 @@ class Compensation(AbstractCompensation):
|
|||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.identifier is None or len(self.identifier) == 0:
|
if self.identifier is None or len(self.identifier) == 0:
|
||||||
# Create new identifier
|
# Create new identifier
|
||||||
new_id = self._generate_new_identifier()
|
new_id = self.generate_new_identifier()
|
||||||
while Compensation.objects.filter(identifier=new_id).exists():
|
while Compensation.objects.filter(identifier=new_id).exists():
|
||||||
new_id = self._generate_new_identifier()
|
new_id = self.generate_new_identifier()
|
||||||
self.identifier = new_id
|
self.identifier = new_id
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@ -322,9 +322,9 @@ class EcoAccount(AbstractCompensation):
|
|||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.identifier is None or len(self.identifier) == 0:
|
if self.identifier is None or len(self.identifier) == 0:
|
||||||
# Create new identifier
|
# Create new identifier
|
||||||
new_id = self._generate_new_identifier()
|
new_id = self.generate_new_identifier()
|
||||||
while EcoAccount.objects.filter(identifier=new_id).exists():
|
while EcoAccount.objects.filter(identifier=new_id).exists():
|
||||||
new_id = self._generate_new_identifier()
|
new_id = self.generate_new_identifier()
|
||||||
self.identifier = new_id
|
self.identifier = new_id
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@ -355,28 +355,28 @@ class EcoAccount(AbstractCompensation):
|
|||||||
"""
|
"""
|
||||||
return self.after_states.all().aggregate(Sum("surface"))["surface__sum"] or 0
|
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
|
""" Calculates available rest surface of the eco account
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
as_percentage (bool): Whether to return the result as m² or %
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
ret_val_total (float): Total amount
|
||||||
|
ret_val_relative (float): Amount as percentage (0-100)
|
||||||
"""
|
"""
|
||||||
deductions = self.deductions.filter(
|
deductions = self.deductions.filter(
|
||||||
intervention__deleted=None,
|
intervention__deleted=None,
|
||||||
)
|
)
|
||||||
deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0
|
deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0
|
||||||
available_surfaces = self.deductable_surface or deductions_surfaces ## no division by zero
|
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:
|
||||||
if available_surfaces > 0:
|
ret_val_relative = int((ret_val_total / available_surfaces) * 100)
|
||||||
ret_val = int((ret_val / available_surfaces) * 100)
|
else:
|
||||||
else:
|
ret_val_relative = 0
|
||||||
ret_val = 0
|
|
||||||
return ret_val
|
return ret_val_total, ret_val_relative
|
||||||
|
|
||||||
def get_LANIS_link(self) -> str:
|
def get_LANIS_link(self) -> str:
|
||||||
""" Generates a link for LANIS depending on the geometry
|
""" Generates a link for LANIS depending on the geometry
|
||||||
|
@ -5,8 +5,8 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 18.12.20
|
Created on: 18.12.20
|
||||||
|
|
||||||
"""
|
"""
|
||||||
COMPENSATION_IDENTIFIER_LENGTH = 10
|
COMPENSATION_IDENTIFIER_LENGTH = 6
|
||||||
COMPENSATION_IDENTIFIER_TEMPLATE = "KOM-{}"
|
COMPENSATION_IDENTIFIER_TEMPLATE = "KOM-{}"
|
||||||
|
|
||||||
ECO_ACCOUNT_IDENTIFIER_LENGTH = 10
|
ECO_ACCOUNT_IDENTIFIER_LENGTH = 6
|
||||||
ECO_ACCOUNT_IDENTIFIER_TEMPLATE = "OEK-{}"
|
ECO_ACCOUNT_IDENTIFIER_TEMPLATE = "OEK-{}"
|
@ -5,6 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 01.12.20
|
Created on: 01.12.20
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -148,6 +149,8 @@ class CompensationTable(BaseTable):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
html = ""
|
html = ""
|
||||||
|
if value is None:
|
||||||
|
value = User.objects.none()
|
||||||
has_access = value.filter(
|
has_access = value.filter(
|
||||||
username=self.user.username
|
username=self.user.username
|
||||||
).exists()
|
).exists()
|
||||||
@ -236,8 +239,8 @@ class EcoAccountTable(BaseTable):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
value = record.get_available_rest(as_percentage=True)
|
value_total, value_relative = record.get_available_rest()
|
||||||
html = render_to_string("konova/custom_widgets/progressbar.html", {"value": value})
|
html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
|
||||||
return format_html(html)
|
return format_html(html)
|
||||||
|
|
||||||
def render_r(self, value, record: EcoAccount):
|
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>
|
</a>
|
||||||
{% if has_access %}
|
{% if has_access %}
|
||||||
{% if is_default_member %}
|
{% 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' %}">
|
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
None
|
{% trans 'None' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -99,7 +99,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<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 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_default_member %}
|
{% 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' %}">
|
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
@ -30,12 +30,12 @@
|
|||||||
<th class="w-25" scope="row">{% trans 'Title' %}</th>
|
<th class="w-25" scope="row">{% trans 'Title' %}</th>
|
||||||
<td class="align-middle">{{obj.title}}</td>
|
<td class="align-middle">{{obj.title}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr {% if not obj.deductable_surface %}class="alert alert-danger" title="{% trans 'No surface deductable' %}" {% endif %}>
|
||||||
<th scope="row">{% trans 'Available' %}</th>
|
<th scope="row">{% trans 'Available' %}</th>
|
||||||
<td class="align-middle">
|
<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 %}
|
{% with available as value %}
|
||||||
{% include 'konova/custom_widgets/progressbar.html' %}
|
{% include 'konova/widgets/progressbar.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
<td class="align-middle">{{obj.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
|
<td class="align-middle">{{obj.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr {% if not obj.responsible.conservation_file_number %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
<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>
|
<td class="align-middle">{{obj.responsible.conservation_file_number|default_if_none:""}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr {% if not obj.responsible.handler %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
<tr {% if not obj.responsible.handler %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
||||||
@ -98,7 +98,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<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.contrib.auth.decorators import login_required
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest, JsonResponse
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render
|
||||||
from django.utils.translation import gettext_lazy as _
|
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.models import Compensation, CompensationState, CompensationAction, CompensationDocument
|
||||||
from compensation.tables import CompensationTable
|
from compensation.tables import CompensationTable
|
||||||
|
from intervention.models import Intervention
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import *
|
from konova.decorators import *
|
||||||
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm
|
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm
|
||||||
from konova.utils.documents import get_document, remove_document
|
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
|
from konova.utils.user_checks import in_group
|
||||||
|
|
||||||
|
|
||||||
@ -44,16 +47,100 @@ def index_view(request: HttpRequest):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@default_group_required
|
@default_group_required
|
||||||
def new_view(request: HttpRequest):
|
@shared_access_required(Intervention, "intervention_id")
|
||||||
# ToDo
|
def new_view(request: HttpRequest, intervention_id: str = None):
|
||||||
pass
|
"""
|
||||||
|
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
|
@login_required
|
||||||
@default_group_required
|
@default_group_required
|
||||||
def edit_view(request: HttpRequest, id: str):
|
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
|
@login_required
|
||||||
|
@ -5,23 +5,26 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 09.08.21
|
Created on: 09.08.21
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.contrib import messages
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.http import HttpRequest, Http404
|
from django.http import HttpRequest, Http404, JsonResponse
|
||||||
from django.shortcuts import render, get_object_or_404
|
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.models import EcoAccount, EcoAccountDocument
|
||||||
from compensation.tables import EcoAccountTable
|
from compensation.tables import EcoAccountTable
|
||||||
from intervention.forms import NewDeductionForm
|
from intervention.forms.modalForms import NewDeductionModalForm
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required
|
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required
|
||||||
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm
|
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm
|
||||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||||
from konova.utils.documents import get_document, remove_document
|
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
|
from konova.utils.user_checks import in_group
|
||||||
|
|
||||||
|
|
||||||
@ -56,15 +59,98 @@ def index_view(request: HttpRequest):
|
|||||||
@login_required
|
@login_required
|
||||||
@default_group_required
|
@default_group_required
|
||||||
def new_view(request: HttpRequest):
|
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
|
@login_required
|
||||||
@default_group_required
|
@default_group_required
|
||||||
def edit_view(request: HttpRequest, id: str):
|
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
|
@login_required
|
||||||
@ -96,7 +182,7 @@ def open_view(request: HttpRequest, id: str):
|
|||||||
diff_states = abs(sum_before_states - sum_after_states)
|
diff_states = abs(sum_before_states - sum_after_states)
|
||||||
|
|
||||||
# Calculate rest of available surface for deductions
|
# 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(
|
deductions = acc.deductions.filter(
|
||||||
intervention__deleted=None,
|
intervention__deleted=None,
|
||||||
@ -111,7 +197,8 @@ def open_view(request: HttpRequest, id: str):
|
|||||||
"sum_before_states": sum_before_states,
|
"sum_before_states": sum_before_states,
|
||||||
"sum_after_states": sum_after_states,
|
"sum_after_states": sum_after_states,
|
||||||
"diff_states": diff_states,
|
"diff_states": diff_states,
|
||||||
"available": available,
|
"available": available_relative,
|
||||||
|
"available_total": available_total,
|
||||||
"is_default_member": in_group(_user, DEFAULT_GROUP),
|
"is_default_member": in_group(_user, DEFAULT_GROUP),
|
||||||
"is_zb_member": in_group(_user, ZB_GROUP),
|
"is_zb_member": in_group(_user, ZB_GROUP),
|
||||||
"is_ets_member": in_group(_user, ETS_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)
|
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(
|
return form.process_request(
|
||||||
request,
|
request,
|
||||||
msg_success=_("Deduction added")
|
msg_success=_("Deduction added")
|
||||||
|
@ -10,7 +10,7 @@ from django.contrib.auth.decorators import login_required
|
|||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.shortcuts import get_object_or_404
|
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 compensation.models import Payment
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from konova.decorators import default_group_required
|
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):
|
def save(self, *args, **kwargs):
|
||||||
if self.identifier is None or len(self.identifier) == 0:
|
if self.identifier is None or len(self.identifier) == 0:
|
||||||
# Create new identifier
|
# Create new identifier
|
||||||
new_id = self._generate_new_identifier()
|
new_id = self.generate_new_identifier()
|
||||||
while Ema.objects.filter(identifier=new_id).exists():
|
while Ema.objects.filter(identifier=new_id).exists():
|
||||||
new_id = self._generate_new_identifier()
|
new_id = self.generate_new_identifier()
|
||||||
self.identifier = new_id
|
self.identifier = new_id
|
||||||
super().save(*args, **kwargs)
|
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-{}"
|
EMA_ACCOUNT_IDENTIFIER_TEMPLATE = "EMA-{}"
|
@ -24,7 +24,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_default_member %}
|
{% 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' %}">
|
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
<td class="align-middle">{{obj.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
|
<td class="align-middle">{{obj.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr {% if not obj.responsible.conservation_file_number %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
<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>
|
<td class="align-middle">{{obj.responsible.conservation_file_number|default_if_none:""}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr {% if not obj.responsible.handler %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
<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 = [
|
urlpatterns = [
|
||||||
path("", index_view, name="index"),
|
path("", index_view, name="index"),
|
||||||
path("new/", new_view, name="new"),
|
path("new/", new_view, name="new"),
|
||||||
|
path("new/id", new_id_view, name="new-id"),
|
||||||
path("<id>", open_view, name="open"),
|
path("<id>", open_view, name="open"),
|
||||||
path('<id>/log', log_view, name='log'),
|
path('<id>/log', log_view, name='log'),
|
||||||
path('<id>/edit', edit_view, name='edit'),
|
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.contrib.auth.decorators import login_required
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest, JsonResponse
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import compensation
|
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 ema.tables import EmaTable
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import conservation_office_group_required
|
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.forms import RemoveModalForm, NewDocumentForm, SimpleGeomForm, RecordModalForm
|
||||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||||
from konova.utils.documents import get_document, remove_document
|
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
|
from konova.utils.user_checks import in_group
|
||||||
|
|
||||||
|
|
||||||
@ -47,7 +50,8 @@ def index_view(request: HttpRequest):
|
|||||||
@login_required
|
@login_required
|
||||||
@conservation_office_group_required
|
@conservation_office_group_required
|
||||||
def new_view(request: HttpRequest):
|
def new_view(request: HttpRequest):
|
||||||
""" Renders the form for a new EMA
|
"""
|
||||||
|
Renders a view for a new eco account creation
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
@ -55,12 +59,54 @@ def new_view(request: HttpRequest):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
template = "generic_index.html"
|
template = "ema/form/view.html"
|
||||||
context = {}
|
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
|
context = BaseContext(request, context).context
|
||||||
return render(request, template, 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
|
@login_required
|
||||||
def open_view(request: HttpRequest, id: str):
|
def open_view(request: HttpRequest, id: str):
|
||||||
""" Renders the detail view of an EMA
|
""" Renders the detail view of an EMA
|
||||||
@ -133,7 +179,38 @@ def log_view(request: HttpRequest, id: str):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def edit_view(request: HttpRequest, id: str):
|
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
|
@login_required
|
||||||
|
@ -12,7 +12,7 @@ from django.db.models import QuerySet, Q
|
|||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from intervention.forms import DummyFilterInput
|
from intervention.inputs import DummyFilterInput
|
||||||
from intervention.models import Intervention
|
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
|
Author: Michel Peltriaux
|
||||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
Created on: 02.12.20
|
Created on: 27.09.21
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
from django import forms
|
|
||||||
from django.contrib.auth.models import User
|
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.db import transaction
|
||||||
|
from django import forms
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from compensation.models import EcoAccountDeduction, EcoAccount
|
from compensation.models import EcoAccount, EcoAccountDeduction
|
||||||
from intervention.models import Intervention, Revocation, RevocationDocument
|
from intervention.inputs import TextToClipboardInput
|
||||||
from konova.forms import BaseForm, BaseModalForm
|
from intervention.models import Revocation, RevocationDocument, Intervention
|
||||||
from konova.settings import DEFAULT_LAT, DEFAULT_LON, DEFAULT_ZOOM, ZB_GROUP, ETS_GROUP
|
from konova.forms import BaseModalForm
|
||||||
|
from konova.settings import ZB_GROUP, ETS_GROUP
|
||||||
from konova.utils.general import format_german_float
|
from konova.utils.general import format_german_float
|
||||||
from konova.utils.messenger import Messenger
|
from konova.utils.messenger import Messenger
|
||||||
from konova.utils.user_checks import in_group
|
from konova.utils.user_checks import in_group
|
||||||
from organisation.models import Organisation
|
|
||||||
from user.models import UserActionLogEntry, UserAction
|
from user.models import UserActionLogEntry, UserAction
|
||||||
|
|
||||||
|
|
||||||
class NewInterventionForm(BaseForm):
|
class ShareInterventionModalForm(BaseModalForm):
|
||||||
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):
|
|
||||||
url = forms.CharField(
|
url = forms.CharField(
|
||||||
label=_("Share link"),
|
label=_("Share link"),
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
@ -247,7 +31,8 @@ class ShareInterventionForm(BaseModalForm):
|
|||||||
required=False,
|
required=False,
|
||||||
widget=TextToClipboardInput(
|
widget=TextToClipboardInput(
|
||||||
attrs={
|
attrs={
|
||||||
"readonly": True
|
"readonly": True,
|
||||||
|
"class": "form-control",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -316,7 +101,7 @@ class ShareInterventionForm(BaseModalForm):
|
|||||||
self.instance.users.set(accessing_users)
|
self.instance.users.set(accessing_users)
|
||||||
|
|
||||||
|
|
||||||
class NewRevocationForm(BaseModalForm):
|
class NewRevocationModalForm(BaseModalForm):
|
||||||
date = forms.DateField(
|
date = forms.DateField(
|
||||||
label=_("Date"),
|
label=_("Date"),
|
||||||
label_suffix=_(""),
|
label_suffix=_(""),
|
||||||
@ -325,6 +110,7 @@ class NewRevocationForm(BaseModalForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"type": "date",
|
"type": "date",
|
||||||
"data-provide": "datepicker",
|
"data-provide": "datepicker",
|
||||||
|
"class": "form-control",
|
||||||
},
|
},
|
||||||
format="%d.%m.%Y"
|
format="%d.%m.%Y"
|
||||||
)
|
)
|
||||||
@ -336,7 +122,7 @@ class NewRevocationForm(BaseModalForm):
|
|||||||
help_text=_("Must be smaller than 15 Mb"),
|
help_text=_("Must be smaller than 15 Mb"),
|
||||||
widget=forms.FileInput(
|
widget=forms.FileInput(
|
||||||
attrs={
|
attrs={
|
||||||
"class": "w-75"
|
"class": "form-control-file"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -350,6 +136,7 @@ class NewRevocationForm(BaseModalForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"cols": 30,
|
"cols": 30,
|
||||||
"rows": 5,
|
"rows": 5,
|
||||||
|
"class": "form-control",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -394,7 +181,7 @@ class NewRevocationForm(BaseModalForm):
|
|||||||
return revocation
|
return revocation
|
||||||
|
|
||||||
|
|
||||||
class RunCheckForm(BaseModalForm):
|
class RunCheckModalForm(BaseModalForm):
|
||||||
checked_intervention = forms.BooleanField(
|
checked_intervention = forms.BooleanField(
|
||||||
label=_("Checked intervention data"),
|
label=_("Checked intervention data"),
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
@ -458,7 +245,7 @@ class RunCheckForm(BaseModalForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class NewDeductionForm(BaseModalForm):
|
class NewDeductionModalForm(BaseModalForm):
|
||||||
""" Form for creating new deduction
|
""" Form for creating new deduction
|
||||||
|
|
||||||
Can be used for Intervention view as well as for EcoAccount views.
|
Can be used for Intervention view as well as for EcoAccount views.
|
||||||
@ -487,6 +274,12 @@ class NewDeductionForm(BaseModalForm):
|
|||||||
label=_("Surface"),
|
label=_("Surface"),
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
help_text=_("in m²"),
|
help_text=_("in m²"),
|
||||||
|
widget=forms.NumberInput(
|
||||||
|
attrs={
|
||||||
|
"class": "form-control",
|
||||||
|
"placeholder": "0,00",
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
intervention = forms.ModelChoiceField(
|
intervention = forms.ModelChoiceField(
|
||||||
label=_("Intervention"),
|
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.form_caption = _("Enter the information for a new deduction from a chosen eco-account")
|
||||||
self.is_intervention_initially = False
|
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
|
# Check for Intervention or EcoAccount
|
||||||
if isinstance(self.instance, Intervention):
|
if isinstance(self.instance, Intervention):
|
||||||
# Form has been called with a given 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()
|
self.save()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
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:
|
if self.identifier is None or len(self.identifier) == 0:
|
||||||
# Create new identifier
|
# No identifier given
|
||||||
new_id = self._generate_new_identifier()
|
self.identifier = self.generate_new_identifier()
|
||||||
while Intervention.objects.filter(identifier=new_id).exists():
|
|
||||||
new_id = self._generate_new_identifier()
|
# Before saving, make sure the set identifier is not used, yet
|
||||||
self.identifier = new_id
|
while Intervention.objects.filter(identifier=self.identifier).exists():
|
||||||
|
self.identifier = self.generate_new_identifier()
|
||||||
super().save(*args, **kwargs)
|
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:
|
def quality_check(self) -> list:
|
||||||
""" Quality check
|
""" Quality check
|
||||||
|
|
||||||
@ -298,7 +325,7 @@ class Intervention(BaseObject):
|
|||||||
ret_msgs.append(_("Registration office file number missing"))
|
ret_msgs.append(_("Registration office file number missing"))
|
||||||
|
|
||||||
if not self.responsible.conservation_file_number or len(self.responsible.conservation_file_number) == 0:
|
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:
|
except AttributeError:
|
||||||
# responsible data not found
|
# responsible data not found
|
||||||
ret_msgs.append(_("Responsible data missing"))
|
ret_msgs.append(_("Responsible data missing"))
|
||||||
|
@ -5,5 +5,5 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 30.11.20
|
Created on: 30.11.20
|
||||||
|
|
||||||
"""
|
"""
|
||||||
INTERVENTION_IDENTIFIER_LENGTH = 10
|
INTERVENTION_IDENTIFIER_LENGTH = 6
|
||||||
INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}"
|
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="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and has_access %}
|
{% 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">
|
<button class="btn btn-outline-default">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'leaf' %}
|
{% fa5_icon 'leaf' %}
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_default_member %}
|
{% 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' %}">
|
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
<td class="align-middle">{{intervention.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
|
<td class="align-middle">{{intervention.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr {% if not intervention.responsible.conservation_file_number %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
<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>
|
<td class="align-middle">{{intervention.responsible.conservation_file_number|default_if_none:""}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr {% if not intervention.responsible.handler %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
<tr {% if not intervention.responsible.handler %}class="alert alert-danger" title="{% trans 'Missing' %}" {% endif %}>
|
||||||
@ -123,7 +123,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<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, \
|
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, \
|
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"
|
app_name = "intervention"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", index_view, name="index"),
|
path("", index_view, name="index"),
|
||||||
path('new/', new_view, name='new'),
|
path('new/', new_view, name='new'),
|
||||||
|
path('new/id', new_id_view, name='new-id'),
|
||||||
path('<id>', open_view, name='open'),
|
path('<id>', open_view, name='open'),
|
||||||
path('<id>/log', log_view, name='log'),
|
path('<id>/log', log_view, name='log'),
|
||||||
path('<id>/edit', edit_view, name='edit'),
|
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.contrib.auth.decorators import login_required
|
||||||
from django.utils.translation import gettext_lazy as _
|
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 django.shortcuts import render, get_object_or_404
|
||||||
|
|
||||||
from intervention.forms import NewInterventionForm, EditInterventionForm, ShareInterventionForm, NewRevocationForm, \
|
from intervention.forms.forms import NewInterventionForm, EditInterventionForm
|
||||||
RunCheckForm, NewDeductionForm
|
from intervention.forms.modalForms import ShareInterventionModalForm, NewRevocationModalForm, \
|
||||||
|
RunCheckModalForm, NewDeductionModalForm
|
||||||
from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
|
from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
|
||||||
from intervention.tables import InterventionTable
|
from intervention.tables import InterventionTable
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
@ -13,7 +13,7 @@ from konova.decorators import *
|
|||||||
from konova.forms import SimpleGeomForm, NewDocumentForm, RemoveModalForm, RecordModalForm
|
from konova.forms import SimpleGeomForm, NewDocumentForm, RemoveModalForm, RecordModalForm
|
||||||
from konova.sub_settings.django_settings import DEFAULT_DATE_FORMAT
|
from konova.sub_settings.django_settings import DEFAULT_DATE_FORMAT
|
||||||
from konova.utils.documents import remove_document, get_document
|
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
|
from konova.utils.user_checks import in_group
|
||||||
|
|
||||||
|
|
||||||
@ -58,25 +58,54 @@ def new_view(request: HttpRequest):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
template = "konova/form.html"
|
template = "intervention/form/view.html"
|
||||||
form = NewInterventionForm(request.POST or None)
|
data_form = NewInterventionForm(request.POST or None)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if form.is_valid():
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
intervention = form.save(request.user)
|
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
||||||
messages.success(request, _("Intervention {} added").format(intervention.title))
|
intervention = data_form.save(request.user, geom_form)
|
||||||
return redirect("intervention:index")
|
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:
|
else:
|
||||||
messages.error(request, _("Invalid input"))
|
messages.error(request, FORM_INVALID)
|
||||||
else:
|
else:
|
||||||
# For clarification: nothing in this case
|
# For clarification: nothing in this case
|
||||||
pass
|
pass
|
||||||
context = {
|
context = {
|
||||||
"form": form,
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, template, 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
|
@login_required
|
||||||
def new_document_view(request: HttpRequest, id: str):
|
def new_document_view(request: HttpRequest, id: str):
|
||||||
""" Renders a form for uploading new documents
|
""" Renders a form for uploading new documents
|
||||||
@ -212,19 +241,26 @@ def edit_view(request: HttpRequest, id: str):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
template = "konova/form.html"
|
template = "intervention/form/view.html"
|
||||||
|
# Get object from db
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
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":
|
if request.method == "POST":
|
||||||
form = EditInterventionForm(request.POST or None, instance=intervention)
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
if form.is_valid():
|
# The data form takes the geom form for processing, as well as the performing user
|
||||||
intervention = form.save(request.user)
|
intervention = data_form.save(request.user, geom_form)
|
||||||
messages.success(request, _("{} edited").format(intervention))
|
messages.success(request, _("Intervention {} edited").format(intervention.identifier))
|
||||||
return redirect("intervention:index")
|
return redirect("intervention:open", id=intervention.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, _("Invalid input"))
|
messages.error(request, FORM_INVALID)
|
||||||
form = EditInterventionForm(instance=intervention)
|
else:
|
||||||
|
# For clarification: nothing in this case
|
||||||
|
pass
|
||||||
context = {
|
context = {
|
||||||
"form": form,
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, template, 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)
|
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(
|
return form.process_request(
|
||||||
request,
|
request,
|
||||||
msg_success=_("Share settings updated")
|
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)
|
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(
|
return form.process_request(
|
||||||
request,
|
request,
|
||||||
msg_success=_("Check performed"),
|
msg_success=_("Check performed"),
|
||||||
@ -363,7 +399,7 @@ def new_revocation_view(request: HttpRequest, id: str):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
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(
|
return form.process_request(
|
||||||
request,
|
request,
|
||||||
msg_success=_("Revocation added")
|
msg_success=_("Revocation added")
|
||||||
@ -407,7 +443,7 @@ def new_deduction_view(request: HttpRequest, id: str):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
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(
|
return form.process_request(
|
||||||
request,
|
request,
|
||||||
msg_success=_("Deduction added")
|
msg_success=_("Deduction added")
|
||||||
|
@ -10,7 +10,8 @@ from django.db.models import Q
|
|||||||
|
|
||||||
from codelist.models import KonovaCode
|
from codelist.models import KonovaCode
|
||||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, \
|
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 compensation.models import EcoAccount
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from organisation.models import Organisation
|
from organisation.models import Organisation
|
||||||
@ -45,12 +46,18 @@ class NonOfficialOrganisationAutocomplete(Select2QuerySetView):
|
|||||||
|
|
||||||
|
|
||||||
class EcoAccountAutocomplete(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):
|
def get_queryset(self):
|
||||||
if self.request.user.is_anonymous:
|
if self.request.user.is_anonymous:
|
||||||
return EcoAccount.objects.none()
|
return EcoAccount.objects.none()
|
||||||
qs = EcoAccount.objects.filter(
|
qs = EcoAccount.objects.filter(
|
||||||
deleted=None,
|
deleted=None,
|
||||||
recorded__isnull=False,
|
recorded__isnull=False,
|
||||||
|
users__in=[self.request.user],
|
||||||
)
|
)
|
||||||
if self.q:
|
if self.q:
|
||||||
qs = qs.filter(
|
qs = qs.filter(
|
||||||
@ -63,6 +70,11 @@ class EcoAccountAutocomplete(Select2QuerySetView):
|
|||||||
|
|
||||||
|
|
||||||
class InterventionAutocomplete(Select2QuerySetView):
|
class InterventionAutocomplete(Select2QuerySetView):
|
||||||
|
""" Autocomplete for intervention entries
|
||||||
|
|
||||||
|
Only returns entries that are accessible for the requesting user
|
||||||
|
|
||||||
|
"""
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
if self.request.user.is_anonymous:
|
if self.request.user.is_anonymous:
|
||||||
return Intervention.objects.none()
|
return Intervention.objects.none()
|
||||||
@ -104,10 +116,18 @@ class KonovaCodeAutocomplete(Select2QuerySetView):
|
|||||||
code_lists__in=[self.c]
|
code_lists__in=[self.c]
|
||||||
)
|
)
|
||||||
if self.q:
|
if self.q:
|
||||||
q_or = Q()
|
# Remove whitespaces from self.q and split input in all keywords (if multiple given)
|
||||||
q_or |= Q(long_name__icontains=self.q)
|
q = dict.fromkeys(self.q.strip().split(" "))
|
||||||
q_or |= Q(short_name__icontains=self.q)
|
# Create one filter looking up for all keys where all keywords can be found in the same result
|
||||||
qs = qs.filter(q_or)
|
_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
|
return qs
|
||||||
|
|
||||||
|
|
||||||
@ -120,6 +140,15 @@ class CompensationActionCodeAutocomplete(KonovaCodeAutocomplete):
|
|||||||
super().__init__(*args, **kwargs)
|
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):
|
class BiotopeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||||
"""
|
"""
|
||||||
Due to limitations of the django dal package, we need to subclass for each code list
|
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 functools import wraps
|
||||||
|
|
||||||
from django.contrib import messages
|
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.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from konova.settings import DEFAULT_GROUP, ETS_GROUP, ZB_GROUP
|
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):
|
def staff_required(function):
|
||||||
@ -80,7 +81,7 @@ def default_group_required(function):
|
|||||||
if has_group:
|
if has_group:
|
||||||
return function(request, *args, **kwargs)
|
return function(request, *args, **kwargs)
|
||||||
else:
|
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 redirect(request.META.get("HTTP_REFERER", reverse("home")))
|
||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
@ -100,7 +101,7 @@ def registration_office_group_required(function):
|
|||||||
if has_group:
|
if has_group:
|
||||||
return function(request, *args, **kwargs)
|
return function(request, *args, **kwargs)
|
||||||
else:
|
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 redirect(request.META.get("HTTP_REFERER", reverse("home")))
|
||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
@ -120,6 +121,35 @@ def conservation_office_group_required(function):
|
|||||||
if has_group:
|
if has_group:
|
||||||
return function(request, *args, **kwargs)
|
return function(request, *args, **kwargs)
|
||||||
else:
|
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 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 import forms
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.gis.forms import GeometryField, OSMWidget
|
from django.contrib.gis.forms import OSMWidget, MultiPolygonField
|
||||||
from django.contrib.gis.geos import Polygon
|
from django.contrib.gis.geos import Polygon, MultiPolygon
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.http import HttpRequest, HttpResponseRedirect
|
from django.http import HttpRequest, HttpResponseRedirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
@ -25,7 +25,8 @@ from compensation.models import EcoAccount, Compensation, EcoAccountDocument, Co
|
|||||||
from ema.models import Ema, EmaDocument
|
from ema.models import Ema, EmaDocument
|
||||||
from intervention.models import Intervention, Revocation, RevocationDocument, InterventionDocument
|
from intervention.models import Intervention, Revocation, RevocationDocument, InterventionDocument
|
||||||
from konova.contexts import BaseContext
|
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 konova.utils.message_templates import FORM_INVALID
|
||||||
from user.models import UserActionLogEntry, UserAction
|
from user.models import UserActionLogEntry, UserAction
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ class BaseForm(forms.Form):
|
|||||||
|
|
||||||
def add_placeholder_for_field(self, field: str, val):
|
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:
|
Args:
|
||||||
field (str): Field name
|
field (str): Field name
|
||||||
@ -184,16 +185,10 @@ class BaseModalForm(BaseForm, BSModalForm):
|
|||||||
"""
|
"""
|
||||||
is_modal_form = True
|
is_modal_form = True
|
||||||
render_submit = True
|
render_submit = True
|
||||||
full_width_fields = False
|
|
||||||
template = "modal/modal_form.html"
|
template = "modal/modal_form.html"
|
||||||
|
|
||||||
def __init__(self, full_width_fields: bool = True, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.full_width_fields = full_width_fields
|
|
||||||
super().__init__(*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):
|
def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None):
|
||||||
""" Generic processing of request
|
""" Generic processing of request
|
||||||
@ -243,32 +238,65 @@ class SimpleGeomForm(BaseForm):
|
|||||||
""" A geometry form for rendering geometry read-only using a widget
|
""" 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,
|
required=False,
|
||||||
disabled=True,
|
disabled=False,
|
||||||
widget=OSMWidget(
|
widget=OSMWidget(
|
||||||
attrs={
|
attrs={
|
||||||
"map_width": 600,
|
"map_width": 600,
|
||||||
"map_height": 400,
|
"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):
|
def __init__(self, *args, **kwargs):
|
||||||
|
read_only = kwargs.pop("read_only", True)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Initialize geometry
|
# Initialize geometry
|
||||||
try:
|
try:
|
||||||
geom = self.instance.geometry.geom
|
geom = self.instance.geometry.geom
|
||||||
if geom is None:
|
self.empty = geom.empty
|
||||||
raise AttributeError
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# catches if no geometry has been added, yet. Replace with empty placeholder polygon.
|
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
||||||
geom = Polygon.from_bbox([0, 0, 0, 0])
|
geom = None
|
||||||
# Zoom out to a very high level, so the user can see directly that there is no geometry for this entry
|
self.empty = True
|
||||||
self.fields["geom"].widget.attrs["default_zoom"] = 1
|
self.fields["geom"].widget.attrs["default_zoom"] = 1
|
||||||
|
|
||||||
self.initialize_form_field("geom", geom)
|
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):
|
class RemoveModalForm(BaseModalForm):
|
||||||
@ -294,15 +322,7 @@ class RemoveModalForm(BaseModalForm):
|
|||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
if isinstance(self.instance, BaseObject):
|
if isinstance(self.instance, BaseObject):
|
||||||
with transaction.atomic():
|
self.instance.mark_as_deleted(self.user)
|
||||||
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()
|
|
||||||
else:
|
else:
|
||||||
# If the class does not provide restorable delete functionality, we must delete the entry finally
|
# If the class does not provide restorable delete functionality, we must delete the entry finally
|
||||||
self.instance.delete()
|
self.instance.delete()
|
||||||
@ -316,6 +336,11 @@ class NewDocumentForm(BaseModalForm):
|
|||||||
label=_("Title"),
|
label=_("Title"),
|
||||||
label_suffix=_(""),
|
label_suffix=_(""),
|
||||||
max_length=500,
|
max_length=500,
|
||||||
|
widget=forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"class": "form-control",
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
creation_date = forms.DateField(
|
creation_date = forms.DateField(
|
||||||
label=_("Created on"),
|
label=_("Created on"),
|
||||||
@ -325,6 +350,7 @@ class NewDocumentForm(BaseModalForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"type": "date",
|
"type": "date",
|
||||||
"data-provide": "datepicker",
|
"data-provide": "datepicker",
|
||||||
|
"class": "form-control",
|
||||||
},
|
},
|
||||||
format="%d.%m.%Y"
|
format="%d.%m.%Y"
|
||||||
)
|
)
|
||||||
@ -335,7 +361,7 @@ class NewDocumentForm(BaseModalForm):
|
|||||||
help_text=_("Must be smaller than 15 Mb"),
|
help_text=_("Must be smaller than 15 Mb"),
|
||||||
widget=forms.FileInput(
|
widget=forms.FileInput(
|
||||||
attrs={
|
attrs={
|
||||||
"class": "w-75"
|
"class": "form-control-file",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -349,6 +375,7 @@ class NewDocumentForm(BaseModalForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"cols": 30,
|
"cols": 30,
|
||||||
"rows": 5,
|
"rows": 5,
|
||||||
|
"class": "form-control",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -21,7 +21,7 @@ class Command(BaseCommand):
|
|||||||
len_ids = len(identifiers)
|
len_ids = len(identifiers)
|
||||||
while len_ids < max_iterations:
|
while len_ids < max_iterations:
|
||||||
tmp_intervention = Intervention()
|
tmp_intervention = Intervention()
|
||||||
_id = tmp_intervention._generate_new_identifier()
|
_id = tmp_intervention.generate_new_identifier()
|
||||||
len_ids = len(identifiers)
|
len_ids = len(identifiers)
|
||||||
if _id not in identifiers:
|
if _id not in identifiers:
|
||||||
if len_ids % (max_iterations/5) == 0:
|
if len_ids % (max_iterations/5) == 0:
|
||||||
|
@ -9,6 +9,8 @@ import os
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
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.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.gis.db.models import MultiPolygonField
|
from django.contrib.gis.db.models import MultiPolygonField
|
||||||
@ -61,8 +63,20 @@ class BaseResource(UuidModel):
|
|||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
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()
|
self.created.delete()
|
||||||
|
except (ObjectDoesNotExist, AttributeError) as e:
|
||||||
|
# Object does not exist anymore - we can skip this
|
||||||
|
pass
|
||||||
super().delete()
|
super().delete()
|
||||||
|
|
||||||
|
|
||||||
@ -81,14 +95,13 @@ class BaseObject(BaseResource):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def mark_as_deleted(self, user: User):
|
||||||
""" Custom delete functionality
|
""" Mark an entry as deleted
|
||||||
|
|
||||||
Does not delete from database but sets a timestamp for being deleted on and which user deleted the object
|
Does not delete from database but sets a timestamp for being deleted on and which user deleted the object
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*args ():
|
user (User): The performing user
|
||||||
**kwargs ():
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
@ -97,13 +110,14 @@ class BaseObject(BaseResource):
|
|||||||
# Nothing to do here
|
# Nothing to do here
|
||||||
return
|
return
|
||||||
|
|
||||||
_user = kwargs.get("user", None)
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
action = UserActionLogEntry.objects.create(
|
action = UserActionLogEntry.objects.create(
|
||||||
user=_user,
|
user=user,
|
||||||
action=UserAction.DELETED
|
action=UserAction.DELETED,
|
||||||
|
timestamp=timezone.now()
|
||||||
)
|
)
|
||||||
self.deleted = action
|
self.deleted = action
|
||||||
|
self.log.add(action)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def add_log_entry(self, action: UserAction, user: User, comment: str):
|
def add_log_entry(self, action: UserAction, user: User, comment: str):
|
||||||
@ -139,7 +153,7 @@ class BaseObject(BaseResource):
|
|||||||
else:
|
else:
|
||||||
return User.objects.none()
|
return User.objects.none()
|
||||||
|
|
||||||
def _generate_new_identifier(self) -> str:
|
def generate_new_identifier(self) -> str:
|
||||||
""" Generates a new identifier for the intervention object
|
""" Generates a new identifier for the intervention object
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -177,7 +191,9 @@ class BaseObject(BaseResource):
|
|||||||
curr_year = str(_now.year)
|
curr_year = str(_now.year)
|
||||||
rand_str = generate_random_string(
|
rand_str = generate_random_string(
|
||||||
length=definitions[self.__class__]["length"],
|
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)
|
_str = "{}{}-{}".format(curr_month, curr_year, rand_str)
|
||||||
return definitions[self.__class__]["template"].format(_str)
|
return definitions[self.__class__]["template"].format(_str)
|
||||||
|
@ -60,6 +60,14 @@ a {
|
|||||||
color: var(--rlp-red);
|
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{
|
.body-content{
|
||||||
margin: 1rem 0rem 0 0rem;
|
margin: 1rem 0rem 0 0rem;
|
||||||
}
|
}
|
||||||
@ -133,6 +141,13 @@ a {
|
|||||||
height: 8rem;
|
height: 8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Overwrites bootstrap .btn:focus box shadow color
|
||||||
|
*/
|
||||||
|
.btn:focus{
|
||||||
|
box-shadow: 0 0 5px .2rem var(--rlp-gray-light);
|
||||||
|
}
|
||||||
|
|
||||||
.btn-default{
|
.btn-default{
|
||||||
color: white;
|
color: white;
|
||||||
background-color: var(--rlp-red);
|
background-color: var(--rlp-red);
|
||||||
@ -171,13 +186,6 @@ a {
|
|||||||
background-color: var(--rlp-gray-light);
|
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{
|
.check-star{
|
||||||
color: goldenrod;
|
color: goldenrod;
|
||||||
}
|
}
|
||||||
@ -216,3 +224,6 @@ No other approach worked to get the autocomplete fields to full width of parent
|
|||||||
.select2-container{
|
.select2-container{
|
||||||
width: 100% !important;
|
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 %}
|
{% block body %}
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{% include 'form/generic_table_form.html' %}
|
{% include 'form/table/generic_table_form.html' %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% 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, \
|
from konova.autocompletes import OrganisationAutocomplete, NonOfficialOrganisationAutocomplete, EcoAccountAutocomplete, \
|
||||||
InterventionAutocomplete, CompensationActionCodeAutocomplete, BiotopeCodeAutocomplete, LawCodeAutocomplete, \
|
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.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
||||||
from konova.sso.sso import KonovaSSOClient
|
from konova.sso.sso import KonovaSSOClient
|
||||||
from konova.views import logout_view, home_view, remove_deadline_view
|
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/orgs/other", NonOfficialOrganisationAutocomplete.as_view(), name="other-orgs-autocomplete"),
|
||||||
path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"),
|
path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"),
|
||||||
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="interventions-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/biotope", BiotopeCodeAutocomplete.as_view(), name="codes-biotope-autocomplete"),
|
||||||
path("atcmplt/codes/law", LawCodeAutocomplete.as_view(), name="codes-law-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/reg-off", RegistrationOfficeCodeAutocomplete.as_view(), name="codes-registration-office-autocomplete"),
|
||||||
path("atcmplt/codes/cons-off", ConservationOfficeCodeAutocomplete.as_view(), name="codes-conservation-office-autocomplete"),
|
path("atcmplt/codes/cons-off", ConservationOfficeCodeAutocomplete.as_view(), name="codes-conservation-office-autocomplete"),
|
||||||
]
|
]
|
||||||
|
@ -9,14 +9,18 @@ import random
|
|||||||
import string
|
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
|
Generates a random string of variable length
|
||||||
"""
|
"""
|
||||||
if only_numbers:
|
elements = []
|
||||||
elements = string.digits
|
if use_numbers:
|
||||||
else:
|
elements.append(string.digits)
|
||||||
elements = string.ascii_letters
|
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))
|
ret_val = "".join(random.choice(elements) for i in range(length))
|
||||||
return ret_val
|
return ret_val
|
||||||
|
@ -10,3 +10,6 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
FORM_INVALID = _("There was an error on this form.")
|
FORM_INVALID = _("There was an error on this form.")
|
||||||
INTERVENTION_INVALID = _("There are errors in this intervention.")
|
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
|
django-tables2==2.3.4
|
||||||
idna==2.10
|
idna==2.10
|
||||||
importlib-metadata==2.1.1
|
importlib-metadata==2.1.1
|
||||||
pkg-resources==0.0.0
|
itsdangerous
|
||||||
psycopg2==2.8.6
|
psycopg2-binary
|
||||||
pytz==2020.4
|
pytz==2020.4
|
||||||
requests==2.25.0
|
requests==2.25.0
|
||||||
six==1.15.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 %}
|
{% endif %}
|
||||||
<form method="post" action="{{ form.action_url }}" {% for attr_key, attr_val in form.form_attrs.items %} {{attr_key}}="{{attr_val}}"{% endfor %}>
|
<form method="post" action="{{ form.action_url }}" {% for attr_key, attr_val in form.form_attrs.items %} {{attr_key}}="{{attr_val}}"{% endfor %}>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'form/generic_table_form_body.html' %}
|
{% include 'form/table/generic_table_form_body.html' %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md">
|
<div class="col-6">
|
||||||
<a href="{{ form.cancel_redirect }}">
|
<a href="{{ form.cancel_redirect }}">
|
||||||
<button class="btn btn-default" type="button" title="{% trans 'Cancel' %}">{% trans 'Cancel' %}</button>
|
<button class="btn btn-default" type="button" title="{% trans 'Cancel' %}">{% trans 'Cancel' %}</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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>
|
<button class="btn btn-default" type="submit" title="{% trans 'Save' %}">{% trans 'Save' %}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -6,6 +6,7 @@
|
|||||||
<tr title="{{ field.help_text }}" class="{% if field.errors %}alert-danger{% endif %}">
|
<tr title="{{ field.help_text }}" class="{% if field.errors %}alert-danger{% endif %}">
|
||||||
<th scope="row" class="col-sm-3">
|
<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>
|
<label for="id_{{ field.name }}">{{ field.label }}<span class="label-required">{% if field.field.required %}*{% endif %}</span></label>
|
||||||
|
<br>
|
||||||
<small>{{ field.help_text }}</small>
|
<small>{{ field.help_text }}</small>
|
||||||
</th>
|
</th>
|
||||||
<td class="col-sm-9">
|
<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.
|
Encapsules the rendering and initializing of a geometry view component, e.g. used in the detail views.
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% if geom_form.empty %}
|
||||||
{% if geom_form.area == 0 %}
|
<div class="w-100">
|
||||||
<div class="alert alert-info">{% trans 'No geometry added, yet.' %}</div>
|
<div class="alert alert-info">{% trans 'No geometry added, yet.' %}</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{geom_form.media}}
|
{{geom_form.media}}
|
||||||
{{geom_form.geom}}
|
{{geom_form.geom}}
|
@ -18,7 +18,7 @@
|
|||||||
<article>
|
<article>
|
||||||
{{ form.form_caption }}
|
{{ form.form_caption }}
|
||||||
</article>
|
</article>
|
||||||
{% include 'form/generic_table_form_body.html' %}
|
{% include 'form/table/generic_table_form_body.html' %}
|
||||||
</div>
|
</div>
|
||||||
{% if form.render_submit %}
|
{% if form.render_submit %}
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% include 'form/generic_table_form.html' %}
|
{% include 'form/table/generic_table_form.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user