3 Commits 1144d992e3 ... 00a8e6b1a2

Author SHA1 Message Date
  SimonBoulier 00a8e6b1a2 Replace get_manageable_offers and get_manageable_users by manageable_by, an operation of the manager 7 years ago
  SimonBoulier 7b7d9d5f7b Automatically generate the codename of a RowLevelPermission 7 years ago
  SimonBoulier 9ffec6ae92 Add a .distinct() 7 years ago
4 changed files with 109 additions and 45 deletions
  1. 10 3
      coin/members/admin.py
  2. 76 37
      coin/members/models.py
  3. 5 5
      coin/offers/admin.py
  4. 18 0
      coin/offers/models.py

+ 10 - 3
coin/members/admin.py

@@ -6,6 +6,7 @@ from django.contrib import admin
 from django.contrib import messages
 from django.contrib.auth.admin import UserAdmin
 from django.contrib.auth.models import Group, Permission
+from django.contrib.contenttypes.models import ContentType
 from django.http import HttpResponseRedirect
 from django.conf.urls import url
 from django.db.models.query import QuerySet
@@ -60,7 +61,7 @@ class OfferSubscriptionInline(admin.TabularInline):
             return super(OfferSubscriptionInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
         else:
             if db_field.name == "offer":
-                kwargs["queryset"] = RowLevelPermission.get_manageable_offers(request.user)
+                kwargs["queryset"] = Offer.objects.manageable_by(request.user)
             return super(OfferSubscriptionInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
     # pas très beau
@@ -73,6 +74,7 @@ class OfferSubscriptionInline(admin.TabularInline):
             return request.user.is_superuser
 
     # sinon on pourrait supprimer les abo qu'on ne peut pas gérer
+    # pourrait peut-être être plus fin, obj réfère ici au member de la page
     def has_delete_permission(self, request, obj=None):
         return request.user.is_superuser
 
@@ -140,7 +142,7 @@ class MemberAdmin(UserAdmin):
         if request.user.is_superuser:
             return qs
         else:
-            offers = RowLevelPermission.get_manageable_offers(request.user)
+            offers = Offer.objects.manageable_by(request.user)
             return qs.filter(offersubscription__offer__in=offers).distinct()
 
     def get_readonly_fields(self, request, obj=None):
@@ -253,9 +255,14 @@ class MembershipFeeAdmin(admin.ModelAdmin):
                     'payment_date')
     form = autocomplete_light.modelform_factory(MembershipFee, fields='__all__')
 
+class RowLevelPermissionAdmin(admin.ModelAdmin):
+    def get_changeform_initial_data(self, request):
+        return {'content_type': ContentType.objects.get_for_model(OfferSubscription)}
+
+
 
 admin.site.register(Member, MemberAdmin)
 admin.site.register(MembershipFee, MembershipFeeAdmin)
 # admin.site.unregister(Group)
 # admin.site.register(LdapUser, LdapUserAdmin)
-admin.site.register(RowLevelPermission)
+admin.site.register(RowLevelPermission, RowLevelPermissionAdmin)

+ 76 - 37
coin/members/models.py

@@ -9,7 +9,7 @@ from django.db import models
 from django.db.models import Q, Max
 from django.db.models.signals import pre_save
 from django.dispatch import receiver
-from django.contrib.auth.models import AbstractUser, Permission
+from django.contrib.auth.models import AbstractUser, Permission, UserManager
 from django.contrib.contenttypes.models import ContentType
 from django.conf import settings
 from django.core.validators import RegexValidator
@@ -22,6 +22,19 @@ from coin.mixins import CoinLdapSyncMixin
 from coin import utils
 
 
+
+class MemberManager(UserManager):
+    def manageable_by(self, user):
+        """" Renvoie la liste des members que l'utilisateur est autorisé à voir
+        dans l'interface d'administration.
+        """
+        if user.is_superuser:
+            return super(MemberManager, self).all()
+        else:
+            offers = Offer.objects.manageable_by(user)
+            return super(MemberManager, self).filter(offersubscription__offer__in=offers).distinct()
+
+
 class Member(CoinLdapSyncMixin, AbstractUser):
 
     # USERNAME_FIELD = 'login'
@@ -81,6 +94,8 @@ class Member(CoinLdapSyncMixin, AbstractUser):
         default=True, verbose_name='relance de cotisation',
         help_text='Précise si l\'utilisateur doit recevoir des mails de relance pour la cotisation. Certains membres n\'ont pas à recevoir de relance (prélèvement automatique, membres d\'honneurs, etc.)')
 
