diff --git a/codelist/management/commands/update_codelist.py b/codelist/management/commands/update_codelist.py index cd9abe1..c6205f0 100644 --- a/codelist/management/commands/update_codelist.py +++ b/codelist/management/commands/update_codelist.py @@ -13,7 +13,7 @@ from codelist.models import KonovaCode, KonovaCodeList from codelist.settings import CODELIST_INTERVENTION_HANDLER_ID, CODELIST_CONSERVATION_OFFICE_ID, \ CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, CODELIST_COMPENSATION_HANDLER_ID, \ CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_CLASS_ID, CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID, \ - CODELIST_COMPENSATION_COMBINATION_ID, CODELIST_BASE_URL, CODELIST_PROCESS_TYPE_ID + CODELIST_COMPENSATION_FUNDING_ID, CODELIST_BASE_URL, CODELIST_PROCESS_TYPE_ID bool_map = { "true": True, @@ -35,7 +35,7 @@ class Command(BaseCommand): CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_CLASS_ID, CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID, - CODELIST_COMPENSATION_COMBINATION_ID, + CODELIST_COMPENSATION_FUNDING_ID, CODELIST_PROCESS_TYPE_ID, ] self._write_warning("Fetching codes...") diff --git a/codelist/models.py b/codelist/models.py index c455d1e..10c197a 100644 --- a/codelist/models.py +++ b/codelist/models.py @@ -53,8 +53,9 @@ class KonovaCode(models.Model): if self.parent: ret_val += self.parent.long_name + " > " ret_val += self.long_name - if self.short_name: - ret_val += " ({})".format(self.short_name) + if self.short_name and self.short_name != self.long_name: + # Only add short name, if we won't have stupid repition like 'thing a (thing a)' due to misused long-short names + ret_val += f" ({self.short_name})" return ret_val @property diff --git a/codelist/settings.py b/codelist/settings.py index e2548b4..15c523a 100644 --- a/codelist/settings.py +++ b/codelist/settings.py @@ -21,4 +21,4 @@ CODELIST_COMPENSATION_HANDLER_ID = 1052 # CLEingreifer CODELIST_COMPENSATION_ACTION_ID = 1026 # CLMassnahmedetail CODELIST_COMPENSATION_ACTION_CLASS_ID = 1034 # CLMassnahmeklasse CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID = 1028 # CLMassnahmetyp, CEF and stuff -CODELIST_COMPENSATION_COMBINATION_ID = 1049 # CLKombimassnahme +CODELIST_COMPENSATION_FUNDING_ID = 1049 # CLKombimassnahme diff --git a/compensation/account_urls.py b/compensation/account_urls.py index 511ee60..158e148 100644 --- a/compensation/account_urls.py +++ b/compensation/account_urls.py @@ -11,6 +11,7 @@ from compensation.views.eco_account_views import * urlpatterns = [ path("", index_view, name="acc-index"), path('new/', new_view, name='acc-new'), + path('new/id', new_id_view, name='acc-new-id'), path('', open_view, name='acc-open'), path('/log', log_view, name='acc-log'), path('/record', record_view, name='acc-record'), diff --git a/compensation/comp_urls.py b/compensation/comp_urls.py index 84979d8..5ec578b 100644 --- a/compensation/comp_urls.py +++ b/compensation/comp_urls.py @@ -11,6 +11,8 @@ from compensation.views.compensation_views import * urlpatterns = [ # Main compensation path("", index_view, name="index"), + path('new/id', new_id_view, name='new-id'), + path('new/', new_view, name='new'), path('new', new_view, name='new'), path('', open_view, name='open'), path('/log', log_view, name='log'), diff --git a/compensation/forms/forms.py b/compensation/forms/forms.py new file mode 100644 index 0000000..000cea2 --- /dev/null +++ b/compensation/forms/forms.py @@ -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 diff --git a/compensation/forms.py b/compensation/forms/modalForms.py similarity index 86% rename from compensation/forms.py rename to compensation/forms/modalForms.py index 9d2df37..2cc1064 100644 --- a/compensation/forms.py +++ b/compensation/forms/modalForms.py @@ -2,7 +2,7 @@ Author: Michel Peltriaux Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany Contact: michel.peltriaux@sgdnord.rlp.de -Created on: 04.12.20 +Created on: 04.10.21 """ from bootstrap_modal_forms.utils import is_ajax @@ -12,40 +12,34 @@ from django.contrib import messages from django.db import transaction from django.http import HttpRequest, HttpResponseRedirect from django.shortcuts import render -from django.utils.translation import gettext_lazy as _ -from django.utils.translation import pgettext_lazy as _con +from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _ from codelist.models import KonovaCode from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION_ID -from compensation.models import Payment, CompensationState, CompensationAction, UnitChoices +from compensation.models import Payment, CompensationState, UnitChoices, CompensationAction from konova.contexts import BaseContext -from konova.forms import BaseForm, BaseModalForm -from konova.models import Deadline, DeadlineType +from konova.forms import BaseModalForm +from konova.models import DeadlineType, Deadline from konova.utils.message_templates import FORM_INVALID from user.models import UserActionLogEntry, UserAction -class NewCompensationForm(BaseForm): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def save(self): - with transaction.atomic(): - user_action = UserActionLogEntry.objects.create( - user=self.user, - action=UserAction.CREATED - ) - # Save action to log - - class NewPaymentForm(BaseModalForm): + """ Form handling payment related input + + """ amount = forms.DecimalField( min_value=0.00, decimal_places=2, label=_con("money", "Amount"), # contextual translation label_suffix=_(""), help_text=_("in Euro"), + widget=forms.NumberInput( + attrs={ + "class": "form-control", + "placeholder": "0,00", + } + ) ) due = forms.DateField( label=_("Due on"), @@ -56,6 +50,7 @@ class NewPaymentForm(BaseModalForm): attrs={ "type": "date", "data-provide": "datepicker", + "class": "form-control", }, format="%d.%m.%Y" ) @@ -69,7 +64,7 @@ class NewPaymentForm(BaseModalForm): widget=forms.Textarea( attrs={ "rows": 5, - "class": "w-100" + "class": "form-control" } ) ) @@ -79,7 +74,6 @@ class NewPaymentForm(BaseModalForm): self.intervention = self.instance self.form_title = _("Payment") self.form_caption = _("Add a payment for intervention '{}'").format(self.intervention.title) - self.add_placeholder_for_field("amount", "0,00") def is_valid(self): """ @@ -129,6 +123,12 @@ class NewPaymentForm(BaseModalForm): class NewStateModalForm(BaseModalForm): + """ Form handling state related input + + Compensation states refer to 'before' and 'after' states of a compensated surface. Basically it means: + What has been on this area before changes/compensations have been applied and what will be the result ('after')? + + """ biotope_type = forms.ModelChoiceField( label=_("Biotope Type"), label_suffix="", @@ -152,14 +152,19 @@ class NewStateModalForm(BaseModalForm): label=_("Surface"), label_suffix="", required=True, - help_text=_("in m²") + help_text=_("in m²"), + widget=forms.NumberInput( + attrs={ + "class": "form-control", + "placeholder": "0,00" + } + ) ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.form_title = _("New state") self.form_caption = _("Insert data for the new state") - self.add_placeholder_for_field("surface", "0,00") def save(self, is_before_state: bool = False): with transaction.atomic(): @@ -232,6 +237,9 @@ class NewStateModalForm(BaseModalForm): class NewDeadlineModalForm(BaseModalForm): + """ Form handling deadline related input + + """ type = forms.ChoiceField( label=_("Deadline Type"), label_suffix="", @@ -240,7 +248,7 @@ class NewDeadlineModalForm(BaseModalForm): choices=DeadlineType.choices, widget=forms.Select( attrs={ - "class": "custom-select" + "class": "form-control" } ) ) @@ -253,6 +261,7 @@ class NewDeadlineModalForm(BaseModalForm): attrs={ "type": "date", "data-provide": "datepicker", + "class": "form-control", }, format="%d.%m.%Y" ) @@ -267,6 +276,7 @@ class NewDeadlineModalForm(BaseModalForm): attrs={ "cols": 30, "rows": 5, + "class": "form-control", } ) ) @@ -301,6 +311,13 @@ class NewDeadlineModalForm(BaseModalForm): class NewActionModalForm(BaseModalForm): + """ Form handling action related input + + Compensation actions are the actions performed on the area, which shall be compensated. Actions will change the + surface of the area, the biotopes, and have an environmental impact. With actions the before-after states can change + (not in the process logic in Konova, but in the real world). + + """ action_type = forms.ModelChoiceField( label=_("Action Type"), label_suffix="", @@ -326,7 +343,7 @@ class NewActionModalForm(BaseModalForm): choices=UnitChoices.choices, widget=forms.Select( attrs={ - "class": "custom-select" + "class": "form-control" } ) ) @@ -337,6 +354,12 @@ class NewActionModalForm(BaseModalForm): help_text=_("Insert the amount"), decimal_places=2, min_value=0.00, + widget=forms.NumberInput( + attrs={ + "class": "form-control", + "placeholder": "0,00", + } + ) ) comment = forms.CharField( required=False, @@ -347,7 +370,7 @@ class NewActionModalForm(BaseModalForm): widget=forms.Textarea( attrs={ "rows": 5, - "class": "w-100" + "class": "form-control", } ) ) @@ -356,7 +379,6 @@ class NewActionModalForm(BaseModalForm): super().__init__(*args, **kwargs) self.form_title = _("New action") self.form_caption = _("Insert data for the new action") - self.add_placeholder_for_field("amount", "0,00") def save(self): with transaction.atomic(): @@ -380,5 +402,4 @@ class NewActionModalForm(BaseModalForm): self.instance.save() self.instance.log.add(edited_action) self.instance.actions.add(comp_action) - return comp_action - + return comp_action \ No newline at end of file diff --git a/compensation/models.py b/compensation/models.py index 4ab3f95..a3384ba 100644 --- a/compensation/models.py +++ b/compensation/models.py @@ -16,7 +16,7 @@ from django.utils.translation import gettext_lazy as _ from codelist.models import KonovaCode from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, \ - CODELIST_COMPENSATION_COMBINATION_ID + CODELIST_COMPENSATION_FUNDING_ID from intervention.models import Intervention, ResponsibilityData from konova.models import BaseObject, BaseResource, Geometry, UuidModel, AbstractDocument, \ generate_document_file_upload_path @@ -144,7 +144,7 @@ class AbstractCompensation(BaseObject): null=True, blank=True, limit_choices_to={ - "code_lists__in": [CODELIST_COMPENSATION_COMBINATION_ID], + "code_lists__in": [CODELIST_COMPENSATION_FUNDING_ID], "is_selectable": True, "is_archived": False, }, @@ -185,9 +185,9 @@ class Compensation(AbstractCompensation): def save(self, *args, **kwargs): if self.identifier is None or len(self.identifier) == 0: # Create new identifier - new_id = self._generate_new_identifier() + new_id = self.generate_new_identifier() while Compensation.objects.filter(identifier=new_id).exists(): - new_id = self._generate_new_identifier() + new_id = self.generate_new_identifier() self.identifier = new_id super().save(*args, **kwargs) @@ -322,9 +322,9 @@ class EcoAccount(AbstractCompensation): def save(self, *args, **kwargs): if self.identifier is None or len(self.identifier) == 0: # Create new identifier - new_id = self._generate_new_identifier() + new_id = self.generate_new_identifier() while EcoAccount.objects.filter(identifier=new_id).exists(): - new_id = self._generate_new_identifier() + new_id = self.generate_new_identifier() self.identifier = new_id super().save(*args, **kwargs) @@ -355,28 +355,28 @@ class EcoAccount(AbstractCompensation): """ return self.after_states.all().aggregate(Sum("surface"))["surface__sum"] or 0 - def get_available_rest(self, as_percentage: bool = False) -> float: + def get_available_rest(self) -> (float, float): """ Calculates available rest surface of the eco account Args: - as_percentage (bool): Whether to return the result as m² or % Returns: - + ret_val_total (float): Total amount + ret_val_relative (float): Amount as percentage (0-100) """ deductions = self.deductions.filter( intervention__deleted=None, ) deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0 available_surfaces = self.deductable_surface or deductions_surfaces ## no division by zero - ret_val = available_surfaces - deductions_surfaces - - if as_percentage: - if available_surfaces > 0: - ret_val = int((ret_val / available_surfaces) * 100) - else: - ret_val = 0 - return ret_val + ret_val_total = available_surfaces - deductions_surfaces + + if available_surfaces > 0: + ret_val_relative = int((ret_val_total / available_surfaces) * 100) + else: + ret_val_relative = 0 + + return ret_val_total, ret_val_relative def get_LANIS_link(self) -> str: """ Generates a link for LANIS depending on the geometry diff --git a/compensation/settings.py b/compensation/settings.py index 7953f34..2ac5464 100644 --- a/compensation/settings.py +++ b/compensation/settings.py @@ -5,8 +5,8 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 18.12.20 """ -COMPENSATION_IDENTIFIER_LENGTH = 10 +COMPENSATION_IDENTIFIER_LENGTH = 6 COMPENSATION_IDENTIFIER_TEMPLATE = "KOM-{}" -ECO_ACCOUNT_IDENTIFIER_LENGTH = 10 +ECO_ACCOUNT_IDENTIFIER_LENGTH = 6 ECO_ACCOUNT_IDENTIFIER_TEMPLATE = "OEK-{}" \ No newline at end of file diff --git a/compensation/tables.py b/compensation/tables.py index c7487dc..5f9f6e7 100644 --- a/compensation/tables.py +++ b/compensation/tables.py @@ -5,6 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 01.12.20 """ +from django.contrib.auth.models import User from django.http import HttpRequest from django.template.loader import render_to_string from django.urls import reverse @@ -148,6 +149,8 @@ class CompensationTable(BaseTable): """ html = "" + if value is None: + value = User.objects.none() has_access = value.filter( username=self.user.username ).exists() @@ -236,8 +239,8 @@ class EcoAccountTable(BaseTable): Returns: """ - value = record.get_available_rest(as_percentage=True) - html = render_to_string("konova/custom_widgets/progressbar.html", {"value": value}) + value_total, value_relative = record.get_available_rest() + html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative}) return format_html(html) def render_r(self, value, record: EcoAccount): diff --git a/compensation/templates/compensation/detail/compensation/includes/comment.html b/compensation/templates/compensation/detail/compensation/includes/comment.html new file mode 100644 index 0000000..aff3dec --- /dev/null +++ b/compensation/templates/compensation/detail/compensation/includes/comment.html @@ -0,0 +1,23 @@ +{% load i18n fontawesome_5 %} + +{% if obj.comment %} +
+
+
+
+
+
+ {% fa5_icon 'info-circle' %} + {% trans 'Comment' %} +
+
+
+
+
+
+ {{obj.comment}} +
+
+
+
+{% endif %} \ No newline at end of file diff --git a/compensation/templates/compensation/detail/compensation/includes/controls.html b/compensation/templates/compensation/detail/compensation/includes/controls.html index 0f5149a..5d6f61b 100644 --- a/compensation/templates/compensation/detail/compensation/includes/controls.html +++ b/compensation/templates/compensation/detail/compensation/includes/controls.html @@ -13,7 +13,7 @@ {% if has_access %} {% if is_default_member %} - + diff --git a/compensation/templates/compensation/detail/compensation/view.html b/compensation/templates/compensation/detail/compensation/view.html index 6401cdb..487f274 100644 --- a/compensation/templates/compensation/detail/compensation/view.html +++ b/compensation/templates/compensation/detail/compensation/view.html @@ -75,7 +75,7 @@
{% empty %} - None + {% trans 'None' %} {% endfor %} @@ -99,7 +99,12 @@
- {% include 'map/geom_form.html' %} +
+ {% include 'map/geom_form.html' %} +
+
+ {% include 'compensation/detail/compensation/includes/comment.html' %} +

