From cc4815c82c1365f4a2f11413956740c2c20d87ee Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Mon, 2 Jul 2018 21:13:13 +0200 Subject: [PATCH] =?UTF-8?q?Permet=20la=20cr=C3=A9ation=20et=20l'=C3=A9diti?= =?UTF-8?q?on=20de=20moyen=20de=20paiement=20personnalis=C3=A9s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cotisations/forms.py | 7 +-- .../0032_chequepayment_comnpaypayment.py | 15 +++-- .../migrations/0033_auto_20180628_2157.py | 20 ------- cotisations/payment_methods/__init__.py | 16 +++++ .../payment_methods/cheque/__init__.py | 2 +- cotisations/payment_methods/cheque/forms.py | 10 ++-- cotisations/payment_methods/cheque/models.py | 15 +++-- .../payment_methods/comnpay/__init__.py | 2 +- cotisations/payment_methods/comnpay/models.py | 15 +++-- cotisations/payment_methods/forms.py | 59 +++++++++++++++++++ cotisations/payment_methods/mixins.py | 21 +++++++ .../templates/cotisations/facture.html | 37 +++++++++++- cotisations/views.py | 41 +++++++++---- 13 files changed, 198 insertions(+), 62 deletions(-) delete mode 100644 cotisations/migrations/0033_auto_20180628_2157.py create mode 100644 cotisations/payment_methods/forms.py create mode 100644 cotisations/payment_methods/mixins.py diff --git a/cotisations/forms.py b/cotisations/forms.py index a5db7b92..0ee4c143 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -242,17 +242,12 @@ class PaiementForm(FormRevMixin, ModelForm): class Meta: model = Paiement # TODO : change moyen to method and type_paiement to payment_type - fields = ['moyen', 'type_paiement', 'allow_self_subscription'] + fields = ['moyen', 'allow_self_subscription'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(PaiementForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['moyen'].label = _("Payment method name") - self.fields['type_paiement'].label = _("Payment type") - self.fields['type_paiement'].help_text = \ - _("The payement type is used for specific behaviour.\ - The \"cheque\" type means a cheque number and a bank name\ - may be added when using this payment method.") # TODO : change paiement to payment diff --git a/cotisations/migrations/0032_chequepayment_comnpaypayment.py b/cotisations/migrations/0032_chequepayment_comnpaypayment.py index 76fc4ea0..d2c67308 100644 --- a/cotisations/migrations/0032_chequepayment_comnpaypayment.py +++ b/cotisations/migrations/0032_chequepayment_comnpaypayment.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-28 17:28 +# Generated by Django 1.10.7 on 2018-07-02 18:56 from __future__ import unicode_literals import cotisations.payment_methods.comnpay.aes_field +import cotisations.payment_methods.models from django.db import migrations, models import django.db.models.deletion @@ -29,6 +30,8 @@ def add_comnpay(apps, schema_editor): comnpay.payment_pass = options.payment_pass comnpay.payment = payment comnpay.save() + payment.moyen = "ComnPay" + payment.save() class Migration(migrations.Migration): @@ -42,18 +45,20 @@ class Migration(migrations.Migration): name='ChequePayment', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='payment_method', to='cotisations.Paiement')), + ('payment', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='payment_method', to='cotisations.Paiement')), ], + bases=(cotisations.payment_methods.models.PaymentMethodMixin, models.Model), ), migrations.CreateModel( name='ComnpayPayment', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('payment_user', models.CharField(blank=True, default='', max_length=255)), + ('payment_credential', models.CharField(blank=True, default='', max_length=255)), ('payment_pass', cotisations.payment_methods.comnpay.aes_field.AESEncryptedField(blank=True, max_length=255, null=True)), - ('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='payment_method', to='cotisations.Paiement')), + ('payment', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='payment_method', to='cotisations.Paiement')), ], + bases=(cotisations.payment_methods.models.PaymentMethodMixin, models.Model), ), - migrations.RunPython(add_cheque), migrations.RunPython(add_comnpay), + migrations.RunPython(add_cheque), ] diff --git a/cotisations/migrations/0033_auto_20180628_2157.py b/cotisations/migrations/0033_auto_20180628_2157.py deleted file mode 100644 index 51509105..00000000 --- a/cotisations/migrations/0033_auto_20180628_2157.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-28 19:57 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('cotisations', '0032_chequepayment_comnpaypayment'), - ] - - operations = [ - migrations.AlterField( - model_name='comnpaypayment', - name='payment_id', - field=models.CharField(blank=True, default='', max_length=255), - ), - ] diff --git a/cotisations/payment_methods/__init__.py b/cotisations/payment_methods/__init__.py index cc21f42b..36904411 100644 --- a/cotisations/payment_methods/__init__.py +++ b/cotisations/payment_methods/__init__.py @@ -1,4 +1,5 @@ from django.conf.urls import include, url +from django.utils.translation import ugettext_lazy as _l from . import comnpay, cheque @@ -7,3 +8,18 @@ urlpatterns = [ url(r'^comnpay/', include(comnpay.urls, namespace='comnpay')), url(r'^cheque/', include(cheque.urls, namespace='cheque')), ] + +PAYMENT_METHODS = [ + comnpay, + cheque, +] + + +def find_payment_method(payment): + for method in PAYMENT_METHODS: + try: + o = method.PaymentMethod.objects.get(payment=payment) + return o + except method.PaymentMethod.DoesNotExist: + pass + return None diff --git a/cotisations/payment_methods/cheque/__init__.py b/cotisations/payment_methods/cheque/__init__.py index 6610cdd8..a6fef640 100644 --- a/cotisations/payment_methods/cheque/__init__.py +++ b/cotisations/payment_methods/cheque/__init__.py @@ -4,4 +4,4 @@ This module contains a method to pay online using cheque. from . import models, urls, views NAME = "CHEQUE" -Payment = models.ChequePayment +PaymentMethod = models.ChequePayment diff --git a/cotisations/payment_methods/cheque/forms.py b/cotisations/payment_methods/cheque/forms.py index 892ba190..ae816a4b 100644 --- a/cotisations/payment_methods/cheque/forms.py +++ b/cotisations/payment_methods/cheque/forms.py @@ -1,10 +1,12 @@ from django import forms from django.utils.translation import ugettext_lazy as _l -from cotisations.models import Banque as Bank +from re2o.mixins import FormRevMixin +from cotisations.models import Facture as Invoice -class ChequeForm(forms.Form): +class InvoiceForm(FormRevMixin, forms.ModelForm): """A simple form to get the bank a the cheque number.""" - bank = forms.ModelChoiceField(Bank.objects.all(), label=_l("Bank")) - number = forms.CharField(label=_l("Cheque number")) + class Meta: + model = Invoice + fields = ['banque', 'cheque'] diff --git a/cotisations/payment_methods/cheque/models.py b/cotisations/payment_methods/cheque/models.py index 336f025e..29c86a41 100644 --- a/cotisations/payment_methods/cheque/models.py +++ b/cotisations/payment_methods/cheque/models.py @@ -2,16 +2,19 @@ from django.db import models from django.shortcuts import redirect from django.urls import reverse -from cotisations.models import Paiement as BasePayment +from cotisations.models import Paiement +from cotisations.payment_methods.mixins import PaymentMethodMixin -class ChequePayment(models.Model): +class ChequePayment(PaymentMethodMixin, models.Model): """ - The model allowing you to pay with a cheque. It redefines post_payment - method. See `cotisations.models.Paiement for further details. + The model allowing you to pay with a cheque. """ - payment = models.OneToOneField(BasePayment, related_name='payment_method') - + payment = models.OneToOneField( + Paiement, + related_name='payment_method', + editable=False + ) def end_payment(self, invoice, request): invoice.valid = False invoice.save() diff --git a/cotisations/payment_methods/comnpay/__init__.py b/cotisations/payment_methods/comnpay/__init__.py index d0289364..58433c80 100644 --- a/cotisations/payment_methods/comnpay/__init__.py +++ b/cotisations/payment_methods/comnpay/__init__.py @@ -3,4 +3,4 @@ This module contains a method to pay online using comnpay. """ from . import models, urls, views NAME = "COMNPAY" -Payment = models.ComnpayPayment +PaymentMethod = models.ComnpayPayment diff --git a/cotisations/payment_methods/comnpay/models.py b/cotisations/payment_methods/comnpay/models.py index 4fc509af..68f883d4 100644 --- a/cotisations/payment_methods/comnpay/models.py +++ b/cotisations/payment_methods/comnpay/models.py @@ -1,19 +1,22 @@ from django.db import models from django.shortcuts import render -from cotisations.models import Paiement as BasePayment +from cotisations.models import Paiement +from cotisations.payment_methods.mixins import PaymentMethodMixin from .aes_field import AESEncryptedField from .views import comnpay -class ComnpayPayment(models.Model): +class ComnpayPayment(PaymentMethodMixin, models.Model): """ - The model allowing you to pay with COMNPAY. It redefines post_payment - method. See `cotisations.models.Paiement for further details. + The model allowing you to pay with COMNPAY. """ - payment = models.OneToOneField(BasePayment, related_name='payment_method') - + payment = models.OneToOneField( + Paiement, + related_name='payment_method', + editable=False + ) payment_credential = models.CharField( max_length=255, default='', diff --git a/cotisations/payment_methods/forms.py b/cotisations/payment_methods/forms.py new file mode 100644 index 00000000..2acc80f0 --- /dev/null +++ b/cotisations/payment_methods/forms.py @@ -0,0 +1,59 @@ +from django import forms +from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _l + +from . import PAYMENT_METHODS, find_payment_method + + +def payment_method_factory(payment, *args, **kwargs): + payment_method = kwargs.pop('instance', find_payment_method(payment)) + if payment_method is not None: + return forms.modelform_factory(type(payment_method), fields='__all__')( + *args, + instance=payment_method, + **kwargs + ) + return PaymentMethodForm(payment_method, *args, **kwargs) + + +class PaymentMethodForm(forms.Form): + """A special form which allows you to add a payment method to a `Payment` + objects if it hasn't one yet, or to edit the existing payment method. + + To do so it replaces itself with a `modelform_factory`. + """ + + payment_method = forms.ChoiceField( + label=_l("Special payment method"), + required=False + ) + + def __init__(self, payment_method, *args, **kwargs): + super(PaymentMethodForm, self).__init__(*args, **kwargs) + if payment_method is None: + prefix = kwargs.get('prefix', None) + self.fields['payment_method'].choices = [(i,p.NAME) for (i,p) in enumerate(PAYMENT_METHODS)] + self.fields['payment_method'].choices.insert(0, ('', _l('no'))) + self.fields['payment_method'].widget.attrs = { + 'id': 'paymentMethodSelect' + } + self.templates = [ + forms.modelform_factory(p.PaymentMethod, fields='__all__')(prefix=prefix) + for p in PAYMENT_METHODS + ] + else: + self.fields = {} + + def save(self, *args, payment=None, **kwargs): + commit = kwargs.pop('commit', True) + choice = self.cleaned_data['payment_method'] + if choice=='': + return + choice = int(choice) + model = PAYMENT_METHODS[choice].PaymentMethod + form = forms.modelform_factory(model, fields='__all__')(self.data, prefix=self.prefix) + payment_method = form.save(commit=False) + payment_method.payment = payment + if commit: + payment_method.save() + return payment_method diff --git a/cotisations/payment_methods/mixins.py b/cotisations/payment_methods/mixins.py new file mode 100644 index 00000000..62ebbf32 --- /dev/null +++ b/cotisations/payment_methods/mixins.py @@ -0,0 +1,21 @@ +from django.db import models + +from cotisations.models import Paiement + + +class PaymentMethodMixin: + """The base class for payment models. They should inherit from this.""" + payment = models.OneToOneField( + Paiement, + related_name='payment_method', + editable=False + ) + + def end_payment(self, invoice, request): + """Redefine this method in order to get a different ending to the + payment session if you whish. + + Must return a HttpResponse-like object. + """ + return self.payment.end_payment( + invoice, request, use_payment_method=False) diff --git a/cotisations/templates/cotisations/facture.html b/cotisations/templates/cotisations/facture.html index dc8648ac..082759c4 100644 --- a/cotisations/templates/cotisations/facture.html +++ b/cotisations/templates/cotisations/facture.html @@ -57,16 +57,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,

{% endif %} {% bootstrap_form factureform %} + {% if payment_method %} + {% bootstrap_form payment_method %} +
+ {% endif %} {% bootstrap_button action_name button_type='submit' icon='star' %} -{% if articlesformset %} +{% if articlesformset or payment_method%} {% endif %} diff --git a/cotisations/views.py b/cotisations/views.py index ea137379..280a3aa9 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -74,6 +74,7 @@ from .forms import ( RechargeForm ) from .tex import render_invoice +from .payment_methods.forms import payment_method_factory @login_required @@ -473,9 +474,15 @@ def add_paiement(request): """ View used to add a payment method. """ - payment = PaiementForm(request.POST or None) - if payment.is_valid(): - payment.save() + payment = PaiementForm(request.POST or None, prefix='payment') + payment_method = payment_method_factory( + payment.instance, + request.POST or None, + prefix='payment_method' + ) + if payment.is_valid() and payment_method.is_valid(): + payment = payment.save() + payment_method.save(payment=payment) messages.success( request, _("The payment method has been successfully created.") @@ -483,6 +490,7 @@ def add_paiement(request): return redirect(reverse('cotisations:index-paiement')) return form({ 'factureform': payment, + 'payment_method': payment_method, 'action_name': _("Add") }, 'cotisations/facture.html', request) @@ -494,17 +502,28 @@ def edit_paiement(request, paiement_instance, **_kwargs): """ View used to edit a payment method. """ - payment = PaiementForm(request.POST or None, instance=paiement_instance) - if payment.is_valid(): - if payment.changed_data: - payment.save() - messages.success( - request, - _("The payement method has been successfully edited.") - ) + payment = PaiementForm( + request.POST or None, + instance=paiement_instance, + prefix="payment" + ) + payment_method = payment_method_factory( + paiement_instance, + request.POST or None, + prefix='payment_method' + ) + + if payment.is_valid() and payment_method.is_valid(): + payment.save() + payment_method.save() + messages.success( + request, + _("The payement method has been successfully edited.") + ) return redirect(reverse('cotisations:index-paiement')) return form({ 'factureform': payment, + 'payment_method': payment_method, 'action_name': _("Edit") }, 'cotisations/facture.html', request)