forms.py 9.91 KB
Newer Older
1 2 3 4 5 6 7
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017  Gabriel Détraz
# Copyright © 2017  Goulven Kermarec
# Copyright © 2017  Augustin Lemesle
8
# Copyright © 2018  Hugo Levy-Falk
9 10 11 12 13 14 15 16 17 18 19 20 21 22
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
"""
24
Forms for the 'cotisation' app of re2o. It highly depends on
25 26 27 28 29 30 31 32 33 34 35
:cotisations:models and is mainly used by :cotisations:views.

The following forms are mainly used to create, edit or delete
anything related to 'cotisations' :
    * Payments Methods
    * Banks
    * Invoices
    * Articles

See the details for each of these operations in the documentation
of each of the method.
36
"""
37 38
from __future__ import unicode_literals

39
from django import forms
40
from django.db.models import Q
Dalahro's avatar
Dalahro committed
41
from django.forms import ModelForm, Form
42
from django.core.validators import MinValueValidator
43
from django.utils.translation import ugettext as _
44
from django.utils.translation import ugettext_lazy as _l
45
from django.shortcuts import get_object_or_404
46

47
from re2o.field_permissions import FieldPermissionFormMixin
48
from re2o.mixins import FormRevMixin
49
from .models import Article, Paiement, Facture, Banque
50
from .payment_methods import balance
51

52

53
class FactureForm(FieldPermissionFormMixin, FormRevMixin, ModelForm):
54
    """
55
    Form used to manage and create an invoice and its fields.
56
    """
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
57

58 59
    def __init__(self, *args, creation=False, **kwargs):
        user = kwargs['user']
60
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
61
        super(FactureForm, self).__init__(*args, prefix=prefix, **kwargs)
62 63
        self.fields['paiement'].empty_label = \
            _("Select a payment method")
64
        self.fields['paiement'].queryset = Paiement.find_allowed_payments(user)
65 66 67 68 69 70 71
        if not creation:
            self.fields['user'].label = _("Member")
            self.fields['user'].empty_label = \
                _("Select the proprietary member")
            self.fields['valid'].label = _("Validated invoice")
        else:
            self.fields = {'paiement': self.fields['paiement']}
72

73 74
    class Meta:
        model = Facture
75
        fields = '__all__'
76 77

    def clean(self):
78
        cleaned_data = super(FactureForm, self).clean()
79
        paiement = cleaned_data.get('paiement')
80
        if not paiement:
81
            raise forms.ValidationError(
82
                _("A payment method must be specified.")
83
            )
84 85
        return cleaned_data

chirac's avatar
chirac committed
86

Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
87
class SelectUserArticleForm(FormRevMixin, Form):
88
    """
89 90
    Form used to select an article during the creation of an invoice for a
    member.
91
    """
chirac's avatar
chirac committed
92
    article = forms.ModelChoiceField(
93 94 95
        queryset=Article.objects.filter(
            Q(type_user='All') | Q(type_user='Adherent')
        ),
96
        label=_l("Article"),
97 98 99
        required=True
    )
    quantity = forms.IntegerField(
100
        label=_l("Quantity"),
101 102 103 104
        validators=[MinValueValidator(1)],
        required=True
    )

105
    def __init__(self, *args, **kwargs):
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
106
        user = kwargs.pop('user')
107
        super(SelectUserArticleForm, self).__init__(*args, **kwargs)
108
        self.fields['article'].queryset = Article.find_allowed_articles(user)
109

110 111

class SelectClubArticleForm(Form):
112
    """
113 114
    Form used to select an article during the creation of an invoice for a
    club.
115
    """
116
    article = forms.ModelChoiceField(
117 118 119
        queryset=Article.objects.filter(
            Q(type_user='All') | Q(type_user='Club')
        ),
120
        label=_l("Article"),
chirac's avatar
chirac committed
121 122 123
        required=True
    )
    quantity = forms.IntegerField(
124
        label=_l("Quantity"),
chirac's avatar
chirac committed
125 126 127 128
        validators=[MinValueValidator(1)],
        required=True
    )

129
    def __init__(self, user, *args, **kwargs):
130
        super(SelectClubArticleForm, self).__init__(*args, **kwargs)
131
        self.fields['article'].queryset = Article.find_allowed_articles(user)
132

133

134
# TODO : change Facture to Invoice
Dalahro's avatar
Dalahro committed
135
class NewFactureFormPdf(Form):
136 137 138
    """
    Form used to create a custom PDF invoice.
    """
139
    paid = forms.BooleanField(label=_l("Paid"), required=False)
140
    # TODO : change dest field to recipient
141 142 143 144 145
    dest = forms.CharField(
        required=True,
        max_length=255,
        label=_l("Recipient")
    )
146
    # TODO : change chambre field to address
147 148 149 150 151 152
    chambre = forms.CharField(
        required=False,
        max_length=10,
        label=_l("Address")
    )

chirac's avatar
chirac committed
153

154
class ArticleForm(FormRevMixin, ModelForm):
155 156 157
    """
    Form used to create an article.
    """
158 159 160 161 162
    class Meta:
        model = Article
        fields = '__all__'

    def __init__(self, *args, **kwargs):
163
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
164
        super(ArticleForm, self).__init__(*args, prefix=prefix, **kwargs)
165
        self.fields['name'].label = _("Article name")
166

chirac's avatar
chirac committed
167

