diff --git a/CHANGELOG.md b/CHANGELOG.md index 01b9b8ca..d846ad09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,3 +94,14 @@ If you to restrict the IP which can see the debug, use the `INTERNAL_IPS` option ``` INTERNAL_IPS = ["10.0.0.1", "10.0.0.2"] ``` + +## MR 174 : Fix online payment + allow users to pay their subscription + +Add the possibility to use custom payment methods. There is also a boolean field on the +Payments allowing every user to use some kinds of payment. You have to add the rights `cotisations.use_every_payment` and `cotisations.buy_every_article` +to the staff members so they can use every type of payment to buy anything. + +Don't forget to run migrations, several settings previously in the `preferences` app ar now +in their own Payment models. + +To have a closer look on how the payments works, pleas go to the wiki. diff --git a/cotisations/forms.py b/cotisations/forms.py index 0ee4c143..9d638221 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -53,16 +53,16 @@ class NewFactureForm(FormRevMixin, ModelForm): Form used to create a new invoice by using a payment method, a bank and a cheque number. """ + def __init__(self, *args, **kwargs): + user = kwargs.pop('user') prefix = kwargs.pop('prefix', self.Meta.model.__name__) - allowed_payment = kwargs.pop('allowed_payment', None) super(NewFactureForm, self).__init__(*args, prefix=prefix, **kwargs) - # TODO : remove the use of cheque and banque and paiement - # for something more generic or at least in English - if allowed_payment: - self.fields['paiement'].queryset = allowed_payment self.fields['paiement'].empty_label = \ _("Select a payment method") + self.fields['paiement'].queryset = Paiement.objects.filter( + pk__in=map(lambda x: x.pk, Paiement.find_allowed_payments(user)) + ) class Meta: model = Facture @@ -71,16 +71,10 @@ class NewFactureForm(FormRevMixin, ModelForm): def clean(self): cleaned_data = super(NewFactureForm, self).clean() paiement = cleaned_data.get('paiement') - cheque = cleaned_data.get('cheque') - banque = cleaned_data.get('banque') if not paiement: raise forms.ValidationError( _("A payment method must be specified.") ) - elif paiement.type_paiement == 'check' and not (cheque and banque): - raise forms.ValidationError( - _("A cheque number and a bank must be specified.") - ) return cleaned_data @@ -103,8 +97,7 @@ class CreditSoldeForm(NewFactureForm): montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True) -class SelectUserArticleForm( - FormRevMixin, Form): +class SelectUserArticleForm(FormRevMixin, Form): """ Form used to select an article during the creation of an invoice for a member. @@ -123,12 +116,11 @@ class SelectUserArticleForm( ) def __init__(self, *args, **kwargs): - self_subscription = kwargs.pop('is_self_subscription', False) + user = kwargs.pop('user') super(SelectUserArticleForm, self).__init__(*args, **kwargs) - if self_subscription: - self.fields['article'].queryset = Article.objects.filter( - Q(type_user='All') | Q(type_user='Adherent') - ).filter(allow_self_subscription=True) + self.fields['article'].queryset = Article.objects.filter( + pk__in=map(lambda x: x.pk, Article.find_allowed_articles(user)) + ) class SelectClubArticleForm(Form): @@ -150,12 +142,11 @@ class SelectClubArticleForm(Form): ) def __init__(self, *args, **kwargs): - self_subscription = kwargs.pop('is_self_subscription', False) + user = kwargs.pop('user') super(SelectClubArticleForm, self).__init__(*args, **kwargs) - if self_subscription: - self.fields['article'].queryset = Article.objects.filter( - Q(type_user='All') | Q(type_user='Club') - ).filter(allow_self_subscription=True) + self.fields['article'].queryset = Article.objects.filter( + pk__in=map(lambda x: x.pk, Article.find_allowed_articles(user)) + ) # TODO : change Facture to Invoice @@ -242,7 +233,7 @@ class PaiementForm(FormRevMixin, ModelForm): class Meta: model = Paiement # TODO : change moyen to method and type_paiement to payment_type - fields = ['moyen', 'allow_self_subscription'] + fields = ['moyen', 'available_for_everyone'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -315,6 +306,7 @@ class NewFactureSoldeForm(NewFactureForm): """ Form used to create an invoice """ + def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(NewFactureSoldeForm, self).__init__( diff --git a/cotisations/migrations/0035_auto_20180703_1005.py b/cotisations/migrations/0035_auto_20180703_1005.py new file mode 100644 index 00000000..3b741d27 --- /dev/null +++ b/cotisations/migrations/0035_auto_20180703_1005.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-07-03 15:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cotisations', '0034_auto_20180703_0929'), + ] + + operations = [ + migrations.AlterModelOptions( + name='paiement', + options={'permissions': (('view_paiement', "Can see a payement's details"), ('use', 'Can use a payement')), 'verbose_name': 'Payment method', 'verbose_name_plural': 'Payment methods'}, + ), + migrations.RemoveField( + model_name='paiement', + name='allow_self_subscription', + ), + migrations.AddField( + model_name='paiement', + name='available_for_everyone', + field=models.BooleanField(default=False, verbose_name='Is available for every user'), + ), + ] diff --git a/cotisations/migrations/0036_auto_20180703_1056.py b/cotisations/migrations/0036_auto_20180703_1056.py new file mode 100644 index 00000000..d14f20ad --- /dev/null +++ b/cotisations/migrations/0036_auto_20180703_1056.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-07-03 15:56 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cotisations', '0035_auto_20180703_1005'), + ] + + operations = [ + migrations.AlterModelOptions( + name='article', + options={'permissions': (('view_article', "Can see an article's details"), ('buy_every_article', 'Can buy every_article')), 'verbose_name': 'Article', 'verbose_name_plural': 'Articles'}, + ), + migrations.RemoveField( + model_name='article', + name='allow_self_subscription', + ), + migrations.AddField( + model_name='article', + name='available_for_everyone', + field=models.BooleanField(default=False, verbose_name='Is available for every user'), + ), + ] diff --git a/cotisations/models.py b/cotisations/models.py index ba285c90..58e0f2d3 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -49,7 +49,6 @@ from django.contrib import messages from machines.models import regen from re2o.field_permissions import FieldPermissionModelMixin from re2o.mixins import AclMixin, RevMixin -from preferences.models import OptionalUser from cotisations.utils import find_payment_method @@ -227,10 +226,11 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): :return: a message and a boolean which is True if the user can create an invoice or if the `options.allow_self_subscription` is set. """ - if OptionalUser.get_cached_value('allow_self_subscription'): - return True, None + nb_payments = len(Paiement.find_allowed_payments(user_request)) + nb_articles = len(Article.find_allowed_articles(user_request)) return ( - user_request.has_perm('cotisations.add_facture'), + user_request.has_perm('cotisations.add_facture') + or (nb_payments*nb_articles), _("You don't have the right to create an invoice.") ) @@ -522,9 +522,9 @@ class Article(RevMixin, AclMixin, models.Model): max_length=255, verbose_name=_l("Type of cotisation") ) - allow_self_subscription = models.BooleanField( + available_for_everyone = models.BooleanField( default=False, - verbose_name=_l("Is available for self subscription") + verbose_name=_l("Is available for every user") ) unique_together = ('name', 'type_user') @@ -532,6 +532,7 @@ class Article(RevMixin, AclMixin, models.Model): class Meta: permissions = ( ('view_article', _l("Can see an article's details")), + ('buy_every_article', _l("Can buy every_article")) ) verbose_name = "Article" verbose_name_plural = "Articles" @@ -549,6 +550,24 @@ class Article(RevMixin, AclMixin, models.Model): def __str__(self): return self.name + def can_buy_article(self, user, *_args, **_kwargs): + """Check if a user can buy this article. + + :param self: The article + :param user: The user requesting buying + :returns: A boolean stating if usage is granted and an explanation + message if the boolean is `False`. + """ + return ( + self.available_for_everyone + or user.has_perm('cotisations.buy_every_article'), + _("You cannot use this Payment.") + ) + + @classmethod + def find_allowed_articles(cls, user): + return [p for p in cls.objects.all() if p.can_buy_article(user)[0]] + class Banque(RevMixin, AclMixin, models.Model): """ @@ -601,14 +620,15 @@ class Paiement(RevMixin, AclMixin, models.Model): default=0, verbose_name=_l("Payment type") ) - allow_self_subscription = models.BooleanField( + available_for_everyone = models.BooleanField( default=False, - verbose_name=_l("Is available for self subscription") + verbose_name=_l("Is available for every user") ) class Meta: permissions = ( ('view_paiement', _l("Can see a payement's details")), + ('use_every_payment', _l("Can use every payement")), ) verbose_name = _l("Payment method") verbose_name_plural = _l("Payment methods") @@ -671,6 +691,24 @@ class Paiement(RevMixin, AclMixin, models.Model): kwargs={'userid': invoice.user.pk} )) + def can_use_payment(self, user, *_args, **_kwargs): + """Check if a user can use this payment. + + :param self: The payment + :param user: The user requesting usage + :returns: A boolean stating if usage is granted and an explanation + message if the boolean is `False`. + """ + return ( + self.available_for_everyone + or user.has_perm('cotisations.use_every_payment'), + _("You cannot use this Payment.") + ) + + @classmethod + def find_allowed_payments(cls, user): + return [p for p in cls.objects.all() if p.can_use_payment(user)[0]] + class Cotisation(RevMixin, AclMixin, models.Model): """ diff --git a/cotisations/views.py b/cotisations/views.py index bee266d0..40b09303 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -98,40 +98,22 @@ def new_facture(request, user, userid): article_list = Article.objects.filter( Q(type_user='All') | Q(type_user=request.user.class_name) ) - # Building the invocie form and the article formset - is_self_subscription = False - can_create_invoice = request.user.has_perm('cotisations.add_facture') - allow_self_subscription = OptionalUser.get_cached_value( - 'allow_self_subscription') - if not can_create_invoice: - if allow_self_subscription: - is_self_subscription = True - article_list = article_list.filter(allow_self_subscription=True) - allowed_payment = Paiement.objects.filter( - allow_self_subscription=True) - invoice_form = NewFactureForm( - request.POST or None, instance=invoice, allowed_payment=allowed_payment) - else: - messages.error( - request, - _("You cannot subscribe. Please ask to the staff.") - ) - return redirect(reverse( - 'users:profil', - kwargs={'userid': userid} - )) - else: - invoice_form = NewFactureForm(request.POST or None, instance=invoice) + # Building the invoice form and the article formset + invoice_form = NewFactureForm( + request.POST or None, + instance=invoice, + user=request.user + ) if request.user.is_class_club: article_formset = formset_factory(SelectClubArticleForm)( request.POST or None, - form_kwargs={'is_self_subscription': is_self_subscription} + form_kwargs={'user': request.user} ) else: article_formset = formset_factory(SelectUserArticleForm)( request.POST or None, - form_kwargs={'is_self_subscription': is_self_subscription} + form_kwargs={'user': request.user} ) if invoice_form.is_valid() and article_formset.is_valid(): @@ -156,7 +138,10 @@ def new_facture(request, user, userid): ) new_purchase.save() - return new_invoice_instance.paiement.end_payment(new_invoice_instance, request) + return new_invoice_instance.paiement.end_payment( + new_invoice_instance, + request + ) messages.error( request,