* adds Ema model (basically Compensation inherited)
* adds index view for EMAs
* fixes drop-down link bug for menu 'More' in navbar
* refactors some more forms to use process_request()
* adds modified attribute to BaseResource for easy last_modified check
* adds setting of modified attribute in all places where UserAction.EDITED is added to log
* adds EMA_ACCOUNT_IDENTIFIER_LENGTH and EMA_ACCOUNT_IDENTIFIER_TEMPLATE to ema/settings.py
* adds EmaAdmin to ema/admin.py
* fixes wrong title in intervention detail view html for revocations
* adds support for subtitle variable to BaseTable and generic_index.html
* drops next_version attribute from models
* adds/updates translations
* adds default ordering for UserActionLogEntry
   * removes extra ordering in log modal rendering
This commit is contained in:
mipel
2021-08-19 13:02:31 +02:00
parent 03fe293cd8
commit d1f43f8c64
34 changed files with 675 additions and 315 deletions

0
ema/__init__.py Normal file
View File

10
ema/admin.py Normal file
View File

@@ -0,0 +1,10 @@
from django.contrib import admin
from compensation.admin import CompensationAdmin
from ema.models import Ema
class EmaAdmin(CompensationAdmin):
pass
admin.site.register(Ema, EmaAdmin)

5
ema/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class EmaConfig(AppConfig):
name = 'ema'

53
ema/filters.py Normal file
View File

@@ -0,0 +1,53 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 19.08.21
"""
from django.db.models import QuerySet
from compensation.filters import CompensationTableFilter
class EmaTableFilter(CompensationTableFilter):
"""
Since EMA and compensation are basically the same, we can reuse CompensationTableFilter and extend the MAE filter
in the future by inheriting.
"""
def _filter_show_all(self, queryset, name, value) -> QuerySet:
""" Filters queryset depending on value of 'show_all' setting
Args:
queryset ():
name ():
value ():
Returns:
"""
if not value:
return queryset.filter(
users__in=[self.user], # requesting user has access
)
else:
return queryset
def _filter_show_recorded(self, queryset, name, value) -> QuerySet:
""" Filters queryset depending on value of 'show_recorded' setting
Args:
queryset ():
name ():
value ():
Returns:
"""
if not value:
return queryset.filter(
recorded=None,
)
else:
return queryset

74
ema/models.py Normal file
View File

@@ -0,0 +1,74 @@
from django.contrib.auth.models import User
from django.db import models
from compensation.models import AbstractCompensation
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
from user.models import UserActionLogEntry
class Ema(AbstractCompensation):
"""
EMA = Ersatzzahlungsmaßnahme
(compensation actions from payments)
Until 2015 the EMA was the data object to keep track of any compensation, which has been funded by payments
previously paid. In 2015 another organization got in charge of this, which led to the creation of the data object
MAE (which is basically the same, just renamed in their system) to differ between the 'old' payment funded ones and
the new. For historical reasons, we need to keep EMAs in our system, since there are still entries done to this day,
which have been performed somewhere before 2015 and therefore needs to be entered.
Further information:
https://snu.rlp.de/de/foerderungen/massnahmen-aus-ersatzzahlungen/uebersicht-mae/
EMA therefore holds data like a compensation: actions, before-/after-states, deadlines, ...
"""
# Users having access on this object
# Not needed in regular Compensation since their access is defined by the linked intervention's access
users = models.ManyToManyField(
User,
help_text="Users having access (shared with)"
)
# Refers to "verzeichnen"
recorded = models.OneToOneField(
UserActionLogEntry,
on_delete=models.SET_NULL,
null=True,
blank=True,
help_text="Holds data on user and timestamp of this action",
related_name="+"
)
def __str__(self):
return "{}".format(self.identifier)
def save(self, *args, **kwargs):
if self.identifier is None or len(self.identifier) == 0:
# Create new identifier
new_id = self._generate_new_identifier()
while Ema.objects.filter(identifier=new_id).exists():
new_id = self._generate_new_identifier()
self.identifier = new_id
super().save(*args, **kwargs)
def get_LANIS_link(self) -> str:
""" Generates a link for LANIS depending on the geometry
Returns:
"""
try:
geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True)
x = geom.centroid.x
y = geom.centroid.y
zoom_lvl = 16
except AttributeError:
# If no geometry has been added, yet.
x = 1
y = 1
zoom_lvl = 6
return LANIS_LINK_TEMPLATE.format(
zoom_lvl,
x,
y,
)

10
ema/settings.py Normal file
View File

@@ -0,0 +1,10 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 19.08.21
"""
EMA_ACCOUNT_IDENTIFIER_LENGTH = 10
EMA_ACCOUNT_IDENTIFIER_TEMPLATE = "EMA-{}"