diff --git a/compensation/templates/compensation/detail/eco_account/includes/comment.html b/compensation/templates/compensation/detail/eco_account/includes/comment.html new file mode 100644 index 0000000..aff3dec --- /dev/null +++ b/compensation/templates/compensation/detail/eco_account/includes/comment.html @@ -0,0 +1,23 @@ +{% load i18n fontawesome_5 %} + +{% if obj.comment %} +
+
+
+
+
+
+ {% fa5_icon 'info-circle' %} + {% trans 'Comment' %} +
+
+
+
+
+
+ {{obj.comment}} +
+
+
+
+{% endif %} \ No newline at end of file diff --git a/compensation/templates/compensation/detail/eco_account/includes/controls.html b/compensation/templates/compensation/detail/eco_account/includes/controls.html index e754361..76e3b2b 100644 --- a/compensation/templates/compensation/detail/eco_account/includes/controls.html +++ b/compensation/templates/compensation/detail/eco_account/includes/controls.html @@ -24,7 +24,7 @@ {% endif %} {% endif %} {% if is_default_member %} -
+ diff --git a/compensation/templates/compensation/detail/eco_account/view.html b/compensation/templates/compensation/detail/eco_account/view.html index f03c22b..efe44bb 100644 --- a/compensation/templates/compensation/detail/eco_account/view.html +++ b/compensation/templates/compensation/detail/eco_account/view.html @@ -30,12 +30,12 @@ {% trans 'Title' %} {{obj.title}} - + {% trans 'Available' %} - {{obj.deductions_surface_sum|floatformat:2}} / {{obj.deductable_surface|floatformat:2}} m² + {{available_total|floatformat:2}} / {{obj.deductable_surface|default_if_none:0.00|floatformat:2}} m² {% with available as value %} - {% include 'konova/custom_widgets/progressbar.html' %} + {% include 'konova/widgets/progressbar.html' %} {% endwith %} @@ -58,7 +58,7 @@ {{obj.responsible.conservation_office.str_as_office|default_if_none:""}} - {% trans 'Conversation office file number' %} + {% trans 'Conservation office file number' %} {{obj.responsible.conservation_file_number|default_if_none:""}} @@ -98,7 +98,12 @@
- {% include 'map/geom_form.html' %} +
+ {% include 'map/geom_form.html' %} +
+
+ {% include 'compensation/detail/compensation/includes/comment.html' %} +

