#26 Annual conservation reports
* adds index form for selecting timespan and office of interest * adds timespan support for TimespanReport * fixes naive datetime issues * fixes missing error message css tag * adds/updates translations
This commit is contained in:
80
analysis/forms.py
Normal file
80
analysis/forms.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 20.10.21
|
||||
|
||||
"""
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
|
||||
from konova.forms import BaseForm
|
||||
|
||||
|
||||
class TimespanReportForm(BaseForm):
|
||||
""" TimespanReporForm is used for allowing simple creation of an e.g. annual report for conservation offices
|
||||
|
||||
"""
|
||||
date_from = forms.DateField(
|
||||
label_suffix="",
|
||||
label=_("From"),
|
||||
widget=forms.DateInput(
|
||||
attrs={
|
||||
"type": "date",
|
||||
"data-provide": "datepicker",
|
||||
"class": "form-control",
|
||||
},
|
||||
format="%d.%m.%Y"
|
||||
)
|
||||
)
|
||||
date_to = forms.DateField(
|
||||
label_suffix="",
|
||||
label=_("To"),
|
||||
widget=forms.DateInput(
|
||||
attrs={
|
||||
"type": "date",
|
||||
"data-provide": "datepicker",
|
||||
"class": "form-control",
|
||||
},
|
||||
format="%d.%m.%Y"
|
||||
)
|
||||
)
|
||||
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")
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Generate report")
|
||||
self.form_caption = _("Select a timespan and the desired conservation office")
|
||||
self.action_url = reverse("analysis:reports")
|
||||
|
||||
def save(self) -> str:
|
||||
""" Generates a redirect url for the detail report
|
||||
|
||||
Returns:
|
||||
detail_report_url (str): The constructed detail report url
|
||||
|
||||
"""
|
||||
date_from = self.cleaned_data.get("date_from", None)
|
||||
date_to = self.cleaned_data.get("date_to", None)
|
||||
office = self.cleaned_data.get("conservation_office", None)
|
||||
detail_report_url = reverse("analysis:report-detail", args=(office.id,)) + f"?df={date_from}&dt={date_to}"
|
||||
return detail_report_url
|
||||
@@ -6,7 +6,7 @@ Created on: 19.10.21
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
# Defines the date of the legal publishing of the LKompVzVo
|
||||
LKOMPVZVO_PUBLISH_DATE = datetime.date.fromisoformat("2018-06-16")
|
||||
from django.utils import timezone
|
||||
|
||||
LKOMPVZVO_PUBLISH_DATE = timezone.make_aware(timezone.datetime.fromisoformat("2018-06-16"))
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<h3>{% trans 'Report' %}</h3>
|
||||
<h5>{{office.long_name}}</h5>
|
||||
<h3>{% trans 'Evaluation report' %} {{office.long_name}}</h3>
|
||||
<h5>{% trans 'From' %} {{report.date_from.date}} {% trans 'to' %} {{report.date_to.date}}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
{% include 'analysis/reports/includes/intervention/card_intervention.html' %}
|
||||
{% include 'analysis/reports/includes/compensation/card_compensation.html' %}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<h3>{% trans 'Reports' %}</h3>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
{% include 'form/table/generic_table_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -7,7 +7,6 @@ Created on: 18.10.21
|
||||
"""
|
||||
from django.contrib.gis.db.models import MultiPolygonField
|
||||
from django.contrib.gis.db.models.functions import NumGeometries
|
||||
from django.contrib.gis.measure import Area
|
||||
from django.db.models import Count, Sum, Q
|
||||
from django.db.models.functions import Cast
|
||||
|
||||
@@ -20,14 +19,18 @@ from konova.models import Geometry
|
||||
|
||||
|
||||
class TimespanReport:
|
||||
""" Holds multiple report elements for a timespan report
|
||||
|
||||
"""
|
||||
office_id = -1
|
||||
date_from = -1
|
||||
date_to = -1
|
||||
|
||||
class InterventionReport:
|
||||
queryset = Intervention.objects.none()
|
||||
queryset_checked = Intervention.objects.none()
|
||||
queryset_recorded = Intervention.objects.none()
|
||||
|
||||
|
||||
# Law related
|
||||
law_sum = -1
|
||||
law_sum_checked = -1
|
||||
@@ -45,11 +48,13 @@ class TimespanReport:
|
||||
deduction_sum_checked = -1
|
||||
deduction_sum_recorded = -1
|
||||
|
||||
def __init__(self, id: str):
|
||||
def __init__(self, id: str, date_from: str, date_to: str):
|
||||
self.queryset = Intervention.objects.filter(
|
||||
responsible__conservation_office__id=id,
|
||||
legal__registration_date__gt=LKOMPVZVO_PUBLISH_DATE,
|
||||
deleted=None,
|
||||
created__timestamp__gte=date_from,
|
||||
created__timestamp__lte=date_to,
|
||||
)
|
||||
self.queryset_checked = self.queryset.filter(
|
||||
checked__isnull=False
|
||||
@@ -160,11 +165,13 @@ class TimespanReport:
|
||||
# Code list id for 'obere Naturschutzbehörde'
|
||||
id_onb = 1943084
|
||||
|
||||
def __init__(self, id: str):
|
||||
def __init__(self, id: str, date_from: str, date_to: str):
|
||||
self.queryset = Compensation.objects.filter(
|
||||
intervention__responsible__conservation_office__id=id,
|
||||
intervention__legal__registration_date__gt=LKOMPVZVO_PUBLISH_DATE,
|
||||
deleted=None,
|
||||
intervention__created__timestamp__gte=date_from,
|
||||
intervention__created__timestamp__lte=date_to,
|
||||
)
|
||||
self.queryset_checked = self.queryset.filter(
|
||||
intervention__checked__isnull=False
|
||||
@@ -282,11 +289,13 @@ class TimespanReport:
|
||||
deductions_sq_m = -1
|
||||
recorded_deductions_sq_m = -1
|
||||
|
||||
def __init__(self, id: str):
|
||||
def __init__(self, id: str, date_from: str, date_to: str):
|
||||
# First fetch all eco account for this office
|
||||
self.queryset_total = EcoAccount.objects.filter(
|
||||
responsible__conservation_office__id=id,
|
||||
deleted=None,
|
||||
created__timestamp__gte=date_from,
|
||||
created__timestamp__lte=date_to,
|
||||
)
|
||||
self.queryset_recorded = self.queryset_total.filter(
|
||||
recorded__isnull=False
|
||||
@@ -320,21 +329,23 @@ class TimespanReport:
|
||||
def _evaluate_deductions(self):
|
||||
self.deductions_sq_m = self.queryset_deductions.aggregate(
|
||||
sum=Sum("surface")
|
||||
)["sum"]
|
||||
)["sum"] or 0
|
||||
self.recorded_deductions_sq_m = self.queryset_deductions_recorded.aggregate(
|
||||
sum=Sum("surface")
|
||||
)["sum"]
|
||||
)["sum"] or 0
|
||||
|
||||
class OldInterventionReport:
|
||||
queryset = Compensation.objects.none()
|
||||
queryset_checked = Compensation.objects.none()
|
||||
queryset_recorded = Compensation.objects.none()
|
||||
|
||||
def __init__(self, id: str):
|
||||
def __init__(self, id: str, date_from: str, date_to: str):
|
||||
self.queryset = Intervention.objects.filter(
|
||||
legal__registration_date__lte=LKOMPVZVO_PUBLISH_DATE,
|
||||
responsible__conservation_office__id=id,
|
||||
deleted=None,
|
||||
created__timestamp__gte=date_from,
|
||||
created__timestamp__lte=date_to,
|
||||
)
|
||||
self.queryset_checked = self.queryset.filter(
|
||||
checked__isnull=False
|
||||
@@ -343,9 +354,12 @@ class TimespanReport:
|
||||
recorded__isnull=False
|
||||
)
|
||||
|
||||
def __init__(self, office_id: str):
|
||||
def __init__(self, office_id: str, date_from: str, date_to: str):
|
||||
self.office_id = office_id
|
||||
self.intervention_report = self.InterventionReport(self.office_id)
|
||||
self.compensation_report = self.CompensationReport(self.office_id)
|
||||
self.eco_account_report = self.EcoAccountReport(self.office_id)
|
||||
self.old_intervention_report = self.OldInterventionReport(self.office_id)
|
||||
self.date_from = date_from
|
||||
self.date_to = date_to
|
||||
|
||||
self.intervention_report = self.InterventionReport(self.office_id, date_from, date_to)
|
||||
self.compensation_report = self.CompensationReport(self.office_id, date_from, date_to)
|
||||
self.eco_account_report = self.EcoAccountReport(self.office_id, date_from, date_to)
|
||||
self.old_intervention_report = self.OldInterventionReport(self.office_id, date_from, date_to)
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils import timezone
|
||||
|
||||
from analysis.forms import TimespanReportForm
|
||||
from analysis.utils.report import TimespanReport
|
||||
from codelist.models import KonovaCode
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import conservation_office_group_required
|
||||
from konova.utils.message_templates import FORM_INVALID, PARAMS_INVALID
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -20,11 +25,26 @@ def index_reports_view(request: HttpRequest):
|
||||
|
||||
"""
|
||||
template = "analysis/reports/index.html"
|
||||
context = {}
|
||||
form = TimespanReportForm(request.POST or None)
|
||||
if request.method == "POST":
|
||||
if form.is_valid():
|
||||
redirect_url = form.save()
|
||||
return redirect(redirect_url)
|
||||
else:
|
||||
messages.error(
|
||||
request,
|
||||
FORM_INVALID,
|
||||
extra_tags="danger",
|
||||
)
|
||||
context = {
|
||||
"form": form
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
@conservation_office_group_required
|
||||
def detail_report_view(request: HttpRequest, id: str):
|
||||
""" Renders the detailed report for a conservation office
|
||||
|
||||
@@ -35,12 +55,27 @@ def detail_report_view(request: HttpRequest, id: str):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
cons_office = get_object_or_404(
|
||||
KonovaCode,
|
||||
id=id,
|
||||
)
|
||||
report = TimespanReport(id)
|
||||
try:
|
||||
cons_office = KonovaCode.objects.get(id=id)
|
||||
except (ObjectDoesNotExist, ValueError) as e:
|
||||
messages.error(
|
||||
request,
|
||||
PARAMS_INVALID,
|
||||
extra_tags="danger",
|
||||
)
|
||||
return redirect("analysis:reports")
|
||||
try:
|
||||
date_from = timezone.make_aware(timezone.datetime.fromisoformat(request.GET.get("df", None)))
|
||||
date_to = timezone.make_aware(timezone.datetime.fromisoformat(request.GET.get("dt", None)))
|
||||
except ValueError:
|
||||
messages.error(
|
||||
request,
|
||||
PARAMS_INVALID,
|
||||
extra_tags="danger",
|
||||
)
|
||||
return redirect("analysis:reports")
|
||||
|
||||
report = TimespanReport(id, date_from, date_to)
|
||||
template = "analysis/reports/detail.html"
|
||||
context = {
|
||||
"office": cons_office,
|
||||
|
||||
Reference in New Issue
Block a user