168
class DelArticleForm(FormRevMixin, Form):
169 170 171 172
    """
    Form used to delete one or more of the currently available articles.
    The user must choose the one to delete by checking the boxes.
    """
chirac's avatar
chirac committed
173
    articles = forms.ModelMultipleChoiceField(
174
        queryset=Article.objects.none(),
175
        label=_l("Existing articles"),
chirac's avatar
chirac committed
176 177 178
        widget=forms.CheckboxSelectMultiple
    )

179 180 181 182 183 184 185 186
    def __init__(self, *args, **kwargs):
        instances = kwargs.pop('instances', None)
        super(DelArticleForm, self).__init__(*args, **kwargs)
        if instances:
            self.fields['articles'].queryset = instances
        else:
            self.fields['articles'].queryset = Article.objects.all()

187

188
# TODO : change Paiement to Payment
189
class PaiementForm(FormRevMixin, ModelForm):
190 191 192 193 194
    """
    Form used to create a new payment method.
    The 'cheque' type is used to associate a specific behaviour requiring
    a cheque number and a bank.
    """
195 196
    class Meta:
        model = Paiement
197
        # TODO : change moyen to method and type_paiement to payment_type
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
198
        fields = ['moyen', 'available_for_everyone']
199 200

    def __init__(self, *args, **kwargs):
201
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
202
        super(PaiementForm, self).__init__(*args, prefix=prefix, **kwargs)
203
        self.fields['moyen'].label = _("Payment method name")
204

chirac's avatar
chirac committed
205

206
# TODO : change paiement to payment
207
class DelPaiementForm(FormRevMixin, Form):
208 209 210 211
    """
    Form used to delete one or more payment methods.
    The user must choose the one to delete by checking the boxes.
    """
212
    # TODO : change paiement to payment
chirac's avatar
chirac committed
213
    paiements = forms.ModelMultipleChoiceField(
214
        queryset=Paiement.objects.none(),
215
        label=_l("Existing payment method"),
chirac's avatar
chirac committed
216 217 218
        widget=forms.CheckboxSelectMultiple
    )

219 220 221 222 223 224 225 226
    def __init__(self, *args, **kwargs):
        instances = kwargs.pop('instances', None)
        super(DelPaiementForm, self).__init__(*args, **kwargs)
        if instances:
            self.fields['paiements'].queryset = instances
        else:
            self.fields['paiements'].queryset = Paiement.objects.all()

227

228
# TODO : change banque to bank
229
class BanqueForm(FormRevMixin, ModelForm):
230 231 232
    """
    Form used to create a bank.
    """
chirac's avatar
chirac committed
233
    class Meta:
234
        # TODO : change banque to bank
chirac's avatar
chirac committed
235 236 237 238
        model = Banque
        fields = ['name']

    def __init__(self, *args, **kwargs):
239
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
240
        super(BanqueForm, self).__init__(*args, prefix=prefix, **kwargs)
241
        self.fields['name'].label = _("Bank name")
chirac's avatar
chirac committed
242

chirac's avatar
chirac committed
243

244
# TODO : change banque to bank
245
class DelBanqueForm(FormRevMixin, Form):
246 247 248 249
    """
    Form used to delete one or more banks.
    The use must choose the one to delete by checking the boxes.
    """
250
    # TODO : change banque to bank
chirac's avatar
chirac committed
251
    banques = forms.ModelMultipleChoiceField(
252
        queryset=Banque.objects.none(),
253
        label=_l("Existing banks"),
chirac's avatar
chirac committed
254 255
        widget=forms.CheckboxSelectMultiple
    )
256 257 258 259 260 261 262 263

    def __init__(self, *args, **kwargs):
        instances = kwargs.pop('instances', None)
        super(DelBanqueForm, self).__init__(*args, **kwargs)
        if instances:
            self.fields['banques'].queryset = instances
        else:
            self.fields['banques'].queryset = Banque.objects.all()
264 265


266
# TODO : Better name and docstring
267
class RechargeForm(FormRevMixin, Form):
268 269 270
    """
    Form used to refill a user's balance
    """
271
    value = forms.FloatField(
272
        label=_l("Amount"),
273
        min_value=0.01,
274
        validators=[]
275
    )
276 277 278 279
    payment = forms.ModelChoiceField(
        queryset=Paiement.objects.none(),
        label=_l("Payment method")
    )
280

281 282
    def __init__(self, *args, user=None, **kwargs):
        self.user = user
283
        super(RechargeForm, self).__init__(*args, **kwargs)
284 285
        self.fields['payment'].empty_label = \
            _("Select a payment method")
286
        self.fields['payment'].queryset = Paiement.find_allowed_payments(user)
287

288
    def clean(self):
289
        """
290
        Returns a cleaned value from the received form by validating
291 292
        the value is well inside the possible limits
        """
293
        value = self.cleaned_data['value']
Maël Kervella's avatar
Maël Kervella committed
294
        balance_method = get_object_or_404(balance.PaymentMethod)
295 296
        if balance_method.maximum_balance is not None and \
           value + self.user.solde > balance_method.maximum_balance:
297 298
            raise forms.ValidationError(
                _("Requested amount is too high. Your balance can't exceed \
299
                %(max_online_balance)s €.") % {
300
                    'max_online_balance': balance_method.maximum_balance
301 302
                }
            )
303
        return self.cleaned_data