diff --git a/compensation/templates/compensation/form/view.html b/compensation/templates/compensation/form/view.html new file mode 100644 index 0000000..fcc2569 --- /dev/null +++ b/compensation/templates/compensation/form/view.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} +{% load i18n l10n %} + +{% block body %} + {% include 'form/collapsable/form.html' %} +{% endblock %} \ No newline at end of file diff --git a/compensation/views/compensation_views.py b/compensation/views/compensation_views.py index 0809107..0d56d81 100644 --- a/compensation/views/compensation_views.py +++ b/compensation/views/compensation_views.py @@ -1,16 +1,19 @@ from django.contrib.auth.decorators import login_required from django.db.models import Sum -from django.http import HttpRequest -from django.shortcuts import render, get_object_or_404 +from django.http import HttpRequest, JsonResponse +from django.shortcuts import render from django.utils.translation import gettext_lazy as _ -from compensation.forms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm +from compensation.forms.forms import NewCompensationForm, EditCompensationForm +from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument from compensation.tables import CompensationTable +from intervention.models import Intervention from konova.contexts import BaseContext from konova.decorators import * from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm from konova.utils.documents import get_document, remove_document +from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED from konova.utils.user_checks import in_group @@ -44,16 +47,100 @@ def index_view(request: HttpRequest): @login_required @default_group_required -def new_view(request: HttpRequest): - # ToDo - pass +@shared_access_required(Intervention, "intervention_id") +def new_view(request: HttpRequest, intervention_id: str = None): + """ + Renders a view for a new compensation creation + + Args: + request (HttpRequest): The incoming request + + Returns: + + """ + template = "compensation/form/view.html" + data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id) + geom_form = SimpleGeomForm(request.POST or None, read_only=False) + if request.method == "POST": + if data_form.is_valid() and geom_form.is_valid(): + generated_identifier = data_form.cleaned_data.get("identifier", None) + comp = data_form.save(request.user, geom_form) + if generated_identifier != comp.identifier: + messages.info( + request, + IDENTIFIER_REPLACED.format( + generated_identifier, + comp.identifier + ) + ) + messages.success(request, _("Compensation {} added").format(comp.identifier)) + return redirect("compensation:open", id=comp.id) + else: + messages.error(request, FORM_INVALID) + else: + # For clarification: nothing in this case + pass + context = { + "form": data_form, + "geom_form": geom_form, + } + context = BaseContext(request, context).context + return render(request, template, context) + + +@login_required +def new_id_view(request: HttpRequest): + """ JSON endpoint + + Provides fetching of free identifiers for e.g. AJAX calls + + """ + tmp = Compensation() + identifier = tmp.generate_new_identifier() + while Compensation.objects.filter(identifier=identifier).exists(): + identifier = tmp.generate_new_identifier() + return JsonResponse( + data={ + "identifier": identifier + } + ) @login_required @default_group_required def edit_view(request: HttpRequest, id: str): - # ToDo - pass + """ + Renders a view for editing compensations + + Args: + request (HttpRequest): The incoming request + + Returns: + + """ + template = "compensation/form/view.html" + # Get object from db + comp = get_object_or_404(Compensation, id=id) + # Create forms, initialize with values from db/from POST request + data_form = EditCompensationForm(request.POST or None, instance=comp) + geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp) + if request.method == "POST": + if data_form.is_valid() and geom_form.is_valid(): + # The data form takes the geom form for processing, as well as the performing user + comp = data_form.save(request.user, geom_form) + messages.success(request, _("Compensation {} edited").format(comp.identifier)) + return redirect("compensation:open", id=comp.id) + else: + messages.error(request, FORM_INVALID) + else: + # For clarification: nothing in this case + pass + context = { + "form": data_form, + "geom_form": geom_form, + } + context = BaseContext(request, context).context + return render(request, template, context) @login_required diff --git a/compensation/views/eco_account_views.py b/compensation/views/eco_account_views.py index c53f534..476bd97 100644 --- a/compensation/views/eco_account_views.py +++ b/compensation/views/eco_account_views.py @@ -5,23 +5,26 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 09.08.21 """ +from django.contrib import messages from django.db.models import Sum from django.urls import reverse from django.utils.translation import gettext_lazy as _ from django.contrib.auth.decorators import login_required from django.core.exceptions import ObjectDoesNotExist -from django.http import HttpRequest, Http404 -from django.shortcuts import render, get_object_or_404 +from django.http import HttpRequest, Http404, JsonResponse +from django.shortcuts import render, get_object_or_404, redirect -from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm +from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm +from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm from compensation.models import EcoAccount, EcoAccountDocument from compensation.tables import EcoAccountTable -from intervention.forms import NewDeductionForm +from intervention.forms.modalForms import NewDeductionModalForm from konova.contexts import BaseContext from konova.decorators import any_group_check, default_group_required, conservation_office_group_required from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP from konova.utils.documents import get_document, remove_document +from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID from konova.utils.user_checks import in_group @@ -56,15 +59,98 @@ def index_view(request: HttpRequest): @login_required @default_group_required def new_view(request: HttpRequest): - # ToDo - pass + """ + Renders a view for a new eco account creation + + Args: + request (HttpRequest): The incoming request + + Returns: + + """ + template = "compensation/form/view.html" + data_form = NewEcoAccountForm(request.POST or None) + geom_form = SimpleGeomForm(request.POST or None, read_only=False) + if request.method == "POST": + if data_form.is_valid() and geom_form.is_valid(): + generated_identifier = data_form.cleaned_data.get("identifier", None) + acc = data_form.save(request.user, geom_form) + if generated_identifier != acc.identifier: + messages.info( + request, + IDENTIFIER_REPLACED.format( + generated_identifier, + acc.identifier + ) + ) + messages.success(request, _("Eco-Account {} added").format(acc.identifier)) + return redirect("compensation:acc-open", id=acc.id) + else: + messages.error(request, FORM_INVALID) + else: + # For clarification: nothing in this case + pass + context = { + "form": data_form, + "geom_form": geom_form, + } + context = BaseContext(request, context).context + return render(request, template, context) + + +@login_required +def new_id_view(request: HttpRequest): + """ JSON endpoint + + Provides fetching of free identifiers for e.g. AJAX calls + + """ + tmp = EcoAccount() + identifier = tmp.generate_new_identifier() + while EcoAccount.objects.filter(identifier=identifier).exists(): + identifier = tmp.generate_new_identifier() + return JsonResponse( + data={ + "identifier": identifier + } + ) @login_required @default_group_required def edit_view(request: HttpRequest, id: str): - # ToDo - pass + """ + Renders a view for editing compensations + + Args: + request (HttpRequest): The incoming request + + Returns: + + """ + template = "compensation/form/view.html" + # Get object from db + acc = get_object_or_404(EcoAccount, id=id) + # Create forms, initialize with values from db/from POST request + data_form = EditEcoAccountForm(request.POST or None, instance=acc) + geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc) + if request.method == "POST": + if data_form.is_valid() and geom_form.is_valid(): + # The data form takes the geom form for processing, as well as the performing user + acc = data_form.save(request.user, geom_form) + messages.success(request, _("Eco-Account {} edited").format(acc.identifier)) + return redirect("compensation:acc-open", id=acc.id) + else: + messages.error(request, FORM_INVALID) + else: + # For clarification: nothing in this case + pass + context = { + "form": data_form, + "geom_form": geom_form, + } + context = BaseContext(request, context).context + return render(request, template, context) @login_required @@ -96,7 +182,7 @@ def open_view(request: HttpRequest, id: str): diff_states = abs(sum_before_states - sum_after_states) # Calculate rest of available surface for deductions - available = acc.get_available_rest(as_percentage=True) + available_total, available_relative = acc.get_available_rest() deductions = acc.deductions.filter( intervention__deleted=None, @@ -111,7 +197,8 @@ def open_view(request: HttpRequest, id: str): "sum_before_states": sum_before_states, "sum_after_states": sum_after_states, "diff_states": diff_states, - "available": available, + "available": available_relative, + "available_total": available_total, "is_default_member": in_group(_user, DEFAULT_GROUP), "is_zb_member": in_group(_user, ZB_GROUP), "is_ets_member": in_group(_user, ETS_GROUP), @@ -340,7 +427,7 @@ def new_deduction_view(request: HttpRequest, id: str): """ acc = get_object_or_404(EcoAccount, id=id) - form = NewDeductionForm(request.POST or None, instance=acc, user=request.user) + form = NewDeductionModalForm(request.POST or None, instance=acc, user=request.user) return form.process_request( request, msg_success=_("Deduction added") diff --git a/compensation/views/payment_views.py b/compensation/views/payment_views.py index a5dc767..8c819fd 100644 --- a/compensation/views/payment_views.py +++ b/compensation/views/payment_views.py @@ -10,7 +10,7 @@ from django.contrib.auth.decorators import login_required from django.http import HttpRequest from django.shortcuts import get_object_or_404 -from compensation.forms import NewPaymentForm +from compensation.forms.modalForms import NewPaymentForm from compensation.models import Payment from intervention.models import Intervention from konova.decorators import default_group_required diff --git a/ema/forms.py b/ema/forms.py new file mode 100644 index 0000000..d516259 --- /dev/null +++ b/ema/forms.py @@ -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 diff --git a/ema/models.py b/ema/models.py index f777a81..d7e214f 100644 --- a/ema/models.py +++ b/ema/models.py @@ -49,9 +49,9 @@ class Ema(AbstractCompensation): def save(self, *args, **kwargs): if self.identifier is None or len(self.identifier) == 0: # Create new identifier - new_id = self._generate_new_identifier() + new_id = self.generate_new_identifier() while Ema.objects.filter(identifier=new_id).exists(): - new_id = self._generate_new_identifier() + new_id = self.generate_new_identifier() self.identifier = new_id super().save(*args, **kwargs) diff --git a/ema/settings.py b/ema/settings.py index 6edf90a..0c6f5b6 100644 --- a/ema/settings.py +++ b/ema/settings.py @@ -6,5 +6,5 @@ Created on: 19.08.21 """ -EMA_ACCOUNT_IDENTIFIER_LENGTH = 10 +EMA_ACCOUNT_IDENTIFIER_LENGTH = 6 EMA_ACCOUNT_IDENTIFIER_TEMPLATE = "EMA-{}" \ No newline at end of file diff --git a/ema/templates/ema/detail/includes/controls.html b/ema/templates/ema/detail/includes/controls.html index 9dc960f..8b24f66 100644 --- a/ema/templates/ema/detail/includes/controls.html +++ b/ema/templates/ema/detail/includes/controls.html @@ -24,7 +24,7 @@ {% endif %} {% endif %} {% if is_default_member %} -
+ diff --git a/ema/templates/ema/detail/view.html b/ema/templates/ema/detail/view.html index 640711b..941e47c 100644 --- a/ema/templates/ema/detail/view.html +++ b/ema/templates/ema/detail/view.html @@ -43,7 +43,7 @@ {{obj.responsible.conservation_office.str_as_office|default_if_none:""}} - {% trans 'Conversation office file number' %} + {% trans 'Conservation office file number' %} {{obj.responsible.conservation_file_number|default_if_none:""}} diff --git a/ema/templates/ema/form/view.html b/ema/templates/ema/form/view.html new file mode 100644 index 0000000..fcc2569 --- /dev/null +++ b/ema/templates/ema/form/view.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} +{% load i18n l10n %} + +{% block body %} + {% include 'form/collapsable/form.html' %} +{% endblock %} \ No newline at end of file diff --git a/ema/urls.py b/ema/urls.py index 860d863..404cdf5 100644 --- a/ema/urls.py +++ b/ema/urls.py @@ -12,6 +12,7 @@ app_name = "ema" urlpatterns = [ path("", index_view, name="index"), path("new/", new_view, name="new"), + path("new/id", new_id_view, name="new-id"), path("", open_view, name="open"), path('/log', log_view, name='log'), path('/edit', edit_view, name='edit'), diff --git a/ema/views.py b/ema/views.py index 30520ca..e479c17 100644 --- a/ema/views.py +++ b/ema/views.py @@ -1,12 +1,14 @@ +from django.contrib import messages from django.contrib.auth.decorators import login_required from django.db.models import Sum -from django.http import HttpRequest -from django.shortcuts import render, get_object_or_404 +from django.http import HttpRequest, JsonResponse +from django.shortcuts import render, get_object_or_404, redirect from django.urls import reverse from django.utils.translation import gettext_lazy as _ import compensation -from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm +from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm +from ema.forms import NewEmaForm, EditEmaForm from ema.tables import EmaTable from konova.contexts import BaseContext from konova.decorators import conservation_office_group_required @@ -14,6 +16,7 @@ from ema.models import Ema, EmaDocument from konova.forms import RemoveModalForm, NewDocumentForm, SimpleGeomForm, RecordModalForm from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP from konova.utils.documents import get_document, remove_document +from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID from konova.utils.user_checks import in_group @@ -47,7 +50,8 @@ def index_view(request: HttpRequest): @login_required @conservation_office_group_required def new_view(request: HttpRequest): - """ Renders the form for a new EMA + """ + Renders a view for a new eco account creation Args: request (HttpRequest): The incoming request @@ -55,12 +59,54 @@ def new_view(request: HttpRequest): Returns: """ - template = "generic_index.html" - context = {} + template = "ema/form/view.html" + data_form = NewEmaForm(request.POST or None) + geom_form = SimpleGeomForm(request.POST or None, read_only=False) + if request.method == "POST": + if data_form.is_valid() and geom_form.is_valid(): + generated_identifier = data_form.cleaned_data.get("identifier", None) + ema = data_form.save(request.user, geom_form) + if generated_identifier != ema.identifier: + messages.info( + request, + IDENTIFIER_REPLACED.format( + generated_identifier, + ema.identifier + ) + ) + messages.success(request, _("EMA {} added").format(ema.identifier)) + return redirect("ema:open", id=ema.id) + else: + messages.error(request, FORM_INVALID) + else: + # For clarification: nothing in this case + pass + context = { + "form": data_form, + "geom_form": geom_form, + } context = BaseContext(request, context).context return render(request, template, context) +@login_required +def new_id_view(request: HttpRequest): + """ JSON endpoint + + Provides fetching of free identifiers for e.g. AJAX calls + + """ + tmp = Ema() + identifier = tmp.generate_new_identifier() + while Ema.objects.filter(identifier=identifier).exists(): + identifier = tmp.generate_new_identifier() + return JsonResponse( + data={ + "identifier": identifier + } + ) + + @login_required def open_view(request: HttpRequest, id: str): """ Renders the detail view of an EMA @@ -133,7 +179,38 @@ def log_view(request: HttpRequest, id: str): @login_required def edit_view(request: HttpRequest, id: str): - get_object_or_404(Ema, id=id) + """ + Renders a view for editing compensations + + Args: + request (HttpRequest): The incoming request + + Returns: + + """ + template = "compensation/form/view.html" + # Get object from db + ema = get_object_or_404(Ema, id=id) + # Create forms, initialize with values from db/from POST request + data_form = EditEmaForm(request.POST or None, instance=ema) + geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema) + if request.method == "POST": + if data_form.is_valid() and geom_form.is_valid(): + # The data form takes the geom form for processing, as well as the performing user + ema = data_form.save(request.user, geom_form) + messages.success(request, _("EMA {} edited").format(ema.identifier)) + return redirect("ema:open", id=ema.id) + else: + messages.error(request, FORM_INVALID) + else: + # For clarification: nothing in this case + pass + context = { + "form": data_form, + "geom_form": geom_form, + } + context = BaseContext(request, context).context + return render(request, template, context) @login_required diff --git a/intervention/filters.py b/intervention/filters.py index 319a099..205b35c 100644 --- a/intervention/filters.py +++ b/intervention/filters.py @@ -12,7 +12,7 @@ from django.db.models import QuerySet, Q from django.utils.translation import gettext_lazy as _ -from intervention.forms import DummyFilterInput +from intervention.inputs import DummyFilterInput from intervention.models import Intervention diff --git a/intervention/forms/forms.py b/intervention/forms/forms.py new file mode 100644 index 0000000..27e2acf --- /dev/null +++ b/intervention/forms/forms.py @@ -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 + diff --git a/intervention/forms.py b/intervention/forms/modalForms.py similarity index 59% rename from intervention/forms.py rename to intervention/forms/modalForms.py index 2b1ce39..1af0ddc 100644 --- a/intervention/forms.py +++ b/intervention/forms/modalForms.py @@ -2,244 +2,28 @@ Author: Michel Peltriaux Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany Contact: michel.peltriaux@sgdnord.rlp.de -Created on: 02.12.20 +Created on: 27.09.21 """ from dal import autocomplete -from django import forms from django.contrib.auth.models import User -from django.contrib.gis import forms as gis_forms -from django.contrib.gis.geos import Polygon from django.db import transaction +from django import forms from django.urls import reverse -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext_lazy as _ -from compensation.models import EcoAccountDeduction, EcoAccount -from intervention.models import Intervention, Revocation, RevocationDocument -from konova.forms import BaseForm, BaseModalForm -from konova.settings import DEFAULT_LAT, DEFAULT_LON, DEFAULT_ZOOM, ZB_GROUP, ETS_GROUP +from compensation.models import EcoAccount, EcoAccountDeduction +from intervention.inputs import TextToClipboardInput +from intervention.models import Revocation, RevocationDocument, Intervention +from konova.forms import BaseModalForm +from konova.settings import ZB_GROUP, ETS_GROUP from konova.utils.general import format_german_float from konova.utils.messenger import Messenger from konova.utils.user_checks import in_group -from organisation.models import Organisation from user.models import UserActionLogEntry, UserAction -class NewInterventionForm(BaseForm): - identifier = forms.CharField( - label=_("Identifier"), - label_suffix="", - max_length=255, - help_text=_("Generated automatically if none was given"), - required=False, - ) - title = forms.CharField( - label=_("Title"), - label_suffix="", - max_length=255, - ) - type = forms.CharField( - label=_("Type"), - label_suffix="", - max_length=255, - help_text=_("Which intervention type is this"), - ) - law = forms.CharField( - label=_("Law"), - label_suffix="", - max_length=255, - help_text=_("Based on which law"), - ) - handler = forms.CharField( - label=_("Intervention handler"), - label_suffix="", - max_length=255, - help_text=_("Who performs the intervention"), - ) - data_provider = forms.ModelChoiceField( - label=_("Data provider"), - label_suffix="", - help_text=_("Who provides the data for the intervention"), - queryset=Organisation.objects.all(), - widget=autocomplete.ModelSelect2( - url="other-orgs-autocomplete", - attrs={ - "data-placeholder": _("Organization"), - "data-minimum-input-length": 3, - } - ), - ) - data_provider_detail = forms.CharField( - label=_("Data provider details"), - label_suffix="", - max_length=255, - help_text=_("Further details"), - required=False, - ) - geometry = gis_forms.MultiPolygonField( - widget=gis_forms.OSMWidget( - attrs={ - "default_lat": DEFAULT_LAT, - "default_lon": DEFAULT_LON, - "default_zoom": DEFAULT_ZOOM, - 'map_width': 800, - 'map_height': 500 - }, - ), - label=_("Map"), - label_suffix="", - help_text=_("Where does the intervention take place") - ) - documents = forms.FileField( - widget=forms.ClearableFileInput( - attrs={ - "multiple": True, - } - ), - label=_("Files"), - label_suffix="", - required=False, - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.form_title = _("New intervention") - self.action_url = reverse("intervention:new") - self.cancel_redirect = reverse("intervention:index") - - def save(self, user: User): - with transaction.atomic(): - identifier = self.cleaned_data.get("identifier", None) - title = self.cleaned_data.get("title", None) - _type = self.cleaned_data.get("type", None) - law = self.cleaned_data.get("law", None) - handler = self.cleaned_data.get("handler", None) - data_provider = self.cleaned_data.get("data_provider", None) - data_provider_detail = self.cleaned_data.get("data_provider_detail", None) - geometry = self.cleaned_data.get("geometry", Polygon()) - documents = self.cleaned_data.get("documents", []) or [] - - action = UserActionLogEntry.objects.create( - user=user, - action=UserAction.CREATED, - ) - intervention = Intervention( - identifier=identifier, - title=title, - type=_type, - law=law, - handler=handler, - data_provider=data_provider, - data_provider_detail=data_provider_detail, - geometry=geometry, - created=action, - ) - intervention.save() - intervention.log.add(action) - return intervention - - -class EditInterventionForm(NewInterventionForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if self.instance is not None: - self.action_url = reverse("intervention:edit", args=(self.instance.id,)) - self.cancel_redirect = reverse("intervention:index") - self.form_title = _("Edit intervention") - self.form_caption = "" - - # Initialize form data - form_data = { - "identifier": self.instance.identifier, - "title": self.instance.title, - "type": self.instance.type, - "law": self.instance.law, - "handler": self.instance.handler, - "data_provider": self.instance.data_provider, - "data_provider_detail": self.instance.data_provider_detail, - "geometry": self.instance.geometry, - "documents": self.instance.documents.all(), - } - disabled_fields = [ - "identifier", - ] - self.load_initial_data( - form_data, - disabled_fields, - ) - - def save(self, user: User): - """ Overwrite instance with new form data - - Args: - user (): - - Returns: - - """ - with transaction.atomic(): - identifier = self.cleaned_data.get("identifier", None) - title = self.cleaned_data.get("title", None) - _type = self.cleaned_data.get("type", None) - law = self.cleaned_data.get("law", None) - handler = self.cleaned_data.get("handler", None) - data_provider = self.cleaned_data.get("data_provider", None) - data_provider_detail = self.cleaned_data.get("data_provider_detail", None) - geometry = self.cleaned_data.get("geometry", Polygon()) - documents = self.cleaned_data.get("documents", []) or [] - - self.instance.identifier = identifier - self.instance.title = title - self.instance.type = _type - self.instance.law = law - self.instance.handler = handler - self.instance.data_provider = data_provider - self.instance.data_provider_detail = data_provider_detail - self.instance.geometry = geometry - self.instance.save() - - user_action = UserActionLogEntry.objects.create( - user=self.user, - action=UserAction.EDITED - ) - self.instance.log.add(user_action) - self.instance.modified = user_action - self.instance.save() - - return self.instance - - -class OpenInterventionForm(EditInterventionForm): - """ - This form is not intended to be used as data-input form. It's used to simplify the rendering of intervention:open - """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Resize map - self.fields["geometry"].widget.attrs["map_width"] = 500 - self.fields["geometry"].widget.attrs["map_height"] = 300 - - # Disable all form fields - for field in self.fields: - self.disable_form_field(field) - - -class DummyFilterInput(forms.HiddenInput): - """ A dummy input widget - - Does not render anything. Can be used to keep filter logic using django_filter without having a pre defined - filter widget being rendered to the template. - - """ - template_name = "konova/custom_widgets/dummy-filter-input.html" - - -class TextToClipboardInput(forms.TextInput): - template_name = "konova/custom_widgets/text-to-clipboard-input.html" - - -class ShareInterventionForm(BaseModalForm): +class ShareInterventionModalForm(BaseModalForm): url = forms.CharField( label=_("Share link"), label_suffix="", @@ -247,7 +31,8 @@ class ShareInterventionForm(BaseModalForm): required=False, widget=TextToClipboardInput( attrs={ - "readonly": True + "readonly": True, + "class": "form-control", } ) ) @@ -316,7 +101,7 @@ class ShareInterventionForm(BaseModalForm): self.instance.users.set(accessing_users) -class NewRevocationForm(BaseModalForm): +class NewRevocationModalForm(BaseModalForm): date = forms.DateField( label=_("Date"), label_suffix=_(""), @@ -325,6 +110,7 @@ class NewRevocationForm(BaseModalForm): attrs={ "type": "date", "data-provide": "datepicker", + "class": "form-control", }, format="%d.%m.%Y" ) @@ -336,7 +122,7 @@ class NewRevocationForm(BaseModalForm): help_text=_("Must be smaller than 15 Mb"), widget=forms.FileInput( attrs={ - "class": "w-75" + "class": "form-control-file" } ) ) @@ -350,6 +136,7 @@ class NewRevocationForm(BaseModalForm): attrs={ "cols": 30, "rows": 5, + "class": "form-control", } ) ) @@ -394,7 +181,7 @@ class NewRevocationForm(BaseModalForm): return revocation -class RunCheckForm(BaseModalForm): +class RunCheckModalForm(BaseModalForm): checked_intervention = forms.BooleanField( label=_("Checked intervention data"), label_suffix="", @@ -458,7 +245,7 @@ class RunCheckForm(BaseModalForm): ) -class NewDeductionForm(BaseModalForm): +class NewDeductionModalForm(BaseModalForm): """ Form for creating new deduction Can be used for Intervention view as well as for EcoAccount views. @@ -487,6 +274,12 @@ class NewDeductionForm(BaseModalForm): label=_("Surface"), label_suffix="", help_text=_("in m²"), + widget=forms.NumberInput( + attrs={ + "class": "form-control", + "placeholder": "0,00", + } + ) ) intervention = forms.ModelChoiceField( label=_("Intervention"), @@ -508,9 +301,6 @@ class NewDeductionForm(BaseModalForm): self.form_caption = _("Enter the information for a new deduction from a chosen eco-account") self.is_intervention_initially = False - # Add a placeholder for field 'surface' without having to define the whole widget above - self.add_placeholder_for_field("surface", "0,00") - # Check for Intervention or EcoAccount if isinstance(self.instance, Intervention): # Form has been called with a given intervention diff --git a/intervention/inputs.py b/intervention/inputs.py new file mode 100644 index 0000000..2e84edc --- /dev/null +++ b/intervention/inputs.py @@ -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" diff --git a/intervention/models.py b/intervention/models.py index 3a8f7ac..1f5fdf8 100644 --- a/intervention/models.py +++ b/intervention/models.py @@ -260,14 +260,41 @@ class Intervention(BaseObject): self.save() def save(self, *args, **kwargs): + """ Custom save functionality + + Performs some pre-save checks: + 1. Checking for existing identifiers + + Args: + *args (): + **kwargs (): + + Returns: + + """ if self.identifier is None or len(self.identifier) == 0: - # Create new identifier - new_id = self._generate_new_identifier() - while Intervention.objects.filter(identifier=new_id).exists(): - new_id = self._generate_new_identifier() - self.identifier = new_id + # No identifier given + self.identifier = self.generate_new_identifier() + + # Before saving, make sure the set identifier is not used, yet + while Intervention.objects.filter(identifier=self.identifier).exists(): + self.identifier = self.generate_new_identifier() super().save(*args, **kwargs) + def delete(self, using=None, keep_parents=False): + to_delete = [ + self.legal, + self.responsible, + self.geometry, + self.log.all() + ] + for entry in to_delete: + try: + entry.delete() + except AttributeError: + pass + super().delete(using, keep_parents) + def quality_check(self) -> list: """ Quality check @@ -298,7 +325,7 @@ class Intervention(BaseObject): ret_msgs.append(_("Registration office file number missing")) if not self.responsible.conservation_file_number or len(self.responsible.conservation_file_number) == 0: - ret_msgs.append(_("Conversation office file number missing")) + ret_msgs.append(_("Conservation office file number missing")) except AttributeError: # responsible data not found ret_msgs.append(_("Responsible data missing")) diff --git a/intervention/settings.py b/intervention/settings.py index 038fa17..2b6ebee 100644 --- a/intervention/settings.py +++ b/intervention/settings.py @@ -5,5 +5,5 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 30.11.20 """ -INTERVENTION_IDENTIFIER_LENGTH = 10 +INTERVENTION_IDENTIFIER_LENGTH = 6 INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}" \ No newline at end of file diff --git a/intervention/templates/intervention/detail/includes/comment.html b/intervention/templates/intervention/detail/includes/comment.html new file mode 100644 index 0000000..2f25356 --- /dev/null +++ b/intervention/templates/intervention/detail/includes/comment.html @@ -0,0 +1,23 @@ +{% load i18n fontawesome_5 %} + +{% if intervention.comment %} +
+
+
+
+
+
+ {% fa5_icon 'info-circle' %} + {% trans 'Comment' %} +
+
+
+
+
+
+ {{intervention.comment}} +
+
+
+
+{% endif %} \ No newline at end of file diff --git a/intervention/templates/intervention/detail/includes/compensations.html b/intervention/templates/intervention/detail/includes/compensations.html index f195d55..894c96a 100644 --- a/intervention/templates/intervention/detail/includes/compensations.html +++ b/intervention/templates/intervention/detail/includes/compensations.html @@ -11,7 +11,7 @@
- {% include 'map/geom_form.html' %} +
+ {% include 'map/geom_form.html' %} +
+
+ {% include 'intervention/detail/includes/comment.html' %} +

diff --git a/intervention/templates/intervention/form/view.html b/intervention/templates/intervention/form/view.html new file mode 100644 index 0000000..fcc2569 --- /dev/null +++ b/intervention/templates/intervention/form/view.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} +{% load i18n l10n %} + +{% block body %} + {% include 'form/collapsable/form.html' %} +{% endblock %} \ No newline at end of file diff --git a/intervention/urls.py b/intervention/urls.py index 51d1162..ac96d62 100644 --- a/intervention/urls.py +++ b/intervention/urls.py @@ -9,12 +9,13 @@ from django.urls import path from intervention.views import index_view, new_view, open_view, edit_view, remove_view, new_document_view, share_view, \ create_share_view, remove_revocation_view, new_revocation_view, run_check_view, log_view, new_deduction_view, \ - record_view, remove_document_view, get_document_view, get_revocation_view + record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view app_name = "intervention" urlpatterns = [ path("", index_view, name="index"), path('new/', new_view, name='new'), + path('new/id', new_id_view, name='new-id'), path('', open_view, name='open'), path('/log', log_view, name='log'), path('/edit', edit_view, name='edit'), diff --git a/intervention/views.py b/intervention/views.py index eb8cab4..8c9679f 100644 --- a/intervention/views.py +++ b/intervention/views.py @@ -1,11 +1,11 @@ -from django.contrib import messages from django.contrib.auth.decorators import login_required from django.utils.translation import gettext_lazy as _ -from django.http import HttpRequest +from django.http import HttpRequest, JsonResponse from django.shortcuts import render, get_object_or_404 -from intervention.forms import NewInterventionForm, EditInterventionForm, ShareInterventionForm, NewRevocationForm, \ - RunCheckForm, NewDeductionForm +from intervention.forms.forms import NewInterventionForm, EditInterventionForm +from intervention.forms.modalForms import ShareInterventionModalForm, NewRevocationModalForm, \ + RunCheckModalForm, NewDeductionModalForm from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument from intervention.tables import InterventionTable from konova.contexts import BaseContext @@ -13,7 +13,7 @@ from konova.decorators import * from konova.forms import SimpleGeomForm, NewDocumentForm, RemoveModalForm, RecordModalForm from konova.sub_settings.django_settings import DEFAULT_DATE_FORMAT from konova.utils.documents import remove_document, get_document -from konova.utils.message_templates import FORM_INVALID, INTERVENTION_INVALID +from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED from konova.utils.user_checks import in_group @@ -58,25 +58,54 @@ def new_view(request: HttpRequest): Returns: """ - template = "konova/form.html" - form = NewInterventionForm(request.POST or None) + template = "intervention/form/view.html" + data_form = NewInterventionForm(request.POST or None) + geom_form = SimpleGeomForm(request.POST or None, read_only=False) if request.method == "POST": - if form.is_valid(): - intervention = form.save(request.user) - messages.success(request, _("Intervention {} added").format(intervention.title)) - return redirect("intervention:index") + if data_form.is_valid() and geom_form.is_valid(): + generated_identifier = data_form.cleaned_data.get("identifier", None) + intervention = data_form.save(request.user, geom_form) + if generated_identifier != intervention.identifier: + messages.info( + request, + IDENTIFIER_REPLACED.format( + generated_identifier, + intervention.identifier + ) + ) + messages.success(request, _("Intervention {} added").format(intervention.identifier)) + return redirect("intervention:open", id=intervention.id) else: - messages.error(request, _("Invalid input")) + messages.error(request, FORM_INVALID) else: # For clarification: nothing in this case pass context = { - "form": form, + "form": data_form, + "geom_form": geom_form, } context = BaseContext(request, context).context return render(request, template, context) +@login_required +def new_id_view(request: HttpRequest): + """ JSON endpoint + + Provides fetching of free identifiers for e.g. AJAX calls + + """ + tmp_intervention = Intervention() + identifier = tmp_intervention.generate_new_identifier() + while Intervention.objects.filter(identifier=identifier).exists(): + identifier = tmp_intervention.generate_new_identifier() + return JsonResponse( + data={ + "identifier": identifier + } + ) + + @login_required def new_document_view(request: HttpRequest, id: str): """ Renders a form for uploading new documents @@ -212,19 +241,26 @@ def edit_view(request: HttpRequest, id: str): Returns: """ - template = "konova/form.html" + template = "intervention/form/view.html" + # Get object from db intervention = get_object_or_404(Intervention, id=id) + # Create forms, initialize with values from db/from POST request + data_form = EditInterventionForm(request.POST or None, instance=intervention) + geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention) if request.method == "POST": - form = EditInterventionForm(request.POST or None, instance=intervention) - if form.is_valid(): - intervention = form.save(request.user) - messages.success(request, _("{} edited").format(intervention)) - return redirect("intervention:index") + if data_form.is_valid() and geom_form.is_valid(): + # The data form takes the geom form for processing, as well as the performing user + intervention = data_form.save(request.user, geom_form) + messages.success(request, _("Intervention {} edited").format(intervention.identifier)) + return redirect("intervention:open", id=intervention.id) else: - messages.error(request, _("Invalid input")) - form = EditInterventionForm(instance=intervention) + messages.error(request, FORM_INVALID) + else: + # For clarification: nothing in this case + pass context = { - "form": form, + "form": data_form, + "geom_form": geom_form, } context = BaseContext(request, context).context return render(request, template, context) @@ -324,7 +360,7 @@ def create_share_view(request: HttpRequest, id: str): """ intervention = get_object_or_404(Intervention, id=id) - form = ShareInterventionForm(request.POST or None, instance=intervention, request=request) + form = ShareInterventionModalForm(request.POST or None, instance=intervention, request=request) return form.process_request( request, msg_success=_("Share settings updated") @@ -343,7 +379,7 @@ def run_check_view(request: HttpRequest, id: str): """ intervention = get_object_or_404(Intervention, id=id) - form = RunCheckForm(request.POST or None, instance=intervention, user=request.user) + form = RunCheckModalForm(request.POST or None, instance=intervention, user=request.user) return form.process_request( request, msg_success=_("Check performed"), @@ -363,7 +399,7 @@ def new_revocation_view(request: HttpRequest, id: str): """ intervention = get_object_or_404(Intervention, id=id) - form = NewRevocationForm(request.POST or None, request.FILES or None, instance=intervention, user=request.user) + form = NewRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, user=request.user) return form.process_request( request, msg_success=_("Revocation added") @@ -407,7 +443,7 @@ def new_deduction_view(request: HttpRequest, id: str): """ intervention = get_object_or_404(Intervention, id=id) - form = NewDeductionForm(request.POST or None, instance=intervention, user=request.user) + form = NewDeductionModalForm(request.POST or None, instance=intervention, user=request.user) return form.process_request( request, msg_success=_("Deduction added") diff --git a/konova/autocompletes.py b/konova/autocompletes.py index a75a711..7acdca9 100644 --- a/konova/autocompletes.py +++ b/konova/autocompletes.py @@ -10,7 +10,8 @@ from django.db.models import Q from codelist.models import KonovaCode from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, \ - CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_PROCESS_TYPE_ID + CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_PROCESS_TYPE_ID, \ + CODELIST_COMPENSATION_FUNDING_ID from compensation.models import EcoAccount from intervention.models import Intervention from organisation.models import Organisation @@ -45,12 +46,18 @@ class NonOfficialOrganisationAutocomplete(Select2QuerySetView): class EcoAccountAutocomplete(Select2QuerySetView): + """ Autocomplete for ecoAccount entries + + Only returns entries that are accessible for the requesting user and already are recorded + + """ def get_queryset(self): if self.request.user.is_anonymous: return EcoAccount.objects.none() qs = EcoAccount.objects.filter( deleted=None, recorded__isnull=False, + users__in=[self.request.user], ) if self.q: qs = qs.filter( @@ -63,6 +70,11 @@ class EcoAccountAutocomplete(Select2QuerySetView): class InterventionAutocomplete(Select2QuerySetView): + """ Autocomplete for intervention entries + + Only returns entries that are accessible for the requesting user + + """ def get_queryset(self): if self.request.user.is_anonymous: return Intervention.objects.none() @@ -104,10 +116,18 @@ class KonovaCodeAutocomplete(Select2QuerySetView): code_lists__in=[self.c] ) if self.q: - q_or = Q() - q_or |= Q(long_name__icontains=self.q) - q_or |= Q(short_name__icontains=self.q) - qs = qs.filter(q_or) + # Remove whitespaces from self.q and split input in all keywords (if multiple given) + q = dict.fromkeys(self.q.strip().split(" ")) + # Create one filter looking up for all keys where all keywords can be found in the same result + _filter = Q() + for keyword in q: + q_or = Q() + q_or |= Q(long_name__icontains=keyword) + q_or |= Q(short_name__icontains=keyword) + q_or |= Q(parent__long_name__icontains=keyword) + q_or |= Q(parent__short_name__icontains=keyword) + _filter.add(q_or, Q.AND) + qs = qs.filter(_filter).distinct() return qs @@ -120,6 +140,15 @@ class CompensationActionCodeAutocomplete(KonovaCodeAutocomplete): super().__init__(*args, **kwargs) +class CompensationFundingCodeAutocomplete(KonovaCodeAutocomplete): + """ + Due to limitations of the django dal package, we need to subclass for each code list + """ + def __init__(self, *args, **kwargs): + self.c = CODELIST_COMPENSATION_FUNDING_ID + super().__init__(*args, **kwargs) + + class BiotopeCodeAutocomplete(KonovaCodeAutocomplete): """ Due to limitations of the django dal package, we need to subclass for each code list diff --git a/konova/decorators.py b/konova/decorators.py index 3d1a547..4edf554 100644 --- a/konova/decorators.py +++ b/konova/decorators.py @@ -9,11 +9,12 @@ Created on: 16.11.20 from functools import wraps from django.contrib import messages -from django.shortcuts import redirect +from django.shortcuts import redirect, get_object_or_404 from django.urls import reverse from django.utils.translation import gettext_lazy as _ from konova.settings import DEFAULT_GROUP, ETS_GROUP, ZB_GROUP +from konova.utils.message_templates import MISSING_GROUP_PERMISSION, DATA_UNSHARED def staff_required(function): @@ -80,7 +81,7 @@ def default_group_required(function): if has_group: return function(request, *args, **kwargs) else: - messages.info(request, _("You need to be part of another user group.")) + messages.info(request, MISSING_GROUP_PERMISSION) return redirect(request.META.get("HTTP_REFERER", reverse("home"))) return wrap @@ -100,7 +101,7 @@ def registration_office_group_required(function): if has_group: return function(request, *args, **kwargs) else: - messages.info(request, _("You need to be part of another user group.")) + messages.info(request, MISSING_GROUP_PERMISSION) return redirect(request.META.get("HTTP_REFERER", reverse("home"))) return wrap @@ -120,6 +121,35 @@ def conservation_office_group_required(function): if has_group: return function(request, *args, **kwargs) else: - messages.info(request, _("You need to be part of another user group.")) + messages.info(request, MISSING_GROUP_PERMISSION) return redirect(request.META.get("HTTP_REFERER", reverse("home"))) - return wrap \ No newline at end of file + 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 \ No newline at end of file diff --git a/konova/forms.py b/konova/forms.py index 2b0f18a..26914d2 100644 --- a/konova/forms.py +++ b/konova/forms.py @@ -13,8 +13,8 @@ from bootstrap_modal_forms.utils import is_ajax from django import forms from django.contrib import messages from django.contrib.auth.models import User -from django.contrib.gis.forms import GeometryField, OSMWidget -from django.contrib.gis.geos import Polygon +from django.contrib.gis.forms import OSMWidget, MultiPolygonField +from django.contrib.gis.geos import Polygon, MultiPolygon from django.db import transaction from django.http import HttpRequest, HttpResponseRedirect from django.shortcuts import render @@ -25,7 +25,8 @@ from compensation.models import EcoAccount, Compensation, EcoAccountDocument, Co from ema.models import Ema, EmaDocument from intervention.models import Intervention, Revocation, RevocationDocument, InterventionDocument from konova.contexts import BaseContext -from konova.models import BaseObject +from konova.models import BaseObject, Geometry +from konova.settings import DEFAULT_SRID from konova.utils.message_templates import FORM_INVALID from user.models import UserActionLogEntry, UserAction @@ -75,7 +76,7 @@ class BaseForm(forms.Form): def add_placeholder_for_field(self, field: str, val): """ - Adds a placeholder to a field after initialization + Adds a placeholder to a field after initialization without the need to redefine the form widget Args: field (str): Field name @@ -184,16 +185,10 @@ class BaseModalForm(BaseForm, BSModalForm): """ is_modal_form = True render_submit = True - full_width_fields = False template = "modal/modal_form.html" - def __init__(self, full_width_fields: bool = True, *args, **kwargs): - self.full_width_fields = full_width_fields + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if self.full_width_fields: - # Automatically add bootstrap w-100 class for maximum width of form fields in modals - for key, val in self.fields.items(): - self.add_widget_html_class(key, "w-100") def process_request(self, request: HttpRequest, msg_success: str = _("Object removed"), msg_error: str = FORM_INVALID, redirect_url: str = None): """ Generic processing of request @@ -243,32 +238,65 @@ class SimpleGeomForm(BaseForm): """ A geometry form for rendering geometry read-only using a widget """ - geom = GeometryField( + geom = MultiPolygonField( + srid=DEFAULT_SRID, + label=_("Geometry"), + help_text=_(""), + label_suffix="", required=False, - disabled=True, + disabled=False, widget=OSMWidget( attrs={ "map_width": 600, "map_height": 400, + # default_zoom defines the nearest possible zoom level from which the JS automatically + # zooms out if geometry requires a larger view port. So define a larger range for smaller geometries + "default_zoom": 25, } ) ) def __init__(self, *args, **kwargs): + read_only = kwargs.pop("read_only", True) super().__init__(*args, **kwargs) # Initialize geometry try: geom = self.instance.geometry.geom - if geom is None: - raise AttributeError + self.empty = geom.empty except AttributeError: - # catches if no geometry has been added, yet. Replace with empty placeholder polygon. - geom = Polygon.from_bbox([0, 0, 0, 0]) - # Zoom out to a very high level, so the user can see directly that there is no geometry for this entry + # If no geometry exists for this form, we simply set the value to None and zoom to the maximum level + geom = None + self.empty = True self.fields["geom"].widget.attrs["default_zoom"] = 1 + self.initialize_form_field("geom", geom) - self.area = geom.area + if read_only: + self.fields["geom"].disabled = True + + def save(self, action: UserActionLogEntry): + """ Saves the form's geometry + + Creates a new geometry entry if none is set, yet + + Args: + action (): + + Returns: + + """ + try: + geometry = self.instance.geometry + geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID)) + geometry.modified = action + geometry.save() + except (AttributeError) as e: + # No geometry or linked instance holding a geometry exist --> create a new one! + geometry = Geometry.objects.create( + geom=self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID)), + created=action, + ) + return geometry class RemoveModalForm(BaseModalForm): @@ -294,15 +322,7 @@ class RemoveModalForm(BaseModalForm): def save(self): if isinstance(self.instance, BaseObject): - with transaction.atomic(): - action = UserActionLogEntry.objects.create( - user=self.user, - timestamp=timezone.now(), - action=UserAction.DELETED, - ) - self.instance.deleted = action - self.instance.log.add(action) - self.instance.save() + self.instance.mark_as_deleted(self.user) else: # If the class does not provide restorable delete functionality, we must delete the entry finally self.instance.delete() @@ -316,6 +336,11 @@ class NewDocumentForm(BaseModalForm): label=_("Title"), label_suffix=_(""), max_length=500, + widget=forms.TextInput( + attrs={ + "class": "form-control", + } + ) ) creation_date = forms.DateField( label=_("Created on"), @@ -325,6 +350,7 @@ class NewDocumentForm(BaseModalForm): attrs={ "type": "date", "data-provide": "datepicker", + "class": "form-control", }, format="%d.%m.%Y" ) @@ -335,7 +361,7 @@ class NewDocumentForm(BaseModalForm): help_text=_("Must be smaller than 15 Mb"), widget=forms.FileInput( attrs={ - "class": "w-75" + "class": "form-control-file", } ), ) @@ -349,6 +375,7 @@ class NewDocumentForm(BaseModalForm): attrs={ "cols": 30, "rows": 5, + "class": "form-control", } ) ) diff --git a/konova/management/commands/test_identifier_generating.py b/konova/management/commands/test_identifier_generating.py index 73088b2..09c3573 100644 --- a/konova/management/commands/test_identifier_generating.py +++ b/konova/management/commands/test_identifier_generating.py @@ -21,7 +21,7 @@ class Command(BaseCommand): len_ids = len(identifiers) while len_ids < max_iterations: tmp_intervention = Intervention() - _id = tmp_intervention._generate_new_identifier() + _id = tmp_intervention.generate_new_identifier() len_ids = len(identifiers) if _id not in identifiers: if len_ids % (max_iterations/5) == 0: diff --git a/konova/models.py b/konova/models.py index 492dcef..ab4ad7a 100644 --- a/konova/models.py +++ b/konova/models.py @@ -9,6 +9,8 @@ import os import uuid from django.contrib.auth.models import User +from django.core.exceptions import ObjectDoesNotExist +from django.utils import timezone from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ from django.contrib.gis.db.models import MultiPolygonField @@ -61,8 +63,20 @@ class BaseResource(UuidModel): abstract = True def delete(self, using=None, keep_parents=False): - if self.created: + """ Base deleting of a resource + + Args: + using (): + keep_parents (): + + Returns: + + """ + try: self.created.delete() + except (ObjectDoesNotExist, AttributeError) as e: + # Object does not exist anymore - we can skip this + pass super().delete() @@ -81,14 +95,13 @@ class BaseObject(BaseResource): class Meta: abstract = True - def delete(self, *args, **kwargs): - """ Custom delete functionality + def mark_as_deleted(self, user: User): + """ Mark an entry as deleted Does not delete from database but sets a timestamp for being deleted on and which user deleted the object Args: - *args (): - **kwargs (): + user (User): The performing user Returns: @@ -97,13 +110,14 @@ class BaseObject(BaseResource): # Nothing to do here return - _user = kwargs.get("user", None) with transaction.atomic(): action = UserActionLogEntry.objects.create( - user=_user, - action=UserAction.DELETED + user=user, + action=UserAction.DELETED, + timestamp=timezone.now() ) self.deleted = action + self.log.add(action) self.save() def add_log_entry(self, action: UserAction, user: User, comment: str): @@ -139,7 +153,7 @@ class BaseObject(BaseResource): else: return User.objects.none() - def _generate_new_identifier(self) -> str: + def generate_new_identifier(self) -> str: """ Generates a new identifier for the intervention object Returns: @@ -177,7 +191,9 @@ class BaseObject(BaseResource): curr_year = str(_now.year) rand_str = generate_random_string( length=definitions[self.__class__]["length"], - only_numbers=True, + use_numbers=True, + use_letters_lc=False, + use_letters_uc=True, ) _str = "{}{}-{}".format(curr_month, curr_year, rand_str) return definitions[self.__class__]["template"].format(_str) diff --git a/konova/static/css/konova.css b/konova/static/css/konova.css index 60e4e9a..6de1f8f 100644 --- a/konova/static/css/konova.css +++ b/konova/static/css/konova.css @@ -60,6 +60,14 @@ a { color: var(--rlp-red); } +.form-control:focus{ + outline: none; + border-color: var(--rlp-red); + box-shadow: 0 0 3px var(--rlp-red); + -moz-box-shadow: 0 0 3px var(--rlp-red); + -webkit-box-shadow: 0 0 3px var(--rlp-red); +} + .body-content{ margin: 1rem 0rem 0 0rem; } @@ -133,6 +141,13 @@ a { height: 8rem; } +/** +Overwrites bootstrap .btn:focus box shadow color +*/ +.btn:focus{ + box-shadow: 0 0 5px .2rem var(--rlp-gray-light); +} + .btn-default{ color: white; background-color: var(--rlp-red); @@ -171,13 +186,6 @@ a { background-color: var(--rlp-gray-light); } -input:focus, textarea:focus, select:focus{ - border-color: var(--rlp-red) !important; - box-shadow: 0 0 3px var(--rlp-red) !important; - -moz-box-shadow: 0 0 3px var(--rlp-red) !important; - -webkit-box-shadow: 0 0 3px var(--rlp-red) !important; -} - .check-star{ color: goldenrod; } @@ -215,4 +223,7 @@ No other approach worked to get the autocomplete fields to full width of parent */ .select2-container{ width: 100% !important; +} +.select2-results__option--highlighted{ + background-color: var(--rlp-red) !important; } \ No newline at end of file diff --git a/konova/templates/konova/choiceColumnForm.html b/konova/templates/konova/choiceColumnForm.html deleted file mode 100644 index 1e62cf4..0000000 --- a/konova/templates/konova/choiceColumnForm.html +++ /dev/null @@ -1,5 +0,0 @@ - -
- {% csrf_token %} - {{ form.as_p }} -
\ No newline at end of file diff --git a/konova/templates/konova/form.html b/konova/templates/konova/form.html index 3efd152..1bdf539 100644 --- a/konova/templates/konova/form.html +++ b/konova/templates/konova/form.html @@ -3,6 +3,6 @@ {% block body %}
- {% include 'form/generic_table_form.html' %} + {% include 'form/table/generic_table_form.html' %}
{% endblock %} \ No newline at end of file diff --git a/konova/templates/konova/custom_widgets/dummy-filter-input.html b/konova/templates/konova/widgets/empty-dummy-input.html similarity index 100% rename from konova/templates/konova/custom_widgets/dummy-filter-input.html rename to konova/templates/konova/widgets/empty-dummy-input.html diff --git a/konova/templates/konova/widgets/generate-content-input.html b/konova/templates/konova/widgets/generate-content-input.html new file mode 100644 index 0000000..66c9198 --- /dev/null +++ b/konova/templates/konova/widgets/generate-content-input.html @@ -0,0 +1,22 @@ +{% load i18n fontawesome_5 %} + +
+ +
+ {% fa5_icon 'dice' %} +
+
+ \ No newline at end of file diff --git a/konova/templates/konova/custom_widgets/progressbar.html b/konova/templates/konova/widgets/progressbar.html similarity index 100% rename from konova/templates/konova/custom_widgets/progressbar.html rename to konova/templates/konova/widgets/progressbar.html diff --git a/konova/templates/konova/custom_widgets/text-to-clipboard-input.html b/konova/templates/konova/widgets/text-to-clipboard-input.html similarity index 100% rename from konova/templates/konova/custom_widgets/text-to-clipboard-input.html rename to konova/templates/konova/widgets/text-to-clipboard-input.html diff --git a/konova/urls.py b/konova/urls.py index 3790925..0c2bef2 100644 --- a/konova/urls.py +++ b/konova/urls.py @@ -19,7 +19,8 @@ from django.urls import path, include from konova.autocompletes import OrganisationAutocomplete, NonOfficialOrganisationAutocomplete, EcoAccountAutocomplete, \ InterventionAutocomplete, CompensationActionCodeAutocomplete, BiotopeCodeAutocomplete, LawCodeAutocomplete, \ - RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete + RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete, ProcessTypeCodeAutocomplete, \ + CompensationFundingCodeAutocomplete from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG from konova.sso.sso import KonovaSSOClient from konova.views import logout_view, home_view, remove_deadline_view @@ -46,9 +47,11 @@ urlpatterns = [ path("atcmplt/orgs/other", NonOfficialOrganisationAutocomplete.as_view(), name="other-orgs-autocomplete"), path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"), path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="interventions-autocomplete"), - path("atcmplt/codes/compensation-action", CompensationActionCodeAutocomplete.as_view(), name="codes-compensation-action-autocomplete"), + path("atcmplt/codes/comp/action", CompensationActionCodeAutocomplete.as_view(), name="codes-compensation-action-autocomplete"), + path("atcmplt/codes/comp/funding", CompensationFundingCodeAutocomplete.as_view(), name="codes-compensation-funding-autocomplete"), path("atcmplt/codes/biotope", BiotopeCodeAutocomplete.as_view(), name="codes-biotope-autocomplete"), path("atcmplt/codes/law", LawCodeAutocomplete.as_view(), name="codes-law-autocomplete"), + path("atcmplt/codes/prc-type", ProcessTypeCodeAutocomplete.as_view(), name="codes-process-type-autocomplete"), path("atcmplt/codes/reg-off", RegistrationOfficeCodeAutocomplete.as_view(), name="codes-registration-office-autocomplete"), path("atcmplt/codes/cons-off", ConservationOfficeCodeAutocomplete.as_view(), name="codes-conservation-office-autocomplete"), ] diff --git a/konova/utils/generators.py b/konova/utils/generators.py index 46a3a4d..dfae78f 100644 --- a/konova/utils/generators.py +++ b/konova/utils/generators.py @@ -9,14 +9,18 @@ import random import string -def generate_random_string(length: int, only_numbers: bool = False) -> str: +def generate_random_string(length: int, use_numbers: bool = False, use_letters_lc: bool = False, use_letters_uc: bool = False) -> str: """ Generates a random string of variable length """ - if only_numbers: - elements = string.digits - else: - elements = string.ascii_letters + elements = [] + if use_numbers: + elements.append(string.digits) + if use_letters_lc: + elements.append(string.ascii_lowercase) + if use_letters_uc: + elements.append(string.ascii_uppercase) + elements = "".join(elements) ret_val = "".join(random.choice(elements) for i in range(length)) return ret_val diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py index 109d7c8..f9c8e2b 100644 --- a/konova/utils/message_templates.py +++ b/konova/utils/message_templates.py @@ -10,3 +10,6 @@ from django.utils.translation import gettext_lazy as _ FORM_INVALID = _("There was an error on this form.") INTERVENTION_INVALID = _("There are errors in this intervention.") +IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since another entry has been added in the meanwhile, which uses this identifier") +DATA_UNSHARED = _("This data is not shared with you") +MISSING_GROUP_PERMISSION = _("You need to be part of another user group.") \ No newline at end of file diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index 51178b8..4e29c29 100644 Binary files a/locale/de/LC_MESSAGES/django.mo and b/locale/de/LC_MESSAGES/django.mo differ diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 757a35b..d50cd07 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -3,20 +3,23 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#: compensation/filters.py:71 compensation/forms.py:47 compensation/forms.py:52 -#: compensation/forms.py:67 compensation/forms.py:264 compensation/forms.py:345 +#: compensation/filters.py:71 compensation/forms/modalForms.py:35 +#: compensation/forms/modalForms.py:46 compensation/forms/modalForms.py:62 +#: compensation/forms/modalForms.py:273 compensation/forms/modalForms.py:368 #: intervention/filters.py:26 intervention/filters.py:40 #: intervention/filters.py:47 intervention/filters.py:48 -#: intervention/forms.py:322 intervention/forms.py:334 -#: intervention/forms.py:347 konova/forms.py:108 konova/forms.py:252 -#: konova/forms.py:287 konova/forms.py:292 konova/forms.py:304 -#: konova/forms.py:316 konova/forms.py:336 user/forms.py:38 +#: intervention/forms/forms.py:53 intervention/forms/forms.py:155 +#: intervention/forms/forms.py:167 intervention/forms/modalForms.py:107 +#: intervention/forms/modalForms.py:120 intervention/forms/modalForms.py:133 +#: konova/forms.py:140 konova/forms.py:244 konova/forms.py:310 +#: konova/forms.py:337 konova/forms.py:347 konova/forms.py:360 +#: konova/forms.py:372 konova/forms.py:393 user/forms.py:38 #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-09-20 12:44+0200\n" +"POT-Creation-Date: 2021-10-13 08:46+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -30,138 +33,267 @@ msgstr "" msgid "Show only unrecorded" msgstr "Nur unverzeichnete anzeigen" -#: compensation/forms.py:48 -msgid "in Euro" -msgstr "in Euro" +#: compensation/forms/forms.py:31 compensation/tables.py:25 +#: compensation/tables.py:167 ema/tables.py:28 intervention/forms/forms.py:27 +#: intervention/tables.py:23 +#: intervention/templates/intervention/detail/includes/compensations.html:30 +msgid "Identifier" +msgstr "Kennung" -#: compensation/forms.py:51 -#: intervention/templates/intervention/detail/includes/payments.html:31 -msgid "Due on" -msgstr "Fällig am" +#: compensation/forms/forms.py:34 intervention/forms/forms.py:30 +msgid "Generated automatically" +msgstr "Automatisch generiert" -#: compensation/forms.py:54 -msgid "Due on which date" -msgstr "Zahlung wird an diesem Datum erwartet" +#: compensation/forms/forms.py:43 compensation/tables.py:30 +#: compensation/tables.py:172 +#: compensation/templates/compensation/detail/compensation/includes/documents.html:28 +#: compensation/templates/compensation/detail/compensation/view.html:31 +#: compensation/templates/compensation/detail/eco_account/includes/documents.html:28 +#: compensation/templates/compensation/detail/eco_account/view.html:31 +#: ema/tables.py:33 ema/templates/ema/detail/includes/documents.html:28 +#: ema/templates/ema/detail/view.html:24 intervention/forms/forms.py:39 +#: intervention/tables.py:28 +#: intervention/templates/intervention/detail/includes/compensations.html:33 +#: intervention/templates/intervention/detail/includes/documents.html:28 +#: intervention/templates/intervention/detail/view.html:31 konova/forms.py:336 +msgid "Title" +msgstr "Bezeichnung" -#: compensation/forms.py:66 compensation/forms.py:263 compensation/forms.py:344 +#: compensation/forms/forms.py:45 intervention/forms/forms.py:41 +msgid "An explanatory name" +msgstr "Aussagekräftiger Titel" + +#: compensation/forms/forms.py:49 ema/forms.py:47 ema/forms.py:105 +msgid "Compensation XY; Location ABC" +msgstr "Kompensation XY; Flur ABC" + +#: compensation/forms/forms.py:55 +msgid "Fundings" +msgstr "Förderungen" + +#: compensation/forms/forms.py:58 +msgid "Select fundings for this compensation" +msgstr "Wählen Sie ggf. Fördermittelprojekte" + +#: compensation/forms/forms.py:67 compensation/forms/forms.py:104 +#: compensation/forms/forms.py:155 intervention/forms/forms.py:63 +#: intervention/forms/forms.py:80 intervention/forms/forms.py:96 +#: intervention/forms/forms.py:112 +msgid "Click for selection" +msgstr "Auswählen..." + +#: compensation/forms/forms.py:73 compensation/forms/modalForms.py:61 +#: compensation/forms/modalForms.py:272 compensation/forms/modalForms.py:367 #: compensation/templates/compensation/detail/compensation/includes/actions.html:34 +#: compensation/templates/compensation/detail/compensation/includes/comment.html:11 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:34 #: compensation/templates/compensation/detail/compensation/includes/documents.html:31 #: compensation/templates/compensation/detail/eco_account/includes/actions.html:34 +#: compensation/templates/compensation/detail/eco_account/includes/comment.html:11 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:34 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:31 #: ema/templates/ema/detail/includes/actions.html:34 #: ema/templates/ema/detail/includes/deadlines.html:34 #: ema/templates/ema/detail/includes/documents.html:31 -#: intervention/forms.py:346 +#: intervention/forms/forms.py:179 intervention/forms/modalForms.py:132 +#: intervention/templates/intervention/detail/includes/comment.html:11 #: intervention/templates/intervention/detail/includes/documents.html:31 #: intervention/templates/intervention/detail/includes/payments.html:34 #: intervention/templates/intervention/detail/includes/revocation.html:38 -#: konova/forms.py:315 +#: konova/forms.py:371 msgid "Comment" msgstr "Kommentar" -#: compensation/forms.py:68 compensation/forms.py:265 compensation/forms.py:346 -#: intervention/forms.py:348 konova/forms.py:317 +#: compensation/forms/forms.py:75 intervention/forms/forms.py:181 +msgid "Additional comment" +msgstr "Zusätzlicher Kommentar" + +#: compensation/forms/forms.py:93 +#: compensation/templates/compensation/detail/eco_account/view.html:58 +#: ema/templates/ema/detail/view.html:42 intervention/forms/forms.py:101 +#: intervention/templates/intervention/detail/view.html:56 +msgid "Conservation office" +msgstr "Eintragungsstelle" + +#: compensation/forms/forms.py:95 +msgid "Select the responsible office" +msgstr "Verantwortliche Stelle" + +#: compensation/forms/forms.py:109 +#: compensation/templates/compensation/detail/eco_account/view.html:62 +#: ema/templates/ema/detail/view.html:46 intervention/forms/forms.py:129 +#: intervention/templates/intervention/detail/view.html:60 +msgid "Conservation office file number" +msgstr "Aktenzeichen Eintragungsstelle" + +#: compensation/forms/forms.py:115 intervention/forms/forms.py:135 +msgid "ETS-123/ABC.456" +msgstr "" + +#: compensation/forms/forms.py:121 +msgid "Eco-account handler" +msgstr "Maßnahmenträger" + +#: compensation/forms/forms.py:125 +msgid "Who handles the eco-account" +msgstr "Wer für die Herrichtung des Ökokontos verantwortlich ist" + +#: compensation/forms/forms.py:128 intervention/forms/forms.py:148 +msgid "Company Mustermann" +msgstr "Firma Mustermann" + +#: compensation/forms/forms.py:146 +#: compensation/templates/compensation/detail/compensation/view.html:35 +msgid "compensates intervention" +msgstr "kompensiert Eingriff" + +#: compensation/forms/forms.py:148 +msgid "Select the intervention for which this compensation compensates" +msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist" + +#: compensation/forms/forms.py:173 +msgid "New compensation" +msgstr "Neue Kompensation" + +#: compensation/forms/forms.py:231 +msgid "Edit compensation" +msgstr "Bearbeite Kompensation" + +#: compensation/forms/forms.py:299 +msgid "New Eco-Account" +msgstr "Neues Ökokonto" + +#: compensation/forms/forms.py:308 +msgid "Eco-Account XY; Location ABC" +msgstr "Ökokonto XY; Flur ABC" + +#: compensation/forms/forms.py:360 +msgid "Available Surface" +msgstr "Verfügbare Fläche" + +#: compensation/forms/forms.py:363 +msgid "The amount that can be used for deductions" +msgstr "Die für Abbuchungen zur Verfügung stehende Menge" + +#: compensation/forms/forms.py:384 +msgid "Edit Eco-Account" +msgstr "Ökokonto bearbeiten" + +#: compensation/forms/modalForms.py:36 +msgid "in Euro" +msgstr "in Euro" + +#: compensation/forms/modalForms.py:45 +#: intervention/templates/intervention/detail/includes/payments.html:31 +msgid "Due on" +msgstr "Fällig am" + +#: compensation/forms/modalForms.py:48 +msgid "Due on which date" +msgstr "Zahlung wird an diesem Datum erwartet" + +#: compensation/forms/modalForms.py:63 compensation/forms/modalForms.py:274 +#: compensation/forms/modalForms.py:369 intervention/forms/modalForms.py:134 +#: konova/forms.py:373 msgid "Additional comment, maximum {} letters" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" -#: compensation/forms.py:80 +#: compensation/forms/modalForms.py:75 msgid "Payment" msgstr "Zahlung" -#: compensation/forms.py:81 +#: compensation/forms/modalForms.py:76 msgid "Add a payment for intervention '{}'" msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen" -#: compensation/forms.py:102 +#: compensation/forms/modalForms.py:96 msgid "If there is no date you can enter, please explain why." msgstr "Falls Sie kein Datum angeben können, erklären Sie bitte weshalb." -#: compensation/forms.py:116 +#: compensation/forms/modalForms.py:110 msgid "Added payment" msgstr "Zahlung hinzufügen" -#: compensation/forms.py:133 compensation/forms.py:145 +#: compensation/forms/modalForms.py:133 compensation/forms/modalForms.py:145 msgid "Biotope Type" msgstr "Biotoptyp" -#: compensation/forms.py:136 +#: compensation/forms/modalForms.py:136 msgid "Select the biotope type" msgstr "Biotoptyp wählen" -#: compensation/forms.py:152 +#: compensation/forms/modalForms.py:152 #: compensation/templates/compensation/detail/compensation/includes/states-after.html:36 #: compensation/templates/compensation/detail/compensation/includes/states-before.html:36 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:36 #: compensation/templates/compensation/detail/eco_account/includes/states-before.html:36 #: ema/templates/ema/detail/includes/states-after.html:36 #: ema/templates/ema/detail/includes/states-before.html:36 -#: intervention/forms.py:479 +#: intervention/forms/modalForms.py:274 msgid "Surface" msgstr "Fläche" -#: compensation/forms.py:155 intervention/forms.py:481 +#: compensation/forms/modalForms.py:155 intervention/forms/modalForms.py:276 msgid "in m²" msgstr "" -#: compensation/forms.py:160 +#: compensation/forms/modalForms.py:166 msgid "New state" msgstr "Neuer Zustand" -#: compensation/forms.py:161 +#: compensation/forms/modalForms.py:167 msgid "Insert data for the new state" msgstr "Geben Sie die Daten des neuen Zustandes ein" -#: compensation/forms.py:169 +#: compensation/forms/modalForms.py:174 msgid "Added state" msgstr "Zustand hinzugefügt" -#: compensation/forms.py:185 konova/forms.py:168 +#: compensation/forms/modalForms.py:190 konova/forms.py:193 msgid "Object removed" msgstr "Objekt entfernt" -#: compensation/forms.py:236 +#: compensation/forms/modalForms.py:244 msgid "Deadline Type" msgstr "Fristart" -#: compensation/forms.py:239 +#: compensation/forms/modalForms.py:247 msgid "Select the deadline type" msgstr "Fristart wählen" -#: compensation/forms.py:248 +#: compensation/forms/modalForms.py:256 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31 #: ema/templates/ema/detail/includes/deadlines.html:31 -#: intervention/forms.py:321 +#: intervention/forms/modalForms.py:106 msgid "Date" msgstr "Datum" -#: compensation/forms.py:251 +#: compensation/forms/modalForms.py:259 msgid "Select date" msgstr "Datum wählen" -#: compensation/forms.py:276 +#: compensation/forms/modalForms.py:286 msgid "New deadline" msgstr "Neue Frist" -#: compensation/forms.py:277 +#: compensation/forms/modalForms.py:287 msgid "Insert data for the new deadline" msgstr "Geben Sie die Daten der neuen Frist ein" -#: compensation/forms.py:294 +#: compensation/forms/modalForms.py:304 msgid "Added deadline" msgstr "Frist/Termin hinzugefügt" -#: compensation/forms.py:305 +#: compensation/forms/modalForms.py:322 msgid "Action Type" msgstr "Maßnahmentyp" -#: compensation/forms.py:308 +#: compensation/forms/modalForms.py:325 msgid "Select the action type" msgstr "Maßnahmentyp wählen" -#: compensation/forms.py:317 +#: compensation/forms/modalForms.py:334 #: compensation/templates/compensation/detail/compensation/includes/actions.html:37 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:37 #: compensation/templates/compensation/detail/compensation/includes/documents.html:34 @@ -187,33 +319,33 @@ msgstr "Maßnahmentyp wählen" msgid "Action" msgstr "Aktionen" -#: compensation/forms.py:322 +#: compensation/forms/modalForms.py:339 msgid "Unit" msgstr "Einheit" -#: compensation/forms.py:325 +#: compensation/forms/modalForms.py:342 msgid "Select the unit" msgstr "Einheit wählen" -#: compensation/forms.py:334 +#: compensation/forms/modalForms.py:351 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:34 #: intervention/templates/intervention/detail/includes/deductions.html:31 msgid "Amount" msgstr "Menge" -#: compensation/forms.py:337 +#: compensation/forms/modalForms.py:354 msgid "Insert the amount" msgstr "Menge eingeben" -#: compensation/forms.py:357 +#: compensation/forms/modalForms.py:380 msgid "New action" msgstr "Neue Maßnahme" -#: compensation/forms.py:358 +#: compensation/forms/modalForms.py:381 msgid "Insert data for the new action" msgstr "Geben Sie die Daten der neuen Maßnahme ein" -#: compensation/forms.py:377 +#: compensation/forms/modalForms.py:399 msgid "Added action" msgstr "Maßnahme hinzugefügt" @@ -256,119 +388,99 @@ msgstr "" "Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar einstellen " "wollen. Kontaktieren Sie die für die Abbuchungen verantwortlichen Nutzer!" -#: compensation/tables.py:24 compensation/tables.py:164 ema/tables.py:28 -#: intervention/forms.py:30 intervention/tables.py:23 -#: intervention/templates/intervention/detail/includes/compensations.html:30 -msgid "Identifier" -msgstr "Kennung" - -#: compensation/tables.py:29 compensation/tables.py:169 -#: compensation/templates/compensation/detail/compensation/includes/documents.html:28 -#: compensation/templates/compensation/detail/compensation/view.html:31 -#: compensation/templates/compensation/detail/eco_account/includes/documents.html:28 -#: compensation/templates/compensation/detail/eco_account/view.html:31 -#: ema/tables.py:33 ema/templates/ema/detail/includes/documents.html:28 -#: ema/templates/ema/detail/view.html:24 intervention/forms.py:37 -#: intervention/tables.py:28 -#: intervention/templates/intervention/detail/includes/compensations.html:33 -#: intervention/templates/intervention/detail/includes/documents.html:28 -#: intervention/templates/intervention/detail/view.html:31 konova/forms.py:286 -msgid "Title" -msgstr "Bezeichnung" - -#: compensation/tables.py:34 +#: compensation/tables.py:35 #: compensation/templates/compensation/detail/compensation/view.html:43 #: intervention/tables.py:33 -#: intervention/templates/intervention/detail/view.html:63 user/models.py:48 +#: intervention/templates/intervention/detail/view.html:68 user/models.py:48 msgid "Checked" msgstr "Geprüft" -#: compensation/tables.py:40 compensation/tables.py:179 +#: compensation/tables.py:41 compensation/tables.py:182 #: compensation/templates/compensation/detail/compensation/view.html:57 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31 #: compensation/templates/compensation/detail/eco_account/view.html:44 #: ema/tables.py:38 ema/templates/ema/detail/view.html:28 #: intervention/tables.py:39 -#: intervention/templates/intervention/detail/view.html:77 user/models.py:49 +#: intervention/templates/intervention/detail/view.html:82 user/models.py:49 msgid "Recorded" msgstr "Verzeichnet" -#: compensation/tables.py:46 compensation/tables.py:185 ema/tables.py:44 +#: compensation/tables.py:47 compensation/tables.py:188 ema/tables.py:44 #: intervention/tables.py:51 msgid "Editable" msgstr "Freigegeben" -#: compensation/tables.py:52 compensation/tables.py:191 ema/tables.py:50 +#: compensation/tables.py:53 compensation/tables.py:194 ema/tables.py:50 #: intervention/tables.py:57 msgid "Last edit" msgstr "Zuletzt bearbeitet" -#: compensation/tables.py:61 +#: compensation/tables.py:62 #: intervention/templates/intervention/detail/includes/compensations.html:8 msgid "Compensations" msgstr "Kompensationen" -#: compensation/tables.py:83 compensation/tables.py:222 ema/tables.py:82 +#: compensation/tables.py:84 compensation/tables.py:225 ema/tables.py:82 #: intervention/tables.py:88 msgid "Open {}" msgstr "Öffne {}" -#: compensation/tables.py:83 +#: compensation/tables.py:84 #: compensation/templates/compensation/detail/compensation/view.html:19 #: konova/templates/konova/home.html:49 templates/navbar.html:28 msgid "Compensation" msgstr "Kompensation" -#: compensation/tables.py:104 intervention/tables.py:107 +#: compensation/tables.py:105 intervention/tables.py:107 msgid "Not checked yet" msgstr "Noch nicht geprüft" -#: compensation/tables.py:109 intervention/tables.py:112 +#: compensation/tables.py:110 intervention/tables.py:112 msgid "Checked on {} by {}" msgstr "Am {} von {} geprüft worden" -#: compensation/tables.py:128 +#: compensation/tables.py:129 #: compensation/templates/compensation/detail/compensation/view.html:60 #: compensation/templates/compensation/detail/eco_account/view.html:47 #: ema/tables.py:101 ema/templates/ema/detail/view.html:31 -#: intervention/models.py:360 intervention/tables.py:131 -#: intervention/templates/intervention/detail/view.html:80 +#: intervention/models.py:379 intervention/tables.py:131 +#: intervention/templates/intervention/detail/view.html:85 msgid "Not recorded yet" msgstr "Noch nicht verzeichnet" -#: compensation/tables.py:133 compensation/tables.py:260 ema/tables.py:106 -#: intervention/models.py:365 intervention/tables.py:136 +#: compensation/tables.py:134 compensation/tables.py:263 ema/tables.py:106 +#: intervention/models.py:384 intervention/tables.py:136 msgid "Recorded on {} by {}" msgstr "Am {} von {} verzeichnet worden" -#: compensation/tables.py:156 compensation/tables.py:283 ema/tables.py:129 +#: compensation/tables.py:159 compensation/tables.py:286 ema/tables.py:129 #: intervention/tables.py:159 msgid "Full access granted" msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden" -#: compensation/tables.py:156 compensation/tables.py:283 ema/tables.py:129 +#: compensation/tables.py:159 compensation/tables.py:286 ema/tables.py:129 #: intervention/tables.py:159 msgid "Access not granted" msgstr "Nicht freigegeben - Datensatz nur lesbar" -#: compensation/tables.py:174 +#: compensation/tables.py:177 #: compensation/templates/compensation/detail/eco_account/view.html:35 -#: konova/templates/konova/custom_widgets/progressbar.html:3 +#: konova/templates/konova/widgets/progressbar.html:3 msgid "Available" msgstr "Verfügbar" -#: compensation/tables.py:200 +#: compensation/tables.py:203 msgid "Eco Accounts" msgstr "Ökokonten" -#: compensation/tables.py:222 +#: compensation/tables.py:225 #: compensation/templates/compensation/detail/eco_account/view.html:19 -#: intervention/forms.py:463 intervention/forms.py:470 +#: intervention/forms/modalForms.py:258 intervention/forms/modalForms.py:265 #: konova/templates/konova/home.html:88 templates/navbar.html:34 msgid "Eco-account" msgstr "Ökokonto" -#: compensation/tables.py:255 +#: compensation/tables.py:258 msgid "Not recorded yet. Can not be used for deductions, yet." msgstr "" "Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden." @@ -455,7 +567,7 @@ msgstr "Frist/Termin hinzufügen" #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:28 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:28 -#: ema/templates/ema/detail/includes/deadlines.html:28 intervention/forms.py:42 +#: ema/templates/ema/detail/includes/deadlines.html:28 msgid "Type" msgstr "Typ" @@ -476,7 +588,7 @@ msgstr "Dokumente" #: compensation/templates/compensation/detail/eco_account/includes/documents.html:14 #: ema/templates/ema/detail/includes/documents.html:14 #: intervention/templates/intervention/detail/includes/documents.html:14 -#: konova/forms.py:335 +#: konova/forms.py:392 msgid "Add new document" msgstr "Neues Dokument hinzufügen" @@ -541,12 +653,8 @@ msgstr "Neuen Ausgangszustand hinzufügen" msgid "Missing surfaces according to states after: " msgstr "Fehlende Flächenmengen laut Zielzustand: " -#: compensation/templates/compensation/detail/compensation/view.html:35 -msgid "compensates intervention" -msgstr "kompensiert Eingriff" - #: compensation/templates/compensation/detail/compensation/view.html:50 -#: intervention/templates/intervention/detail/view.html:70 +#: intervention/templates/intervention/detail/view.html:75 msgid "Checked on " msgstr "Geprüft am " @@ -554,29 +662,41 @@ msgstr "Geprüft am " #: compensation/templates/compensation/detail/compensation/view.html:64 #: compensation/templates/compensation/detail/eco_account/view.html:51 #: ema/templates/ema/detail/view.html:35 -#: intervention/templates/intervention/detail/view.html:70 -#: intervention/templates/intervention/detail/view.html:84 +#: intervention/templates/intervention/detail/view.html:75 +#: intervention/templates/intervention/detail/view.html:89 msgid "by" msgstr "von" #: compensation/templates/compensation/detail/compensation/view.html:64 #: compensation/templates/compensation/detail/eco_account/view.html:51 #: ema/templates/ema/detail/view.html:35 -#: intervention/templates/intervention/detail/view.html:84 +#: intervention/templates/intervention/detail/view.html:89 msgid "Recorded on " msgstr "Verzeichnet am" #: compensation/templates/compensation/detail/compensation/view.html:71 -#: compensation/templates/compensation/detail/eco_account/view.html:83 +#: compensation/templates/compensation/detail/eco_account/view.html:70 #: ema/templates/ema/detail/view.html:54 -#: intervention/templates/intervention/detail/view.html:103 +msgid "Funded by" +msgstr "Gefördert mit" + +#: compensation/templates/compensation/detail/compensation/view.html:79 +#: compensation/templates/compensation/detail/eco_account/view.html:78 +#: ema/templates/ema/detail/view.html:62 +msgid "None" +msgstr "" + +#: compensation/templates/compensation/detail/compensation/view.html:84 +#: compensation/templates/compensation/detail/eco_account/view.html:83 +#: ema/templates/ema/detail/view.html:67 +#: intervention/templates/intervention/detail/view.html:108 msgid "Last modified" msgstr "Zuletzt bearbeitet" -#: compensation/templates/compensation/detail/compensation/view.html:79 +#: compensation/templates/compensation/detail/compensation/view.html:92 #: compensation/templates/compensation/detail/eco_account/view.html:91 -#: ema/templates/ema/detail/view.html:69 intervention/forms.py:255 -#: intervention/templates/intervention/detail/view.html:111 +#: ema/templates/ema/detail/view.html:82 intervention/forms/modalForms.py:40 +#: intervention/templates/intervention/detail/view.html:116 msgid "Shared with" msgstr "Freigegeben für" @@ -617,6 +737,10 @@ msgstr "Erstellt" msgid "Remove Deduction" msgstr "Abbuchung entfernen" +#: compensation/templates/compensation/detail/eco_account/view.html:34 +msgid "No surface deductable" +msgstr "Keine Flächenmenge für Abbuchungen eingegeben. Bitte bearbeiten." + #: compensation/templates/compensation/detail/eco_account/view.html:57 #: compensation/templates/compensation/detail/eco_account/view.html:61 #: compensation/templates/compensation/detail/eco_account/view.html:65 @@ -625,97 +749,96 @@ msgstr "Abbuchung entfernen" #: intervention/templates/intervention/detail/view.html:30 #: intervention/templates/intervention/detail/view.html:34 #: intervention/templates/intervention/detail/view.html:38 -#: intervention/templates/intervention/detail/view.html:42 -#: intervention/templates/intervention/detail/view.html:46 -#: intervention/templates/intervention/detail/view.html:50 -#: intervention/templates/intervention/detail/view.html:54 -#: intervention/templates/intervention/detail/view.html:58 -#: intervention/templates/intervention/detail/view.html:90 -#: intervention/templates/intervention/detail/view.html:94 -msgid "Missing" -msgstr "Fehlt" - -#: compensation/templates/compensation/detail/eco_account/view.html:58 -#: ema/templates/ema/detail/view.html:42 +#: intervention/templates/intervention/detail/view.html:47 #: intervention/templates/intervention/detail/view.html:51 -msgid "Conservation office" -msgstr "Naturschutzbehörde" - -#: compensation/templates/compensation/detail/eco_account/view.html:62 -#: ema/templates/ema/detail/view.html:46 #: intervention/templates/intervention/detail/view.html:55 -msgid "Conversation office file number" -msgstr "Aktenzeichen Naturschutzbehörde" +#: intervention/templates/intervention/detail/view.html:59 +#: intervention/templates/intervention/detail/view.html:63 +#: intervention/templates/intervention/detail/view.html:95 +#: intervention/templates/intervention/detail/view.html:99 +msgid "Missing" +msgstr "Fehlt" #: compensation/templates/compensation/detail/eco_account/view.html:66 -#: ema/templates/ema/detail/view.html:50 intervention/forms.py:54 -#: intervention/templates/intervention/detail/view.html:59 +#: ema/templates/ema/detail/view.html:50 intervention/forms/forms.py:141 +#: intervention/templates/intervention/detail/view.html:64 msgid "Intervention handler" msgstr "Eingriffsverursacher" -#: compensation/templates/compensation/detail/eco_account/view.html:70 -msgid "Funded by" -msgstr "Gefördert mit" +#: compensation/views/compensation_views.py:76 +msgid "Compensation {} added" +msgstr "Kompensation {} hinzugefügt" +#: compensation/views/compensation_views.py:131 +msgid "Compensation {} edited" +msgstr "Kompensation {} bearbeitet" -#: compensation/views/compensation_views.py:123 -#: compensation/views/eco_account_views.py:190 ema/views.py:128 -#: intervention/views.py:391 +#: compensation/views/compensation_views.py:210 +#: compensation/views/eco_account_views.py:277 ema/views.py:174 +#: intervention/views.py:427 msgid "Log" msgstr "Log" -#: compensation/views/compensation_views.py:144 +#: compensation/views/compensation_views.py:231 msgid "Compensation removed" msgstr "Kompensation entfernt" -#: compensation/views/compensation_views.py:163 -#: compensation/views/eco_account_views.py:289 ema/views.py:250 -#: intervention/views.py:94 +#: compensation/views/compensation_views.py:250 +#: compensation/views/eco_account_views.py:376 ema/views.py:327 +#: intervention/views.py:123 msgid "Document added" msgstr "Dokument hinzugefügt" -#: compensation/views/compensation_views.py:219 -#: compensation/views/eco_account_views.py:233 ema/views.py:194 +#: compensation/views/compensation_views.py:306 +#: compensation/views/eco_account_views.py:320 ema/views.py:271 msgid "State added" msgstr "Zustand hinzugefügt" -#: compensation/views/compensation_views.py:238 -#: compensation/views/eco_account_views.py:252 ema/views.py:213 +#: compensation/views/compensation_views.py:325 +#: compensation/views/eco_account_views.py:339 ema/views.py:290 msgid "Action added" msgstr "Maßnahme hinzugefügt" -#: compensation/views/compensation_views.py:257 -#: compensation/views/eco_account_views.py:271 ema/views.py:232 +#: compensation/views/compensation_views.py:344 +#: compensation/views/eco_account_views.py:358 ema/views.py:309 msgid "Deadline added" msgstr "Frist/Termin hinzugefügt" -#: compensation/views/compensation_views.py:276 +#: compensation/views/compensation_views.py:363 msgid "State removed" msgstr "Zustand gelöscht" -#: compensation/views/compensation_views.py:295 +#: compensation/views/compensation_views.py:382 msgid "Action removed" msgstr "Maßnahme entfernt" -#: compensation/views/eco_account_views.py:140 +#: compensation/views/eco_account_views.py:86 +msgid "Eco-Account {} added" +msgstr "Ökokonto {} hinzugefügt" + +#: compensation/views/eco_account_views.py:141 +msgid "Eco-Account {} edited" +msgstr "Ökokonto {} bearbeitet" + +#: compensation/views/eco_account_views.py:227 msgid "Eco-account removed" msgstr "Ökokonto entfernt" -#: compensation/views/eco_account_views.py:167 +#: compensation/views/eco_account_views.py:254 msgid "Deduction removed" msgstr "Abbuchung entfernt" -#: compensation/views/eco_account_views.py:210 ema/views.py:171 -#: intervention/views.py:431 +#: compensation/views/eco_account_views.py:297 ema/views.py:248 +#: intervention/views.py:467 msgid "{} unrecorded" msgstr "{} entzeichnet" -#: compensation/views/eco_account_views.py:210 ema/views.py:171 -#: intervention/views.py:431 +#: compensation/views/eco_account_views.py:297 ema/views.py:248 +#: intervention/views.py:467 msgid "{} recorded" msgstr "{} verzeichnet" -#: compensation/views/eco_account_views.py:346 intervention/views.py:413 +#: compensation/views/eco_account_views.py:433 intervention/views.py:449 msgid "Deduction added" msgstr "Abbuchung hinzugefügt" @@ -727,6 +850,14 @@ msgstr "Zahlung hinzugefügt" msgid "Payment removed" msgstr "Zahlung gelöscht" +#: ema/forms.py:38 +msgid "New EMA" +msgstr "Neue EMA hinzufügen" + +#: ema/forms.py:99 +msgid "Edit EMA" +msgstr "Bearbeite EMA" + #: ema/tables.py:59 templates/navbar.html:43 msgid "Payment funded compensations" msgstr "Ersatzzahlungsmaßnahmen (EMA)" @@ -747,7 +878,15 @@ msgstr "" msgid "Payment funded compensation" msgstr "Ersatzzahlungsmaßnahme" -#: ema/views.py:154 +#: ema/views.py:77 +msgid "EMA {} added" +msgstr "EMA {} hinzugefügt" + +#: ema/views.py:201 +msgid "EMA {} edited" +msgstr "EMA {} bearbeitet" + +#: ema/views.py:231 msgid "EMA removed" msgstr "EMA entfernt" @@ -767,120 +906,113 @@ msgstr "Gemarkung" msgid "Search for district" msgstr "Nach Gemarkung suchen" -#: intervention/forms.py:33 -msgid "Generated automatically if none was given" -msgstr "Wird automatisch erzeugt, falls nicht angegeben" +#: intervention/forms/forms.py:45 +msgid "Construction XY; Location ABC" +msgstr "Bauvorhaben XY; Flur ABC" -#: intervention/forms.py:45 -msgid "Which intervention type is this" -msgstr "Welcher Eingriffstyp" +#: intervention/forms/forms.py:51 +#: intervention/templates/intervention/detail/view.html:35 +msgid "Process type" +msgstr "Verfahrenstyp" -#: intervention/forms.py:48 +#: intervention/forms/forms.py:68 #: intervention/templates/intervention/detail/view.html:39 msgid "Law" msgstr "Gesetz" -#: intervention/forms.py:51 -msgid "Based on which law" -msgstr "Basiert auf welchem Recht" - -#: intervention/forms.py:57 -msgid "Who performs the intervention" -msgstr "Wer führt den Eingriff durch" - -#: intervention/forms.py:60 -msgid "Data provider" -msgstr "Datenbereitsteller" - -#: intervention/forms.py:62 -msgid "Who provides the data for the intervention" -msgstr "Wer stellt die Daten für den Eingriff zur Verfügung" +#: intervention/forms/forms.py:70 +msgid "Multiple selection possible" +msgstr "Mehrfachauswahl möglich" -#: intervention/forms.py:67 -msgid "Organization" -msgstr "Organisation" +#: intervention/forms/forms.py:85 +#: intervention/templates/intervention/detail/view.html:48 +msgid "Registration office" +msgstr "Zulassungsbehörde" -#: intervention/forms.py:73 -msgid "Data provider details" -msgstr "Datenbereitsteller Details" +#: intervention/forms/forms.py:117 +#: intervention/templates/intervention/detail/view.html:52 +msgid "Registration office file number" +msgstr "Aktenzeichen Zulassungsbehörde" -#: intervention/forms.py:76 -msgid "Further details" -msgstr "Weitere Details" +#: intervention/forms/forms.py:123 +msgid "ZB-123/ABC.456" +msgstr "" -#: intervention/forms.py:89 -msgid "Map" -msgstr "Karte" +#: intervention/forms/forms.py:145 +msgid "Who performs the intervention" +msgstr "Wer führt den Eingriff durch" -#: intervention/forms.py:91 -msgid "Where does the intervention take place" -msgstr "Wo findet der Eingriff statt" +#: intervention/forms/forms.py:154 +#: intervention/templates/intervention/detail/view.html:96 +msgid "Registration date" +msgstr "Datum Zulassung bzw. Satzungsbeschluss" -#: intervention/forms.py:99 -msgid "Files" -msgstr "Dateien" +#: intervention/forms/forms.py:166 +#: intervention/templates/intervention/detail/view.html:100 +msgid "Binding on" +msgstr "Datum Bestandskraft" -#: intervention/forms.py:106 +#: intervention/forms/forms.py:192 msgid "New intervention" msgstr "Neuer Eingriff" -#: intervention/forms.py:148 +#: intervention/forms/forms.py:273 msgid "Edit intervention" msgstr "Eingriff bearbeiten" -#: intervention/forms.py:244 +#: intervention/forms/modalForms.py:28 msgid "Share link" msgstr "Freigabelink" -#: intervention/forms.py:246 +#: intervention/forms/modalForms.py:30 msgid "Send this link to users who you want to have writing access on the data" msgstr "Andere Nutzer erhalten über diesen Link Zugriff auf die Daten" -#: intervention/forms.py:258 +#: intervention/forms/modalForms.py:43 msgid "Remove check to remove access for this user" msgstr "Wählen Sie die Nutzer ab, die keinen Zugriff mehr haben sollen" -#: intervention/forms.py:269 +#: intervention/forms/modalForms.py:54 #: intervention/templates/intervention/detail/includes/controls.html:15 msgid "Share" msgstr "Freigabe" -#: intervention/forms.py:270 +#: intervention/forms/modalForms.py:55 msgid "Share settings for {}" msgstr "Freigabe Einstellungen für {}" -#: intervention/forms.py:323 +#: intervention/forms/modalForms.py:108 msgid "Date of revocation" msgstr "Datum des Widerspruchs" -#: intervention/forms.py:333 +#: intervention/forms/modalForms.py:119 #: intervention/templates/intervention/detail/includes/revocation.html:35 msgid "Document" msgstr "Dokument" -#: intervention/forms.py:336 konova/forms.py:305 +#: intervention/forms/modalForms.py:122 konova/forms.py:361 msgid "Must be smaller than 15 Mb" msgstr "Muss kleiner als 15 Mb sein" -#: intervention/forms.py:359 +#: intervention/forms/modalForms.py:146 #: intervention/templates/intervention/detail/includes/revocation.html:18 msgid "Add revocation" msgstr "Widerspruch hinzufügen" -#: intervention/forms.py:399 +#: intervention/forms/modalForms.py:186 msgid "Checked intervention data" msgstr "Eingriffsdaten geprüft" -#: intervention/forms.py:405 +#: intervention/forms/modalForms.py:192 msgid "Checked compensations data and payments" msgstr "Kompensationen und Zahlungen geprüft" -#: intervention/forms.py:413 +#: intervention/forms/modalForms.py:200 #: intervention/templates/intervention/detail/includes/controls.html:19 msgid "Run check" msgstr "Prüfung vornehmen" -#: intervention/forms.py:414 konova/forms.py:389 +#: intervention/forms/modalForms.py:201 konova/forms.py:446 msgid "" "I, {} {}, confirm that all necessary control steps have been performed by " "myself." @@ -888,30 +1020,30 @@ msgstr "" "Ich, {} {}, bestätige, dass die notwendigen Kontrollschritte durchgeführt " "wurden:" -#: intervention/forms.py:465 +#: intervention/forms/modalForms.py:260 msgid "Only recorded accounts can be selected for deductions" msgstr "Nur verzeichnete Ökokonten können für Abbuchungen verwendet werden." -#: intervention/forms.py:484 intervention/forms.py:491 +#: intervention/forms/modalForms.py:285 intervention/forms/modalForms.py:292 #: intervention/tables.py:88 #: intervention/templates/intervention/detail/view.html:19 #: konova/templates/konova/home.html:11 templates/navbar.html:22 msgid "Intervention" msgstr "Eingriff" -#: intervention/forms.py:486 +#: intervention/forms/modalForms.py:287 msgid "Only shared interventions can be selected" msgstr "Nur freigegebene Eingriffe können gewählt werden" -#: intervention/forms.py:499 +#: intervention/forms/modalForms.py:300 msgid "New Deduction" msgstr "Neue Abbuchung" -#: intervention/forms.py:500 +#: intervention/forms/modalForms.py:301 msgid "Enter the information for a new deduction from a chosen eco-account" msgstr "Geben Sie die Informationen für eine neue Abbuchung ein." -#: intervention/forms.py:536 +#: intervention/forms/modalForms.py:334 msgid "" "Eco-account {} is not recorded yet. You can only deduct from recorded " "accounts." @@ -919,7 +1051,7 @@ msgstr "" "Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von " "verzeichneten Ökokonten erfolgen." -#: intervention/forms.py:549 +#: intervention/forms/modalForms.py:347 msgid "" "The account {} has not enough surface for a deduction of {} m². There are " "only {} m² left" @@ -927,38 +1059,38 @@ msgstr "" "Das Ökokonto {} hat für eine Abbuchung von {} m² nicht ausreichend " "Restfläche. Es stehen noch {} m² zur Verfügung." -#: intervention/models.py:306 +#: intervention/models.py:325 msgid "Registration office file number missing" msgstr "Aktenzeichen Zulassungsbehörde fehlt" -#: intervention/models.py:309 -msgid "Conversation office file number missing" +#: intervention/models.py:328 +msgid "Conservation office file number missing" msgstr "Aktenzeichen Naturschutzbehörde fehlt" -#: intervention/models.py:312 +#: intervention/models.py:331 msgid "Responsible data missing" msgstr "Daten zu Verantwortlichen fehlen" -#: intervention/models.py:326 +#: intervention/models.py:345 msgid "Revocation exists" msgstr "Widerspruch liegt vor" -#: intervention/models.py:329 +#: intervention/models.py:348 msgid "Registration date missing" msgstr "Datum Zulassung bzw. Satzungsbeschluss fehlt" -#: intervention/models.py:332 +#: intervention/models.py:351 msgid "Binding on missing" msgstr "Datum Bestandskraft fehlt" -#: intervention/models.py:334 +#: intervention/models.py:353 msgid "Legal data missing" msgstr "Rechtliche Daten fehlen" #: intervention/tables.py:45 #: intervention/templates/intervention/detail/includes/revocation.html:8 #: intervention/templates/intervention/detail/includes/revocation.html:55 -#: intervention/templates/intervention/detail/view.html:99 +#: intervention/templates/intervention/detail/view.html:104 msgid "Revocation" msgstr "Widerspruch" @@ -1020,43 +1152,19 @@ msgstr "Vom" msgid "Remove revocation" msgstr "Widerspruch entfernen" -#: intervention/templates/intervention/detail/view.html:35 -msgid "Process type" -msgstr "Verfahrenstyp" - -#: intervention/templates/intervention/detail/view.html:43 -msgid "Registration office" -msgstr "Zulassungsbehörde" - -#: intervention/templates/intervention/detail/view.html:47 -msgid "Registration office file number" -msgstr "Aktenzeichen Zulassungsbehörde" - -#: intervention/templates/intervention/detail/view.html:91 -msgid "Registration date" -msgstr "Datum Zulassung bzw. Satzungsbeschluss" - -#: intervention/templates/intervention/detail/view.html:95 -msgid "Binding on" -msgstr "Datum Bestandskraft" - -#: intervention/templates/intervention/detail/view.html:98 +#: intervention/templates/intervention/detail/view.html:103 msgid "Exists" msgstr "vorhanden" -#: intervention/views.py:66 +#: intervention/views.py:76 msgid "Intervention {} added" msgstr "Eingriff {} hinzugefügt" -#: intervention/views.py:69 intervention/views.py:224 -msgid "Invalid input" -msgstr "Eingabe fehlerhaft" - -#: intervention/views.py:182 +#: intervention/views.py:211 msgid "This intervention has a revocation from {}" msgstr "Es existiert ein Widerspruch vom {}" -#: intervention/views.py:198 +#: intervention/views.py:227 msgid "" "Remember: This data has not been shared with you, yet. This means you can " "only read but can not edit or perform any actions like running a check or " @@ -1066,55 +1174,55 @@ msgstr "" "bedeutet, dass Sie nur lesenden Zugriff hierauf haben und weder bearbeiten, " "noch Prüfungen durchführen oder verzeichnen können." -#: intervention/views.py:221 -msgid "{} edited" -msgstr "{} bearbeitet" +#: intervention/views.py:254 +msgid "Intervention {} edited" +msgstr "Eingriff {} bearbeitet" -#: intervention/views.py:250 +#: intervention/views.py:286 msgid "{} removed" msgstr "{} entfernt" -#: intervention/views.py:271 +#: intervention/views.py:307 msgid "Revocation removed" msgstr "Widerspruch entfernt" -#: intervention/views.py:297 +#: intervention/views.py:333 msgid "{} has already been shared with you" msgstr "{} wurde bereits für Sie freigegeben" -#: intervention/views.py:302 +#: intervention/views.py:338 msgid "{} has been shared with you" msgstr "{} ist nun für Sie freigegeben" -#: intervention/views.py:309 +#: intervention/views.py:345 msgid "Share link invalid" msgstr "Freigabelink ungültig" -#: intervention/views.py:330 +#: intervention/views.py:366 msgid "Share settings updated" msgstr "Freigabe Einstellungen aktualisiert" -#: intervention/views.py:349 +#: intervention/views.py:385 msgid "Check performed" msgstr "Prüfung durchgeführt" -#: intervention/views.py:369 +#: intervention/views.py:405 msgid "Revocation added" msgstr "Widerspruch hinzugefügt" -#: intervention/views.py:436 +#: intervention/views.py:472 msgid "There are errors on this intervention:" msgstr "Es liegen Fehler in diesem Eingriff vor:" -#: konova/decorators.py:29 +#: konova/decorators.py:30 msgid "You need to be staff to perform this action!" msgstr "Hierfür müssen Sie Mitarbeiter sein!" -#: konova/decorators.py:44 +#: konova/decorators.py:45 msgid "You need to be administrator to perform this action!" msgstr "Hierfür müssen Sie Administrator sein!" -#: konova/decorators.py:62 +#: konova/decorators.py:63 msgid "" "+++ Attention: You are not part of any group. You won't be able to create, " "edit or do anything. Please contact an administrator. +++" @@ -1123,64 +1231,64 @@ msgstr "" "somit nichts eingeben, bearbeiten oder sonstige Aktionen ausführen. " "Kontaktieren Sie bitte einen Administrator. +++" -#: konova/decorators.py:83 konova/decorators.py:103 konova/decorators.py:123 -msgid "You need to be part of another user group." -msgstr "Hierfür müssen Sie einer anderen Nutzergruppe angehören!" - -#: konova/forms.py:68 +#: konova/forms.py:69 msgid "Not editable" msgstr "Nicht editierbar" -#: konova/forms.py:107 konova/forms.py:251 +#: konova/forms.py:139 konova/forms.py:309 msgid "Confirm" msgstr "Bestätige" -#: konova/forms.py:119 konova/forms.py:260 +#: konova/forms.py:151 konova/forms.py:318 msgid "Remove" msgstr "Löschen" -#: konova/forms.py:121 +#: konova/forms.py:153 msgid "You are about to remove {} {}" msgstr "Sie sind dabei {} {} zu löschen" -#: konova/forms.py:261 +#: konova/forms.py:243 templates/form/collapsable/form.html:45 +msgid "Geometry" +msgstr "Geometrie" + +#: konova/forms.py:319 msgid "Are you sure?" msgstr "Sind Sie sicher?" -#: konova/forms.py:291 +#: konova/forms.py:346 msgid "Created on" msgstr "Erstellt" -#: konova/forms.py:293 +#: konova/forms.py:348 msgid "When has this file been created? Important for photos." msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?" -#: konova/forms.py:303 +#: konova/forms.py:359 #: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231 msgid "File" msgstr "Datei" -#: konova/forms.py:366 +#: konova/forms.py:423 msgid "Added document" msgstr "Dokument hinzugefügt" -#: konova/forms.py:380 +#: konova/forms.py:437 msgid "Confirm record" msgstr "Verzeichnen bestätigen" -#: konova/forms.py:388 +#: konova/forms.py:445 msgid "Record data" msgstr "Daten verzeichnen" -#: konova/forms.py:395 +#: konova/forms.py:452 msgid "Confirm unrecord" msgstr "Entzeichnen bestätigen" -#: konova/forms.py:396 +#: konova/forms.py:453 msgid "Unrecord data" msgstr "Daten entzeichnen" -#: konova/forms.py:397 +#: konova/forms.py:454 msgid "I, {} {}, confirm that this data must be unrecorded." msgstr "" "Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen." @@ -1209,30 +1317,22 @@ msgstr "Wenn meine freigegebenen Daten gelöscht wurden" msgid "On registered data edited" msgstr "Wenn meine freigegebenen Daten bearbeitet wurden" -#: konova/models.py:190 +#: konova/models.py:206 msgid "Finished" msgstr "Umgesetzt bis" -#: konova/models.py:191 +#: konova/models.py:207 msgid "Maintain" msgstr "Unterhaltung bis" -#: konova/models.py:192 +#: konova/models.py:208 msgid "Control" msgstr "Kontrolle am" -#: konova/models.py:193 +#: konova/models.py:209 msgid "Other" msgstr "Sonstige" -#: konova/templates/konova/custom_widgets/text-to-clipboard-input.html:6 -msgid "Copy to clipboard" -msgstr "In Zwischenablage kopieren" - -#: konova/templates/konova/custom_widgets/text-to-clipboard-input.html:16 -msgid "Copied to clipboard" -msgstr "In Zwischenablage kopiert" - #: konova/templates/konova/home.html:23 konova/templates/konova/home.html:61 #: konova/templates/konova/home.html:100 msgid "Total" @@ -1257,6 +1357,18 @@ msgstr "Anzeigen" msgid "Deduct" msgstr "Abbuchen" +#: konova/templates/konova/widgets/generate-content-input.html:6 +msgid "Generate new" +msgstr "Neu generieren" + +#: konova/templates/konova/widgets/text-to-clipboard-input.html:6 +msgid "Copy to clipboard" +msgstr "In Zwischenablage kopieren" + +#: konova/templates/konova/widgets/text-to-clipboard-input.html:16 +msgid "Copied to clipboard" +msgstr "In Zwischenablage kopiert" + #: konova/utils/documents.py:52 msgid "Document '{}' deleted" msgstr "Dokument '{}' gelöscht" @@ -1269,6 +1381,22 @@ msgstr "Es gab einen Fehler im Formular." msgid "There are errors in this intervention." msgstr "Es liegen Fehler in diesem Eingriff vor:" +#: konova/utils/message_templates.py:13 +msgid "" +"The identifier '{}' had to be changed to '{}' since another entry has been " +"added in the meanwhile, which uses this identifier" +msgstr "" +"Die Kennung '{}' musste zu '{}' geändert werden, da ein anderer Eintrag in " +"der Zwischenzeit angelegt wurde, welcher diese Kennung nun bereits verwendet" + +#: konova/utils/message_templates.py:14 +msgid "This data is not shared with you" +msgstr "Diese Daten sind für Sie nicht freigegeben" + +#: konova/utils/message_templates.py:15 +msgid "You need to be part of another user group." +msgstr "Hierfür müssen Sie einer anderen Nutzergruppe angehören!" + #: konova/utils/messenger.py:69 msgid "{} checked" msgstr "{} geprüft" @@ -1325,15 +1453,42 @@ msgstr "" msgid "Contact" msgstr "Kontakt" -#: templates/form/generic_table_form.html:23 +#: templates/form/collapsable/form.html:14 +msgid "" +"\n" +" First enter the most basic data. Of course you can " +"change everything later.\n" +" All further data, like documents or further details, can " +"be added in the detail view after saving\n" +" your new entry.\n" +" " +msgstr "" +"\n" +"Geben Sie zunächst die grundlegenden Daten ein. Sie können diese später " +"immer noch ändern.\n" +"Alle weiteren Daten, wie Dokumente oder weitere Details, können nach dem " +"Speichern des neuen Eintrags hinzugefügt werden.\n" +" " + +#: templates/form/collapsable/form.html:20 +msgid "Open the input topic with a simple click." +msgstr "Mit einem Linksklick öffnen Sie den jeweiligen Formularbereich." + +#: templates/form/collapsable/form.html:30 +msgid "General data" +msgstr "Allgemeine Daten" + +#: templates/form/collapsable/form.html:58 +#: templates/form/table/generic_table_form.html:23 msgid "Cancel" msgstr "Abbrechen" -#: templates/form/generic_table_form.html:27 +#: templates/form/collapsable/form.html:62 +#: templates/form/table/generic_table_form.html:27 msgid "Save" msgstr "Speichern" -#: templates/form/generic_table_form_body.html:23 +#: templates/form/table/generic_table_form_body.html:24 msgid "Fields with * are required." msgstr "* sind Pflichtfelder." @@ -2693,6 +2848,42 @@ msgstr "" msgid "A fontawesome icon field" msgstr "" +#~ msgid "Funding by..." +#~ msgstr "Gefördert mit..." + +#~ msgid "Invalid input" +#~ msgstr "Eingabe fehlerhaft" + +#~ msgid "Map" +#~ msgstr "Karte" + +#~ msgid "Where does the intervention take place" +#~ msgstr "Wo findet der Eingriff statt" + +#~ msgid "Which intervention type is this" +#~ msgstr "Welcher Eingriffstyp" + +#~ msgid "Based on which law" +#~ msgstr "Basiert auf welchem Recht" + +#~ msgid "Data provider" +#~ msgstr "Datenbereitsteller" + +#~ msgid "Who provides the data for the intervention" +#~ msgstr "Wer stellt die Daten für den Eingriff zur Verfügung" + +#~ msgid "Organization" +#~ msgstr "Organisation" + +#~ msgid "Data provider details" +#~ msgstr "Datenbereitsteller Details" + +#~ msgid "Further details" +#~ msgstr "Weitere Details" + +#~ msgid "Files" +#~ msgstr "Dateien" + #~ msgid "Transfer note" #~ msgstr "Verwendungszweck" @@ -2714,9 +2905,6 @@ msgstr "" #~ msgid "Actions" #~ msgstr "Aktionen" -#~ msgid "Additional comment" -#~ msgstr "Zusätzlicher Kommentar" - #~ msgid "Missing surfaces: " #~ msgstr "Fehlende Flächen: " @@ -2738,18 +2926,6 @@ msgstr "" #~ msgid "Add new eco account" #~ msgstr "Neues Ökokonto hinzufügen" -#~ msgid "Edit eco account" -#~ msgstr "Ökokonto bearbeiten" - -#~ msgid "Delete eco account" -#~ msgstr "Ökokonto löschen" - -#~ msgid "Add new EMA" -#~ msgstr "Neue EMA hinzufügen" - -#~ msgid "Edit EMA" -#~ msgstr "Bearbeite EMA" - #~ msgid "Delete EMA" #~ msgstr "Lösche EMA" @@ -2771,18 +2947,12 @@ msgstr "" #~ msgid "Show intervention" #~ msgstr "Zeige Eingriffe" -#~ msgid "Show compensation" -#~ msgstr "Zeige Kompensationen" - #~ msgid "Eco-account management" #~ msgstr "Ökokontoverwaltung" #~ msgid "Show eco-accounts" #~ msgstr "Zeige Ökokonten" -#~ msgid "New eco-account" -#~ msgstr "Neues Ökokonto" - #~ msgid "Deduct from eco-account" #~ msgstr "Von Konto abbuchen" diff --git a/requirements.txt b/requirements.txt index a6c9dcf..360af59 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,8 +13,8 @@ django-simple-sso==1.1.0 django-tables2==2.3.4 idna==2.10 importlib-metadata==2.1.1 -pkg-resources==0.0.0 -psycopg2==2.8.6 +itsdangerous +psycopg2-binary pytz==2020.4 requests==2.25.0 six==1.15.0 diff --git a/templates/form/collapsable/form.html b/templates/form/collapsable/form.html new file mode 100644 index 0000000..61adeb4 --- /dev/null +++ b/templates/form/collapsable/form.html @@ -0,0 +1,65 @@ +{% load i18n l10n fontawesome_5 %} +
+ {% csrf_token %} +

{{form.form_title}}

+
+
+
+ + {% fa5_icon 'question-circle' 'far' %} + +
+
+ + {% 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 %} +
+ {% trans 'Open the input topic with a simple click.' %} +
+
+
+
+
+
+
+
+ {% fa5_icon 'list' %} + {% trans 'General data' %} +
+
+
+
+ {% include 'form/table/generic_table_form_body.html' %} +
+
+
+
+
+
+
+
+ {% fa5_icon 'map-marked-alt' %} + {% trans 'Geometry' %} +
+
+
+
+ {% include 'map/geom_form.html' %} +
+
+
+
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/templates/form/generic_table_form.html b/templates/form/table/generic_table_form.html similarity index 84% rename from templates/form/generic_table_form.html rename to templates/form/table/generic_table_form.html index d5cbef4..7d32143 100644 --- a/templates/form/generic_table_form.html +++ b/templates/form/table/generic_table_form.html @@ -16,14 +16,14 @@ {% endif %}
{% csrf_token %} - {% include 'form/generic_table_form_body.html' %} + {% include 'form/table/generic_table_form_body.html' %}
-
+ -
+
diff --git a/templates/form/generic_table_form_body.html b/templates/form/table/generic_table_form_body.html similarity index 97% rename from templates/form/generic_table_form_body.html rename to templates/form/table/generic_table_form_body.html index dbd6290..0f9b790 100644 --- a/templates/form/generic_table_form_body.html +++ b/templates/form/table/generic_table_form_body.html @@ -6,6 +6,7 @@ +
{{ field.help_text }} diff --git a/templates/map/geom_form.html b/templates/map/geom_form.html index 6d07903..8b9f463 100644 --- a/templates/map/geom_form.html +++ b/templates/map/geom_form.html @@ -4,9 +4,10 @@ Encapsules the rendering and initializing of a geometry view component, e.g. used in the detail views. {% endcomment %} - -{% if geom_form.area == 0 %} -
{% trans 'No geometry added, yet.' %}
+{% if geom_form.empty %} +
+
{% trans 'No geometry added, yet.' %}
+
{% endif %} {{geom_form.media}} {{geom_form.geom}} \ No newline at end of file diff --git a/templates/modal/modal_form.html b/templates/modal/modal_form.html index 46e109f..eda1730 100644 --- a/templates/modal/modal_form.html +++ b/templates/modal/modal_form.html @@ -18,7 +18,7 @@
{{ form.form_caption }}
- {% include 'form/generic_table_form_body.html' %} + {% include 'form/table/generic_table_form_body.html' %}
{% if form.render_submit %}