From a6c51aede6b36c87065b7a9be104f9476cc9db1b Mon Sep 17 00:00:00 2001 From: mipel Date: Mon, 26 Jul 2021 11:29:05 +0200 Subject: [PATCH] Payments add modal form * adds help texts to add payment form * adds removing button for payments * refactors user fetching into BaseForm * adds generic RemoveModalForm which is intended to be used for every modal form which shall remove something * adds translations * removes unused html * prepares payment amount field to be able to process german inputs like '1.000,50' which is not the international default --- compensation/admin.py | 4 +- compensation/forms.py | 16 ++-- compensation/urls.py | 2 +- compensation/views.py | 43 ++++++++- .../templates/intervention/detail-view.html | 24 +++-- konova/forms.py | 29 ++++++ konova/static/css/konova.css | 9 ++ konova/sub_settings/django_settings.py | 6 +- locale/de/LC_MESSAGES/django.mo | Bin 8749 -> 9041 bytes locale/de/LC_MESSAGES/django.po | 86 +++++++++++------- templates/base.html | 2 +- templates/modal/modal_form.html | 17 +--- 12 files changed, 174 insertions(+), 64 deletions(-) diff --git a/compensation/admin.py b/compensation/admin.py index 1c15be75..656b5240 100644 --- a/compensation/admin.py +++ b/compensation/admin.py @@ -41,7 +41,9 @@ class PaymentAdmin(admin.ModelAdmin): list_display = [ "id", "amount", - "due_on" + "due_on", + "created_by", + "created_on", ] diff --git a/compensation/forms.py b/compensation/forms.py index 30b03046..9c435644 100644 --- a/compensation/forms.py +++ b/compensation/forms.py @@ -20,15 +20,18 @@ class NewCompensationForm(BaseForm): class NewPaymentForm(BaseModalForm): - amount = forms.FloatField( - min_value=0.01, + amount = forms.DecimalField( + min_value=0.00, + decimal_places=2, label=_("Amount"), label_suffix=_(""), + help_text=_("Amount in Euro"), + localize=True, ) due = forms.DateField( - required=False, label=_("Due on"), label_suffix=_(""), + help_text=_("Due on which date"), widget=forms.DateInput( attrs={ "type": "date", @@ -41,14 +44,13 @@ class NewPaymentForm(BaseModalForm): max_length=1000, required=False, label_suffix=_(""), - label=_("Transfer note") + label=_("Transfer note"), + help_text=_("Note for money transfer") ) def __init__(self, *args, **kwargs): - self.user = kwargs.pop("request", None).user - self.intervention = kwargs.pop("intervention", None) super().__init__(*args, **kwargs) - + self.intervention = self.instance self.form_title = _("Payment") self.form_caption = _("Add a payment for intervention '{}'").format(self.intervention.title) diff --git a/compensation/urls.py b/compensation/urls.py index c22f0322..c51d026f 100644 --- a/compensation/urls.py +++ b/compensation/urls.py @@ -22,7 +22,7 @@ urlpatterns = [ path('pay//new', new_payment_view, name='pay-new'), path('pay/', open_view, name='pay-open'), path('pay//edit', edit_view, name='pay-edit'), - path('pay//remove', remove_view, name='pay-remove'), + path('pay//remove', payment_remove_view, name='pay-remove'), # Eco-account path("acc/", account_index_view, name="acc-index"), diff --git a/compensation/views.py b/compensation/views.py index 482ab537..507aa2e0 100644 --- a/compensation/views.py +++ b/compensation/views.py @@ -4,11 +4,12 @@ from django.shortcuts import render, get_object_or_404 from django.utils.translation import gettext_lazy as _ from compensation.forms import NewPaymentForm -from compensation.models import Compensation, EcoAccount +from compensation.models import Compensation, EcoAccount, Payment from compensation.tables import CompensationTable, EcoAccountTable from intervention.models import Intervention from konova.contexts import BaseContext from konova.decorators import * +from konova.forms import RemoveModalForm @login_required @@ -126,7 +127,7 @@ def new_payment_view(request: HttpRequest, intervention_id: str): """ template = "modal/modal_form.html" intervention = get_object_or_404(Intervention, id=intervention_id) - form = NewPaymentForm(request.POST or None, intervention=intervention, request=request) + form = NewPaymentForm(request.POST or None, instance=intervention, user=request.user) if request.method == "POST": if form.is_valid(): payment = form.save() @@ -149,3 +150,41 @@ def new_payment_view(request: HttpRequest, intervention_id: str): return render(request, template, context) else: raise NotImplementedError + + +@login_required +def payment_remove_view(request: HttpRequest, id: str): + """ Renders a modal view for adding new payments + + Args: + request (HttpRequest): The incoming request + id (str): The payment's id + + Returns: + + """ + template = "modal/modal_form.html" + payment = get_object_or_404(Payment, id=id) + form = RemoveModalForm(request.POST or None, instance=payment, user=request.user) + if request.method == "POST": + if form.is_valid(): + form.save() + messages.success( + request, + _("Payment removed") + ) + return redirect(request.META.get("HTTP_REFERER", "home")) + else: + messages.info( + request, + _("There was an error on this form.") + ) + return redirect(request.META.get("HTTP_REFERER", "home")) + elif request.method == "GET": + context = { + "form": form, + } + context = BaseContext(request, context).context + return render(request, template, context) + else: + raise NotImplementedError diff --git a/intervention/templates/intervention/detail-view.html b/intervention/templates/intervention/detail-view.html index c3e1fb70..444a0f2d 100644 --- a/intervention/templates/intervention/detail-view.html +++ b/intervention/templates/intervention/detail-view.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% load i18n static fontawesome_5 %} +{% load i18n l10n static fontawesome_5 humanize %} {% block head %} @@ -129,7 +129,7 @@ {% trans 'Last modified' %} - {{intervention.created_on|default_if_none:""}} + {{intervention.created_on|default_if_none:""|naturalday}}
{% trans 'by' %} {{intervention.created_by|default_if_none:""}} @@ -171,7 +171,7 @@ -
+
@@ -219,16 +219,22 @@ -
+
+ + @@ -236,10 +242,16 @@ + + {% endfor %} @@ -271,7 +283,7 @@ -
+
{% trans 'Amount' %} + {% trans 'Due on' %} + {% trans 'Transfer comment' %} + {% trans 'Action' %} +
- {{ pay.amount }} + {{ pay.amount|floatformat:2 }} € {{ pay.due_on }} {{ pay.comment }} + +
diff --git a/konova/forms.py b/konova/forms.py index 0ee4187c..b1a9a294 100644 --- a/konova/forms.py +++ b/konova/forms.py @@ -29,6 +29,7 @@ class BaseForm(forms.Form): def __init__(self, *args, **kwargs): self.instance = kwargs.pop("instance", None) + self.user = kwargs.pop("user", None) super().__init__(*args, **kwargs) @abstractmethod @@ -137,6 +138,34 @@ class SimpleGeomForm(BaseForm): self.area = geom.area +class RemoveModalForm(BaseModalForm): + """ Generic removing modal form + + Can be used for anything, where removing shall be confirmed by the user a second time. + + """ + confirm = forms.BooleanField( + label=_("Confirm"), + label_suffix=_(""), + widget=forms.CheckboxInput(), + required=True, + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.form_title = _("Remove") + self.form_caption = _("Are you sure?") + + def save(self): + if hasattr(self.instance, "deleted_on"): + self.instance.deleted_on = timezone.now() + self.instance.deleted_by = self.user + self.instance.save() + else: + # If the class does not provide restorable delete functionality, we must delete the entry finally + self.instance.delete() + + class RemoveDocumentForm(BaseModalForm): confirm = forms.BooleanField( label=_("Confirm"), diff --git a/konova/static/css/konova.css b/konova/static/css/konova.css index d4ca9d39..c3854362 100644 --- a/konova/static/css/konova.css +++ b/konova/static/css/konova.css @@ -194,4 +194,13 @@ input:focus, textarea:focus, select:focus{ .page-item.active > .page-link{ background-color: var(--rlp-red); border-color: var(--rlp-red); +} + +.label-required{ + color: var(--rlp-red); +} + +.scroll-300{ + max-height: 300px; + overflow: auto; } \ No newline at end of file diff --git a/konova/sub_settings/django_settings.py b/konova/sub_settings/django_settings.py index 0f54ef8c..ece5d073 100644 --- a/konova/sub_settings/django_settings.py +++ b/konova/sub_settings/django_settings.py @@ -53,6 +53,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.gis', + 'django.contrib.humanize', 'simple_sso.sso_server', 'django_tables2', 'bootstrap_modal_forms', @@ -143,9 +144,12 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = 'de' + +USE_THOUSAND_SEPARATOR = True DEFAULT_DATE_TIME_FORMAT = '%d.%m.%Y %H:%M:%S' +DATE_FORMAT = '%d.%m.%Y' TIME_ZONE = 'Europe/Berlin' diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index 83f9726b3a15dbbc15d3cd9aacf52852edc38fa5..acf03ddbf4ddb82d11ef362e936e44476fdc0f2e 100644 GIT binary patch delta 3336 zcmYk-32anF9LMq50;LC~7pH)IQ0`KtP=tbrr3C~_1*Db3gQa~vq`M2-EwvmjlvA#t zMKn?1s&lgx4_R|KA4D|Fax!MEacBj;bRyUp#}`2I^JmAidx7{ z)CAr@ZOLKOf=;3q*lhg)!&=$z_QD<1UUg;zH9$P7or3DH2Wq0}sDVe=`&p=d#vp%Y z5+Axh3wvS_(zWrS`dN+2z?LNPuLs`Zf@b&uYKC8-W_}UX@lEW3w^12MB+tDu9ed$K z)cs;qe?C+{8&K~|1GZ+wwh!C;$CAmvX7&jew6d@5gBR=rKiT#lsEquJty{(PHDDrg z$qYtKY#i$4D!@Hhg{_(7rp8G@cH0aPy*%s@?K7V3*$REGi7Kp`BAt5GZ5 zkNxlz4#%sg=MvaQUGIU}n2ow$i6d|q>bcJ`O6R|cie_>am4RlQ>@nst9-{5Ap+oTu z>WeMbzwklY9e4pW;Ap%LXQSSUDn7J;4X8|RL8bm>)K=`p-a7xssE`%Ygqpw=)G@q{ zT5(6-G!2}A`i0C!JvSSb+J&f&D^SO;7S&%pYL9oLCa?z`Jb*e?r!j`{O%oN~6Vr@Z z!8P4L4-It~hZ?XuvS^cL+hgthTpYyp0^1JS`>Snx6KX=QpfYt3HStpz)-Tj~DrDPS zM$PyZj>W%GsTxf>ltJ3(l4#yVE#OPkLeE*R_96ee z@Fy3Pq8Re44iZrV^s?TMn&?2(fFn^U9D{mUb5L6_4fR|xl0>r@wenS{mwPAb*zQC9 zPx-Jf`PT$4aY5chrO3mFI!Z&m0~t6B=io?Og_(F1m6=PJh4J^d4Kxw8;zCra=brgY^joOO6sEHgxUITLywZiXE6OHTN)=@8Of7JEisPBzKt-KH& zT!@*t3bmkjknzIi1QqSgXV&wmy}gQh`>!FpVp>qC>&nKFZIg~;Fc-C=T6Ay?4#j;q z0h>^nicW7^SSG6fY>YEQm?;&_JP$oMj))@W6B?+5n9%Oh(gvt#eJXjxvxE|R zZ>gZMnwU*IdC!d)TPG!EI^hsrLT5$4ETf3IM1ZIvIF{yNf|reR7v__q-P61Kq8hfW z#eu{ELYw&rp>q)=^!5xR<`54ND(cE@t?@%`&B67A4!+6~Vj@vUJV&&T-+BN?(5@$z z6VDT)iCM%VLS+&$o_K(mLa6AR?P#qT^BB&v^<~y-tMCHVu2%p}6ud@LsPTIw_} zBvka1a&OUZXM3r&6`h5r2>l%Cbu1tT5h^+_MMNc$PV^_r2$fC569jLl6377}bRHfh zLPRdHozU4(=|F5D%8ALuN@57165EDZfj(kt?~{2`~(?@Xx)1|p4JHhGfs zYg{MbcWNstODddFZ^(^I?|RMC&2`EGLB|*HyLC<|==E2Zxxq+AQnDx73A(<(^2n&< z`JNamuCvrz7g?X;cIrCW<;n8@yT9J^sAtduZ$(v&zuc*<43;`xzf)T2R=YkY-y5p& zIc~7l8w|Oj$gbYgq6QSxbp53~UA>~#Em`7}HGUj)8sA*3>y_19Z@AHC@bSL&o}~6( p%iXHR6V)XZkv08}cw!1#>#n~cD=js#{Snt6V(R`#ZrX2A{{iN8RLB4T delta 3099 zcmZA3eN5F=9LMo|H2s5>tizOoQ!jMP2un zZ68Oi~Gx%sEjLNJecz z25O=!Pz&2&-GW-s9^2lJ9_`IxDjJ{z)qWrKzzUa*;9+ftpwl5^ z1oEu`C!^X<)cHizLgv}?3vK`MQ1Y)E*3qF1RM-ovQ4`sY{LBFkn$S`7y+qha`x@%L zI+mvuH=+h^L*``OMlI|(Dudmq>w8hpJMXcTE0{&ckEof(a#rs|BI?F8RDU{ZMVY9H z=c5bPU_95Z#I2YeU zo&O9|@hRtI8S%V4VVhx;#Q8*j5(3Pn1>M>g1|86R}umhFS6UY~5dQqQO zAL@Z$q4xF$YT!HQ!XeaGGl|jl;4owtO*Cravuyt|)bol^*l6{YlqJ<)|) zNiVXk<|494^9^d|zo1t5ht;3_svVBXObY7$bky@Rt&33;%|ne>6h;1(x?(!?c9x=2 zzsX+Mh%CyqpjLhq^)`D^pJzX6;Oo{QRAwCfOQwOGs69`?BrL>7a2KXwPc->gO0Lr} z8z*wN2FOCKbQLPa#i*3IQ5mU5rL+k(@q?(;A45&(9BKlWt%Inq>nGHN9I+$U&Gb;w ziA)^85>)Dv=;UvUNk_GFYV1V(md~;U}oK`*S3jW&o9`-;hO{e{mkhJUB9u zBBaY>N~z4`#BR*Q4^b()iCWos{u7`9LQn%tL9NJ%4t$jGBi0Zacs=nXFitc&d z){Cs6`u_LP*ke0Hv#n=iKCzToM`)5N^G7hRTKRk9d%0f0R-)ea??ragRN1!PGZigP zWijzAx5{Uw0E}^oMc)?dQ=0&Ui(5UEpSJ_HvJC_qB#Atb# zN;0vVcv(YNP|=&Zk|-rqvWUlsRALFCqQ47mzM3()_>8S@w$@n1OT;+hH8pHW$1+0i zPykU(_?E8E=Ki9$bhOmjN*X>*=+jFemJ#uUir(@<__o7) zJ|xKD9SkjSc;|$b`FoFrcRRdwk-2{DL(vImVwxP@cVbUE!k5<6SGenH+$N{CYFo9t Trrx#ri*sAcE4^;lUBCYTI&KGQ diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 63ad9ffa..f68d970d 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -3,16 +3,16 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#: compensation/forms.py:27 compensation/forms.py:32 compensation/forms.py:44 +#: compensation/forms.py:26 compensation/forms.py:32 compensation/forms.py:45 #: intervention/filters.py:25 intervention/filters.py:31 #: intervention/filters.py:38 intervention/filters.py:39 konova/forms.py:73 -#: konova/forms.py:144 user/forms.py:38 +#: konova/forms.py:149 konova/forms.py:172 user/forms.py:38 #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-07-26 10:18+0200\n" +"POT-Creation-Date: 2021-07-26 10:44+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -22,24 +22,36 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: compensation/forms.py:26 +#: compensation/forms.py:25 #: intervention/templates/intervention/detail-view.html:227 msgid "Amount" msgstr "Betrag" +#: compensation/forms.py:27 +msgid "Amount in Euro" +msgstr "Betrag in Euro" + #: compensation/forms.py:31 msgid "Due on" msgstr "Fällig am" -#: compensation/forms.py:45 +#: compensation/forms.py:33 +msgid "Due on which date" +msgstr "Zahlung wird an diesem Datum erwartet" + +#: compensation/forms.py:46 msgid "Transfer note" msgstr "Verwendungszweck" -#: compensation/forms.py:53 +#: compensation/forms.py:47 +msgid "Note for money transfer" +msgstr "Verwendungszweck für Überweisung" + +#: compensation/forms.py:55 msgid "Payment" msgstr "Zahlung" -#: compensation/forms.py:54 +#: compensation/forms.py:56 msgid "Add a payment for intervention '{}'" msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen" @@ -53,7 +65,7 @@ msgstr "Kennung" #: intervention/tables.py:28 #: intervention/templates/intervention/detail-view.html:62 #: intervention/templates/intervention/detail-view.html:182 -#: intervention/templates/intervention/detail-view.html:279 +#: intervention/templates/intervention/detail-view.html:287 msgid "Title" msgstr "Bezeichnung" @@ -94,14 +106,18 @@ msgstr "Lösche {}" msgid "Eco Accounts" msgstr "Ökokonten" -#: compensation/views.py:135 +#: compensation/views.py:136 msgid "Payment added" msgstr "Zahlung hinzugefügt" -#: compensation/views.py:141 konova/views.py:137 +#: compensation/views.py:142 compensation/views.py:180 konova/views.py:137 msgid "There was an error on this form." msgstr "Es gab einen Fehler im Formular." +#: compensation/views.py:174 +msgid "Payment removed" +msgstr "Zahlung gelöscht" + #: intervention/filters.py:24 msgid "Show all" msgstr "Alle anzeigen" @@ -325,23 +341,28 @@ msgstr "Neue Zahlung hinzufügen" msgid "Transfer comment" msgstr "Verwendungszweck" -#: intervention/templates/intervention/detail-view.html:259 -msgid "Documents" -msgstr "Dokumente" - -#: intervention/templates/intervention/detail-view.html:264 -msgid "Add new document" -msgstr "Neues Dokument hinzufügen" - -#: intervention/templates/intervention/detail-view.html:282 -msgid "Comment" -msgstr "Kommentar" - -#: intervention/templates/intervention/detail-view.html:285 +#: intervention/templates/intervention/detail-view.html:233 +#: intervention/templates/intervention/detail-view.html:293 msgid "Action" msgstr "Aktionen" -#: intervention/templates/intervention/detail-view.html:299 konova/forms.py:152 +#: intervention/templates/intervention/detail-view.html:247 +msgid "Remove payment" +msgstr "Zahlung entfernen" + +#: intervention/templates/intervention/detail-view.html:267 +msgid "Documents" +msgstr "Dokumente" + +#: intervention/templates/intervention/detail-view.html:272 +msgid "Add new document" +msgstr "Neues Dokument hinzufügen" + +#: intervention/templates/intervention/detail-view.html:290 +msgid "Comment" +msgstr "Kommentar" + +#: intervention/templates/intervention/detail-view.html:307 konova/forms.py:180 msgid "Remove document" msgstr "Dokument löschen" @@ -383,19 +404,23 @@ msgstr "Hierfür müssen Sie einer anderen Nutzergruppe angehören!" msgid "Not editable" msgstr "Nicht editierbar" -#: konova/forms.py:72 konova/forms.py:143 +#: konova/forms.py:72 konova/forms.py:148 konova/forms.py:171 msgid "Confirm" msgstr "Bestätige" -#: konova/forms.py:84 +#: konova/forms.py:84 konova/forms.py:156 msgid "Remove" -msgstr "Entferne" +msgstr "Löschen" #: konova/forms.py:86 msgid "You are about to remove {} {}" msgstr "Sie sind dabei {} {} zu löschen" -#: konova/forms.py:153 +#: konova/forms.py:157 +msgid "Are you sure?" +msgstr "" + +#: konova/forms.py:181 msgid "This will remove '{}'. Are you sure?" msgstr "Hiermit wird '{}' gelöscht. Sind Sie sicher?" @@ -483,7 +508,7 @@ msgstr "" msgid "Contact" msgstr "Kontakt" -#: templates/modal/modal_form.html:33 +#: templates/modal/modal_form.html:24 msgid "Continue" msgstr "Weiter" @@ -1923,9 +1948,6 @@ msgstr "" #~ "Eingriffe müssen zu einem Vorgang gehören. Bitte geben SIe die fehlenden " #~ "Daten für den Vorgang ein." -#~ msgid "Intervention {} removed" -#~ msgstr "Eingriff {} gelöscht" - #~ msgid "You are working as" #~ msgstr "Sie arbeiten gerade als " diff --git a/templates/base.html b/templates/base.html index f1a713cb..65487e21 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,5 +1,5 @@ -{% load static i18n fontawesome_5 bootstrap4 %} +{% load static i18n l10n fontawesome_5 bootstrap4 %} diff --git a/templates/modal/modal_form.html b/templates/modal/modal_form.html index 93b28317..79f347ff 100644 --- a/templates/modal/modal_form.html +++ b/templates/modal/modal_form.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n l10n %} {% comment %} A generic modal form template which is based on django-bootstrap-modal-forms package https://pypi.org/project/django-bootstrap-modal-forms/ @@ -15,18 +15,9 @@