diff --git a/compensation/models.py b/compensation/models.py index ee78a46e..3393dd9b 100644 --- a/compensation/models.py +++ b/compensation/models.py @@ -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): diff --git a/compensation/templates/compensation/detail/compensation/view.html b/compensation/templates/compensation/detail/compensation/view.html index 16084e2d..63d00aa2 100644 --- a/compensation/templates/compensation/detail/compensation/view.html +++ b/compensation/templates/compensation/detail/compensation/view.html @@ -63,9 +63,9 @@ {% trans 'Last modified' %} - {{obj.created.timestamp|default_if_none:""|naturalday}} + {{obj.modified.timestamp|default_if_none:""|naturalday}}
- {{obj.created.user.username}} + {{obj.modified.user.username}} diff --git a/compensation/templates/compensation/detail/eco_account/view.html b/compensation/templates/compensation/detail/eco_account/view.html index 51d0645d..3f9ddb75 100644 --- a/compensation/templates/compensation/detail/eco_account/view.html +++ b/compensation/templates/compensation/detail/eco_account/view.html @@ -55,9 +55,9 @@ {% trans 'Last modified' %} - {{obj.created.timestamp|default_if_none:""|naturalday}} + {{obj.modified.timestamp|default_if_none:""|naturalday}}
- {{obj.created.user.username}} + {{obj.modified.user.username}} diff --git a/compensation/views/compensation_views.py b/compensation/views/compensation_views.py index 7d4bbcd1..0403af29 100644 --- a/compensation/views/compensation_views.py +++ b/compensation/views/compensation_views.py @@ -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"), ) diff --git a/ema/models.py b/ema/models.py index e5aee4cb..aab3a86f 100644 --- a/ema/models.py +++ b/ema/models.py @@ -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 \ No newline at end of file diff --git a/ema/templates/ema/detail/includes/actions.html b/ema/templates/ema/detail/includes/actions.html new file mode 100644 index 00000000..8fab1234 --- /dev/null +++ b/ema/templates/ema/detail/includes/actions.html @@ -0,0 +1,61 @@ +{% load i18n l10n fontawesome_5 humanize %} +
+
+
+
+
+ {{obj.actions.count}} + {% trans 'Actions' context 'Compensation' %} +
+
+
+
+ {% if is_default_member and has_access %} + + {% endif %} +
+
+
+
+
+ + + + + + + + + + + {% for action in obj.actions.all %} + + + + + + + {% endfor %} + +
+ {% trans 'Action type' %} + + {% trans 'Amount' context 'Compensation' %} + + {% trans 'Comment' %} + + {% trans 'Action' %} +
+ {{ action.action_type }} + {{ action.amount|floatformat:2|intcomma }} {{ action.unit_humanize }}{{ action.comment|default_if_none:"" }} + {% if is_default_member and has_access %} + + {% endif %} +
+
+
\ No newline at end of file diff --git a/ema/templates/ema/detail/includes/controls.html b/ema/templates/ema/detail/includes/controls.html new file mode 100644 index 00000000..9dc960fa --- /dev/null +++ b/ema/templates/ema/detail/includes/controls.html @@ -0,0 +1,40 @@ +{% load i18n l10n fontawesome_5 %} + +
+ + + + + + + {% if has_access %} + {% if is_ets_member %} + {% if obj.recorded %} + + {% else %} + + {% endif %} + {% endif %} + {% if is_default_member %} + + + + + + {% endif %} + {% endif %} +
\ No newline at end of file diff --git a/ema/templates/ema/detail/includes/deadlines.html b/ema/templates/ema/detail/includes/deadlines.html new file mode 100644 index 00000000..63ed3b67 --- /dev/null +++ b/ema/templates/ema/detail/includes/deadlines.html @@ -0,0 +1,61 @@ +{% load i18n l10n fontawesome_5 %} +
+
+
+
+
+ {{obj.deadlines.count}} + {% trans 'Deadlines' %} +
+
+
+
+ {% if is_default_member and has_access %} + + {% endif %} +
+
+
+
+
+ + + + + + + + + + + {% for deadline in obj.deadlines.all %} + + + + + + + {% endfor %} + +
+ {% trans 'Type' %} + + {% trans 'Date' %} + + {% trans 'Comment' %} + + {% trans 'Action' %} +
+ {% trans deadline.type_humanized %} + {{ deadline.date }}{{ deadline.comment }} + {% if is_default_member and has_access %} + + {% endif %} +
+
+
\ No newline at end of file diff --git a/ema/templates/ema/detail/includes/documents.html b/ema/templates/ema/detail/includes/documents.html new file mode 100644 index 00000000..0670bbe7 --- /dev/null +++ b/ema/templates/ema/detail/includes/documents.html @@ -0,0 +1,59 @@ +{% load i18n l10n fontawesome_5 %} +
+
+
+
+
+ {{obj.documents.count}} + {% trans 'Documents' %} +
+
+
+
+ {% if is_default_member and has_access %} + + {% endif %} +
+
+
+
+
+ + + + + + + + + + {% for doc in obj.documents.all %} + + + + + + {% endfor %} + +
+ {% trans 'Title' %} + + {% trans 'Comment' %} + + {% trans 'Action' %} +
+ + {{ doc.title }} + + {{ doc.comment }} + {% if is_default_member and has_access %} + + {% endif %} +
+
+
\ No newline at end of file diff --git a/ema/templates/ema/detail/includes/states-after.html b/ema/templates/ema/detail/includes/states-after.html new file mode 100644 index 00000000..02253b38 --- /dev/null +++ b/ema/templates/ema/detail/includes/states-after.html @@ -0,0 +1,62 @@ +{% load i18n l10n fontawesome_5 %} +
+
+
+
+
+ {{obj.after_states.count}} + {% trans 'States after' %} +
+
+
+
+ {% if is_default_member and has_access %} + + {% endif %} +
+
+
+
+
+ {% if sum_before_states > sum_after_states %} +
+ {% trans 'Missing surfaces according to states before: ' %}{{ diff_states|floatformat:2 }} m² +
+ {% endif %} + + + + + + + + + + {% for state in after_states %} + + + + + + {% endfor %} + +
+ {% trans 'Biotope type' %} + + {% trans 'Surface' %} + + {% trans 'Action' %} +
+ {{ state.biotope_type }} + {{ state.surface|floatformat:2 }} m² + {% if is_default_member and has_access %} + + {% endif %} +
+
+
\ No newline at end of file diff --git a/ema/templates/ema/detail/includes/states-before.html b/ema/templates/ema/detail/includes/states-before.html new file mode 100644 index 00000000..74220659 --- /dev/null +++ b/ema/templates/ema/detail/includes/states-before.html @@ -0,0 +1,62 @@ +{% load i18n l10n fontawesome_5 %} +
+
+
+
+
+ {{obj.before_states.count}} + {% trans 'States before' %} +
+
+
+
+ {% if is_default_member and has_access %} + + {% endif %} +
+
+
+
+
+ {% if sum_before_states < sum_after_states %} +
+ {% trans 'Missing surfaces according to states after: ' %}{{ diff_states|floatformat:2 }} m² +
+ {% endif %} + + + + + + + + + + {% for state in before_states %} + + + + + + {% endfor %} + +
+ {% trans 'Biotope type' %} + + {% trans 'Surface' %} + + {% trans 'Action' %} +
+ {{ state.biotope_type }} + {{ state.surface|floatformat:2 }} m² + {% if is_default_member and has_access %} + + {% endif %} +
+
+
\ No newline at end of file diff --git a/ema/templates/ema/detail/view.html b/ema/templates/ema/detail/view.html new file mode 100644 index 00000000..57a73c8e --- /dev/null +++ b/ema/templates/ema/detail/view.html @@ -0,0 +1,100 @@ +{% extends 'base.html' %} +{% load i18n l10n static fontawesome_5 humanize %} + +{% block head %} + +{% endblock %} + +{% block body %} + +
+
+

