* fixes created timestamp in detail views where modified needs to be displayed
* adds fallback timestamp if data has not been edited, yet --> show created timestamp
* fixes bug where deleting of certain data didn't redirect to the index view
* adds quality_check() method for EMA, needed for recording
* adds all functions which are provided for compensations to EMA
* adds/updates translations
This commit is contained in:
mipel 2021-08-19 13:44:06 +02:00
parent 510d77422a
commit 5c0b1c412d
18 changed files with 717 additions and 18 deletions

View File

@ -257,9 +257,17 @@ class EcoAccount(AbstractCompensation):
y,
)
def quality_check(self) -> (bool, dict):
# ToDo
pass
def quality_check(self) -> list:
""" Quality check
Returns:
ret_msgs (list): Holds error messages
"""
ret_msgs = []
# ToDo: Add check methods!
return ret_msgs
class EcoAccountWithdraw(BaseResource):

View File

@ -63,9 +63,9 @@
<tr>
<th scope="row">{% trans 'Last modified' %}</th>
<td class="align-middle">
{{obj.created.timestamp|default_if_none:""|naturalday}}
{{obj.modified.timestamp|default_if_none:""|naturalday}}
<br>
{{obj.created.user.username}}
{{obj.modified.user.username}}
</td>
</tr>
<tr>

View File

@ -55,9 +55,9 @@
<tr>
<th scope="row">{% trans 'Last modified' %}</th>
<td class="align-middle">
{{obj.created.timestamp|default_if_none:""|naturalday}}
{{obj.modified.timestamp|default_if_none:""|naturalday}}
<br>
{{obj.created.user.username}}
{{obj.modified.user.username}}
</td>
</tr>
<tr>

View File

@ -141,7 +141,7 @@ def remove_view(request: HttpRequest, id: str):
return form.process_request(
request=request,
msg_success=_("Compensation removed"),
redirect_url="",
redirect_url=reverse("compensation:index"),
)

View File

@ -72,3 +72,15 @@ class Ema(AbstractCompensation):
x,
y,
)
def quality_check(self) -> list:
""" Quality check
Returns:
ret_msgs (list): Holds error messages
"""
ret_msgs = []
# ToDo: Add check methods!
return ret_msgs

View File

