* adds NewEcoAccountForm
* refactors NewCompensationForm into AbstractCompensationForm so main fields can be reused again
* fixes template bug in account detail view where the amount of deductions has been displayed instead of the available rest
* refactors _generate_new_identifier() into generate_new_identifier()
* refactors get_available_rest() into returning both, the total and relative amount
* improves saving of SimpleGeometryForm()
* adds/updates translations
This commit is contained in:
mipel
2021-10-05 16:35:24 +02:00
parent 0342d96a1f
commit ac665c9268
16 changed files with 416 additions and 174 deletions

View File

@@ -11,6 +11,7 @@ from compensation.views.eco_account_views import *
urlpatterns = [
path("", index_view, name="acc-index"),
path('new/', new_view, name='acc-new'),
path('new/id', new_id_view, name='acc-new-id'),
path('<id>', open_view, name='acc-open'),
path('<id>/log', log_view, name='acc-log'),
path('<id>/record', record_view, name='acc-record'),

View File

@@ -13,18 +13,18 @@ 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
from compensation.models import Compensation
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
from intervention.models import Intervention, ResponsibilityData
from konova.forms import BaseForm, SimpleGeomForm
from user.models import UserActionLogEntry, UserAction
class NewCompensationForm(BaseForm):
""" Form for creating new compensations.
class AbstractCompensationForm(BaseForm):
""" Abstract form for compensations
Can be initialized with an intervention id for preselecting the related intervention.
Holds all important form fields, which are used in compensation and eco account forms
"""
identifier = forms.CharField(
@@ -35,7 +35,7 @@ class NewCompensationForm(BaseForm):
widget=GenerateInput(
attrs={
"class": "form-control",
"url": reverse_lazy("compensation:new-id"),
"url": None, # Needs to be set in inheriting constructors
}
)
)
@@ -51,21 +51,6 @@ class NewCompensationForm(BaseForm):
}
)
)
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": _("Intervention"),
"data-minimum-input-length": 3,
}
),
)
fundings = forms.ModelMultipleChoiceField(
label=_("Fundings"),
label_suffix="",
@@ -96,6 +81,41 @@ class NewCompensationForm(BaseForm):
)
)
class Meta:
abstract = True
class NewCompensationForm(AbstractCompensationForm):
""" Form for creating new compensations.
Can be initialized with an intervention id for preselecting the related intervention.
"""
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": _("Intervention"),
"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)
@@ -114,8 +134,9 @@ class NewCompensationForm(BaseForm):
self.cancel_redirect = reverse("compensation:index")
tmp = Compensation()
identifier = tmp._generate_new_identifier()
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():
@@ -199,4 +220,125 @@ class EditCompensationForm(NewCompensationForm):
self.instance.save()
self.instance.log.add(action)
return self.instance
return self.instance
class NewEcoAccountForm(AbstractCompensationForm):
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={
}
),
)
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",
}
)
)
surface = forms.DecimalField(
min_value=0.00,
decimal_places=2,
label=_("Available Surface"),
label_suffix="",
help_text=_("The amount that can be used for deductions"),
widget=forms.NumberInput(
attrs={
"class": "form-control",
}
)
)
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 = _("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)
deductable_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.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=deductable_surface,
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

View File

@@ -185,9 +185,9 @@ class Compensation(AbstractCompensation):
def save(self, *args, **kwargs):
if self.identifier is None or len(self.identifier) == 0:
# Create new identifier
new_id = self._generate_new_identifier()
new_id = self.generate_new_identifier()
while Compensation.objects.filter(identifier=new_id).exists():
new_id = self._generate_new_identifier()
new_id = self.generate_new_identifier()
self.identifier = new_id
super().save(*args, **kwargs)
@@ -322,9 +322,9 @@ class EcoAccount(AbstractCompensation):
def save(self, *args, **kwargs):
if self.identifier is None or len(self.identifier) == 0:
# Create new identifier
new_id = self._generate_new_identifier()
new_id = self.generate_new_identifier()
while EcoAccount.objects.filter(identifier=new_id).exists():
new_id = self._generate_new_identifier()
new_id = self.generate_new_identifier()
self.identifier = new_id
super().save(*args, **kwargs)
@@ -355,28 +355,28 @@ class EcoAccount(AbstractCompensation):
"""
return self.after_states.all().aggregate(Sum("surface"))["surface__sum"] or 0
def get_available_rest(self, as_percentage: bool = False) -> float:
def get_available_rest(self) -> (float, float):
""" Calculates available rest surface of the eco account
Args:
as_percentage (bool): Whether to return the result as m² or %
Returns:
ret_val_total (float): Total amount
ret_val_relative (float): Amount as percentage (0-100)
"""
deductions = self.deductions.filter(
intervention__deleted=None,
)
deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0
available_surfaces = self.deductable_surface or deductions_surfaces ## no division by zero
ret_val = available_surfaces - deductions_surfaces
ret_val_total = available_surfaces - deductions_surfaces
if as_percentage:
if available_surfaces > 0:
ret_val = int((ret_val / available_surfaces) * 100)
else:
ret_val = 0
return ret_val
if available_surfaces > 0:
ret_val_relative = int((ret_val_total / available_surfaces) * 100)
else:
ret_val_relative = 0
return ret_val_total, ret_val_relative
def get_LANIS_link(self) -> str:
""" Generates a link for LANIS depending on the geometry

View File

@@ -239,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/custom_widgets/progressbar.html", {"value": value_relative})
return format_html(html)
def render_r(self, value, record: EcoAccount):

View File

@@ -33,7 +33,7 @@
<tr>
<th scope="row">{% trans 'Available' %}</th>
<td class="align-middle">
{{obj.deductions_surface_sum|floatformat:2}} / {{obj.deductable_surface|floatformat:2}} m²
{{available_total|floatformat:2}} / {{obj.deductable_surface|floatformat:2}} m²
{% with available as value %}
{% include 'konova/custom_widgets/progressbar.html' %}
{% endwith %}

View File

@@ -97,9 +97,9 @@ def new_id_view(request: HttpRequest):
"""
tmp = Compensation()
identifier = tmp._generate_new_identifier()
identifier = tmp.generate_new_identifier()
while Compensation.objects.filter(identifier=identifier).exists():
identifier = tmp._generate_new_identifier()
identifier = tmp.generate_new_identifier()
return JsonResponse(
data={
"identifier": identifier

View File

@@ -5,14 +5,16 @@ 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.forms import NewEcoAccountForm
from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
from compensation.models import EcoAccount, EcoAccountDocument
from compensation.tables import EcoAccountTable
@@ -22,6 +24,7 @@ from konova.decorators import any_group_check, default_group_required, conservat
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,8 +59,62 @@ 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/new/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,
"url": reverse("compensation:acc-new-id")
}
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
@@ -96,7 +153,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 +168,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),