+    objects = MemberManager()
+
     # Following fields are managed by the parent class AbstractUser :
     # username, first_name, last_name, email
     # However we hack the model to force theses fields to be required. (see
@@ -494,42 +509,6 @@ class LdapUser(ldapdb.models.Model):
 # managed = False  # Indique à Django de ne pas intégrer ce model en base
 
 
-class RowLevelPermission(Permission):
-    offer = models.ForeignKey(
-        'offers.Offer', null=True, verbose_name="Offre",
-        help_text="Offre dont l'utilisateur est autorisé à voir et modifier les membres et les abonnements.")
-    description = models.TextField(blank=True)
-
-    @classmethod
-    def get_manageable_offers(cls, user):
-        """" Renvoie la liste des offres dont l'utilisateur est autorisé à
-        voir les membres et les abonnements dans l'interface d'administration.
-        """
-        # toutes les permissions appliquées à cet utilisateur
-        # (liste de chaines de caractères)
-        perms = user.get_all_permissions()
-        allowedcodenames = [ s.split('offers.',1)[1] for s in perms if s.startswith('offers.')]
-        # parmi toutes les RowLevelpermission, celles qui sont relatives à des OfferSubscription et qui sont dans allowedcodenames
-        rowperms = cls.objects.filter(content_type=ContentType.objects.get_for_model(OfferSubscription), codename__in=allowedcodenames)
-        # toutes les Offers pour lesquelles il existe une RowLevelpermission correspondante dans rowperms
-        return Offer.objects.filter(rowlevelpermission__in=rowperms) # @JocelynD: un petit .distinct() ici non ?
-
-    @classmethod
-    def get_manageable_users(cls, user):
-        """" Renvoie la liste des members que l'utilisateur est autorisé à voir
-        dans l'interface d'administration.
-        """
-        if user.is_superuser:
-            return Member.objects.all()
-        else:
-            offers = RowLevelPermission.get_manageable_offers(user)
-            return Member.objects.filter(offersubscription__offer__in=offers).distinct()
-
-    class Meta:
-        verbose_name = 'permission fine'
-        verbose_name_plural = 'permissions fines'
-
-
 
 @receiver(pre_save, sender=Member)
 def define_username(sender, instance, **kwargs):
@@ -550,3 +529,63 @@ def define_display_name(sender, instance, **kwargs):
     if not instance.display_name:
         instance.display_name = '%s %s' % (instance.first_name,
                                            instance.last_name)
+
+
+
+class RowLevelPermission(Permission):
+    offer = models.ForeignKey(
+        'offers.Offer', null=True, verbose_name="Offre",
+        help_text="Offre dont l'utilisateur est autorisé à voir et modifier les membres et les abonnements.")
+    description = models.TextField(blank=True)
+
+    class Meta:
+        verbose_name = 'permission fine'
+        verbose_name_plural = 'permissions fines'
+
+
+RowLevelPermission._meta.get_field('codename').blank = True
+RowLevelPermission._meta.get_field('codename').help_text = 'Laisser vide pour le générer automatiquement'
+RowLevelPermission._meta.get_field('content_type').help_text = "Garder 'abonnement' pour une utilisation normale"
+
+def get_automatic_codename(perm):
+    """
+    Calcule le codename automatiquement en fonction du name.
+    """
+    if perm.name:
+        codename = perm.name
+    else:
+        raise Exception('Il n\'y a pas sufissement d\'informations pour déterminer un codename automatiquement')
+
+    # Remplacer ou enlever les caractères non ascii
+    codename = unicodedata.normalize('NFD', codename)\
+        .encode('ascii', 'ignore')
+    # Enlever ponctuation (sauf _-.) et espace
+    punctuation = ('!"#$%&\'()*+,/:;<=>?@[\\]^`{|}~').encode('ascii')
+    codename = codename.translate(None, punctuation)
+    # En minuscule
+    codename = codename.lower()
+    # Maximum de 30 char
+    codename = codename[:30]
+    codename = codename.strip().replace(' ', '-')
+
+    # Recherche dans les membres existants un codename identique
+    perm = Permission.objects.filter(codename=codename)
+    base_codename = codename
+    incr = 2
+    # Tant qu'une permission est trouvée, incrémente un entier à la fin
+    while perm:
+        codename = base_codename + str(incr)
+        perm = Permission.objects.filter(codename=codename)
+        incr += 1
+
+    return codename
+
+
+@receiver(pre_save, sender=RowLevelPermission)
+def define_codename(sender, instance, **kwargs):
+    """
+    Lors de la sauvegarde d'une RowLevelPermission. Si le champ codename n'est pas définit,
+    le calcul automatiquement.
+    """
+    if not instance.codename:
+        instance.codename = get_automatic_codename(instance)