@ -0,0 +1,61 @@
{% load i18n l10n fontawesome_5 humanize %}
<div id="actions" class="card">
<div class="card-header rlp-r">
<div class="row">
<div class="col-sm-6">
<h5>
<span class="badge badge-light">{{obj.actions.count}}</span>
{% trans 'Actions' context 'Compensation' %}
</h5>
</div>
<div class="col-sm-6">
<div class="d-flex justify-content-end">
{% if is_default_member and has_access %}
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-action' obj.id %}" title="{% trans 'Add new action' %}">
{% fa5_icon 'plus' %}
{% fa5_icon 'seedling' %}
</button>
{% endif %}
</div>
</div>
</div>
</div>
<div class="card-body scroll-300">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">
{% trans 'Action type' %}
</th>
<th scope="col">
{% trans 'Amount' context 'Compensation' %}
</th>
<th scope="col">
{% trans 'Comment' %}
</th>
<th scope="col">
{% trans 'Action' %}
</th>
</tr>
</thead>
<tbody>
{% for action in obj.actions.all %}
<tr>
<td class="align-middle">
{{ action.action_type }}
</td>
<td class="align-middle">{{ action.amount|floatformat:2|intcomma }} {{ action.unit_humanize }}</td>
<td class="align-middle">{{ action.comment|default_if_none:"" }}</td>
<td>
{% if is_default_member and has_access %}
<button data-form-url="{% url 'ema:action-remove' action.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove action' %}">
{% fa5_icon 'trash' %}
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,40 @@
{% load i18n l10n fontawesome_5 %}
<div class="d-flex justify-content-end">
<a href="{{LANIS_LINK}}" class="mr-2" target="_blank">
<button class="btn btn-default" title="{% trans 'Open in LANIS' %}">
LANIS
</button>
</a>
<a href="{% url 'home' %}" class="mr-2">
<button class="btn btn-default" title="{% trans 'Public report' %}">
{% fa5_icon 'file-alt' %}
</button>
</a>
{% if has_access %}
{% if is_ets_member %}
{% if obj.recorded %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Unrecord' %}" data-form-url="{% url 'ema:record' obj.id %}">
{% fa5_icon 'bookmark' 'far' %}
</button>
{% else %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Record' %}" data-form-url="{% url 'ema:record' obj.id %}">
{% fa5_icon 'bookmark' %}
</button>
{% endif %}
{% endif %}
{% if is_default_member %}
<a href="{% url 'home' %}" class="mr-2">
<button class="btn btn-default" title="{% trans 'Edit' %}">
{% fa5_icon 'edit' %}
</button>
</a>
<button class="btn btn-default btn-modal mr-2" data-form-url="{% url 'ema:log' obj.id %}" title="{% trans 'Show log' %}">
{% fa5_icon 'history' %}
</button>
<button class="btn btn-default btn-modal" data-form-url="{% url 'ema:remove' obj.id %}" title="{% trans 'Delete' %}">
{% fa5_icon 'trash' %}
</button>
{% endif %}
{% endif %}
</div>

View File

@ -0,0 +1,61 @@
{% load i18n l10n fontawesome_5 %}
<div id="deadlines" class="card">
<div class="card-header rlp-r">
<div class="row">
<div class="col-sm-6">
<h5>
<span class="badge badge-light">{{obj.deadlines.count}}</span>
{% trans 'Deadlines' %}
</h5>
</div>
<div class="col-sm-6">
<div class="d-flex justify-content-end">
{% if is_default_member and has_access %}
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-deadline' obj.id %}" title="{% trans 'Add new deadline' %}">
{% fa5_icon 'plus' %}
{% fa5_icon 'calendar-check' %}
</button>
{% endif %}
</div>
</div>
</div>
</div>
<div class="card-body scroll-300">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">
{% trans 'Type' %}
</th>
<th scope="col">
{% trans 'Date' %}
</th>
<th scope="col">
{% trans 'Comment' %}
</th>
<th scope="col">
{% trans 'Action' %}
</th>
</tr>
</thead>
<tbody>
{% for deadline in obj.deadlines.all %}
<tr>
<td class="align-middle">
{% trans deadline.type_humanized %}
</td>
<td class="align-middle">{{ deadline.date }}</td>
<td class="align-middle">{{ deadline.comment }}</td>
<td>
{% if is_default_member and has_access %}
<button data-form-url="{% url 'deadline-remove' deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove deadline' %}">
{% fa5_icon 'trash' %}
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,59 @@
{% load i18n l10n fontawesome_5 %}
<div id="documents" class="card">
<div class="card-header rlp-r">
<div class="row">
<div class="col-sm-6">
<h5>
<span class="badge badge-light">{{obj.documents.count}}</span>
{% trans 'Documents' %}
</h5>
</div>
<div class="col-sm-6">
<div class="d-flex justify-content-end">
{% if is_default_member and has_access %}
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-doc' obj.id %}" title="{% trans 'Add new document' %}">
{% fa5_icon 'plus' %}
{% fa5_icon 'file' %}
</button>
{% endif %}
</div>
</div>
</div>
</div>
<div class="card-body scroll-300">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">
{% trans 'Title' %}
</th>
<th scope="col">
{% trans 'Comment' %}
</th>
<th scope="col">
{% trans 'Action' %}
</th>
</tr>
</thead>
<tbody>
{% for doc in obj.documents.all %}
<tr>
<td class="align-middle">
<a href="{% url 'doc-open' doc.id %}">
{{ doc.title }}
</a>
</td>
<td class="align-middle">{{ doc.comment }}</td>
<td>
{% if is_default_member and has_access %}
<button data-form-url="{% url 'doc-remove' doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
{% fa5_icon 'trash' %}
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,62 @@
{% load i18n l10n fontawesome_5 %}
<div id="states-after" class="card">
<div class="card-header rlp-r">
<div class="row">
<div class="col-sm-6">
<h5>
<span class="badge badge-light">{{obj.after_states.count}}</span>
{% trans 'States after' %}
</h5>
</div>
<div class="col-sm-6">
<div class="d-flex justify-content-end">
{% if is_default_member and has_access %}
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-state' obj.id %}" title="{% trans 'Add new state after' %}">
{% fa5_icon 'plus' %}
{% fa5_icon 'layer-group' %}
</button>
{% endif %}
</div>
</div>
</div>
</div>
<div class="card-body scroll-300">
{% if sum_before_states > sum_after_states %}
<div class="row alert alert-danger">
{% trans 'Missing surfaces according to states before: ' %}{{ diff_states|floatformat:2 }} m²
</div>
{% endif %}
<table class="table table-hover">
<thead>
<tr>
<th scope="col">
{% trans 'Biotope type' %}
</th>
<th scope="col">
{% trans 'Surface' %}
</th>
<th scope="col">
{% trans 'Action' %}
</th>
</tr>
</thead>
<tbody>
{% for state in after_states %}
<tr>
<td class="align-middle">
{{ state.biotope_type }}
</td>
<td class="align-middle">{{ state.surface|floatformat:2 }} m²</td>
<td>
{% if is_default_member and has_access %}
<button data-form-url="{% url 'ema:state-remove' state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
{% fa5_icon 'trash' %}
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,62 @@
{% load i18n l10n fontawesome_5 %}
<div id="states-before" class="card">
<div class="card-header rlp-r">
<div class="row">
<div class="col-sm-6">
<h5>
<span class="badge badge-light">{{obj.before_states.count}}</span>
{% trans 'States before' %}
</h5>
</div>
<div class="col-sm-6">
<div class="d-flex justify-content-end">
{% if is_default_member and has_access %}
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-state' obj.id %}?before=true" title="{% trans 'Add new state before' %}">
{% fa5_icon 'plus' %}
{% fa5_icon 'layer-group' %}
</button>
{% endif %}
</div>
</div>
</div>
</div>
<div class="card-body scroll-300">
{% if sum_before_states < sum_after_states %}
<div class="row alert alert-danger">
{% trans 'Missing surfaces according to states after: ' %}{{ diff_states|floatformat:2 }} m²
</div>
{% endif %}
<table class="table table-hover">
<thead>
<tr>
<th scope="col">
{% trans 'Biotope type' %}
</th>
<th scope="col">
{% trans 'Surface' %}
</th>
<th scope="col">
{% trans 'Action' %}
</th>
</tr>
</thead>
<tbody>
{% for state in before_states %}
<tr>
<td class="align-middle">
{{ state.biotope_type }}
</td>
<td class="align-middle">{{ state.surface|floatformat:2 }} m²</td>
<td>
{% if is_default_member and has_access %}
<button data-form-url="{% url 'ema:state-remove' state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
{% fa5_icon 'trash' %}
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,100 @@
{% extends 'base.html' %}
{% load i18n l10n static fontawesome_5 humanize %}
{% block head %}
{% endblock %}
{% block body %}
<div id="detail-header" class="row">
<div class="col-sm-12 col-md-12 col-lg-6">
<h3>{{obj.identifier}}</h3>
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
{% include 'ema/detail/includes/controls.html' %}
</div>
</div>
<hr>
<div id="data" class="row">
<div class="col-sm-12 col-md-12 col-lg-6">
<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}}</td>
</tr>
<tr>
<th scope="row">{% trans 'Recorded' %}</th>
<td class="align-middle">
{% if obj.recorded is None %}
<span title="{% trans 'Not recorded yet' %}">
{% fa5_icon 'bookmark' 'far' %}
</span>
{% else %}
<span class="registered-bookmark" title="{% trans 'Recorded on '%} {{obj.recorded.timestamp}} {% trans 'by' %} {{obj.recorded.user}}">
{% fa5_icon 'bookmark' %}
</span>
{% endif %}
</td>
</tr>
<tr>
<th scope="row">{% trans 'Last modified' %}</th>
<td class="align-middle">
{% if obj.modified %}
{{obj.modified.timestamp|default_if_none:""|naturalday}}
<br>
{{obj.modified.user.username}}
{% else %}
{{obj.created.timestamp|default_if_none:""|naturalday}}
<br>
{{obj.created.user.username}}
{% endif %}
</td>
</tr>
<tr>
<th scope="row">{% trans 'Shared with' %}</th>
<td class="align-middle">
{% for user in obj.users.all %}
{% include 'user/includes/contact_modal_button.html' %}
{% endfor %}
</td>
</tr>
</table>
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
{% include 'map/geom_form.html' %}
</div>
</div>
<hr>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-6">
{% include 'ema/detail/includes/states-before.html' %}
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
{% include 'ema/detail/includes/states-after.html' %}
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-6">
{% include 'ema/detail/includes/actions.html' %}
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
{% include 'ema/detail/includes/deadlines.html' %}
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-6">
{% include 'ema/detail/includes/documents.html' %}
</div>
</div>
{% with 'btn-modal' as btn_class %}
{% include 'modal/modal_form_script.html' %}
{% endwith %}
{% endblock %}