132
ema/tables.py Normal file
View File

@@ -0,0 +1,132 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 19.08.21
"""
from django.http import HttpRequest
from django.utils.html import format_html
from django.utils.timezone import localtime
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
import django_tables2 as tables
from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT
from konova.utils.tables import BaseTable
from ema.filters import EmaTableFilter
from ema.models import Ema
class EmaTable(BaseTable):
"""
Since EMA and compensation are basically the same, we can reuse CompensationTableFilter and extend the EMA filter
in the future by inheriting.
"""
id = tables.Column(
verbose_name=_("Identifier"),
orderable=True,
accessor="identifier",
)
t = tables.Column(
verbose_name=_("Title"),
orderable=True,
accessor="title",
)
r = tables.Column(
verbose_name=_("Recorded"),
orderable=True,
empty_values=[],
accessor="recorded",
)
e = tables.Column(
verbose_name=_("Editable"),
orderable=True,
empty_values=[],
accessor="users",
)
lm = tables.Column(
verbose_name=_("Last edit"),
orderable=True,
accessor="created__timestamp",
)
class Meta(BaseTable.Meta):
template_name = "django_tables2/bootstrap4.html"
def __init__(self, request: HttpRequest, *args, **kwargs):
self.title = _("Payment funded compensations")
self.subtitle = _("EMA explanation")
self.add_new_url = reverse("ema:new")
qs = kwargs.get("queryset", None)
self.filter = EmaTableFilter(
user=request.user,
data=request.GET,
queryset=qs,
)
super().__init__(request, self.filter, *args, **kwargs)
def render_id(self, value, record: Ema):
""" Renders the id column for a EMA
Args:
value (str): The identifier value
record (EMA): The EMA record
Returns:
"""
html = ""
html += self.render_link(
tooltip=_("Open {}").format(_("EMA")),
href=reverse("ema:open", args=(record.id,)),
txt=value,
new_tab=False,
)
return format_html(html)
def render_r(self, value, record: Ema):
""" Renders the registered column for a EMA
Args:
value (str): The identifier value
record (Ema): The EMA record
Returns:
"""
html = ""
recorded = value is not None
tooltip = _("Not recorded yet")
if recorded:
value = value.timestamp
value = localtime(value)
on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
tooltip = _("Recorded on {} by {}").format(on, record.recorded.user)
html += self.render_bookmark(
tooltip=tooltip,
icn_filled=recorded,
)
return format_html(html)
def render_e(self, value, record: Ema):
""" Renders the editable column for a EMA
Args:
value (str): The identifier value
record (Ema): The EMA record
Returns:
"""
html = ""
has_access = value.filter(
username=self.user.username
).exists()
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",
)
return format_html(html)

3
ema/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

16
ema/urls.py Normal file
View File

@@ -0,0 +1,16 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 19.08.21
"""
from django.urls import path
from ema.views import index_view, new_view, open_view
app_name = "ema"
urlpatterns = [
path("", index_view, name="index"),
path("new/", new_view, name="new"),
path("<id>", open_view, name="open"),
]

70
ema/views.py Normal file
View File

@@ -0,0 +1,70 @@
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest
from django.shortcuts import render, get_object_or_404
from ema.tables import EmaTable
from konova.contexts import BaseContext
from konova.decorators import conservation_office_group_required
from ema.models import Ema
@login_required
def index_view(request: HttpRequest):
""" Renders the index view for MAEs
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "generic_index.html"
emas = Ema.objects.filter(
deleted=None,
).order_by(
"-modified"
)
table = EmaTable(
request,
queryset=emas
)
context = {
"table": table,
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@conservation_office_group_required
def new_view(request: HttpRequest):
""" Renders the form for a new MAE
Args:
request (HttpRequest): The incoming request
Returns:
"""
template = "generic_index.html"
context = {}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
def open_view(request: HttpRequest, id: str):
""" Renders the form for a new MAE
Args:
request (HttpRequest): The incoming request
id (str): The MAE id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
template = "generic_index.html"
context = {}
context = BaseContext(request, context).context
return render(request, template, context)