From 784ef5d598079df179c4a8f021eec1c8ede4ebc2 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 28 Dec 2017 02:08:02 +0100 Subject: [PATCH] Utilisation de django field permission sur le controle facture --- cotisations/forms.py | 17 +++----- cotisations/models.py | 15 +++++-- cotisations/views.py | 10 +---- re2o/field_permissions.py | 88 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 23 deletions(-) create mode 100644 re2o/field_permissions.py diff --git a/cotisations/forms.py b/cotisations/forms.py index d72241eb..5845611a 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -43,6 +43,8 @@ from django.forms import ModelForm, Form from django.core.validators import MinValueValidator from .models import Article, Paiement, Facture, Banque +from re2o.field_permissions import FieldPermissionFormMixin + class NewFactureForm(ModelForm): """Creation d'une facture, moyen de paiement, banque et numero @@ -141,27 +143,18 @@ class NewFactureFormPdf(Form): ) -class EditFactureForm(NewFactureForm): +class EditFactureForm(FieldPermissionFormMixin, NewFactureForm): """Edition d'une facture : moyen de paiement, banque, user parent""" class Meta(NewFactureForm.Meta): - fields = ['paiement', 'banque', 'cheque', 'user'] + model = Facture + fields = '__all__' def __init__(self, *args, **kwargs): super(EditFactureForm, self).__init__(*args, **kwargs) self.fields['user'].label = 'Adherent' self.fields['user'].empty_label = "Séléctionner\ l'adhérent propriétaire" - - -class TrezEditFactureForm(EditFactureForm): - """Vue pour édition controle trésorier""" - class Meta(EditFactureForm.Meta): - fields = '__all__' - - def __init__(self, *args, **kwargs): - super(TrezEditFactureForm, self).__init__(*args, **kwargs) self.fields['valid'].label = 'Validité de la facture' - self.fields['control'].label = 'Contrôle de la facture' class ArticleForm(ModelForm): diff --git a/cotisations/models.py b/cotisations/models.py index d8da0be7..7bef2bcd 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -56,8 +56,10 @@ from django.db.models import Max from django.utils import timezone from machines.models import regen +from re2o.field_permissions import FieldPermissionModelMixin -class Facture(models.Model): + +class Facture(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 @@ -76,6 +78,9 @@ class Facture(models.Model): valid = models.BooleanField(default=True) control = models.BooleanField(default=False) + class Meta: + abstract = False + def prix(self): """Renvoie le prix brut sans les quantités. Méthode dépréciée""" @@ -144,12 +149,16 @@ class Facture(models.Model): else: return True, None - def can_change_control(user_request, *args, **kwargs): - return user_request.has_perms(('tresorier',)), "Vous ne pouvez pas éditer le controle sans droit trésorier" + def can_change_control(user, *args, **kwargs): + return user.has_perms(('tresorier',)), "Vous ne pouvez pas éditer le controle sans droit trésorier" def can_change_pdf(user_request, *args, **kwargs): return user_request.has_perms(('tresorier',)), "Vous ne pouvez pas éditer une facture sans droit trésorier" + field_permissions = { + 'control': can_change_control, + } + def __str__(self): return str(self.user) + ' ' + str(self.date) diff --git a/cotisations/views.py b/cotisations/views.py index cabaa8db..e9ba2b9c 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -57,7 +57,6 @@ from preferences.models import OptionalUser, AssoOption, GeneralOption from .models import Facture, Article, Vente, Paiement, Banque from .forms import ( NewFactureForm, - TrezEditFactureForm, EditFactureForm, ArticleForm, DelArticleForm, @@ -73,6 +72,7 @@ from .forms import ( from .tex import render_invoice + @login_required @can_create(Facture) @can_edit(User) @@ -243,13 +243,7 @@ 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""" - if request.user.has_perms(['tresorier']): - facture_form = TrezEditFactureForm( - request.POST or None, - instance=facture - ) - else: - facture_form = EditFactureForm(request.POST or None, instance=facture) + facture_form = EditFactureForm(request.POST or None, instance=facture, user=request.user) ventes_objects = Vente.objects.filter(facture=facture) vente_form_set = modelformset_factory( Vente, diff --git a/re2o/field_permissions.py b/re2o/field_permissions.py new file mode 100644 index 00000000..c3eb10be --- /dev/null +++ b/re2o/field_permissions.py @@ -0,0 +1,88 @@ +from django.db import models +from django import forms +from functools import partial + + +class FieldPermissionModelMixin: + field_permissions = {} # {'field_name': callable} + FIELD_PERM_CODENAME = 'can_change_{model}_{name}' + FIELD_PERMISSION_GETTER = 'can_change_{name}' + FIELD_PERMISSION_MISSING_DEFAULT = True + + class Meta: + abstract = True + + def has_perm(self, user, perm): + return user.has_perm(perm) # Never give 'obj' argument here + + def has_field_perm(self, user, field): + if field in self.field_permissions: + checks = self.field_permissions[field] + if not isinstance(checks, (list, tuple)): + checks = [checks] + for i, perm in enumerate(checks): + if callable(perm): + checks[i] = partial(perm, field=field) + + else: + checks = [] + + # Consult the optional field-specific hook. + getter_name = self.FIELD_PERMISSION_GETTER.format(name=field) + if hasattr(self, getter_name): + checks.append(getattr(self, getter_name)) + + # Try to find a static permission for the field + else: + perm_label = self.FIELD_PERM_CODENAME.format(**{ + 'model': self._meta.model_name, + 'name': field, + }) + if perm_label in dict(self._meta.permissions): + checks.append(perm_label) + + # No requirements means no restrictions. + if not len(checks): + return self.FIELD_PERMISSION_MISSING_DEFAULT + + # Try to find a user setting that qualifies them for permission. + for perm in checks: + if callable(perm): + result, plop = perm(user=user) + if result is not None: + return result + else: + result = user.has_perm(perm) # Don't supply 'obj', or else infinite recursion. + if result: + return True + + # If no requirement can be met, then permission is denied. + return False + +class FieldPermissionModel(FieldPermissionModelMixin, models.Model): + class Meta: + abstract = True + + +class FieldPermissionFormMixin: + """ + ModelForm logic for removing fields when a user is found not to have change permissions. + """ + def __init__(self, *args, **kwargs): + user = kwargs.pop('user') + + super(FieldPermissionFormMixin, self).__init__(*args, **kwargs) + + model = self.Meta.model + model_field_names = [f.name for f in model._meta.get_fields()] # this might be too broad + for name in model_field_names: + if name in self.fields and not self.instance.has_field_perm(user, field=name): + self.remove_unauthorized_field(name) + + def remove_unauthorized_field(self, name): + del self.fields[name] + + +class FieldPermissionForm(FieldPermissionFormMixin, forms.ModelForm): + pass +