diff --git a/cotisations/acl.py b/cotisations/acl.py index 868e3411..4e147a82 100644 --- a/cotisations/acl.py +++ b/cotisations/acl.py @@ -25,6 +25,7 @@ Here are defined some functions to check acl on the application. """ +from django.utils.translation import ugettext as _ def can_view(user): """Check if an user can view the application. @@ -37,4 +38,4 @@ def can_view(user): viewing is granted and msg is a message (can be None). """ can = user.has_module_perms('cotisations') - return can, None if can else "Vous ne pouvez pas voir cette application." + return can, None if can else _("You don't have the rights to see this application.") diff --git a/cotisations/forms.py b/cotisations/forms.py index 3459f0de..e2095f7b 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -40,6 +40,8 @@ from django import forms from django.db.models import Q from django.forms import ModelForm, Form from django.core.validators import MinValueValidator,MaxValueValidator +from django.utils.translation import ugettext as _ + from .models import Article, Paiement, Facture, Banque from preferences.models import OptionalUser from users.models import User @@ -50,15 +52,18 @@ from re2o.mixins import FormRevMixin class NewFactureForm(FormRevMixin, ModelForm): """Creation d'une facture, moyen de paiement, banque et numero de cheque""" + # TODO : translate doc string in English def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) 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 self.fields['cheque'].required = False self.fields['banque'].required = False - self.fields['cheque'].label = 'Numero de chèque' - self.fields['banque'].empty_label = "Non renseigné" - self.fields['paiement'].empty_label = "Séléctionner\ - un moyen de paiement" + self.fields['cheque'].label = _("Cheque number") + self.fields['banque'].empty_label = _("Not specified") + self.fields['paiement'].empty_label = \ + _("Select a payment method") paiement_list = Paiement.objects.filter(type_paiement=1) if paiement_list: self.fields['paiement'].widget\ @@ -70,41 +75,47 @@ 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") + paiement = cleaned_data.get('paiement') + cheque = cleaned_data.get('cheque') + banque = cleaned_data.get('banque') if not paiement: - raise forms.ValidationError("Le moyen de paiement est obligatoire") - elif paiement.type_paiement == "check" and not (cheque and banque): - raise forms.ValidationError("Le numéro de chèque et\ - la banque sont obligatoires.") + 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 class CreditSoldeForm(NewFactureForm): """Permet de faire des opérations sur le solde si il est activé""" + # TODO : translate docstring to English class Meta(NewFactureForm.Meta): model = Facture fields = ['paiement', 'banque', 'cheque'] def __init__(self, *args, **kwargs): super(CreditSoldeForm, self).__init__(*args, **kwargs) + # TODO : change solde to balance self.fields['paiement'].queryset = Paiement.objects.exclude( moyen='solde' - ).exclude(moyen="Solde") + ).exclude(moyen='Solde') montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True) class SelectUserArticleForm(FormRevMixin, Form): """Selection d'un article lors de la creation d'une facture""" + # TODO : translate docstring to English article = forms.ModelChoiceField( queryset=Article.objects.filter(Q(type_user='All') | Q(type_user='Adherent')), - label="Article", + label=_("Article"), required=True ) quantity = forms.IntegerField( - label="Quantité", + label=_("Quantity"), validators=[MinValueValidator(1)], required=True ) @@ -112,54 +123,63 @@ class SelectUserArticleForm(FormRevMixin, Form): class SelectClubArticleForm(Form): """Selection d'un article lors de la creation d'une facture""" + # TODO : translate docstring to English article = forms.ModelChoiceField( queryset=Article.objects.filter(Q(type_user='All') | Q(type_user='Club')), - label="Article", + label=_("Article"), required=True ) quantity = forms.IntegerField( - label="Quantité", + label=_("Quantity"), validators=[MinValueValidator(1)], required=True ) - +# TODO : change Facture to Invoice class NewFactureFormPdf(Form): """Creation d'un pdf facture par le trésorier""" + # TODO : translate docstring to English article = forms.ModelMultipleChoiceField( queryset=Article.objects.all(), - label="Article" + label=_("Article") ) number = forms.IntegerField( - label="Quantité", + label=_("Quantity"), validators=[MinValueValidator(1)] ) - paid = forms.BooleanField(label="Payé", required=False) - dest = forms.CharField(required=True, max_length=255, label="Destinataire") - chambre = forms.CharField(required=False, max_length=10, label="Adresse") + paid = forms.BooleanField(label=_("Paid"), required=False) + # TODO : change dest field to recipient + dest = forms.CharField(required=True, max_length=255, label=_("Recipient")) + # TODO : change chambre field to address + chambre = forms.CharField(required=False, max_length=10, label=_("Address")) + # TODO : change fid field to invoice_id fid = forms.CharField( required=True, max_length=10, - label="Numéro de la facture" + label=_("Invoice number") ) - +# TODO : change Facture to Invoice class EditFactureForm(FieldPermissionFormMixin, NewFactureForm): """Edition d'une facture : moyen de paiement, banque, user parent""" + # TODO : translate docstring to English class Meta(NewFactureForm.Meta): + # TODO : change Facture to Invoice model = Facture fields = '__all__' def __init__(self, *args, **kwargs): + # TODO : change Facture to Invoice super(EditFactureForm, self).__init__(*args, **kwargs) - self.fields['user'].label = 'Adherent' - self.fields['user'].empty_label = "Séléctionner\ - l'adhérent propriétaire" - self.fields['valid'].label = 'Validité de la facture' + self.fields['user'].label = _("Member") + self.fields['user'].empty_label = \ + _("Select the proprietary member") + self.fields['valid'].label = _("Validated invoice") class ArticleForm(FormRevMixin, ModelForm): """Creation d'un article. Champs : nom, cotisation, durée""" + # TODO : translate docstring to English class Meta: model = Article fields = '__all__' @@ -167,15 +187,16 @@ class ArticleForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(ArticleForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['name'].label = "Désignation de l'article" + self.fields['name'].label = _("Article name") class DelArticleForm(FormRevMixin, Form): """Suppression d'un ou plusieurs articles en vente. Choix parmis les modèles""" + # TODO : translate docstring to English articles = forms.ModelMultipleChoiceField( queryset=Article.objects.none(), - label="Articles actuels", + label=_("Existing articles"), widget=forms.CheckboxSelectMultiple ) @@ -188,26 +209,36 @@ class DelArticleForm(FormRevMixin, Form): self.fields['articles'].queryset = Article.objects.all() +# TODO : change Paiement to Payment class PaiementForm(FormRevMixin, ModelForm): """Creation d'un moyen de paiement, champ text moyen et type permettant d'indiquer si il s'agit d'un chèque ou non pour le form""" + # TODO : translate docstring to English class Meta: model = Paiement + # TODO : change moyen to method and type_paiement to payment_type fields = ['moyen', 'type_paiement'] 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 = 'Moyen de paiement à ajouter' - self.fields['type_paiement'].label = 'Type de paiement à ajouter' + self.fields['moyen'].label = _("Payment method name") + self.fields['type_paiement'].label = _("Payment type") + self.fields['type_paiement'].help_text = \ + _("The payement type is use 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 class DelPaiementForm(FormRevMixin, Form): """Suppression d'un ou plusieurs moyens de paiements, selection parmis les models""" + # TODO : translate docstring to English + # TODO : change paiement to payment paiements = forms.ModelMultipleChoiceField( queryset=Paiement.objects.none(), - label="Moyens de paiement actuels", + label=_("Existing payment method"), widget=forms.CheckboxSelectMultiple ) @@ -220,23 +251,29 @@ class DelPaiementForm(FormRevMixin, Form): self.fields['paiements'].queryset = Paiement.objects.all() +# TODO : change banque to bank class BanqueForm(FormRevMixin, ModelForm): """Creation d'une banque, field name""" + # TODO : translate docstring to Englishh class Meta: + # TODO : change banque to bank model = Banque fields = ['name'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(BanqueForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['name'].label = 'Banque à ajouter' + self.fields['name'].label = _("Bank name") +# TODO : change banque to bank class DelBanqueForm(FormRevMixin, Form): """Selection d'une ou plusieurs banques, pour suppression""" + # TODO : translate docstrign to English + # TODO : change banque to bank banques = forms.ModelMultipleChoiceField( queryset=Banque.objects.none(), - label="Banques actuelles", + label=_("Existing banks"), widget=forms.CheckboxSelectMultiple ) @@ -249,43 +286,56 @@ class DelBanqueForm(FormRevMixin, Form): self.fields['banques'].queryset = Banque.objects.all() +# TODO : change facture to Invoice class NewFactureSoldeForm(NewFactureForm): """Creation d'une facture, moyen de paiement, banque et numero de cheque""" + # TODO : translate docstring to English def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) self.fields['cheque'].required = False self.fields['banque'].required = False - self.fields['cheque'].label = 'Numero de chèque' - self.fields['banque'].empty_label = "Non renseigné" - self.fields['paiement'].empty_label = "Séléctionner\ - une bite de paiement" + self.fields['cheque'].label = _('Cheque number') + self.fields['banque'].empty_label = _("Not specified") + self.fields['paiement'].empty_label = \ + _("Select a payment method") + # TODO : change paiement to payment paiement_list = Paiement.objects.filter(type_paiement=1) if paiement_list: self.fields['paiement'].widget\ .attrs['data-cheque'] = paiement_list.first().id class Meta: + # TODO : change facture to invoice model = Facture + # TODO : change paiement to payment and baque to bank fields = ['paiement', 'banque'] def clean(self): cleaned_data = super(NewFactureSoldeForm, self).clean() + # TODO : change paiement to payment paiement = cleaned_data.get("paiement") cheque = cleaned_data.get("cheque") + # TODO : change banque to bank banque = cleaned_data.get("banque") + # TODO : change paiement to payment if not paiement: - raise forms.ValidationError("Le moyen de paiement est obligatoire") + raise forms.ValidationError( + _("A payment method must be specified.") + ) + # TODO : change paiement and banque to payment and bank elif paiement.type_paiement == "check" and not (cheque and banque): - raise forms.ValidationError("Le numéro de chèque et\ - la banque sont obligatoires.") + raise forms.ValidationError( + _("A cheque number and a bank must be specified.") + ) return cleaned_data +# TODO : Better name and docstring class RechargeForm(FormRevMixin, Form): value = forms.FloatField( - label='Valeur', + label=_("Amount"), min_value=0.01, validators = [] ) @@ -297,7 +347,21 @@ class RechargeForm(FormRevMixin, Form): def clean_value(self): value = self.cleaned_data['value'] if value < OptionalUser.get_cached_value('min_online_payment'): - raise forms.ValidationError("Montant inférieur au montant minimal de paiement en ligne (%s) €" % OptionalUser.get_cached_value('min_online_payment')) + raise forms.ValidationError( + _("Requested amount is too small. Minimum amount possible : \ + %(min_online_amount)s €") % { + min_online_amount: OptionalUser.get_cached_value( + 'min_online_payment' + ) + } + ) if value + self.user.solde > OptionalUser.get_cached_value('max_solde'): - raise forms.ValidationError("Le solde ne peux excéder %s " % OptionalUser.get_cached_value('max_solde')) + raise forms.ValidationError( + _("Requested amount is too high. Your balance can't exceed \ + %(max_online_balance)s €") % { + max_online_balance: OptionalUser.get_cached_value( + 'max_solde' + ) + } + ) return value diff --git a/cotisations/models.py b/cotisations/models.py index cdf73a39..c529c281 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -42,6 +42,7 @@ Post_save et Post_delete : sychronisation des services et régénération des services d'accès réseau (ex dhcp) lors de la vente d'une cotisation par exemple """ +# TODO : translate docstring to English from __future__ import unicode_literals from dateutil.relativedelta import relativedelta @@ -55,55 +56,86 @@ from django.core.validators import MinValueValidator from django.db.models import Max from django.utils import timezone from machines.models import regen +from django.utils.translation import ugettext as _ from re2o.field_permissions import FieldPermissionModelMixin from re2o.mixins import AclMixin, RevMixin +# TODO : change facture to invoice class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): """ Définition du modèle des factures. Une facture regroupe une ou plusieurs ventes, rattachée à un user, et reliée à un moyen de paiement et si il y a lieu un numero pour les chèques. Possède les valeurs valides et controle (trésorerie)""" - PRETTY_NAME = "Factures émises" + # TODO : translate docstrign to English user = models.ForeignKey('users.User', on_delete=models.PROTECT) + # TODO : change paiement to payment paiement = models.ForeignKey('Paiement', on_delete=models.PROTECT) + # TODO : change banque to bank banque = models.ForeignKey( 'Banque', on_delete=models.PROTECT, blank=True, - null=True) - cheque = models.CharField(max_length=255, blank=True) - date = models.DateTimeField(auto_now_add=True) - valid = models.BooleanField(default=True) - control = models.BooleanField(default=False) + null=True + ) + # TODO : maybe change to cheque nummber because not evident + cheque = models.CharField( + max_length=255, + blank=True, + verbose_name=_("Cheque number") + ) + date = models.DateTimeField( + auto_now_add=True, + verbose_name=_("Date") + ) + # TODO : change name to validity for clarity + valid = models.BooleanField( + default=True, + verbose_name=_("Validated") + ) + # TODO : changed name to controlled for clarity + control = models.BooleanField( + default=False, + verbose_name=_("Controlled") + ) class Meta: abstract = False permissions = ( - ("change_facture_control", "Peut changer l'etat de controle"), - ("change_facture_pdf", "Peut éditer une facture pdf"), - ("view_facture", "Peut voir un objet facture"), - ("change_all_facture", "Superdroit, peut modifier toutes les factures"), + # TODO : change facture to invoice + ('change_facture_control', _("Can change the controlled state")), + # TODO : seems more likely to be call create_facture_pdf or create_invoice_pdf + ('change_facture_pdf', _("Can create a custom PDF invoice")), + ('view_facture', _("Can see an invoice's details")), + ('change_all_facture', _("Can edit all the previous invoices")), ) + verbose_name = _("Invoice") + verbose_name_plural = _("Invoices") def linked_objects(self): """Return linked objects : machine and domain. Usefull in history display""" return self.vente_set.all() + # TODO : change prix to price def prix(self): """Renvoie le prix brut sans les quantités. Méthode dépréciée""" + # TODO : translate docstring to English + # TODO : change prix to price prix = Vente.objects.filter( facture=self ).aggregate(models.Sum('prix'))['prix__sum'] return prix + # TODO : change prix to price def prix_total(self): """Prix total : somme des produits prix_unitaire et quantité des ventes de l'objet""" + # TODO : translate docstrign to English + # TODO : change Vente to somethingelse return Vente.objects.filter( facture=self ).aggregate( @@ -115,6 +147,7 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): def name(self): """String, somme des name des ventes de self""" + # TODO : translate docstring to English name = ' - '.join(Vente.objects.filter( facture=self ).values_list('name', flat=True)) @@ -122,44 +155,41 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): def can_edit(self, user_request, *args, **kwargs): if not user_request.has_perm('cotisations.change_facture'): - return False, u"Vous n'avez pas le droit d'éditer les factures" + return False, _("You don't have the right to edit an invoice.") elif not user_request.has_perm('cotisations.change_all_facture') and not self.user.can_edit(user_request, *args, **kwargs)[0]: - return False, u"Vous ne pouvez pas éditer les factures de cet user protégé" + return False, _("You don't have the right to edit this user's invoices.") elif not user_request.has_perm('cotisations.change_all_facture') and\ (self.control or not self.valid): - return False, u"Vous n'avez pas le droit d'éditer une facture\ - controlée ou invalidée par un trésorier" + return False, _("You don't have the right to edit an invoice already controlled or invalidated.") else: return True, None def can_delete(self, user_request, *args, **kwargs): if not user_request.has_perm('cotisations.delete_facture'): - return False, u"Vous n'avez pas le droit de supprimer une facture" + return False, _("You don't have the right to delete an invoice.") if not self.user.can_edit(user_request, *args, **kwargs)[0]: - return False, u"Vous ne pouvez pas éditer les factures de cet user protégé" + return False, _("You don't have the right to delete this user's invoices.") if self.control or not self.valid: - return False, u"Vous ne pouvez pas supprimer une facture\ - contrôlée ou invalidée par un trésorier" + return False, _("You don't have the right to delete an invoice already controlled or invalidated.") else: return True, None def can_view(self, user_request, *args, **kwargs): if not user_request.has_perm('cotisations.view_facture') and\ self.user != user_request: - return False, u"Vous ne pouvez pas afficher l'historique d'une\ - facture d'un autre user que vous sans droit cableur" + return False, _("You don't have the right to see someone else's invoices history.") elif not self.valid: - return False, u"La facture est invalidée et ne peut être affichée" + return False, _("The invoice has been invalidated.") else: return True, None @staticmethod def can_change_control(user_request, *args, **kwargs): - return user_request.has_perm('cotisations.change_facture_control'), "Vous ne pouvez pas éditer le controle sans droit trésorier" + return user_request.has_perm('cotisations.change_facture_control'), _("You don't have the right to edit the controlled state.") @staticmethod def can_change_pdf(user_request, *args, **kwargs): - return user_request.has_perm('cotisations.change_facture_pdf'), "Vous ne pouvez pas éditer une facture sans droit trésorier" + return user_request.has_perm('cotisations.change_facture_pdf'), _("You don't have the right to edit an invoice.") def __init__(self, *args, **kwargs): super(Facture, self).__init__(*args, **kwargs) @@ -174,6 +204,7 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): @receiver(post_save, sender=Facture) def facture_post_save(sender, **kwargs): """Post save d'une facture, synchronise l'user ldap""" + # TODO : translate docstrign into English facture = kwargs['instance'] user = facture.user user.ldap_sync(base=False, access_refresh=True, mac_refresh=False) @@ -182,51 +213,82 @@ def facture_post_save(sender, **kwargs): @receiver(post_delete, sender=Facture) def facture_post_delete(sender, **kwargs): """Après la suppression d'une facture, on synchronise l'user ldap""" + # TODO : translate docstring into English user = kwargs['instance'].user user.ldap_sync(base=False, access_refresh=True, mac_refresh=False) +# TODO : change Vente to Purchase class Vente(RevMixin, AclMixin, models.Model): """Objet vente, contient une quantité, une facture parente, un nom, un prix. Peut-être relié à un objet cotisation, via le boolean iscotisation""" - PRETTY_NAME = "Ventes effectuées" + # TODO : translate docstring into English + # TODO : change this to English COTISATION_TYPE = ( - ('Connexion', 'Connexion'), - ('Adhesion', 'Adhesion'), - ('All', 'All'), + ('Connexion', _("Connexion")), + ('Adhesion', _("Membership")), + ('All', _("Both of them")), ) - facture = models.ForeignKey('Facture', on_delete=models.CASCADE) - number = models.IntegerField(validators=[MinValueValidator(1)]) - name = models.CharField(max_length=255) - prix = models.DecimalField(max_digits=5, decimal_places=2) + # TODO : change facture to invoice + facture = models.ForeignKey( + 'Facture', + on_delete=models.CASCADE, + verbose_name=_("Invoice") + ) + # TODO : change number to amount for clarity + number = models.IntegerField( + validators=[MinValueValidator(1)], + verbose_name=_("Amount") + ) + # TODO : change this field for a ForeinKey to Article + name = models.CharField( + max_length=255, + verbose_name=_("Article") + ) + # TODO : change prix to price + # TODO : this field is not needed if you use Article ForeignKey + prix = models.DecimalField( + max_digits=5, + decimal_places=2, + verbose_name=_("Price")) + # TODO : this field is not needed if you use Article ForeignKey duration = models.PositiveIntegerField( - help_text="Durée exprimée en mois entiers", blank=True, - null=True) + null=True, + verbose_name=_("Duration (in whole month)") + ) + # TODO : this field is not needed if you use Article ForeignKey type_cotisation = models.CharField( choices=COTISATION_TYPE, blank=True, null=True, - max_length=255 + max_length=255, + verbose_name=_("Type of cotisation") ) class Meta: permissions = ( - ("view_vente", "Peut voir un objet vente"), - ("change_all_vente", "Superdroit, peut modifier toutes les ventes"), + ('view_vente', _("Can see a purchase's details")), + ('change_all_vente', _("Can edit all the previous purchases")), ) + verbose_name = _("Purchase") + verbose_name_plural = _("Purchases") + + # TODO : change prix_total to total_price def prix_total(self): """Renvoie le prix_total de self (nombre*prix)""" + # TODO : translate docstring to english return self.prix*self.number def update_cotisation(self): """Mets à jour l'objet related cotisation de la vente, si il existe : update la date de fin à partir de la durée de la vente""" + # TODO : translate docstring to English if hasattr(self, 'cotisation'): cotisation = self.cotisation cotisation.date_end = cotisation.date_start + relativedelta( @@ -237,6 +299,7 @@ class Vente(RevMixin, AclMixin, models.Model): """Update et crée l'objet cotisation associé à une facture, prend en argument l'user, la facture pour la quantitéi, et l'article pour la durée""" + # TODO : translate docstring to English if not hasattr(self, 'cotisation') and self.type_cotisation: cotisation = Cotisation(vente=self) cotisation.type_cotisation = self.type_cotisation @@ -264,41 +327,40 @@ class Vente(RevMixin, AclMixin, models.Model): return def save(self, *args, **kwargs): + # TODO : ecrire une docstring # On verifie que si iscotisation, duration est présent if self.type_cotisation and not self.duration: - raise ValidationError("Cotisation et durée doivent être présents\ - ensembles") + raise ValidationError( + _("A cotisation should always have a duration.") + ) self.update_cotisation() super(Vente, self).save(*args, **kwargs) def can_edit(self, user_request, *args, **kwargs): if not user_request.has_perm('cotisations.change_vente'): - return False, u"Vous n'avez pas le droit d'éditer les ventes" + return False, _("You don't have the rights to edit the purchases.") elif not user_request.has_perm('cotisations.change_all_facture') and not self.facture.user.can_edit(user_request, *args, **kwargs)[0]: - return False, u"Vous ne pouvez pas éditer les factures de cet user protégé" + return False, _("You don't have the right to edit this user's purchases.") elif not user_request.has_perm('cotisations.change_all_vente') and\ (self.facture.control or not self.facture.valid): - return False, u"Vous n'avez pas le droit d'éditer une vente\ - controlée ou invalidée par un trésorier" + return False, _("You don't have the right to edit a purchase already controlled or invalidated.") else: return True, None def can_delete(self, user_request, *args, **kwargs): if not user_request.has_perm('cotisations.delete_vente'): - return False, u"Vous n'avez pas le droit de supprimer une vente" + return False, _("You don't have the right to delete a purchase.") if not self.facture.user.can_edit(user_request, *args, **kwargs)[0]: - return False, u"Vous ne pouvez pas éditer les factures de cet user protégé" + return False, _("You don't have the right to delete this user's purchases.") if self.facture.control or not self.facture.valid: - return False, u"Vous ne pouvez pas supprimer une vente\ - contrôlée ou invalidée par un trésorier" + return False, _("You don't have the right to delete a purchase already controlled or invalidated.") else: return True, None def can_view(self, user_request, *args, **kwargs): if not user_request.has_perm('cotisations.view_vente') and\ self.facture.user != user_request: - return False, u"Vous ne pouvez pas afficher l'historique d'une\ - facture d'un autre user que vous sans droit cableur" + return False, _("You don't have the right to see someone else's purchase history.") else: return True, None @@ -306,10 +368,13 @@ class Vente(RevMixin, AclMixin, models.Model): return str(self.name) + ' ' + str(self.facture) +# TODO : change vente to purchase @receiver(post_save, sender=Vente) def vente_post_save(sender, **kwargs): """Post save d'une vente, déclencge la création de l'objet cotisation si il y a lieu(si iscotisation) """ + # TODO : translate docstring to English + # TODO : change vente to purchase vente = kwargs['instance'] if hasattr(vente, 'cotisation'): vente.cotisation.vente = vente @@ -321,10 +386,13 @@ def vente_post_save(sender, **kwargs): user.ldap_sync(base=False, access_refresh=True, mac_refresh=False) +# TODO : change vente to purchase @receiver(post_delete, sender=Vente) def vente_post_delete(sender, **kwargs): """Après suppression d'une vente, on synchronise l'user ldap (ex suppression d'une cotisation""" + # TODO : translate docstring to English + # TODO : change vente to purchase vente = kwargs['instance'] if vente.type_cotisation: user = vente.facture.user @@ -334,53 +402,69 @@ def vente_post_delete(sender, **kwargs): class Article(RevMixin, AclMixin, models.Model): """Liste des articles en vente : prix, nom, et attribut iscotisation et duree si c'est une cotisation""" - PRETTY_NAME = "Articles en vente" + # TODO : translate docstring to English + # TODO : Either use TYPE or TYPES in both choices but not both USER_TYPES = ( - ('Adherent', 'Adherent'), - ('Club', 'Club'), - ('All', 'All'), + ('Adherent', _("Member")), + ('Club', _("Club")), + ('All', _("Both of them")), ) COTISATION_TYPE = ( - ('Connexion', 'Connexion'), - ('Adhesion', 'Adhesion'), - ('All', 'All'), + ('Connexion', _("Connexion")), + ('Adhesion', _("Membership")), + ('All', _("Both of them")), ) - name = models.CharField(max_length=255) - prix = models.DecimalField(max_digits=5, decimal_places=2) + name = models.CharField( + max_length=255, + verbose_name=_("Designation") + ) + # TODO : change prix to price + prix = models.DecimalField( + max_digits=5, + decimal_places=2, + verbose_name=_("Unitary price") + ) duration = models.PositiveIntegerField( - help_text="Durée exprimée en mois entiers", blank=True, null=True, - validators=[MinValueValidator(0)]) + validators=[MinValueValidator(0)], + verbose_name=_("Duration (in whole month)") + ) type_user = models.CharField( choices=USER_TYPES, default='All', - max_length=255 + max_length=255, + verbose_name=_("Type of users concerned") ) type_cotisation = models.CharField( choices=COTISATION_TYPE, default=None, blank=True, null=True, - max_length=255 + max_length=255, + verbose_name=_("Type of cotisation") ) unique_together = ('name', 'type_user') class Meta: permissions = ( - ("view_article", "Peut voir un objet article"), + ('view_article', _("Can see an article's details")), ) + verbose_name = "Article" + verbose_name_plural = "Articles" def clean(self): - if self.name.lower() == "solde": - raise ValidationError("Solde est un nom d'article invalide") + if self.name.lower() == 'solde': + raise ValidationError( + _("Solde is a reserved article name") + ) if self.type_cotisation and not self.duration: raise ValidationError( - "La durée est obligatoire si il s'agit d'une cotisation" + _("Duration must be specified for a cotisation") ) def __str__(self): @@ -389,34 +473,51 @@ class Article(RevMixin, AclMixin, models.Model): class Banque(RevMixin, AclMixin, models.Model): """Liste des banques""" - PRETTY_NAME = "Banques enregistrées" + # TODO : translate docstring to English - name = models.CharField(max_length=255) + name = models.CharField( + max_length=255, + verbose_name=_("Name") + ) class Meta: permissions = ( - ("view_banque", "Peut voir un objet banque"), + ('view_banque', _("Can see a bank's details")), ) + verbose_name=_("Bank") + verbose_name_plural=_("Banks") def __str__(self): return self.name +# TODO : change Paiement to Payment class Paiement(RevMixin, AclMixin, models.Model): """Moyens de paiement""" - PRETTY_NAME = "Moyens de paiement" + # TODO : translate docstring to English + PAYMENT_TYPES = ( - (0, 'Autre'), - (1, 'Chèque'), + (0, _("Standard")), + (1, _("Cheque")), ) - moyen = models.CharField(max_length=255) - type_paiement = models.IntegerField(choices=PAYMENT_TYPES, default=0) + # TODO : change moyen to method + moyen = models.CharField( + max_length=255, + verbose_name=_("Method") + ) + type_paiement = models.IntegerField( + choices=PAYMENT_TYPES, + default=0, + verbose_name=_("Payment type") + ) class Meta: permissions = ( - ("view_paiement", "Peut voir un objet paiement"), + ('view_paiement', _("Can see a payement's details")), ) + verbose_name = _("Payment method") + verbose_name_plural = _("Payment methods") def __str__(self): return self.moyen @@ -426,61 +527,71 @@ class Paiement(RevMixin, AclMixin, models.Model): def save(self, *args, **kwargs): """Un seul type de paiement peut-etre cheque...""" + # TODO : translate docstring to English if Paiement.objects.filter(type_paiement=1).count() > 1: - raise ValidationError("On ne peut avoir plusieurs mode de paiement\ - chèque") + raise ValidationError( + _("You cannot have multiple payment method of type cheque") + ) super(Paiement, self).save(*args, **kwargs) class Cotisation(RevMixin, AclMixin, models.Model): """Objet cotisation, debut et fin, relié en onetoone à une vente""" - PRETTY_NAME = "Cotisations" + # TODO : translate docstring to English COTISATION_TYPE = ( - ('Connexion', 'Connexion'), - ('Adhesion', 'Adhesion'), - ('All', 'All'), + ('Connexion', _("Connexion")), + ('Adhesion', _("Adhesion")), + ('All', _("Both of them")), ) - vente = models.OneToOneField('Vente', on_delete=models.CASCADE, null=True) + # TODO : change vente to purchase + vente = models.OneToOneField( + 'Vente', + on_delete=models.CASCADE, + null=True, + verbose_name=_("Purchase") + ) type_cotisation = models.CharField( choices=COTISATION_TYPE, max_length=255, default='All', + verbose_name=_("Type of cotisation") + ) + date_start = models.DateTimeField( + verbose_name=_("Starting date") + ) + date_end = models.DateTimeField( + verbose_name=_("Ending date") ) - date_start = models.DateTimeField() - date_end = models.DateTimeField() class Meta: permissions = ( - ("view_cotisation", "Peut voir un objet cotisation"), - ("change_all_cotisation", "Superdroit, peut modifier toutes les cotisations"), + ('view_cotisation', _("Can see a cotisation's details")), + ('change_all_cotisation', _("Can edit the previous cotisations")), ) def can_edit(self, user_request, *args, **kwargs): if not user_request.has_perm('cotisations.change_cotisation'): - return False, u"Vous n'avez pas le droit d'éditer les cotisations" + return False, _("You don't have the right to edit a cotisation.") elif not user_request.has_perm('cotisations.change_all_cotisation') and\ (self.vente.facture.control or not self.vente.facture.valid): - return False, u"Vous n'avez pas le droit d'éditer une cotisation\ - controlée ou invalidée par un trésorier" + return False, _("You don't have the right to edit a cotisation already controlled or invalidated.") else: return True, None def can_delete(self, user_request, *args, **kwargs): if not user_request.has_perm('cotisations.delete_cotisation'): - return False, u"Vous n'avez pas le droit de supprimer une cotisations" + return False, _("You don't have the right to delete a cotisation.") if self.vente.facture.control or not self.vente.facture.valid: - return False, u"Vous ne pouvez pas supprimer une cotisations\ - contrôlée ou invalidée par un trésorier" + return False, _("You don't have the right to delete a cotisation already controlled or invalidated.") else: return True, None def can_view(self, user_request, *args, **kwargs): if not user_request.has_perm('cotisations.view_cotisation') and\ self.vente.facture.user != user_request: - return False, u"Vous ne pouvez pas afficher l'historique d'une\ - cotisation d'un autre user que vous sans droit cableur" + return False, _("You don't have the right to display someone else's cotisation history.") else: return True, None @@ -491,15 +602,18 @@ class Cotisation(RevMixin, AclMixin, models.Model): @receiver(post_save, sender=Cotisation) def cotisation_post_save(sender, **kwargs): """Après modification d'une cotisation, regeneration des services""" + # TODO : translate docstring to English regen('dns') regen('dhcp') regen('mac_ip_list') regen('mailing') +# TODO : should be name cotisation_post_delete @receiver(post_delete, sender=Cotisation) def vente_post_delete(sender, **kwargs): """Après suppression d'une vente, régénération des services""" + # TODO : translate docstring to English cotisation = kwargs['instance'] regen('mac_ip_list') regen('mailing') diff --git a/cotisations/payment.py b/cotisations/payment.py index f9a66bf6..a83660af 100644 --- a/cotisations/payment.py +++ b/cotisations/payment.py @@ -8,6 +8,7 @@ from django.contrib.auth.decorators import login_required from django.contrib import messages from django.views.decorators.csrf import csrf_exempt from django.utils.datastructures import MultiValueDictKeyError +from django.utils.translation import ugettext as _ from django.http import HttpResponse, HttpResponseBadRequest from collections import OrderedDict @@ -22,7 +23,9 @@ def accept_payment(request, factureid): facture = get_object_or_404(Facture, id=factureid) messages.success( request, - "Le paiement de {} € a été accepté.".format(facture.prix()) + _("The payment of %(amount)s € has been accepted.") % { + amount: facture.prix() + } ) return redirect(reverse('users:profil', kwargs={'userid':request.user.id})) @@ -32,7 +35,7 @@ def accept_payment(request, factureid): def refuse_payment(request): messages.error( request, - "Le paiement a été refusé." + _("The payment has been refused.") ) return redirect(reverse('users:profil', kwargs={'userid':request.user.id})) @@ -53,6 +56,7 @@ def ipn(request): idTransaction = request.POST['idTransaction'] # On vérifie que le paiement nous est destiné + # TODO : translate comment to English if not idTpe == AssoOption.get_cached_value('payment_id'): return HttpResponseBadRequest("HTTP/1.1 400 Bad Request") @@ -63,6 +67,7 @@ def ipn(request): facture = get_object_or_404(Facture, id=factureid) + # TODO : translate comments to English # On vérifie que le paiement est valide if not result: # Le paiement a échoué : on effectue les actions nécessaires (On indique qu'elle a échoué) diff --git a/cotisations/views.py b/cotisations/views.py index 545f9ebe..fd6256a9 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -36,6 +36,7 @@ from django.db import transaction from django.db.models import Q from django.forms import modelformset_factory, formset_factory from django.utils import timezone +from django.utils.translation import ugettext as _ from django.views.decorators.csrf import csrf_exempt from django.views.decorators.debug import sensitive_variables # Import des models, forms et fonctions re2o @@ -88,12 +89,16 @@ def new_facture(request, user, userid): enfin sauve la facture parente. TODO : simplifier cette fonction, déplacer l'intelligence coté models Facture et Vente.""" + # TODO : translate docstring to English + # TODO : change facture to invoice facture = Facture(user=user) + # TODO : change comment to English # Le template a besoin de connaitre les articles pour le js article_list = Article.objects.filter( Q(type_user='All') | Q(type_user=request.user.class_name) ) # On envoie la form fature et un formset d'articles + # TODO : change facture to invoice facture_form = NewFactureForm(request.POST or None, instance=facture) if request.user.is_class_club: article_formset = formset_factory(SelectClubArticleForm)(request.POST or None) @@ -104,26 +109,31 @@ def new_facture(request, user, userid): articles = article_formset # Si au moins un article est rempli if any(art.cleaned_data for art in articles): + # TODO : change solde to balance user_solde = OptionalUser.get_cached_value('user_solde') solde_negatif = OptionalUser.get_cached_value('solde_negatif') # Si on paye par solde, que l'option est activée, # on vérifie que le négatif n'est pas atteint if user_solde: + # TODO : change Paiement to Payment if new_facture_instance.paiement == Paiement.objects.get_or_create( moyen='solde' )[0]: prix_total = 0 for art_item in articles: if art_item.cleaned_data: + # change prix to price prix_total += art_item.cleaned_data['article']\ .prix*art_item.cleaned_data['quantity'] if float(user.solde) - float(prix_total) < solde_negatif: - messages.error(request, "Le solde est insuffisant pour\ - effectuer l'opération") + messages.error( + request, + _("Your balance is too low for this operation.") + ) return redirect(reverse( 'users:profil', kwargs={'userid': userid} - )) + )) new_facture_instance.save() for art_item in articles: if art_item.cleaned_data: @@ -142,34 +152,43 @@ def new_facture(request, user, userid): for art_item in articles if art_item.cleaned_data): messages.success( request, - "La cotisation a été prolongée\ - pour l'adhérent %s jusqu'au %s" % ( - user.pseudo, user.end_adhesion() - ) - ) + _("The cotisation of %(member_name)s has been \ + extended to %(end_date)s.") % { + member_name: user.pseudo, + end_date: user.end_adhesion() + } + ) else: - messages.success(request, "La facture a été crée") + messages.success( + request, + _("The invoice has been created.") + ) return redirect(reverse( 'users:profil', kwargs={'userid': userid} - )) + )) messages.error( request, - u"Il faut au moins un article valide pour créer une facture" - ) - return form({ - 'factureform': facture_form, - 'venteform': article_formset, - 'articlelist': article_list - }, 'cotisations/new_facture.html', request) + _("You need to choose at least one article.") + ) + return form( + { + 'factureform': facture_form, + 'venteform': article_formset, + 'articlelist': article_list + }, + 'cotisations/new_facture.html', request + ) +# TODO : change facture to invoice @login_required @can_change(Facture, 'pdf') def new_facture_pdf(request): """Permet de générer un pdf d'une facture. Réservée au trésorier, permet d'emettre des factures sans objet Vente ou Facture correspondant en bdd""" + # TODO : translate docstring to English facture_form = NewFactureFormPdf(request.POST or None) if facture_form.is_valid(): tbl = [] @@ -204,6 +223,7 @@ def new_facture_pdf(request): }, 'cotisations/facture.html', request) +# TODO : change facture to invoice @login_required @can_view(Facture) def facture_pdf(request, facture, factureid): @@ -211,7 +231,8 @@ def facture_pdf(request, facture, factureid): et génére une facture avec le total, le moyen de paiement, l'adresse de l'adhérent, etc. Réservée à self pour un user sans droits, les droits cableurs permettent d'afficher toute facture""" - + # TODO : translate docstring to English + # TODO : change vente to purchase ventes_objects = Vente.objects.all().filter(facture=facture) ventes = [] for vente in ventes_objects: @@ -233,12 +254,14 @@ def facture_pdf(request, facture, factureid): }) +# TODO : change facture to invoice @login_required @can_edit(Facture) def edit_facture(request, facture, factureid): """Permet l'édition d'une facture. On peut y éditer les ventes déjà effectuer, ou rendre une facture invalide (non payées, chèque en bois etc). Mets à jour les durée de cotisation attenantes""" + # TODO : translate docstring to English facture_form = EditFactureForm(request.POST or None, instance=facture, user=request.user) ventes_objects = Vente.objects.filter(facture=facture) vente_form_set = modelformset_factory( @@ -252,7 +275,10 @@ def edit_facture(request, facture, factureid): if facture_form.changed_data: facture_form.save() vente_form.save() - messages.success(request, "La facture a bien été modifiée") + messages.success( + request, + _("The invoice has been successfully edited.") + ) return redirect(reverse('cotisations:index')) return form({ 'factureform': facture_form, @@ -260,14 +286,18 @@ def edit_facture(request, facture, factureid): }, 'cotisations/edit_facture.html', request) +# TODO : change facture to invoice @login_required @can_delete(Facture) def del_facture(request, facture, factureid): """Suppression d'une facture. Supprime en cascade les ventes et cotisations filles""" + # TODO : translate docstring to English if request.method == "POST": - facture.delete() - messages.success(request, "La facture a été détruite") + messages.success( + request, + _("The invoice has been successfully deleted") + ) return redirect(reverse('cotisations:index')) return form({ 'objet': facture, @@ -275,11 +305,14 @@ def del_facture(request, facture, factureid): }, 'cotisations/delete.html', request) +# TODO : change solde to balance @login_required @can_create(Facture) @can_edit(User) def credit_solde(request, user, userid): """ Credit ou débit de solde """ + # TODO : translate docstring to English + # TODO : change facture to invoice facture = CreditSoldeForm(request.POST or None) if facture.is_valid(): facture_instance = facture.save(commit=False) @@ -292,7 +325,10 @@ def credit_solde(request, user, userid): number=1 ) new_vente.save() - messages.success(request, "Solde modifié") + messages.success( + request, + _("Banlance successfully updated.") + ) return redirect(reverse('cotisations:index')) return form({'factureform': facture, 'action_name' : 'Créditer'}, 'cotisations/facture.html', request) @@ -307,10 +343,14 @@ def add_article(request): aux articles en vente. La désignation, le prix... sont copiés à la création de la facture. Un changement de prix n'a PAS de conséquence sur les ventes déjà faites""" + # TODO : translate docstring to English article = ArticleForm(request.POST or None) if article.is_valid(): article.save() - messages.success(request, "L'article a été ajouté") + messages.success( + request, + _("The article has been successfully created.") + ) return redirect(reverse('cotisations:index-article')) return form({'factureform': article, 'action_name' : 'Ajouter'}, 'cotisations/facture.html', request) @@ -320,11 +360,15 @@ def add_article(request): def edit_article(request, article_instance, articleid): """Edition d'un article (designation, prix, etc) Réservé au trésorier""" + # TODO : translate dosctring to English article = ArticleForm(request.POST or None, instance=article_instance) if article.is_valid(): if article.changed_data: article.save() - messages.success(request, "Type d'article modifié") + messages.success( + request, + _("The article has been successfully edited.") + ) return redirect(reverse('cotisations:index-article')) return form({'factureform': article, 'action_name' : 'Editer'}, 'cotisations/facture.html', request) @@ -333,45 +377,62 @@ def edit_article(request, article_instance, articleid): @can_delete_set(Article) def del_article(request, instances): """Suppression d'un article en vente""" + # TODO : translate docstring to English article = DelArticleForm(request.POST or None, instances=instances) if article.is_valid(): article_del = article.cleaned_data['articles'] article_del.delete() - messages.success(request, "Le/les articles ont été supprimé") + messages.success( + request, + _("The article(s) have been successfully deleted.") + ) return redirect(reverse('cotisations:index-article')) return form({'factureform': article, 'action_name' : 'Supprimer'}, 'cotisations/facture.html', request) +# TODO : change paiement to payment @login_required @can_create(Paiement) def add_paiement(request): """Ajoute un moyen de paiement. Relié aux factures via foreign key""" + # TODO : translate docstring to English + # TODO : change paiement to Payment paiement = PaiementForm(request.POST or None) if paiement.is_valid(): paiement.save() - messages.success(request, "Le moyen de paiement a été ajouté") + messages.success( + request, + _("The payment method has been successfully created.") + ) return redirect(reverse('cotisations:index-paiement')) return form({'factureform': paiement, 'action_name' : 'Ajouter'}, 'cotisations/facture.html', request) +# TODO : chnage paiement to Payment @login_required @can_edit(Paiement) def edit_paiement(request, paiement_instance, paiementid): """Edition d'un moyen de paiement""" + # TODO : translate docstring to English paiement = PaiementForm(request.POST or None, instance=paiement_instance) if paiement.is_valid(): if paiement.changed_data: paiement.save() - messages.success(request, "Type de paiement modifié") + messages.success( + request, + _("The payement method has been successfully edited.") + ) return redirect(reverse('cotisations:index-paiement')) return form({'factureform': paiement, 'action_name' : 'Editer'}, 'cotisations/facture.html', request) +# TODO : change paiement to payment @login_required @can_delete_set(Paiement) def del_paiement(request, instances): """Suppression d'un moyen de paiement""" + # TODO : translate docstring to English paiement = DelPaiementForm(request.POST or None, instances=instances) if paiement.is_valid(): paiement_dels = paiement.cleaned_data['paiements'] @@ -380,67 +441,97 @@ def del_paiement(request, instances): paiement_del.delete() messages.success( request, - "Le moyen de paiement a été supprimé" - ) + _("The payment method %(method_name)s has been \ + successfully deleted") % { + method_name: paiement_del + } + ) except ProtectedError: messages.error( request, - "Le moyen de paiement %s est affecté à au moins une\ - facture, vous ne pouvez pas le supprimer" % paiement_del + _("The payment method %(method_name) can't be deleted \ + because there are invoices using it.") % { + method_name: paiement_del + } ) return redirect(reverse('cotisations:index-paiement')) return form({'factureform': paiement, 'action_name' : 'Supprimer'}, 'cotisations/facture.html', request) +# TODO : change banque to bank @login_required @can_create(Banque) def add_banque(request): """Ajoute une banque à la liste des banques""" + # TODO : tranlate docstring to English banque = BanqueForm(request.POST or None) if banque.is_valid(): banque.save() - messages.success(request, "La banque a été ajoutée") + messages.success( + request, + _("The bank has been successfully created.") + ) return redirect(reverse('cotisations:index-banque')) return form({'factureform': banque, 'action_name' : 'Ajouter'}, 'cotisations/facture.html', request) +# TODO : change banque to bank @login_required @can_edit(Banque) def edit_banque(request, banque_instance, banqueid): """Edite le nom d'une banque""" + # TODO : translate docstring to English banque = BanqueForm(request.POST or None, instance=banque_instance) if banque.is_valid(): if banque.changed_data: banque.save() - messages.success(request, "Banque modifiée") + messages.success( + request, + _("The bank has been successfully edited") + ) return redirect(reverse('cotisations:index-banque')) return form({'factureform': banque, 'action_name' : 'Editer'}, 'cotisations/facture.html', request) +# TODO : chnage banque to bank @login_required @can_delete_set(Banque) def del_banque(request, instances): """Supprime une banque""" + # TODO : translate docstring to English banque = DelBanqueForm(request.POST or None, instances=instances) if banque.is_valid(): banque_dels = banque.cleaned_data['banques'] for banque_del in banque_dels: try: banque_del.delete() - messages.success(request, "La banque a été supprimée") + messages.success( + request, + _("The bank %(bank_name)s has been successfully \ + deleted.") % { + bank_name: banque_del + } + ) except ProtectedError: - messages.error(request, "La banque %s est affectée à au moins\ - une facture, vous ne pouvez pas la supprimer" % banque_del) + messages.error( + request, + _("The bank %(bank_name)s can't be deleted \ + because there are invoices using it.") % { + bank_name: banque_del + } + ) return redirect(reverse('cotisations:index-banque')) return form({'factureform': banque, 'action_name' : 'Supprimer'}, 'cotisations/facture.html', request) +# TODO : change facture to invoice @login_required @can_view_all(Facture) @can_change(Facture, 'control') def control(request): """Pour le trésorier, vue pour controler en masse les factures.Case à cocher, pratique""" + # TODO : translate docstring to English pagination_number = GeneralOption.get_cached_value('pagination_number') facture_list = Facture.objects.select_related('user').select_related('paiement') facture_list = SortTable.sort( @@ -470,26 +561,31 @@ def control(request): @can_view_all(Article) def index_article(request): """Affiche l'ensemble des articles en vente""" + # TODO : translate docstrign to English article_list = Article.objects.order_by('name') return render(request, 'cotisations/index_article.html', { 'article_list': article_list }) +# TODO : change paiement to payment @login_required @can_view_all(Paiement) def index_paiement(request): """Affiche l'ensemble des moyens de paiement en vente""" + # TODO : translate docstring to English paiement_list = Paiement.objects.order_by('moyen') return render(request, 'cotisations/index_paiement.html', { 'paiement_list': paiement_list }) +# TODO : change banque to bank @login_required @can_view_all(Banque) def index_banque(request): """Affiche l'ensemble des banques""" + # TODO : translate docstring to English banque_list = Banque.objects.order_by('name') return render(request, 'cotisations/index_banque.html', { 'banque_list': banque_list @@ -500,6 +596,7 @@ def index_banque(request): @can_view_all(Facture) def index(request): """Affiche l'ensemble des factures, pour les cableurs et +""" + # TODO : translate docstring to English pagination_number = GeneralOption.get_cached_value('pagination_number') facture_list = Facture.objects.select_related('user')\ .select_related('paiement').prefetch_related('vente_set') @@ -515,6 +612,7 @@ def index(request): }) +# TODO : change facture to invoice @login_required def new_facture_solde(request, userid): """Creation d'une facture pour un user. Renvoie la liste des articles @@ -524,10 +622,12 @@ def new_facture_solde(request, userid): enfin sauve la facture parente. TODO : simplifier cette fonction, déplacer l'intelligence coté models Facture et Vente.""" + # TODO : translate docstring to English user = request.user facture = Facture(user=user) paiement, _created = Paiement.objects.get_or_create(moyen='Solde') facture.paiement = paiement + # TODO : translate comments to English # Le template a besoin de connaitre les articles pour le js article_list = Article.objects.filter( Q(type_user='All') | Q(type_user=request.user.class_name) @@ -551,12 +651,14 @@ def new_facture_solde(request, userid): prix_total += art_item.cleaned_data['article']\ .prix*art_item.cleaned_data['quantity'] if float(user.solde) - float(prix_total) < solde_negatif: - messages.error(request, "Le solde est insuffisant pour\ - effectuer l'opération") + messages.error( + request, + _("The balance is too low for this operation.") + ) return redirect(reverse( 'users:profil', kwargs={'userid': userid} - )) + )) facture.save() for art_item in articles: if art_item.cleaned_data: @@ -575,21 +677,25 @@ def new_facture_solde(request, userid): for art_item in articles if art_item.cleaned_data): messages.success( request, - "La cotisation a été prolongée\ - pour l'adhérent %s jusqu'au %s" % ( - user.pseudo, user.end_adhesion() - ) - ) + _("The balance of %(member_name)s has been successfully \ + extended to %(end_date)s") % { + member_name: user.pseudo, + end_date: user.end_adhesion() + } + ) else: - messages.success(request, "La facture a été crée") + messages.success( + request, + _("The invoice has been successuflly created.") + ) return redirect(reverse( 'users:profil', kwargs={'userid': userid} - )) + )) messages.error( request, - u"Il faut au moins un article valide pour créer une facture" - ) + _("You need to choose at least one article.") + ) return redirect(reverse( 'users:profil', kwargs={'userid': userid} @@ -601,12 +707,13 @@ def new_facture_solde(request, userid): }, 'cotisations/new_facture_solde.html', request) +# TODO : change recharge to reload @login_required def recharge(request): if AssoOption.get_cached_value('payment') == 'NONE': messages.error( request, - "Le paiement en ligne est désactivé." + _("Online payment is disabled.") ) return redirect(reverse( 'users:profil',