From d139119a1545c54d70470c84676ddc1357925d43 Mon Sep 17 00:00:00 2001
From: mipel <hadunan@gmail.com>
Date: Wed, 21 Jul 2021 15:40:34 +0200
Subject: [PATCH] Intervention tables and model adjustments

* adds user access relation to certain models
* adds pagination to tables
* adds checked_on/_by attributes to intervention model
* adds custom column rendering for checked and registered columns
* adds first simple index filtering of default interventions for user
* adds translations
---
 compensation/models.py                 |   4 ++
 intervention/models.py                 |  19 +++++--
 intervention/tables.py                 |  54 +++++++++++++++++-
 intervention/views.py                  |   4 +-
 konova/models.py                       |   6 +-
 konova/static/css/konova.css           |  28 +++++++++-
 konova/sub_settings/django_settings.py |   2 +-
 konova/utils/tables.py                 |  22 ++++++++
 locale/de/LC_MESSAGES/django.mo        | Bin 5619 -> 5865 bytes
 locale/de/LC_MESSAGES/django.po        |  74 +++++++++++++++----------
 10 files changed, 170 insertions(+), 43 deletions(-)

diff --git a/compensation/models.py b/compensation/models.py
index 9d3e0ca5..ceca5733 100644
--- a/compensation/models.py
+++ b/compensation/models.py
@@ -5,6 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 Created on: 17.11.20
 
 """
+from django.contrib.auth.models import User
 from django.contrib.gis.db import models
 from django.core.validators import MinValueValidator
 from django.utils import timezone
@@ -74,6 +75,9 @@ class Compensation(BaseObject):
     geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
     documents = models.ManyToManyField("konova.Document", blank=True)
 
+    # Users having access on this object
+    users = models.ManyToManyField(User)
+
     @staticmethod
     def _generate_new_identifier() -> str:
         """ Generates a new identifier for the intervention object
diff --git a/intervention/models.py b/intervention/models.py
index 58cebcfb..f8243079 100644
--- a/intervention/models.py
+++ b/intervention/models.py
@@ -33,22 +33,29 @@ class Intervention(BaseObject):
     geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
     documents = models.ManyToManyField("konova.Document", blank=True)
 
+    # Checks
+    checked_on = models.DateTimeField(default=None, null=True, blank=True)
+    checked_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name='+')
+
     # Refers to "zugelassen am"
     registration_date = models.DateField(null=True, blank=True)
 
     # Refers to "Bestandskraft am"
-    binding_date = models.DateField(null=True, blank=True)
+    binding_on = models.DateField(null=True, blank=True)
 
     # Refers to "verzeichnen"
-    recorded_on = models.DateTimeField(default=None)
-    recorded_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)
+    recorded_on = models.DateTimeField(default=None, null=True, blank=True)
+    recorded_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name='+')
 
     # Holds which intervention is simply a newer version of this dataset
-    next_version = models.ForeignKey("Intervention", null=True, on_delete=models.DO_NOTHING)
+    next_version = models.ForeignKey("Intervention", null=True, blank=True, on_delete=models.DO_NOTHING)
 
     # Compensation or payments, one-directional
-    payments = models.ManyToManyField(Payment, related_name="+")
-    compensations = models.ManyToManyField(Compensation, related_name="+")
+    payments = models.ManyToManyField(Payment, related_name="+", blank=True)
+    compensations = models.ManyToManyField(Compensation, related_name="+", blank=True)
+
+    # Users having access on this object
+    users = models.ManyToManyField(User)
 
     def delete(self, *args, **kwargs):
         """ Custom delete functionality
diff --git a/intervention/tables.py b/intervention/tables.py
index 9d19ea7c..dea5ec7f 100644
--- a/intervention/tables.py
+++ b/intervention/tables.py
@@ -7,9 +7,11 @@ Created on: 01.12.20
 """
 from django.urls import reverse
 from django.utils.html import format_html
