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

Add ability for users to change their bank accounts `

parent ae180f9f
......@@ -33,6 +33,14 @@ payments.apps module
:undoc-members:
:show-inheritance:
payments.forms module
---------------------
.. automodule:: payments.forms
:members:
:undoc-members:
:show-inheritance:
payments.models module
----------------------
......@@ -49,6 +57,22 @@ payments.services module
:undoc-members:
:show-inheritance:
payments.urls module
--------------------
.. automodule:: payments.urls
:members:
:undoc-members:
:show-inheritance:
payments.views module
---------------------
.. automodule:: payments.views
:members:
:undoc-members:
:show-inheritance:
payments.widgets module
-----------------------
......
from django.db import migrations
def forwards_func(apps, schema_editor):
Membership = apps.get_model('members', 'membership')
db_alias = schema_editor.connection.alias
Membership.objects.using(db_alias).filter(
type='supporter').update(type='benefactor')
def reverse_func(apps, schema_editor):
Membership = apps.get_model('members', 'membership')
db_alias = schema_editor.connection.alias
Membership.objects.using(db_alias).filter(
type='benefactor').update(type='supporter')
class Migration(migrations.Migration):
dependencies = [
('members', '0029_profile_address_country'),
]
operations = [
migrations.RunPython(forwards_func, reverse_func),
]
......@@ -3,14 +3,17 @@ import csv
from django.contrib import admin, messages
from django.contrib.admin.utils import model_ngettext
from django.db.models.query_utils import Q
from django.http import HttpResponse
from django.urls import path
from django.urls import path, reverse
from django.utils import timezone
from django.utils.html import format_html
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy as _
from payments import services, admin_views
from .models import Payment
from payments.forms import BankAccountAdminForm
from .models import Payment, BankAccount
def _show_message(admin, request, n, message, error):
......@@ -169,3 +172,55 @@ class PaymentAdmin(admin.ModelAdmin):
])
return response
export_csv.short_description = _('Export')
class ValidAccountFilter(admin.SimpleListFilter):
"""Filter the memberships by whether they are active or not"""
title = _('mandates')
parameter_name = 'active'
def lookups(self, request, model_name):
return (
('valid', _('Valid')),
('invalid', _('Invalid')),
('none', _('None')),
)
def queryset(self, request, queryset):
now = timezone.now()
if self.value() == 'valid':
return queryset.filter(Q(valid_from__gte=now) &
Q(valid_until=None) |
Q(valid_until__lt=now))
if self.value() == 'invalid':
return queryset.filter(valid_until__gte=now)
if self.value() == 'none':
return queryset.filter(valid_from=None)
@admin.register(BankAccount)
class BankAccountAdmin(admin.ModelAdmin):
""" Manage bank accounts """
list_display = ('iban', 'initials', 'last_name',
'owner_link', 'valid_from', 'valid_until')
list_filter = (ValidAccountFilter,)
fields = ('created_at', 'owner', 'iban', 'bic', 'initials', 'last_name',
'mandate_no', 'valid_from', 'valid_until', 'signature')
readonly_fields = ('created_at',)
search_fields = ('owner__username', 'owner__first_name',
'owner__last_name', 'iban')
form = BankAccountAdminForm
def owner_link(self, obj):
if obj.owner:
return format_html("<a href='{}'>{}</a>",
reverse('admin:auth_user_change',
args=[obj.owner.pk]),
obj.owner.get_full_name())
return ''
owner_link.admin_order_field = 'owner'
owner_link.short_description = _('owner')
from django import forms
from payments.models import BankAccount
from payments.widgets import SignatureWidget
from django.utils.translation import gettext as _
class BankAccountForm(forms.ModelForm):
"""
Custom admin form for BankAccount model
to add the widget for the signature
"""
direct_debit = forms.BooleanField(
required=False,
label=_('I want to use this account for direct debits')
)
class Meta:
fields = ('initials', 'last_name', 'iban', 'bic',
'signature', 'valid_from', 'mandate_no', 'owner')
model = BankAccount
widgets = {
'signature': SignatureWidget(),
}
class BankAccountAdminForm(forms.ModelForm):
"""
Custom admin form for BankAccount model
to add the widget for the signature
"""
class Meta:
fields = '__all__'
model = BankAccount
widgets = {
'signature': SignatureWidget(),
}
# Generated by Django 2.2 on 2019-04-27 19:41
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import localflavor.generic.models
import uuid
class Migration(migrations.Migration):
dependencies = [
('members', '0031_benefactor_model_value'),
('payments', '0003_auto_20190204_2111'),
]
operations = [
migrations.CreateModel(
name='BankAccount',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created at')),
('initials', models.CharField(max_length=20, verbose_name='initials')),
('last_name', models.CharField(max_length=255, verbose_name='last name')),
('iban', localflavor.generic.models.IBANField(include_countries=('AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HR', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'SM'), max_length=34, use_nordea_extensions=False, verbose_name='IBAN')),
('bic', localflavor.generic.models.BICField(blank=True, help_text='This field is optional for Dutch bank accounts.', max_length=11, null=True, verbose_name='BIC')),
('valid_from', models.DateField(blank=True, null=True, verbose_name='valid from')),
('valid_until', models.DateField(blank=True, null=True, verbose_name='valid until')),
('signature', models.TextField(blank=True, null=True, verbose_name='signature')),
('mandate_no', models.CharField(blank=True, max_length=255, null=True, unique=True, verbose_name='mandate number')),
('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bank_accounts', to='members.Member', verbose_name='owner')),
],
options={
'ordering': ('created_at',),
},
),
]
......@@ -2,10 +2,13 @@
import uuid
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from localflavor.generic.countries.sepa import IBAN_SEPA_COUNTRIES
from localflavor.generic.models import IBANField, BICField
class Payment(models.Model):
......@@ -93,3 +96,135 @@ class Payment(models.Model):
def __str__(self):
return _("Payment of {amount}").format(amount=self.amount)
class BankAccount(models.Model):
"""
Describes a bank account
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_at = models.DateTimeField(_('created at'), default=timezone.now)
owner = models.ForeignKey(
to='members.Member',
verbose_name=_('owner'),
related_name='bank_accounts',
on_delete=models.SET_NULL,
blank=False,
null=True,
)
initials = models.CharField(
verbose_name=_('initials'),
max_length=20,
)
last_name = models.CharField(
verbose_name=_('last name'),
max_length=255,
)
iban = IBANField(
verbose_name=_('IBAN'),
include_countries=IBAN_SEPA_COUNTRIES,
)
bic = BICField(
verbose_name=_('BIC'),
blank=True,
null=True,
help_text=_('This field is optional for Dutch bank accounts.')
)
valid_from = models.DateField(
verbose_name=_('valid from'),
blank=True,
null=True,
)
valid_until = models.DateField(
verbose_name=_('valid until'),
blank=True,
null=True,
)
signature = models.TextField(
verbose_name=_('signature'),
blank=True,
null=True,
)
mandate_no = models.CharField(
verbose_name=_('mandate number'),
max_length=255,
blank=True,
null=True,
unique=True
)
def clean(self):
super().clean()
errors = {}
if self.bic is None and self.iban[0:2] != 'NL':
errors.update({
'bic': _('This field is required for foreign bank accounts.')
})
if not self.owner:
errors.update({
'owner': _('This field is required.')
})
mandate_fields = [
('valid_from', self.valid_from),
('signature', self.signature),
('mandate_no', self.mandate_no)
]
if (any(not field[1] for field in mandate_fields) and any(
field[1] for field in mandate_fields)):
for field in mandate_fields:
if not field[1]:
errors.update({
field[0]: _('This field is required '
'to complete the mandate.')
})
if (self.valid_from and self.valid_until and
self.valid_from > self.valid_until):
errors.update({
'valid_until': _('This date cannot be before the from date.')
})
if self.valid_until and not self.valid_from:
errors.update({
'valid_until': _('This field cannot have a value.')
})
if errors:
raise ValidationError(errors)
@property
def name(self):
return f'{self.initials} {self.last_name}'
@property
def valid(self):
if self.valid_from and self.valid_until:
return self.valid_from < timezone.now().date() < self.valid_until
return self.valid_from and self.valid_from < timezone.now().date()
def __str__(self):
return f'{self.iban} - {self.owner.get_full_name()}'
class Meta:
ordering = ('created_at',)
.signature-row .signature-container {
float: left;
border-radius: 4px;
border: 1px solid #ccc;
}
.signature-row:after {
content: "";
display: table;
clear: both;
}
#payments-account-form {
#id_initials {
flex: .3 1 auto;
}
#id_address_postal_code {
flex: .2 1 auto;
}
#id_signature_canvas {
height: 10rem;
}
#canvas-container {
position: relative;
.canvas-btn {
position: absolute;
height: 2rem;
width: 2rem;
top: 0.25rem;
color: $black;
display: inline-block;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
font-size: 14px;
line-height: 2.5;
&#canvas-undo-btn {
right: 2.75rem;
}
&#canvas-clear-btn {
right: 0.5rem;
}
}
}
}
$(function () {
var signatureField = $('#id_signature');
var signatureCanvas = document.getElementById("id_signature_canvas");
var signaturePad = new SignaturePad(signatureCanvas);
function canvasToField() {
if (signaturePad.toData().length) {
signatureField.val(signaturePad.toDataURL());
}
}
signaturePad.fromDataURL(signatureField.val());
signaturePad.onEnd = canvasToField;
function resizeCanvas() {
var ratio = Math.max(window.devicePixelRatio || 1, 1);
signatureCanvas.width = signatureCanvas.offsetWidth * ratio;
signatureCanvas.height = signatureCanvas.offsetHeight * ratio;
signatureCanvas.getContext("2d").scale(ratio, ratio);
signaturePad.clear(); // otherwise isEmpty() might return incorrect value
}
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
$('#canvas-undo-btn').click(function (e) {
e.preventDefault();
var data = signaturePad.toData();
if (data) {
data.pop();
signaturePad.fromData(data);
if (data.length) {
canvasToField();
} else {
signatureField.val('');
}
}
});
$('#canvas-clear-btn').click(function (e) {
e.preventDefault();
signaturePad.clear();
signatureField.val('');
});
var ddCheckbox = $('#id_direct_debit');
function showDirectDebitFields() {
if (ddCheckbox.is(':checked')) {
$('.direct-debit-fields').removeClass('d-none');
$('.normal-fields').addClass('col-lg-6');
} else {
$('.direct-debit-fields').addClass('d-none');
$('.normal-fields').removeClass('col-lg-6');
}
}
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();
});
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.SignaturePad=e()}(this,function(){"use strict";function t(t,e,i){this.x=t,this.y=e,this.time=i||(new Date).getTime()}function e(t,e,i,o){this.startPoint=t,this.control1=e,this.control2=i,this.endPoint=o}function i(t,e,i){var o,n,s,r=null,h=0;i||(i={});var a=function(){h=!1===i.leading?0:Date.now(),r=null,s=t.apply(o,n),r||(o=n=null)};return function(){var c=Date.now();h||!1!==i.leading||(h=c);var d=e-(c-h);return o=this,n=arguments,d<=0||d>e?(r&&(clearTimeout(r),r=null),h=c,s=t.apply(o,n),r||(o=n=null)):r||!1===i.trailing||(r=setTimeout(a,d)),s}}function o(t,e){var n=this,s=e||{};this.velocityFilterWeight=s.velocityFilterWeight||.7,this.minWidth=s.minWidth||.5,this.maxWidth=s.maxWidth||2.5,this.throttle="throttle"in s?s.throttle:16,this.minDistance="minDistance"in s?s.minDistance:5,this.throttle?this._strokeMoveUpdate=i(o.prototype._strokeUpdate,this.throttle):this._strokeMoveUpdate=o.prototype._strokeUpdate,this.dotSize=s.dotSize||function(){return(this.minWidth+this.maxWidth)/2},this.penColor=s.penColor||"black",this.backgroundColor=s.backgroundColor||"rgba(0,0,0,0)",this.onBegin=s.onBegin,this.onEnd=s.onEnd,this._canvas=t,this._ctx=t.getContext("2d"),this.clear(),this._handleMouseDown=function(t){1===t.which&&(n._mouseButtonDown=!0,n._strokeBegin(t))},this._handleMouseMove=function(t){n._mouseButtonDown&&n._strokeMoveUpdate(t)},this._handleMouseUp=function(t){1===t.which&&n._mouseButtonDown&&(n._mouseButtonDown=!1,n._strokeEnd(t))},this._handleTouchStart=function(t){if(1===t.targetTouches.length){var e=t.changedTouches[0];n._strokeBegin(e)}},this._handleTouchMove=function(t){t.preventDefault();var e=t.targetTouches[0];n._strokeMoveUpdate(e)},this._handleTouchEnd=function(t){t.target===n._canvas&&(t.preventDefault(),n._strokeEnd(t))},this.on()}return t.prototype.velocityFrom=function(t){return this.time!==t.time?this.distanceTo(t)/(this.time-t.time):1},t.prototype.distanceTo=function(t){return Math.sqrt(Math.pow(this.x-t.x,2)+Math.pow(this.y-t.y,2))},t.prototype.equals=function(t){return this.x===t.x&&this.y===t.y&&this.time===t.time},e.prototype.length=function(){for(var t=0,e=void 0,i=void 0,o=0;o<=10;o+=1){var n=o/10,s=this._point(n,this.startPoint.x,this.control1.x,this.control2.x,this.endPoint.x),r=this._point(n,this.startPoint.y,this.control1.y,this.control2.y,this.endPoint.y);if(o>0){var h=s-e,a=r-i;t+=Math.sqrt(h*h+a*a)}e=s,i=r}return t},e.prototype._point=function(t,e,i,o,n){return e*(1-t)*(1-t)*(1-t)+3*i*(1-t)*(1-t)*t+3*o*(1-t)*t*t+n*t*t*t},o.prototype.clear=function(){var t=this._ctx,e=this._canvas;t.fillStyle=this.backgroundColor,t.clearRect(0,0,e.width,e.height),t.fillRect(0,0,e.width,e.height),this._data=[],this._reset(),this._isEmpty=!0},o.prototype.fromDataURL=function(t){var e=this,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=new Image,n=i.ratio||window.devicePixelRatio||1,s=i.width||this._canvas.width/n,r=i.height||this._canvas.height/n;this._reset(),o.src=t,o.onload=function(){e._ctx.drawImage(o,0,0,s,r)},this._isEmpty=!1},o.prototype.toDataURL=function(t){var e;switch(t){case"image/svg+xml":return this._toSVG();default:for(var i=arguments.length,o=Array(i>1?i-1:0),n=1;n<i;n++)o[n-1]=arguments[n];return(e=this._canvas).toDataURL.apply(e,[t].concat(o))}},o.prototype.on=function(){this._handleMouseEvents(),this._handleTouchEvents()},o.prototype.off=function(){this._canvas.removeEventListener("mousedown",this._handleMouseDown),this._canvas.removeEventListener("mousemove",this._handleMouseMove),document.removeEventListener("mouseup",this._handleMouseUp),this._canvas.removeEventListener("touchstart",this._handleTouchStart),this._canvas.removeEventListener("touchmove",this._handleTouchMove),this._canvas.removeEventListener("touchend",this._handleTouchEnd)},o.prototype.isEmpty=function(){return this._isEmpty},o.prototype._strokeBegin=function(t){this._data.push([]),this._reset(),this._strokeUpdate(t),"function"==typeof this.onBegin&&this.onBegin(t)},o.prototype._strokeUpdate=function(t){var e=t.clientX,i=t.clientY,o=this._createPoint(e,i),n=this._data[this._data.length-1],s=n&&n[n.length-1],r=s&&o.distanceTo(s)<this.minDistance;if(!s||!r){var h=this._addPoint(o),a=h.curve,c=h.widths;a&&c&&this._drawCurve(a,c.start,c.end),this._data[this._data.length-1].push({x:o.x,y:o.y,time:o.time,color:this.penColor})}},o.prototype._strokeEnd=function(t){var e=this.points.length>2,i=this.points[0];if(!e&&i&&this._drawDot(i),i){var o=this._data[this._data.length-1],n=o[o.length-1];i.equals(n)||o.push({x:i.x,y:i.y,time:i.time,color:this.penColor})}"function"==typeof this.onEnd&&this.onEnd(t)},o.prototype._handleMouseEvents=function(){this._mouseButtonDown=!1,this._canvas.addEventListener("mousedown",this._handleMouseDown),this._canvas.addEventListener("mousemove",this._handleMouseMove),document.addEventListener("mouseup",this._handleMouseUp)},o.prototype._handleTouchEvents=function(){this._canvas.style.msTouchAction="none",this._canvas.style.touchAction="none",this._canvas.addEventListener("touchstart",this._handleTouchStart),this._canvas.addEventListener("touchmove",this._handleTouchMove),this._canvas.addEventListener("touchend",this._handleTouchEnd)},o.prototype._reset=function(){this.points=[],this._lastVelocity=0,this._lastWidth=(this.minWidth+this.maxWidth)/2,this._ctx.fillStyle=this.penColor},o.prototype._createPoint=function(e,i,o){var n=this._canvas.getBoundingClientRect();return new t(e-n.left,i-n.top,o||(new Date).getTime())},o.prototype._addPoint=function(t){var i=this.points,o=void 0;if(i.push(t),i.length>2){3===i.length&&i.unshift(i[0]),o=this._calculateCurveControlPoints(i[0],i[1],i[2]);var n=o.c2;o=this._calculateCurveControlPoints(i[1],i[2],i[3]);var s=o.c1,r=new e(i[1],n,s,i[2]),h=this._calculateCurveWidths(r);return i.shift(),{curve:r,widths:h}}return{}},o.prototype._calculateCurveControlPoints=function(e,i,o){var n=e.x-i.x,s=e.y-i.y,r=i.x-o.x,h=i.y-o.y,a={x:(e.x+i.x)/2,y:(e.y+i.y)/2},c={x:(i.x+o.x)/2,y:(i.y+o.y)/2},d=Math.sqrt(n*n+s*s),l=Math.sqrt(r*r+h*h),u=a.x-c.x,v=a.y-c.y,p=l/(d+l),_={x:c.x+u*p,y:c.y+v*p},y=i.x-_.x,f=i.y-_.y;return{c1:new t(a.x+y,a.y+f),c2:new t(c.x+y,c.y+f)}},o.prototype._calculateCurveWidths=function(t){var e=t.startPoint,i=t.endPoint,o={start:null,end:null},n=this.velocityFilterWeight*i.velocityFrom(e)+(1-this.velocityFilterWeight)*this._lastVelocity,s=this._strokeWidth(n);return o.start=this._lastWidth,o.end=s,this._lastVelocity=n,this._lastWidth=s,o},o.prototype._strokeWidth=function(t){return Math.max(this.maxWidth/(t+1),this.minWidth)},o.prototype._drawPoint=function(t,e,i){var o=this._ctx;o.moveTo(t,e),o.arc(t,e,i,0,2*Math.PI,!1),this._isEmpty=!1},o.prototype._drawCurve=function(t,e,i){var o=this._ctx,n=i-e,s=Math.floor(t.length());o.beginPath();for(var r=0;r<s;r+=1){var h=r/s,a=h*h,c=a*h,d=1-h,l=d*d,u=l*d,v=u*t.startPoint.x;v+=3*l*h*t.control1.x,v+=3*d*a*t.control2.x,v+=c*t.endPoint.x;var p=u*t.startPoint.y;p+=3*l*h*t.control1.y,p+=3*d*a*t.control2.y,p+=c*t.endPoint.y;var _=e+c*n;this._drawPoint(v,p,_)}o.closePath(),o.fill()},o.prototype._drawDot=function(t){var e=this._ctx,i="function"==typeof this.dotSize?this.dotSize():this.dotSize;e.beginPath(),this._drawPoint(t.x,t.y,i),e.closePath(),e.fill()},o.prototype._fromData=function(e,i,o){for(var n=0;n<e.length;n+=1){var s=e[n];if(s.length>1)for(var r=0;r<s.length;r+=1){var h=s[r],a=new t(h.x,h.y,h.time),c=h.color;if(0===r)this.penColor=c,this._reset(),this._addPoint(a);else if(r!==s.length-1){var d=this._addPoint(a),l=d.curve,u=d.widths;l&&u&&i(l,u,c)}}else{this._reset();o(s[0])}}},o.prototype._toSVG=function(){var t=this,e=this._data,i=this._canvas,o=Math.max(window.devicePixelRatio||1,1),n=i.width/o,s=i.height/o,r=document.createElementNS("http://www.w3.org/2000/svg","svg");r.setAttributeNS(null,"width",i.width),r.setAttributeNS(null,"height",i.height),this._fromData(e,function(t,e,i){var o=document.createElement("path");if(!(isNaN(t.control1.x)||isNaN(t.control1.y)||isNaN(t.control2.x)||isNaN(t.control2.y))){var n="M "+t.startPoint.x.toFixed(3)+","+t.startPoint.y.toFixed(3)+" C "+t.control1.x.toFixed(3)+","+t.control1.y.toFixed(3)+" "+t.control2.x.toFixed(3)+","+t.control2.y.toFixed(3)+" "+t.endPoint.x.toFixed(3)+","+t.endPoint.y.toFixed(3);o.setAttribute("d",n),o.setAttribute("stroke-width",(2.25*e.end).toFixed(3)),o.setAttribute("stroke",i),o.setAttribute("fill","none"),o.setAttribute("stroke-linecap","round"),r.appendChild(o)}},function(e){var i=document.createElement("circle"),o="function"==typeof t.dotSize?t.dotSize():t.dotSize;i.setAttribute("r",o),i.setAttribute("cx",e.x),i.setAttribute("cy",e.y),i.setAttribute("fill",e.color),r.appendChild(i)});var h='<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 '+n+" "+s+'" width="'+n+'" height="'+s+'">',a=r.innerHTML;if(void 0===a){var c=document.createElement("dummy"),d=r.childNodes;c.innerHTML="";for(var l=0;l<d.length;l+=1)c.appendChild(d[l].cloneNode(!0));a=c.innerHTML}var u=h+a+"</svg>";return"data:image/svg+xml;base64,"+btoa(u)},o.prototype.fromData=function(t){var e=this;this.clear(),this._fromData(t,function(t,i){return e._drawCurve(t,i.start,i.end)},function(t){return e._drawDot(t)}),this._data=t},o.prototype.toData=function(){return this._data},o});
{% extends "base.html" %}
{% load i18n static bootstrap4 compress alert %}
{% block title %}{% trans "add bank account"|capfirst %} —
{% trans "finances"|capfirst %} — {{ block.super }}{% endblock %}
{% block opengraph_title %}{% trans "add bank account"|capfirst %} —
{% trans "finances"|capfirst %} — {{ block.super }}{% endblock %}
{% block body %}
<section class="page-section" id="payments-account-form">
<div class="container">
<h1 class="text-center section-title">{% trans "add bank account" %}</h1>
<form class="form-horizontal row" method="post" class="row">
{% csrf_token %}
<fieldset class="col-12 col-lg-6 normal-fields">
<div class="form-group">
<label for="id_initials">{% trans "Initials" %}</label>
&
<label
for="id_last_name">{% trans "last name"|capfirst %}</label>
<div class="input-group">
<input type="text"
name="initials"
maxlength="20"