+ 5 - 5
coin/offers/admin.py

@@ -5,7 +5,7 @@ from django.contrib import admin
 from django.db.models import Q
 from polymorphic.admin import PolymorphicChildModelAdmin
 
-from coin.members.models import RowLevelPermission
+from coin.members.models import Member
 from coin.offers.models import Offer, OfferSubscription
 from coin.offers.offersubscription_filter import\
             OfferSubscriptionTerminationFilter,\
@@ -59,10 +59,10 @@ class OfferSubscriptionAdmin(admin.ModelAdmin):
             return super(OfferSubscriptionAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
         else:
             if db_field.name == "member":
-                kwargs["queryset"] = RowLevelPermission.get_manageable_users(request.user)
+                kwargs["queryset"] = Member.objects.manageable_by(request.user)
             if db_field.name == "offer":
-                # pouah c'est pas beau, faut faire mieux, ça serait bien que get_manageable_offers renvoie un QuerrySet plutôt qu'une liste
-                kwargs["queryset"] = Offer.objects.filter(id__in=[p.id for p in RowLevelPermission.get_manageable_offers(request.user)])
+                # pouah c'est pas beau, faut faire mieux, ça serait bien que manageable_by renvoie un QuerrySet plutôt qu'une liste
+                kwargs["queryset"] = Offer.objects.filter(id__in=[p.id for p in Offer.objects.manageable_by(request.user)])
             return super(OfferSubscriptionAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
     # Si pas super user on restreint la liste des offres que l'on peut voir
@@ -71,7 +71,7 @@ class OfferSubscriptionAdmin(admin.ModelAdmin):
         if request.user.is_superuser:
             return qs
         else:
-            offers = RowLevelPermission.get_manageable_offers(request.user)
+            offers = Offer.objects.manageable_by(request.user)
             return qs.filter(offer__in=offers)
 
     def get_inline_instances(self, request, obj=None):

+ 18 - 0
coin/offers/models.py

@@ -7,8 +7,24 @@ from django.conf import settings
 from django.db import models
 from django.db.models import Count, Q
 from django.core.validators import MinValueValidator
+from django.contrib.contenttypes.models import ContentType
 
 
+class OfferManager(models.Manager):
+    def manageable_by(self, user):
+        """" Renvoie la liste des offres dont l'utilisateur est autorisé à
+        voir les membres et les abonnements dans l'interface d'administration.
+        """
+        from coin.members.models import RowLevelPermission
+        # toutes les permissions appliquées à cet utilisateur
+        # (liste de chaines de caractères)
+        perms = user.get_all_permissions()
+        allowedcodenames = [ s.split('offers.',1)[1] for s in perms if s.startswith('offers.')]
+        # parmi toutes les RowLevelPermission, celles qui sont relatives à des OfferSubscription et qui sont dans allowedcodenames
+        rowperms = RowLevelPermission.objects.filter(content_type=ContentType.objects.get_for_model(OfferSubscription), codename__in=allowedcodenames)
+        # toutes les Offers pour lesquelles il existe une RowLevelpermission correspondante dans rowperms
+        return super(OfferManager, self).filter(rowlevelpermission__in=rowperms).distinct()
+
 class Offer(models.Model):
     """Description of an offer available to subscribers.
 
@@ -44,6 +60,8 @@ class Offer(models.Model):
                                        verbose_name='n\'est pas facturable',
                                        help_text='L\'offre ne sera pas facturée par la commande charge_members')
 
+    objects = OfferManager()
+
     def get_configuration_type_display(self):
         """
         Renvoi le nom affichable du type de configuration