{{obj.identifier}}

+
+
+ {% include 'ema/detail/includes/controls.html' %} +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + +
{% trans 'Title' %}{{obj.title}}
{% trans 'Recorded' %} + {% if obj.recorded is None %} + + {% fa5_icon 'bookmark' 'far' %} + + {% else %} + + {% fa5_icon 'bookmark' %} + + {% endif %} +
{% trans 'Last modified' %} + {% if obj.modified %} + {{obj.modified.timestamp|default_if_none:""|naturalday}} +
+ {{obj.modified.user.username}} + {% else %} + {{obj.created.timestamp|default_if_none:""|naturalday}} +
+ {{obj.created.user.username}} + + {% endif %} +
{% trans 'Shared with' %} + {% for user in obj.users.all %} + {% include 'user/includes/contact_modal_button.html' %} + {% endfor %} +
+
+
+
+ {% include 'map/geom_form.html' %} +
+
+
+ +
+
+ {% include 'ema/detail/includes/states-before.html' %} +
+
+ {% include 'ema/detail/includes/states-after.html' %} +
+
+
+
+ {% include 'ema/detail/includes/actions.html' %} +
+
+ {% include 'ema/detail/includes/deadlines.html' %} +
+
+
+
+ {% include 'ema/detail/includes/documents.html' %} +
+
+ + +{% with 'btn-modal' as btn_class %} + {% include 'modal/modal_form_script.html' %} +{% endwith %} + +{% endblock %} \ No newline at end of file diff --git a/ema/urls.py b/ema/urls.py index 97cef3b3..c3f5bd31 100644 --- a/ema/urls.py +++ b/ema/urls.py @@ -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("", open_view, name="open"), + path('/log', log_view, name='log'), + path('/edit', edit_view, name='edit'), + path('/remove', remove_view, name='remove'), + path('/record', record_view, name='record'), + path('/state/new', state_new_view, name='new-state'), + path('/action/new', action_new_view, name='new-action'), + path('/deadline/new', deadline_new_view, name="new-deadline"), + + # Documents + # Document remove route can be found in konova/urls.py + path('/document/new/', document_new_view, name='new-doc'), + + # Generic state routes + path('state//remove', state_remove_view, name='state-remove'), + + # Generic action routes + path('action//remove', action_remove_view, name='action-remove'), ] \ No newline at end of file diff --git a/ema/views.py b/ema/views.py index febf4f4c..2f6a6650 100644 --- a/ema/views.py +++ b/ema/views.py @@ -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 + ) + diff --git a/intervention/models.py b/intervention/models.py index 67d75d04..22bbf943 100644 --- a/intervention/models.py +++ b/intervention/models.py @@ -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 = [] diff --git a/konova/forms.py b/konova/forms.py index 474f7f77..7142f6cf 100644 --- a/konova/forms.py +++ b/konova/forms.py @@ -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: diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index cef9d833..6841eca9 100644 Binary files a/locale/de/LC_MESSAGES/django.mo and b/locale/de/LC_MESSAGES/django.mo differ diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index c26bd1b1..96e44af1 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -1276,7 +1276,7 @@ msgstr "Filter anwenden" #: templates/log.html:7 msgid "Timestamp" -msgstr "" +msgstr "Zeitpunkt" #: templates/log.html:13 msgid "User"