+from django.utils.timezone import localtime
 from django.utils.translation import gettext_lazy as _
 
 from intervention.models import Intervention
+from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT
 from konova.utils.tables import BaseTable
 import django_tables2 as tables
 
@@ -28,11 +30,13 @@ class InterventionTable(BaseTable):
     c = tables.Column(
         verbose_name=_("Checked"),
         orderable=True,
-        accessor="recorded_on",
+        empty_values=[],
+        accessor="checked_on",
     )
     r = tables.Column(
         verbose_name=_("Registered"),
         orderable=True,
+        empty_values=[],
         accessor="recorded_on",
     )
     lm = tables.Column(
@@ -52,7 +56,7 @@ class InterventionTable(BaseTable):
     """
 
     class Meta(BaseTable.Meta):
-        pass
+        template_name = "django_tables2/bootstrap4.html"
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -78,6 +82,52 @@ class InterventionTable(BaseTable):
         )
         return format_html(html)
 
+    def render_c(self, value, record: Intervention):
+        """ Renders the checked column for an intervention
+
+        Args:
+            value (str): The identifier value
+            record (Intervention): The intervention record
+
+        Returns:
+
+        """
+        html = ""
+        checked = value is not None
+        tooltip = _("Not checked yet")
+        if checked:
+            value = localtime(value)
+            checked_on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
+            tooltip = _("Checked on {} by {}").format(checked_on, record.checked_by)
+        html += self.render_checked_star(
+            tooltip=tooltip,
+            icn_filled=checked,
+        )
+        return format_html(html)
+
+    def render_r(self, value, record: Intervention):
+        """ Renders the registered column for an intervention
+
+        Args:
+            value (str): The identifier value
+            record (Intervention): The intervention record
+
+        Returns:
+
+        """
+        html = ""
+        checked = value is not None
+        tooltip = _("Not registered yet")
+        if checked:
+            value = localtime(value)
+            checked_on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
+            tooltip = _("Registered on {} by {}").format(checked_on, record.checked_by)
+        html += self.render_bookmark(
+            tooltip=tooltip,
+            icn_filled=checked,
+        )
+        return format_html(html)
+
     def render_ac(self, value, record):
         """
         Renders possible actions for this record, such as delete.
diff --git a/intervention/views.py b/intervention/views.py
index ba7b1c66..722a7e05 100644
--- a/intervention/views.py
+++ b/intervention/views.py
@@ -26,7 +26,9 @@ def index_view(request: HttpRequest):
     template = "generic_index.html"
     user = request.user
     interventions = Intervention.objects.filter(
-
+        deleted_on=None,  # not deleted
+        next_version=None,  # only newest versions
+        users__in=[user],  # requesting user has access
     )
     table = InterventionTable(
         request=request,
diff --git a/konova/models.py b/konova/models.py
index 5bf5b2a8..eb96dc76 100644
--- a/konova/models.py
+++ b/konova/models.py
@@ -35,9 +35,9 @@ class BaseObject(BaseResource):
     """
     identifier = models.CharField(max_length=1000, null=True, blank=True)
     title = models.CharField(max_length=1000, null=True, blank=True)
-    deleted_on = models.DateTimeField(null=True)
-    deleted_by = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, related_name="+")
-    comment = models.TextField()
+    deleted_on = models.DateTimeField(null=True, blank=True)
+    deleted_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name="+")
+    comment = models.TextField(null=True, blank=True)
 
     class Meta:
         abstract = True
diff --git a/konova/static/css/konova.css b/konova/static/css/konova.css
index 5f311331..dceb780e 100644
--- a/konova/static/css/konova.css
+++ b/konova/static/css/konova.css
@@ -72,7 +72,7 @@ a {
     text-decoration: none;
 }
 
-nav{
+.navbar{
     background-color: var(--rlp-red);
 }
 
@@ -144,9 +144,35 @@ nav{
     cursor: pointer;
 }
 
+.dropdown-item.selected{
+    background-color: var(--rlp-gray-light);
+}
+
 input:focus, textarea:focus, select:focus{
     border: 1px solid var(--rlp-red) !important;
     box-shadow: 0 0 3px var(--rlp-red) !important;
     -moz-box-shadow: 0 0 3px var(--rlp-red) !important;
     -webkit-box-shadow: 0 0 3px var(--rlp-red) !important;
+}
+
+.check-star{
+    color: goldenrod;
+}
+.registered-bookmark{
+    color: green;
+}
+
+/* PAGINATION */
+.page-item > .page-link{
+    color: var(--rlp-red);
+}
+.page-link:focus{
+    border: 1px solid var(--rlp-red) !important;
+    box-shadow: 0 0 3px var(--rlp-red) !important;
+    -moz-box-shadow: 0 0 3px var(--rlp-red) !important;
+    -webkit-box-shadow: 0 0 3px var(--rlp-red) !important;
+}
+.page-item.active > .page-link{
+    background-color: var(--rlp-red);
+    border-color: var(--rlp-red);
 }
\ No newline at end of file
diff --git a/konova/sub_settings/django_settings.py b/konova/sub_settings/django_settings.py
index 20b28fa8..e06c1382 100644
--- a/konova/sub_settings/django_settings.py
+++ b/konova/sub_settings/django_settings.py
@@ -144,7 +144,7 @@ AUTH_PASSWORD_VALIDATORS = [
 
 LANGUAGE_CODE = 'en-us'
 
-DEFAULT_DATE_TIME_FORMAT = 'YYYY-MM-DD hh:mm:ss'
+DEFAULT_DATE_TIME_FORMAT = '%d.%m.%Y %H:%M:%S'
 
 TIME_ZONE = 'Europe/Berlin'
 
diff --git a/konova/utils/tables.py b/konova/utils/tables.py
index 65f9f4e3..196515ac 100644
--- a/konova/utils/tables.py
+++ b/konova/utils/tables.py
@@ -104,6 +104,28 @@ class BaseTable(tables.tables.Table):
             icon
         )
 
+    def render_checked_star(self, tooltip: str = None, icn_filled: bool = False):
+        """
+        Returns a star icon
+        """
+        icon = "fas fa-star check-star" if icn_filled else "far fa-star"
+        return format_html(
+            "<em title='{}' class='{}'></em>",
+            tooltip,
+            icon
+        )
+
+    def render_bookmark(self, tooltip: str = None, icn_filled: bool = False):
+        """
+        Returns a bookmark icon
+        """
+        icon = "fas fa-bookmark registered-bookmark" if icn_filled else "far fa-bookmark"
+        return format_html(
+            "<em title='{}' class='{}'></em>",
+            tooltip,
+            icon
+        )
+
 
 class ChoicesColumnForm(BaseForm):
     select = forms.ChoiceField(
diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo
index 9e5396ebe88a173815768b23c5ad880c3d173614..11732223371648ad128147e6435b07eeb00915c4 100644
GIT binary patch
delta 2295
zcmY+_eN0t#9LMqR#ajp>-Y#THkS8n+uNVqpK7dFMnkZ>XS;~@!qp<2imoplo-SxDz
zt>&`YD*iC~gNm)??jPlJwbsU@e~hw!Y}H0<!^Zk2=aSXd`*ZK_I{NK?&+9z=&hO#-
z`yKczcDXY7OEmnpQCf*oB0R?|iPJZ5piJeOoy5z?&yLPDql&$PQ9O<};t*Eihggmi
zsQOp12>-zXjO3Zk$1>!3(qfqtt41}n0rRmDRdEO2jQfzEJ<CB24dOgJhGBdQZ^Cy`
z0~<vR;7hy}$53;c#05Bo6`JbboT%e+dR0Re&Q+*^RC7>=^;nEer~w^74Y1$&GSZa2
ziOSFrs@+rWdIZ(ZD3T2O0>eu6*RH}Cs)27Yf)`Z*e@Bh@4^#s)s2S!meGQ}pHIORQ
z0P9ih?8b%IidupI7vnKpgu|Frg|qI#_o$RjqdL0c3~@o9R*0&<7&WshRQ<K60dGPL
zc&B^bg6eob)?qJd=AWYKeO^HRIk$5hv^L|;3$DVCs248bGW-pfU_R3=!xgv!x1#Fx
zx%)@3hU@p-^9#6=>lswN<%OC4stU=!QdP^1#*kS9j&S`F`Budk)6z(5P^nvo>C~b&
zRV!*>1E~6^P+!r<$XIO*Y0`c`l4+BujQxq)BUh80=*1b-n$BShm*7HF%2zu#qB`E<
zu6JQNwWv(=qB?v7wI|+3b#xlFch2D5IPUIWLuEV}qF2o@7xh8`YCy%P%~XckENf5$
zXhMG0;_N~-G>Cfd1$X_L^8{+<@1Xh_M$LQ#S&gKPa-tNSK~+4Dn%PApDK>@Lbk~rd
zMR+KeqGn!;dT$SEDGp!-zKT_N8Y^)Um4OJeT8#~ur|&<(iBfk2)$nmtgF~pz_7Q4k
z=baNs_UsZWBh#p_;VNqB{zY}Lh;?9wwhi_EUQ|DAsEoxikNz#-L@9Y5wdOCPHqq-y
zTlNWRreEPb_%o`(BGTevDc*&3xB(BMGBJ!A*f^@)?@$B!3EAuEd&$Q*QBg-4^%jEm
zZ6BdQX+|m!6TD};bU|q%YKa)3Wm1RP@&G5*#BIbn;ub=g%ptVYt;8CFlC(!NCw2$w
z6VpErt(Rt~!m`;DguZjlazCM2+)pS=Dh)(4;SpMr&BSs-Wm^W@j@p20iH*da#44?S
zBPS}_<y!ySbwSY@tJD#jh^<5o@d%MEJ2=_xF0|>C?%74lr6tK0t-C(QJ?_SOt^ech
zB1=6d4-tC_eUd6I8PdmlIW8qu653GO<=WZVqP_AUp-rrEUk2Oh)E_R(_tGWxS?>H$
zw9D^!%J1}g;@;p<uYG`H>g?QKL$MC`V89QoxhL@a&hDVC{b@gak?;?7_XU1}7kQo<
z&d&)IU%xZEsSoBI4wr6zMs4?J?jQ1d6W^RW7<kY1Bs%?gsw5K0S>Myq<;A-@x&klz
x*qYOq>b2WXw6`Vb_j;v%KXKUSdE5_9ZjUZYJsIr_E%{$Bvub2JnkXvI`49GE?w<ev

delta 2037
zcmYk-S!`5Q7{Ku}wYJm_9a>N}3#GEvQba5uMV11kEM-xaLgc{~npnWZOi+1Hh9@wD
zm^3bg1QUa%tS@DF!N3a%+Y1<ol7QMosYFSL2~lFAiU0r1nWQK4-QT(Qo_m(>o|*Z*
z;c<QD=c?k*17!>GJaNA$gbbc}ngiuTX$YU<N#qme4+-HZY{4pAfhD*V8*m%eVh`H>
z947Gs4#n&7`klO)&`-q<9^f<h2yOTeK8vMgA@B*+9PDTcmSZCp<07oY7IZ@G=)gO0
zET+-5e1a+b4D0Y4EODtfsMyi>(YxqG`q2UZz!CTaolugSPOvsQ1GyCzpqW{YE~qV@
zuSff7N7fX!qvO5feN&yLVh8WzFx(d#^q>!Zfe!pNI`DaPLRZn}e#GbSHyn=t;b^QR
zO{1_Ly}uN_{wA8C_b}rCA5qbR$g|-vx`Jb9!;|R5`p}tQiubRg1Kz;-_zSwi8alIG
z9op}74!(kU(Ps31Ym)r?KpPh(<3=2hA7V8g#TW5gwB10w{s<d6Pm&JrH{&$ig|<6_
z4tNaB&<R|e2;nRq=lrjVe7i4G<lmX}vNKH0Y4m~1=;pbJPV6DtaTRG`>@XJTBTPjW
z9p<2!T7vF@RcQOQ=%(C^V{j*$;r-E`3>61F8ZY#sDeXfua2Xx&S9A~ji4Ii4mS;|(
z0$;*$==C+|$~U14+ZwNTpcCpw_f8t!8<_)CoWNJ;Oix8Gq8)vQKKMgC?~e|kD}RU%
z^aNe`zetvIotdEuI<;LjGWSr2j?<Jk6IN4krEj4P_o693h;`V9Gw>cZV3PE>;wF3<
zcVI4qXr_9RrXh>=cLiO*t>`^uj^RF*`ThT!io4ys_>M}^j%(4CY(P8cL<iW3W@ZmM
z@LqJ~AEWQ~5c*4c82N;A99-aaoP`h2{;GMOHGcmmQ<;jbI1k@NGjIl-*lo1qyXb@l
zkj<Pc>!Yk8m!&1>pzVZ{a#LGY5j+-FdqQa?ULi7mecVWsh(c+mVg{V`Y{DOpaRh%2
zxe08+LU|)rUO+e01Y#QD3N5~hO@yy<3bC0mi;D=pVXi;_<FOLQ6Q*<lVFm`5<(#<D
z8i~2YM8f^Bkg$v-e6KZx#m#1EB3>hw63d7+M4_y3{I&7SS75pamzkWrI_QG$-Ceyd
zUYmm(W4(Z{(6WK>D`|N>PwqI2qyLcgM7gVRH%})D#m%#r@SR&)@`SeNnCw95s6@81
z?5D(#uI=5sJ34oDWk;76C9*S<9~5V=3`-TIy0^W(yT+T{UFpt_bT&2oNFsY}#Dt>%
E0W{^Kq5uE@

diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po
index f1595f1e..ba953669 100644
--- a/locale/de/LC_MESSAGES/django.po
+++ b/locale/de/LC_MESSAGES/django.po
@@ -3,13 +3,13 @@
 # This file is distributed under the same license as the PACKAGE package.
 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 #
-#: konova/forms.py:67 user/forms.py:38
+#: konova/forms.py:69 user/forms.py:38
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-07-21 13:28+0200\n"
+"POT-Creation-Date: 2021-07-21 15:07+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -19,49 +19,49 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: compensation/tables.py:18 compensation/tables.py:68 intervention/forms.py:26
-#: intervention/tables.py:19
+#: compensation/tables.py:18 compensation/tables.py:71 intervention/forms.py:26
+#: intervention/tables.py:22
 msgid "Identifier"
 msgstr "Kennung"
 
-#: compensation/tables.py:23 compensation/tables.py:73 intervention/forms.py:33
-#: intervention/tables.py:24
+#: compensation/tables.py:23 compensation/tables.py:76 intervention/forms.py:33
+#: intervention/tables.py:27
 msgid "Title"
 msgstr "Titel"
 
-#: compensation/tables.py:28 compensation/tables.py:78
+#: compensation/tables.py:28 compensation/tables.py:81
 msgid "Created on"
 msgstr "Erstellt"
 
-#: compensation/tables.py:33 compensation/tables.py:83
+#: compensation/tables.py:33 compensation/tables.py:86
 msgid "Actions"
 msgstr "Aktionen"
 
-#: compensation/tables.py:41
+#: compensation/tables.py:44
 msgid "Compensations"
 msgstr "Kompensationen"
 
-#: compensation/tables.py:48 compensation/tables.py:98
+#: compensation/tables.py:51 compensation/tables.py:104
 #: konova/templates/konova/home.html:49 templates/navbar.html:28
 msgid "Compensation"
 msgstr "Kompensation"
 
-#: compensation/tables.py:51 compensation/tables.py:101
-#: intervention/tables.py:74 intervention/tables.py:88
+#: compensation/tables.py:54 compensation/tables.py:107
+#: intervention/tables.py:79 intervention/tables.py:139
 msgid "Open {}"
 msgstr "Öffne {}"
 
-#: compensation/tables.py:56 compensation/tables.py:106
-#: intervention/tables.py:92
+#: compensation/tables.py:59 compensation/tables.py:112
+#: intervention/tables.py:143
 msgid "Edit {}"
 msgstr "Bearbeite {}"
 
-#: compensation/tables.py:60 compensation/tables.py:110
-#: intervention/tables.py:96
+#: compensation/tables.py:63 compensation/tables.py:116
+#: intervention/tables.py:147
 msgid "Delete {}"
 msgstr "Lösche {}"
 
-#: compensation/tables.py:91
+#: compensation/tables.py:97
 msgid "Eco Accounts"
 msgstr "Ökokonten"
 
@@ -133,41 +133,57 @@ msgstr "Neuer Eingriff"
 msgid "Edit intervention"
 msgstr "Eingriff bearbeiten"
 
-#: intervention/tables.py:29
+#: intervention/tables.py:32
 msgid "Checked"
 msgstr "Geprüft"
 
-#: intervention/tables.py:34
+#: intervention/tables.py:38
 msgid "Registered"
 msgstr "Verzeichnet"
 
-#: intervention/tables.py:39
+#: intervention/tables.py:44
 msgid "Last edit"
 msgstr "Zuletzt bearbeitet"
 
-#: intervention/tables.py:59
+#: intervention/tables.py:64
 msgid "Interventions"
 msgstr "Eingriffe"
 
-#: intervention/tables.py:74 intervention/tables.py:85
+#: intervention/tables.py:79 intervention/tables.py:136
 #: intervention/templates/intervention/open.html:8
 #: konova/templates/konova/home.html:11 templates/navbar.html:22
 msgid "Intervention"
 msgstr "Eingriff"
 
+#: intervention/tables.py:98
+msgid "Not checked yet"
+msgstr "Noch nicht geprüft"
+
+#: intervention/tables.py:102
+msgid "Checked on {} by {}"
+msgstr "Am {} von {} geprüft worden"
+
+#: intervention/tables.py:121
+msgid "Not registered yet"
+msgstr "Noch nicht verzeichnet"
+
+#: intervention/tables.py:125
+msgid "Registered on {} by {}"
+msgstr "Am {} von {} verzeichnet worden"
+
 #: intervention/templates/intervention/open.html:12
 msgid "Edit"
 msgstr "Bearbeiten"
 
-#: intervention/views.py:58
+#: intervention/views.py:60
 msgid "Intervention {} added"
 msgstr "Eingriff {} hinzugefügt"
 
-#: intervention/views.py:61 intervention/views.py:114
+#: intervention/views.py:63 intervention/views.py:116
 msgid "Invalid input"
 msgstr "Eingabe fehlerhaft"
 
-#: intervention/views.py:111
+#: intervention/views.py:113
 msgid "{} edited"
 msgstr "{} bearbeitet"
 
@@ -183,19 +199,19 @@ msgstr "Hierfür müssen Sie Administrator sein!"
 msgid "You need to be part of another user group."
 msgstr "Hierfür müssen Sie einer anderen Nutzergruppe angehören!"
 
-#: konova/forms.py:40
+#: konova/forms.py:42
 msgid "Not editable"
 msgstr "Nicht editierbar"
 
-#: konova/forms.py:66
+#: konova/forms.py:68
 msgid "Confirm"
 msgstr "Bestätigen"
 
-#: konova/forms.py:78
+#: konova/forms.py:80
 msgid "Remove"
 msgstr "Entferne"
 
-#: konova/forms.py:80
+#: konova/forms.py:82
 msgid "You are about to remove {} {}"
 msgstr "Sie sind dabei {} {} zu löschen"