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
This commit is contained in:
mipel 2021-07-26 11:29:05 +02:00
parent 23afe2654e
commit 2889b10e0d
12 changed files with 174 additions and 64 deletions

View File

@ -41,7 +41,9 @@ class PaymentAdmin(admin.ModelAdmin):
list_display = [ list_display = [
"id", "id",
"amount", "amount",
"due_on" "due_on",
"created_by",
"created_on",
] ]

View File

@ -20,15 +20,18 @@ class NewCompensationForm(BaseForm):
class NewPaymentForm(BaseModalForm): class NewPaymentForm(BaseModalForm):
amount = forms.FloatField( amount = forms.DecimalField(
min_value=0.01, min_value=0.00,
decimal_places=2,
label=_("Amount"), label=_("Amount"),
label_suffix=_(""), label_suffix=_(""),
help_text=_("Amount in Euro"),
localize=True,
) )
due = forms.DateField( due = forms.DateField(
required=False,
label=_("Due on"), label=_("Due on"),
label_suffix=_(""), label_suffix=_(""),
help_text=_("Due on which date"),
widget=forms.DateInput( widget=forms.DateInput(
attrs={ attrs={
"type": "date", "type": "date",
@ -41,14 +44,13 @@ class NewPaymentForm(BaseModalForm):
max_length=1000, max_length=1000,
required=False, required=False,
label_suffix=_(""), label_suffix=_(""),
label=_("Transfer note") label=_("Transfer note"),
help_text=_("Note for money transfer")
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.user = kwargs.pop("request", None).user
self.intervention = kwargs.pop("intervention", None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.intervention = self.instance
self.form_title = _("Payment") self.form_title = _("Payment")
self.form_caption = _("Add a payment for intervention '{}'").format(self.intervention.title) self.form_caption = _("Add a payment for intervention '{}'").format(self.intervention.title)

View File

@ -22,7 +22,7 @@ urlpatterns = [
path('pay/<intervention_id>/new', new_payment_view, name='pay-new'), path('pay/<intervention_id>/new', new_payment_view, name='pay-new'),
path('pay/<id>', open_view, name='pay-open'), path('pay/<id>', open_view, name='pay-open'),
path('pay/<id>/edit', edit_view, name='pay-edit'), path('pay/<id>/edit', edit_view, name='pay-edit'),
path('pay/<id>/remove', remove_view, name='pay-remove'), path('pay/<id>/remove', payment_remove_view, name='pay-remove'),
# Eco-account # Eco-account
path("acc/", account_index_view, name="acc-index"), path("acc/", account_index_view, name="acc-index"),

View File

@ -4,11 +4,12 @@ from django.shortcuts import render, get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from compensation.forms import NewPaymentForm 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 compensation.tables import CompensationTable, EcoAccountTable
from intervention.models import Intervention from intervention.models import Intervention
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import * from konova.decorators import *
from konova.forms import RemoveModalForm
@login_required @login_required
@ -126,7 +127,7 @@ def new_payment_view(request: HttpRequest, intervention_id: str):
""" """
template = "modal/modal_form.html" template = "modal/modal_form.html"
intervention = get_object_or_404(Intervention, id=intervention_id) 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 request.method == "POST":
if form.is_valid(): if form.is_valid():
payment = form.save() payment = form.save()
@ -149,3 +150,41 @@ def new_payment_view(request: HttpRequest, intervention_id: str):
return render(request, template, context) return render(request, template, context)
else: else:
raise NotImplementedError 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

View File

@ -1,5 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n static fontawesome_5 %} {% load i18n l10n static fontawesome_5 humanize %}
{% block head %} {% block head %}
@ -129,7 +129,7 @@
<tr> <tr>
<th scope="row">{% trans 'Last modified' %}</th> <th scope="row">{% trans 'Last modified' %}</th>
<td class="align-middle"> <td class="align-middle">
{{intervention.created_on|default_if_none:""}} {{intervention.created_on|default_if_none:""|naturalday}}
<br> <br>
{% trans 'by' %} {% trans 'by' %}
{{intervention.created_by|default_if_none:""}} {{intervention.created_by|default_if_none:""}}
@ -171,7 +171,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body scroll-300">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
@ -219,16 +219,22 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body scroll-300">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col"> <th scope="col">
{% trans 'Amount' %} {% trans 'Amount' %}
</th> </th>
<th scope="col">
{% trans 'Due on' %}
</th>
<th scope="col"> <th scope="col">
{% trans 'Transfer comment' %} {% trans 'Transfer comment' %}
</th> </th>
<th scope="col">
{% trans 'Action' %}
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -236,10 +242,16 @@
<tr> <tr>
<td class="align-middle"> <td class="align-middle">
<a href="{% url 'compensation:pay-open' pay.id %}"> <a href="{% url 'compensation:pay-open' pay.id %}">
{{ pay.amount }} {{ pay.amount|floatformat:2 }}
</a> </a>
</td> </td>
<td class="align-middle">{{ pay.due_on }}</td>
<td class="align-middle">{{ pay.comment }}</td> <td class="align-middle">{{ pay.comment }}</td>
<td>
<button data-form-url="{% url 'compensation:pay-remove' pay.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove payment' %}">
{% fa5_icon 'trash' %}
</button>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -271,7 +283,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body scroll-300">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>

View File

@ -29,6 +29,7 @@ class BaseForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.instance = kwargs.pop("instance", None) self.instance = kwargs.pop("instance", None)
self.user = kwargs.pop("user", None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@abstractmethod @abstractmethod
@ -137,6 +138,34 @@ class SimpleGeomForm(BaseForm):
self.area = geom.area 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): class RemoveDocumentForm(BaseModalForm):
confirm = forms.BooleanField( confirm = forms.BooleanField(
label=_("Confirm"), label=_("Confirm"),

View File

@ -195,3 +195,12 @@ input:focus, textarea:focus, select:focus{
background-color: var(--rlp-red); background-color: var(--rlp-red);
border-color: var(--rlp-red); border-color: var(--rlp-red);
} }
.label-required{
color: var(--rlp-red);
}
.scroll-300{
max-height: 300px;
overflow: auto;
}

View File

@ -53,6 +53,7 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.gis', 'django.contrib.gis',
'django.contrib.humanize',
'simple_sso.sso_server', 'simple_sso.sso_server',
'django_tables2', 'django_tables2',
'bootstrap_modal_forms', 'bootstrap_modal_forms',
@ -143,9 +144,12 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/ # 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' DEFAULT_DATE_TIME_FORMAT = '%d.%m.%Y %H:%M:%S'
DATE_FORMAT = '%d.%m.%Y'
TIME_ZONE = 'Europe/Berlin' TIME_ZONE = 'Europe/Berlin'

Binary file not shown.

View File

@ -3,16 +3,16 @@
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, 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:25 intervention/filters.py:31
#: intervention/filters.py:38 intervention/filters.py:39 konova/forms.py:73 #: 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 #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -22,24 +22,36 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: compensation/forms.py:26 #: compensation/forms.py:25
#: intervention/templates/intervention/detail-view.html:227 #: intervention/templates/intervention/detail-view.html:227
msgid "Amount" msgid "Amount"
msgstr "Betrag" msgstr "Betrag"
#: compensation/forms.py:27
msgid "Amount in Euro"
msgstr "Betrag in Euro"
#: compensation/forms.py:31 #: compensation/forms.py:31
msgid "Due on" msgid "Due on"
msgstr "Fällig am" 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" msgid "Transfer note"
msgstr "Verwendungszweck" 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" msgid "Payment"
msgstr "Zahlung" msgstr "Zahlung"
#: compensation/forms.py:54 #: compensation/forms.py:56
msgid "Add a payment for intervention '{}'" msgid "Add a payment for intervention '{}'"
msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen" msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen"
@ -53,7 +65,7 @@ msgstr "Kennung"
#: intervention/tables.py:28 #: intervention/tables.py:28
#: intervention/templates/intervention/detail-view.html:62 #: intervention/templates/intervention/detail-view.html:62
#: intervention/templates/intervention/detail-view.html:182 #: intervention/templates/intervention/detail-view.html:182
#: intervention/templates/intervention/detail-view.html:279 #: intervention/templates/intervention/detail-view.html:287
msgid "Title" msgid "Title"
msgstr "Bezeichnung" msgstr "Bezeichnung"
@ -94,14 +106,18 @@ msgstr "Lösche {}"
msgid "Eco Accounts" msgid "Eco Accounts"
msgstr "Ökokonten" msgstr "Ökokonten"
#: compensation/views.py:135 #: compensation/views.py:136
msgid "Payment added" msgid "Payment added"
msgstr "Zahlung hinzugefügt" 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." msgid "There was an error on this form."
msgstr "Es gab einen Fehler im Formular." msgstr "Es gab einen Fehler im Formular."
#: compensation/views.py:174
msgid "Payment removed"
msgstr "Zahlung gelöscht"
#: intervention/filters.py:24 #: intervention/filters.py:24
msgid "Show all" msgid "Show all"
msgstr "Alle anzeigen" msgstr "Alle anzeigen"
@ -325,23 +341,28 @@ msgstr "Neue Zahlung hinzufügen"
msgid "Transfer comment" msgid "Transfer comment"
msgstr "Verwendungszweck" msgstr "Verwendungszweck"
#: intervention/templates/intervention/detail-view.html:259 #: intervention/templates/intervention/detail-view.html:233
msgid "Documents" #: intervention/templates/intervention/detail-view.html:293
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
msgid "Action" msgid "Action"
msgstr "Aktionen" 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" msgid "Remove document"
msgstr "Dokument löschen" msgstr "Dokument löschen"
@ -383,19 +404,23 @@ msgstr "Hierfür müssen Sie einer anderen Nutzergruppe angehören!"
msgid "Not editable" msgid "Not editable"
msgstr "Nicht editierbar" 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" msgid "Confirm"
msgstr "Bestätige" msgstr "Bestätige"
#: konova/forms.py:84 #: konova/forms.py:84 konova/forms.py:156
msgid "Remove" msgid "Remove"
msgstr "Entferne" msgstr "Löschen"
#: konova/forms.py:86 #: konova/forms.py:86
msgid "You are about to remove {} {}" msgid "You are about to remove {} {}"
msgstr "Sie sind dabei {} {} zu löschen" 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?" msgid "This will remove '{}'. Are you sure?"
msgstr "Hiermit wird '{}' gelöscht. Sind Sie sicher?" msgstr "Hiermit wird '{}' gelöscht. Sind Sie sicher?"
@ -483,7 +508,7 @@ msgstr ""
msgid "Contact" msgid "Contact"
msgstr "Kontakt" msgstr "Kontakt"
#: templates/modal/modal_form.html:33 #: templates/modal/modal_form.html:24
msgid "Continue" msgid "Continue"
msgstr "Weiter" msgstr "Weiter"
@ -1923,9 +1948,6 @@ msgstr ""
#~ "Eingriffe müssen zu einem Vorgang gehören. Bitte geben SIe die fehlenden " #~ "Eingriffe müssen zu einem Vorgang gehören. Bitte geben SIe die fehlenden "
#~ "Daten für den Vorgang ein." #~ "Daten für den Vorgang ein."
#~ msgid "Intervention {} removed"
#~ msgstr "Eingriff {} gelöscht"
#~ msgid "You are working as" #~ msgid "You are working as"
#~ msgstr "Sie arbeiten gerade als " #~ msgstr "Sie arbeiten gerade als "

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
{% load static i18n fontawesome_5 bootstrap4 %} {% load static i18n l10n fontawesome_5 bootstrap4 %}
<html lang="{{ language }}"> <html lang="{{ language }}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">

View File

@ -1,4 +1,4 @@
{% load i18n %} {% load i18n l10n %}
{% comment %} {% comment %}
A generic modal form template which is based on django-bootstrap-modal-forms package A generic modal form template which is based on django-bootstrap-modal-forms package
https://pypi.org/project/django-bootstrap-modal-forms/ https://pypi.org/project/django-bootstrap-modal-forms/
@ -15,18 +15,9 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<!-- <article>
<article class="mb-5">{{form.form_caption}}</article> {{ form.form_caption }}
{% for field in form %} </article>
<div class="form-group{% if field.errors %} invalid{% endif %}">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
{% for error in field.errors %}
<p class="help-block">{{ error }}</p>
{% endfor %}
</div>
{% endfor %}
-->
{% include 'table/generic_table_form_body.html' %} {% include 'table/generic_table_form_body.html' %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">