Verified Commit de656358 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Re-use bank account signature in backend

parent 4f4d5677
# Generated by Django 2.2 on 2019-04-28 10:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0032_remove_profile_bank_account'),
]
operations = [
migrations.RemoveField(
model_name='profile',
name='bank_account',
),
migrations.RenameField(
model_name='profile',
old_name='direct_debit_authorized',
new_name='auto_renew',
),
migrations.AlterField(
model_name='profile',
name='auto_renew',
field=models.BooleanField(choices=[(True, 'Yes, enable auto renewal.'), (False, 'No, manual renewal required.')], default=False, verbose_name='Automatically renew membership'),
),
]
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% block title %}{% trans "members"|capfirst %} — {{ block.super }}{% endblock %}
{% block opengraph_title %}{% trans "members"|capfirst %} — {{ block.super }}{% endblock %}
{% block body %}
<section class="page-section">
<div class="container">
<h1 class="text-center section-title">{% trans 'Account' %}</h1>
<div class="row">
<div class="col-lg-6 offset-lg-3">
<p class="text-center">
{% blocktrans trimmed with user=request.member.username %}
You’re currently logged in as <strong>{{ user }}</strong>
{% endblocktrans %}.
</p>
<hr>
<div>
<a href="{% url 'members:profile' request.member.pk %}">{% trans "show public profile"|capfirst %}</a>
<p>{% blocktrans %}Take a look at your own profile.{% endblocktrans %}</p>
</div>
<hr>
<div>
<a href="{% url 'registrations:renew' %}">{% trans "manage membership"|capfirst %}</a>
<p>{% blocktrans %}Get information about your membership or renew it.{% endblocktrans %}</p>
</div>
<hr>
<div>
<a href="{% url 'members:edit-profile' %}">{% trans "edit profile"|capfirst %}</a>
<p>{% blocktrans %}Edit your profile and avatar.{% endblocktrans %}</p>
</div>
<hr>
<div>
<a href="{% url 'payments:bankaccount-list' %}">{% trans "manage bank account(s)"|capfirst %}</a>
<p>{% blocktrans %}Change the financial information known to Thalia.{% endblocktrans %}</p>
</div>
<hr>
<div>
<a href="{% url 'password_change' %}">{% trans "change password"|capfirst %}</a>
<p>{% blocktrans %}Change your accounts' password.{% endblocktrans %}</p>
</div>
<hr>
<div>
<a href="{% url 'logout' %}">{% trans "logout"|capfirst %}</a>
<p>{% blocktrans %}Leave the restricted area of the website.{% endblocktrans %}</p>
</div>
</div>
</div>
</div>
</section>
{% endblock %}}
......@@ -203,7 +203,7 @@ class ValidAccountFilter(admin.SimpleListFilter):
@admin.register(BankAccount)
class BankAccountAdmin(admin.ModelAdmin):
""" Manage bank accounts """
"""Manage bank accounts"""
list_display = ('iban', 'initials', 'last_name',
'owner_link', 'valid_from', 'valid_until')
......@@ -224,3 +224,25 @@ class BankAccountAdmin(admin.ModelAdmin):
return ''
owner_link.admin_order_field = 'owner'
owner_link.short_description = _('owner')
def export_csv(self, request, queryset):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment;\
filename="accounts.csv"'
writer = csv.writer(response)
headers = [_('created'), _('name'), _('reference'), _('iban'),
_('bic'), _('valid from'), _('valid until'), _('signature')]
writer.writerow([capfirst(x) for x in headers])
for account in queryset:
writer.writerow([
account.created_at,
account.name,
account.mandate_no,
account.iban,
account.bic or '',
account.valid_from or '',
account.valid_until or '',
account.signature or ''
])
return response
export_csv.short_description = _('Export')
......@@ -20,9 +20,6 @@ class BankAccountForm(forms.ModelForm):
fields = ('initials', 'last_name', 'iban', 'bic',
'signature', 'valid_from', 'mandate_no', 'owner')
model = BankAccount
widgets = {
'signature': SignatureWidget(),
}
class BankAccountAdminForm(forms.ModelForm):
......
......@@ -218,7 +218,7 @@ class BankAccount(models.Model):
return self.valid_from and self.valid_from <= timezone.now().date()
def __str__(self):
return f'{self.iban} - {self.owner.get_full_name()}'
return f'{self.iban} - {self.name}'
class Meta:
ordering = ('created_at',)
.signature-row .signature-container {
float: left;
border-radius: 4px;
border: 1px solid #ccc;
.signature-row #canvas-container {
position: relative;
float: left;
border-radius: 4px;
border: 1px solid #ccc;
height: 9rem;
width: 26rem;
}
.signature-row:after {
content: "";
display: table;
clear: both;
.signature-row #canvas-container canvas {
width: 100%;
height: 100%;
}
.signature-row #canvas-container .canvas-btn {
position: absolute;
height: 2rem;
width: 2rem;
top: 0.25rem;
color: #000000;
display: inline-block;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
font-size: 14px;
line-height: 2.5;
}
.signature-row #canvas-container .canvas-btn#canvas-undo-btn {
display: none;
}
.signature-row #canvas-container .canvas-btn#canvas-clear-btn {
right: 0.5rem;
background-image: url('/static/admin/img/icon-deletelink.svg');
background-repeat: no-repeat;
background-position: center center;
}
.signature-row #canvas-container:after {
content: "";
display: table;
clear: both;
}
$(function () {
(django.jQuery || jQuery)(function () {
$ = django.jQuery || jQuery;
var signatureField = $('#id_signature');
var signatureCanvas = document.getElementById("id_signature_canvas");
var signaturePad = new SignaturePad(signatureCanvas);
......@@ -12,7 +13,6 @@ $(function () {
signaturePad.fromDataURL(signatureField.val());
signaturePad.onEnd = canvasToField;
function resizeCanvas() {
var ratio = Math.max(window.devicePixelRatio || 1, 1);
signatureCanvas.width = signatureCanvas.offsetWidth * ratio;
......@@ -59,33 +59,6 @@ $(function () {
showDirectDebitFields();
ddCheckbox.change(showDirectDebitFields);
// // Returns signature image as data URL (see https://mdn.io/todataurl for the list of possible parameters)
// signaturePad.toDataURL(); // save image as PNG
// signaturePad.toDataURL("image/jpeg"); // save image as JPEG
// signaturePad.toDataURL("image/svg+xml"); // save image as SVG
//
// // Draws signature image from data URL.
// // NOTE: This method does not populate internal data structure that represents drawn signature. Thus, after using #fromDataURL, #toData won't work properly.
// signaturePad.fromDataURL("data:image/png;base64,iVBORw0K...");
//
// // Returns signature image as an array of point groups
// const data = signaturePad.toData();
//
// // Draws signature image from an array of point groups
// signaturePad.fromData(data);
//
// // Clears the canvas
// signaturePad.clear();
//
// // Returns true if canvas is empty, otherwise returns false
// signaturePad.isEmpty();
//
// // Unbinds all event handlers
// signaturePad.off();
//
// // Rebinds all event handlers
// signaturePad.on();
});
......
......@@ -168,31 +168,7 @@
id="id_signature"
>
<div id="canvas-container">
<a href="#undo"
id="canvas-undo-btn"
class="canvas-btn"
data-toggle="tooltip"
data-placement="top"
title="{% trans 'Undo' %}"
>
<i class="fas fa-undo"></i>
</a>
<a href="#clear"
id="canvas-clear-btn"
class="canvas-btn"
data-toggle="tooltip"
data-placement="top"
title="{% trans 'Clear' %}"
>
<i class="fas fa-eraser"></i>
</a>
<canvas
id="id_signature_canvas"
class="form-control {% if form.errors.signature %}is-invalid{% endif %}"
>
</canvas>
</div>
{% include 'payments/includes/signature_canvas.html' %}
</div>
</fieldset>
</div>
......
......@@ -9,7 +9,7 @@
{% block body %}
<section class="page-section" id="payments-account-overview">
<div class="container">
<h1 class="text-center section-title">{% trans "bank accounts" %}</h1>
<h1 class="text-center section-title">{% trans "bank accounts"|capfirst %}</h1>
{% if messages %}
{% for message in messages %}
......@@ -59,22 +59,22 @@
{% trans "IBAN" %}
</th>
<th scope="col">
{% trans "Name" %}
{% trans "name"|capfirst %}
</th>
<th scope="col">
{% trans "Created" %}
{% trans "created"|capfirst %}
</th>
<th scope="col">
{% trans "Direct Debit" %}
{% trans "direct debit"|capfirst %}
</th>
<th scope="col">
{% trans "Reference" %}
{% trans "reference"|capfirst %}
</th>
<th scope="col">
{% trans "Valid from" %}
{% trans "valid from"|capfirst %}
</th>
<th scope="col">
{% trans "Valid until" %}
{% trans "valid until"|capfirst %}
</th>
<th scope="col">
</th>
......
{% load i18n %}
<div id="canvas-container">
<a href="#undo"
id="canvas-undo-btn"
class="canvas-btn"
data-toggle="tooltip"
data-placement="top"
title="{% trans 'Undo' %}"
>
<i class="fas fa-undo"></i>
</a>
<a href="#clear"
id="canvas-clear-btn"
class="canvas-btn"
data-toggle="tooltip"
data-placement="top"
title="{% trans 'Clear' %}"
>
<i class="fas fa-eraser"></i>
</a>
<canvas
id="id_signature_canvas"
class="form-control {% if form.errors.signature %}is-invalid{% endif %}"
>
</canvas>
</div>
{% load i18n %}
<div class="readonly signature-row">
{% if widget.value %}
<div class="signature-container">
<img src="{{ widget.value }}" alt="Signature" />
</div>
{% else %}
No signature found.
{% endif %}
<input type="hidden" value="{% if widget.value %}{{ widget.value }}{% endif %}" name="{{ widget.name }}" />
{% include 'payments/includes/signature_canvas.html' %}
<input type="hidden" id="id_signature" value="{% if widget.value %}{{ widget.value }}{% endif %}" name="{{ widget.name }}" />
</div>
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.messages.views import SuccessMessageMixin
from django.http import Http404
from django.db.models import QuerySet
from django.http import Http404, HttpResponse
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
......@@ -22,19 +23,19 @@ class BankAccountCreateView(SuccessMessageMixin, CreateView):
success_url = reverse_lazy('payments:bankaccount-list')
success_message = _('Bank account saved successfully.')
def _derive_mandate_no(self):
def _derive_mandate_no(self) -> str:
count = BankAccount.objects.filter(
owner=self.request.member
).exclude(mandate_no=None).count() + 1
return f'{self.request.member.pk}-{count}'
def get_context_data(self, **kwargs):
def get_context_data(self, **kwargs) -> dict:
context = super().get_context_data(**kwargs)
context['mandate_no'] = self._derive_mandate_no()
context['creditor_id'] = settings.SEPA_CREDITOR_ID
return context
def post(self, request, *args, **kwargs):
def post(self, request, *args, **kwargs) -> HttpResponse:
request.POST = request.POST.dict()
request.POST['owner'] = self.request.member.pk
if 'direct_debit' in request.POST:
......@@ -46,7 +47,7 @@ class BankAccountCreateView(SuccessMessageMixin, CreateView):
request.POST['signature'] = None
return super().post(request, *args, **kwargs)
def form_valid(self, form):
def form_valid(self, form: BankAccountForm) -> HttpResponse:
BankAccount.objects.filter(
owner=self.request.member, mandate_no=None).delete()
BankAccount.objects.filter(
......@@ -62,13 +63,13 @@ class BankAccountRevokeView(SuccessMessageMixin, UpdateView):
success_url = reverse_lazy('payments:bankaccount-list')
success_message = _('Direct debit authorisation successfully revoked.')
def get_queryset(self):
def get_queryset(self) -> QuerySet:
return super().get_queryset().filter(owner=self.request.member)
def get(self, **kwargs):
def get(self, **kwargs) -> HttpResponse:
raise Http404
def post(self, request, *args, **kwargs):
def post(self, request, *args, **kwargs) -> HttpResponse:
request.POST = request.POST.dict()
request.POST['valid_until'] = timezone.now()
return super().post(request, *args, **kwargs)
......@@ -78,5 +79,5 @@ class BankAccountRevokeView(SuccessMessageMixin, UpdateView):
class BankAccountListView(ListView):
model = BankAccount
def get_queryset(self):
def get_queryset(self) -> QuerySet:
return super().get_queryset().filter(owner=self.request.member)
......@@ -11,7 +11,7 @@ class PaymentWidget(Widget):
"""
template_name = 'payments/widgets/payment.html'
def get_context(self, name, value, attrs):
def get_context(self, name, value, attrs) -> dict:
context = super().get_context(name, value, attrs)
if value:
payment = Payment.objects.get(pk=value)
......@@ -30,6 +30,7 @@ class SignatureWidget(Widget):
template_name = 'payments/widgets/signature.html'
class Media:
js = ('payments/js/signature_pad.min.js', 'payments/js/main.js',)
css = {
'all': ('admin/payments/css/signature.css',)
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment