Database performance
* optimizes the db fetching for all index views and detail views * introduces usage of managers.py in all necessary apps for wrapping basic fetch settings * moves comment.html to comment_card.html under /konova/templates/konova/ * adds/updates translations * fixes document typos * drops comment rendering in public reports * opens public reports in new tabs if button is clicked from the detail view
This commit is contained in:
73
compensation/managers.py
Normal file
73
compensation/managers.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 14.10.21
|
||||
|
||||
"""
|
||||
from django.db import models
|
||||
|
||||
|
||||
class CompensationActionManager(models.Manager):
|
||||
""" Holds default db fetch setting for this model type
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_related(
|
||||
"action_type",
|
||||
"action_type__parent"
|
||||
)
|
||||
|
||||
|
||||
class CompensationStateManager(models.Manager):
|
||||
""" Holds default db fetch setting for this model type
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_related(
|
||||
"biotope_type",
|
||||
"biotope_type__parent"
|
||||
)
|
||||
|
||||
|
||||
class CompensationManager(models.Manager):
|
||||
""" Holds default db fetch setting for this model type
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_related(
|
||||
"modified",
|
||||
"intervention",
|
||||
"intervention__recorded",
|
||||
"intervention__recorded__user",
|
||||
"intervention__modified",
|
||||
"intervention__checked",
|
||||
"intervention__checked__user",
|
||||
)
|
||||
|
||||
|
||||
class EcoAccountManager(models.Manager):
|
||||
""" Holds default db fetch setting for this model type
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_related(
|
||||
"recorded",
|
||||
"recorded__user",
|
||||
"modified",
|
||||
"modified__user",
|
||||
).prefetch_related(
|
||||
"users",
|
||||
)
|
||||
|
||||
|
||||
class EcoAccountDeductionManager(models.Manager):
|
||||
""" Holds default db fetch setting for this model type
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_related(
|
||||
"intervention",
|
||||
"intervention__recorded",
|
||||
"created",
|
||||
)
|
||||
@@ -17,6 +17,8 @@ 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_FUNDING_ID
|
||||
from compensation.managers import CompensationStateManager, EcoAccountDeductionManager, CompensationActionManager, \
|
||||
EcoAccountManager, CompensationManager
|
||||
from intervention.models import Intervention, ResponsibilityData
|
||||
from konova.models import BaseObject, BaseResource, Geometry, UuidModel, AbstractDocument, \
|
||||
generate_document_file_upload_path
|
||||
@@ -67,6 +69,8 @@ class CompensationState(UuidModel):
|
||||
)
|
||||
surface = models.FloatField()
|
||||
|
||||
objects = CompensationStateManager()
|
||||
|
||||
def __str__(self):
|
||||
return "{} | {} m²".format(self.biotope_type, self.surface)
|
||||
|
||||
@@ -102,6 +106,8 @@ class CompensationAction(BaseResource):
|
||||
unit = models.CharField(max_length=100, null=True, blank=True, choices=UnitChoices.choices)
|
||||
comment = models.TextField(blank=True, null=True, help_text="Additional comment")
|
||||
|
||||
objects = CompensationActionManager()
|
||||
|
||||
def __str__(self):
|
||||
return "{} | {} {}".format(self.action_type, self.amount, self.unit)
|
||||
|
||||
@@ -178,6 +184,8 @@ class Compensation(AbstractCompensation):
|
||||
related_name='compensations'
|
||||
)
|
||||
|
||||
objects = CompensationManager()
|
||||
|
||||
def __str__(self):
|
||||
return "{}".format(self.identifier)
|
||||
|
||||
@@ -301,6 +309,8 @@ class EcoAccount(AbstractCompensation):
|
||||
default=0,
|
||||
)
|
||||
|
||||
objects = EcoAccountManager()
|
||||
|
||||
def __str__(self):
|
||||
return "{}".format(self.identifier)
|
||||
|
||||
@@ -500,5 +510,7 @@ class EcoAccountDeduction(BaseResource):
|
||||
related_name="deductions",
|
||||
)
|
||||
|
||||
objects = EcoAccountDeductionManager()
|
||||
|
||||
def __str__(self):
|
||||
return "{} of {}".format(self.surface, self.account)
|
||||
|
||||
@@ -148,14 +148,13 @@ class CompensationTable(BaseTable):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
html = ""
|
||||
if value is None:
|
||||
value = User.objects.none()
|
||||
has_access = value.filter(
|
||||
username=self.user.username
|
||||
id=self.user.id
|
||||
).exists()
|
||||
|
||||
html += self.render_icn(
|
||||
html = self.render_icn(
|
||||
tooltip=_("Full access granted") if has_access else _("Access not granted"),
|
||||
icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",
|
||||
)
|
||||
@@ -244,7 +243,7 @@ class EcoAccountTable(BaseTable):
|
||||
return format_html(html)
|
||||
|
||||
def render_r(self, value, record: EcoAccount):
|
||||
""" Renders the registered column for an eco account
|
||||
""" Renders the recorded column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
@@ -268,7 +267,7 @@ class EcoAccountTable(BaseTable):
|
||||
return format_html(html)
|
||||
|
||||
def render_e(self, value, record: EcoAccount):
|
||||
""" Renders the registered column for an eco account
|
||||
""" Renders the editable column for an eco account
|
||||
|
||||
Args:
|
||||
value (str): The identifier value
|
||||
@@ -278,10 +277,9 @@ class EcoAccountTable(BaseTable):
|
||||
|
||||
"""
|
||||
html = ""
|
||||
has_access = value.filter(
|
||||
username=self.user.username
|
||||
).exists()
|
||||
|
||||
# Do not use value in here, since value does use unprefetched 'users' manager, where record has already
|
||||
# prefetched users data
|
||||
has_access = self.user in record.users.all()
|
||||
html += self.render_icn(
|
||||
tooltip=_("Full access granted") if has_access else _("Access not granted"),
|
||||
icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{% load i18n fontawesome_5 %}
|
||||
|
||||
{% if obj.comment %}
|
||||
<div class="w-100">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header rlp-gd">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<h5 class="card-title">
|
||||
{% fa5_icon 'info-circle' %}
|
||||
{% trans 'Comment' %}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-text font-italic">
|
||||
{{obj.comment}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -6,7 +6,7 @@
|
||||
LANIS
|
||||
</button>
|
||||
</a>
|
||||
<a href="{% url 'compensation:report' obj.id %}" class="mr-2">
|
||||
<a href="{% url 'compensation:report' obj.id %}" target="_blank" class="mr-2">
|
||||
<button class="btn btn-default" title="{% trans 'Public report' %}">
|
||||
{% fa5_icon 'file-alt' %}
|
||||
</button>
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'compensation/detail/compensation/includes/comment.html' %}
|
||||
{% include 'konova/comment_card.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h5>
|
||||
<span class="badge badge-light">{{obj.actions.count}}</span>
|
||||
<span class="badge badge-light">{{actions.count}}</span>
|
||||
{% trans 'Actions' context 'Compensation' %}
|
||||
</h5>
|
||||
</div>
|
||||
@@ -33,13 +33,15 @@
|
||||
<th scope="col">
|
||||
{% trans 'Comment' %}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{% trans 'Action' %}
|
||||
</th>
|
||||
{% if default_member and has_access %}
|
||||
<th scope="col">
|
||||
{% trans 'Action' %}
|
||||
</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for action in obj.actions.all %}
|
||||
{% for action in actions %}
|
||||
<tr>
|
||||
<td class="align-middle">
|
||||
{{ action.action_type }}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{% load i18n fontawesome_5 %}
|
||||
|
||||
{% if obj.comment %}
|
||||
<div class="w-100">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header rlp-gd">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<h5 class="card-title">
|
||||
{% fa5_icon 'info-circle' %}
|
||||
{% trans 'Comment' %}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-text font-italic">
|
||||
{{obj.comment}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -6,7 +6,7 @@
|
||||
LANIS
|
||||
</button>
|
||||
</a>
|
||||
<a href="{% url 'compensation:acc-report' obj.id %}" class="mr-2">
|
||||
<a href="{% url 'compensation:acc-report' obj.id %}" target="_blank" class="mr-2">
|
||||
<button class="btn btn-default" title="{% trans 'Public report' %}">
|
||||
{% fa5_icon 'file-alt' %}
|
||||
</button>
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
{% if deduction.intervention.recorded %}
|
||||
<em title='{{ deduction.intervention.recorded_tooltip }}' class='fas fa-bookmark registered-bookmark'></em>
|
||||
<em title="{% trans 'Recorded on' %} {{obj.recorded.timestamp}} {% trans 'by' %} {{obj.recorded.user}}" class='fas fa-bookmark registered-bookmark'></em>
|
||||
{% else %}
|
||||
<em title='{{ deduction.intervention.recorded_tooltip }}' class='far fa-bookmark'></em>
|
||||
<em title="{% trans 'Not recorded yet' %}" class='far fa-bookmark'></em>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h5>
|
||||
<span class="badge badge-light">{{obj.after_states.count}}</span>
|
||||
<span class="badge badge-light">{{after_states.count}}</span>
|
||||
{% trans 'States after' %}
|
||||
</h5>
|
||||
</div>
|
||||
@@ -35,9 +35,11 @@
|
||||
<th scope="col">
|
||||
{% trans 'Surface' %}
|
||||
</th>
|
||||
{% if is_default_member and has_access %}
|
||||
<th scope="col">
|
||||
{% trans 'Action' %}
|
||||
</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h5>
|
||||
<span class="badge badge-light">{{obj.before_states.count}}</span>
|
||||
<span class="badge badge-light">{{before_states.count}}</span>
|
||||
{% trans 'States before' %}
|
||||
</h5>
|
||||
</div>
|
||||
@@ -35,9 +35,11 @@
|
||||
<th scope="col">
|
||||
{% trans 'Surface' %}
|
||||
</th>
|
||||
{% if is_default_member and has_access %}
|
||||
<th scope="col">
|
||||
{% trans 'Action' %}
|
||||
</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'compensation/detail/compensation/includes/comment.html' %}
|
||||
{% include 'konova/comment_card.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -50,9 +50,6 @@
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'intervention/detail/includes/comment.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<h4>{% trans 'Open in browser' %}</h4>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
{% extends 'public_base.html' %}
|
||||
{% load i18n fontawesome_5 humanize %}
|
||||
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<h3>{% trans 'Report' %}</h3>
|
||||
<h4>{{obj.identifier}}</h4>
|
||||
<div class="table-container">
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<th class="w-25" scope="row">{% trans 'Title' %}</th>
|
||||
<td class="align-middle">{{obj.title|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Conservation office' %}</th>
|
||||
<td class="align-middle">{{obj.responsible.conservation_office.str_as_office|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Conservation office file number' %}</th>
|
||||
<td class="align-middle">{{obj.responsible.conservation_file_number|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Action handler' %}</th>
|
||||
<td class="align-middle">{{obj.responsible.handler|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Funded by' %}</th>
|
||||
<td class="align-middle">
|
||||
{% with obj.fundings.all as fundings %}
|
||||
{% for funding in fundings %}
|
||||
<div class="badge pill-badge rlp-r-outline">{{funding.short_name}}</div>
|
||||
<br>
|
||||
{% empty %}
|
||||
{% trans 'None' %}
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Deductions for' %}</th>
|
||||
<td class="align-middle">
|
||||
{% for deduction in deductions %}
|
||||
<a href="{% url 'intervention:report' deduction.intervention__id %}">
|
||||
{{deduction.intervention__identifier}}
|
||||
</a>
|
||||
<br>
|
||||
{% empty %}
|
||||
{% trans 'None' %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Last modified' %}</th>
|
||||
<td class="align-middle">
|
||||
{{obj.modified.timestamp|default_if_none:""|naturalday}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% include 'compensation/detail/compensation/includes/states-before.html' %}
|
||||
{% include 'compensation/detail/compensation/includes/states-after.html' %}
|
||||
{% include 'compensation/detail/compensation/includes/actions.html' %}
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<h4>{% trans 'Open in browser' %}</h4>
|
||||
{{ qrcode|safe }}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<h4>{% trans 'View in LANIS' %}</h4>
|
||||
{{ qrcode_lanis|safe }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -42,7 +42,6 @@ def index_view(request: HttpRequest):
|
||||
A rendered view
|
||||
"""
|
||||
template = "generic_index.html"
|
||||
user = request.user
|
||||
eco_accounts = EcoAccount.objects.filter(
|
||||
deleted=None,
|
||||
)
|
||||
@@ -167,27 +166,36 @@ def detail_view(request: HttpRequest, id: str):
|
||||
|
||||
"""
|
||||
template = "compensation/detail/eco_account/view.html"
|
||||
acc = get_object_or_404(EcoAccount, id=id)
|
||||
acc = get_object_or_404(
|
||||
EcoAccount.objects.prefetch_related(
|
||||
"deadlines",
|
||||
).select_related(
|
||||
'geometry',
|
||||
'responsible',
|
||||
),
|
||||
id=id
|
||||
)
|
||||
geom_form = SimpleGeomForm(instance=acc)
|
||||
_user = request.user
|
||||
is_data_shared = acc.is_shared_with(_user)
|
||||
|
||||
# Order states according to surface
|
||||
before_states = acc.before_states.all().order_by("-surface")
|
||||
after_states = acc.after_states.all().order_by("-surface")
|
||||
before_states = acc.before_states.order_by("-surface")
|
||||
after_states = acc.after_states.order_by("-surface")
|
||||
|
||||
# Precalculate logical errors between before- and after-states
|
||||
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
||||
sum_before_states = before_states.aggregate(Sum("surface"))["surface__sum"] or 0
|
||||
sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
|
||||
diff_states = abs(sum_before_states - sum_after_states)
|
||||
|
||||
# Calculate rest of available surface for deductions
|
||||
available_total, available_relative = acc.get_available_rest()
|
||||
|
||||
# Prefetch related data to decrease the amount of db connections
|
||||
deductions = acc.deductions.filter(
|
||||
intervention__deleted=None,
|
||||
)
|
||||
actions = acc.actions.all()
|
||||
|
||||
context = {
|
||||
"obj": acc,
|
||||
@@ -205,6 +213,7 @@ def detail_view(request: HttpRequest, id: str):
|
||||
"is_ets_member": in_group(_user, ETS_GROUP),
|
||||
"LANIS_LINK": acc.get_LANIS_link(),
|
||||
"deductions": deductions,
|
||||
"actions": actions,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
@@ -446,7 +455,7 @@ def report_view(request:HttpRequest, id: str):
|
||||
|
||||
"""
|
||||
# Reuse the compensation report template since EcoAccounts are structurally identical
|
||||
template = "compensation/report/report.html"
|
||||
template = "compensation/report/eco_account/report.html"
|
||||
acc = get_object_or_404(EcoAccount, id=id)
|
||||
|
||||
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
||||
@@ -454,18 +463,39 @@ def report_view(request:HttpRequest, id: str):
|
||||
template = "report/unavailable.html"
|
||||
return render(request, template, {})
|
||||
|
||||
# Prepare data for map viewer
|
||||
geom_form = SimpleGeomForm(
|
||||
instance=acc
|
||||
)
|
||||
qrcode_img = generate_qr_code(
|
||||
request.build_absolute_uri(reverse("compensation:acc-report", args=(id,))),
|
||||
request.build_absolute_uri(reverse("ema:report", args=(id,))),
|
||||
10
|
||||
)
|
||||
qrcode_img_lanis = generate_qr_code(
|
||||
acc.get_LANIS_link(),
|
||||
7
|
||||
)
|
||||
# Order states by surface
|
||||
before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent")
|
||||
after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent")
|
||||
actions = acc.actions.all().select_related("action_type__parent")
|
||||
|
||||
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier)
|
||||
deductions = acc.deductions.all()\
|
||||
.distinct("intervention")\
|
||||
.select_related("intervention")\
|
||||
.values_list("intervention__id", "intervention__identifier", named=True)
|
||||
|
||||
context = {
|
||||
"obj": acc,
|
||||
"qrcode": qrcode_img,
|
||||
"qrcode_lanis": qrcode_img_lanis,
|
||||
"has_access": False, # disables action buttons during rendering
|
||||
"before_states": before_states,
|
||||
"after_states": after_states,
|
||||
"geom_form": geom_form,
|
||||
"actions": actions,
|
||||
"deductions": deductions,
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
Reference in New Issue
Block a user