View File

@ -6,11 +6,28 @@ Created on: 19.08.21
"""
from django.urls import path
from ema.views import index_view, new_view, open_view
from ema.views import *
app_name = "ema"
urlpatterns = [
path("", index_view, name="index"),
path("new/", new_view, name="new"),
path("<id>", open_view, name="open"),
path('<id>/log', log_view, name='log'),
path('<id>/edit', edit_view, name='edit'),
path('<id>/remove', remove_view, name='remove'),
path('<id>/record', record_view, name='record'),
path('<id>/state/new', state_new_view, name='new-state'),
path('<id>/action/new', action_new_view, name='new-action'),
path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
# Documents
# Document remove route can be found in konova/urls.py
path('<id>/document/new/', document_new_view, name='new-doc'),
# Generic state routes
path('state/<id>/remove', state_remove_view, name='state-remove'),
# Generic action routes
path('action/<id>/remove', action_remove_view, name='action-remove'),
]

View File

@ -1,16 +1,24 @@
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.urls import reverse
from django.utils.translation import gettext_lazy as _
import compensation
from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
from ema.tables import EmaTable
from konova.contexts import BaseContext
from konova.decorators import conservation_office_group_required
from ema.models import Ema
from konova.forms import RemoveModalForm, NewDocumentForm, SimpleGeomForm, RecordForm
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.utils.user_checks import in_group
@login_required
def index_view(request: HttpRequest):
""" Renders the index view for MAEs
""" Renders the index view for EMAs
Args:
request (HttpRequest): The incoming request
@ -38,7 +46,7 @@ def index_view(request: HttpRequest):
@login_required
@conservation_office_group_required
def new_view(request: HttpRequest):
""" Renders the form for a new MAE
""" Renders the form for a new EMA
Args:
request (HttpRequest): The incoming request
@ -54,17 +62,224 @@ def new_view(request: HttpRequest):
@login_required
def open_view(request: HttpRequest, id: str):
""" Renders the form for a new MAE
""" Renders the detail view of an EMA
Args:
request (HttpRequest): The incoming request
id (str): The MAE id
id (str): The EMA id
Returns:
"""
template = "ema/detail/view.html"
ema = get_object_or_404(Ema, id=id, deleted=None)
geom_form = SimpleGeomForm(instance=ema)
_user = request.user
is_data_shared = ema.is_shared_with(_user)
# Order states according to surface
before_states = ema.before_states.all().order_by("-surface")
after_states = ema.after_states.all().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)
context = {
"obj": ema,
"geom_form": geom_form,
"has_access": is_data_shared,
"before_states": before_states,
"after_states": after_states,
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP),
"LANIS_LINK": ema.get_LANIS_link(),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
def log_view(request: HttpRequest, id: str):
""" Renders a log view using modal
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
template = "generic_index.html"
context = {}
template = "modal/modal_generic.html"
body_template = "log.html"
context = {
"modal_body_template": body_template,
"log": ema.log.all(),
"modal_title": _("Log"),
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
def edit_view(request: HttpRequest, id: str):
get_object_or_404(Ema, id=id)
@login_required
def remove_view(request: HttpRequest, id: str):
""" Renders a modal view for removing the EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = RemoveModalForm(request.POST or None, instance=ema, user=request.user)
return form.process_request(
request=request,
msg_success=_("EMA removed"),
redirect_url=reverse("ema:index"),
)
@login_required
def record_view(request: HttpRequest, id: str):
""" Renders a modal view for recording the EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = RecordForm(request.POST or None, instance=ema, user=request.user)
return form.process_request(
request=request,
msg_success=_("EMA recorded"),
)
@login_required
def state_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new states for an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id to which the new state will be related
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = NewStateModalForm(request.POST or None, instance=ema, user=request.user)
return form.process_request(
request,
msg_success=_("State added")
)
@login_required
def action_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new actions for an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id to which the new state will be related
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = NewActionModalForm(request.POST or None, instance=ema, user=request.user)
return form.process_request(
request,
msg_success=_("Action added")
)
@login_required
def deadline_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new states for an EMA
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id to which the new state will be related
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = NewDeadlineModalForm(request.POST or None, instance=ema, user=request.user)
return form.process_request(
request,
msg_success=_("Deadline added")
)
@login_required
def document_new_view(request: HttpRequest, id: str):
""" Renders a form for uploading new documents
Args:
request (HttpRequest): The incoming request
id (str): The EMA's id to which the new document will be related
Returns:
"""
ema = get_object_or_404(Ema, id=id)
form = NewDocumentForm(request.POST or None, request.FILES or None, instance=ema, user=request.user)
return form.process_request(
request,
msg_success=_("Document added")
)
@login_required
def state_remove_view(request: HttpRequest, id: str):
""" Renders a form for removing an EMA state
Args:
request (HttpRequest): The incoming request
id (str): The state's id
Returns:
"""
return compensation.views.compensation_views.state_remove_view(
request,
id
)
@login_required
def action_remove_view(request: HttpRequest, id: str):
""" Renders a form for removing an EMA state
Args:
request (HttpRequest): The incoming request
id (str): The state's id
Returns:
"""
# Reuses the route logic from compensation view
return compensation.views.compensation_views.action_remove_view(
request,
id
)

View File

@ -174,7 +174,7 @@ class Intervention(BaseObject):
""" Quality check
Returns:
ret_msgs (list): True if quality acceptable, False otherwise
ret_msgs (list): Holds error messages
"""
ret_msgs = []

View File

@ -22,6 +22,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount
from ema.models import Ema
from intervention.models import Intervention
from konova.contexts import BaseContext
from konova.models import Document, BaseObject
@ -370,7 +371,8 @@ class RecordForm(BaseModalForm):
implemented_cls_logic = {
Intervention,
EcoAccount
EcoAccount,
Ema,
}
instance_name = self.instance.__class__
if instance_name not in implemented_cls_logic:

Binary file not shown.

View File

@ -1276,7 +1276,7 @@ msgstr "Filter anwenden"
#: templates/log.html:7
msgid "Timestamp"
msgstr ""
msgstr "Zeitpunkt"
#: templates/log.html:13
msgid "User"