Browse Source

Add abstract model CoinLdapSyncModel which call sync_to_ldap method when saving. If this method fail (LDAP unreachable or other errors then nothing saved in database)

Fabs 11 years ago
parent
commit
55494cb950
2 changed files with 76 additions and 36 deletions
  1. 35 36
      coin/members/models.py
  2. 41 0
      coin/models.py

+ 35 - 36
coin/members/models.py

@@ -8,7 +8,6 @@ import unicodedata
 import string
 import datetime
 from django.db import models
-from django.db import transaction
 from django.db.models import Q
 from django.db.models.signals import post_save, pre_save, post_delete
 from django.dispatch import receiver
@@ -16,8 +15,10 @@ from django.core import exceptions
 from ldapdb.models.fields import CharField, IntegerField, ListField
 from south.modelsinspector import add_ignored_fields
 from coin.offers.models import OfferSubscription
+from coin.models import CoinLdapModel
 
-class Member(models.Model):
+
+class Member(CoinLdapSyncModel):
 
     MEMBER_TYPE_CHOICES = (
         ('personne_physique', 'Personne physique'),
@@ -36,11 +37,11 @@ class Member(models.Model):
     first_name = models.CharField(max_length=200, verbose_name=u'Prénom')
     last_name = models.CharField(max_length=200, verbose_name=u'Nom')
     ldap_cn = models.CharField(max_length=200, blank=True,
-        help_text='Clé avec le LDAP. Laisser vide pour la générer'
-                  'automatiquement')
+                               help_text='Clé avec le LDAP. Laisser vide pour '
+                               'la générer automatiquement')
     organization_name = models.CharField(max_length=200, blank=True,
-        verbose_name='Nom de l\'organisme',
-        help_text='Pour une personne morale')
+                                         verbose_name='Nom de l\'organisme',
+                                         help_text='Pour une personne morale')
     email = models.EmailField(max_length=254, verbose_name=u'Courriel')
     home_phone_number = models.CharField(max_length=25, blank=True,
                                          verbose_name=u'Téléphone fixe')
@@ -59,7 +60,8 @@ class Member(models.Model):
                                   default=datetime.date.today,
                                   verbose_name='Date de première adhésion')
     resign_date = models.DateField(null=True, blank=True,
-        verbose_name='Date de départ de l\'association')
+                                   verbose_name='Date de départ de '
+                                   'l\'association')
 
     def __unicode__(self):
         name = self.first_name + ' ' + self.last_name
@@ -110,9 +112,19 @@ class Member(models.Model):
         Update LDAP data when a member is saved
         """
         if not creation:
-            ldap_user = LdapUser.objects.get(pk=self.ldap_cn)
+            if not self.ldap_cn:
+                # ldap_cn must be set to update a member in LDAP
+                raise Exception(
+                    'Can\'t sync with LDAP because missing ldap_cn value '
+                    'of Member : %s' % self)
+            else:
+                ldap_user = LdapUser.objects.get(pk=self.ldap_cn)
 
         if creation:
+            if not self.ldap_cn:
+                # If no ldap_cn specified for creation, generate one
+                self.ldap_cn = self.get_automatic_ldap_cn()
+
             max_uidNumber = LdapUser.objects.order_by('-uidNumber')[0].uidNumber
 
             ldap_user = LdapUser()
@@ -130,30 +142,6 @@ class Member(models.Model):
             ldap_group.members.append(ldap_user.pk)
             ldap_group.save()
 
-    @transaction.commit_manually
-    def save(self, *args, **kwargs):
-        # Détermine si on est dans une création ou une sauvegarde
-        creation = (self.pk == None)
-
-        # Si pas de ldap_cn défint et création de l'objet, alors génère un
-        # ldap_cn depuis le nom et prénom
-        if not self.ldap_cn and creation:
-            self.ldap_cn = self.get_automatic_ldap_cn()
-
-        # Sauvegarde en base de donnée (mais sans commit, cf decorator)
-        super(Member, self).save(*args, **kwargs)
-
-        # Sauvegarde dans le LDAP
-        # Si la sauvegarde LDAP échoue, Rollback la sauvegarde en base, sinon
-        # commit
-        try:
-            self.sync_with_ldap(creation)
-        except:
-            transaction.rollback()
-            raise
-        else:
-            transaction.commit()
-
     class Meta:
         verbose_name = 'membre'
 
@@ -191,7 +179,7 @@ class MembershipFee(models.Model):
 
     def __unicode__(self):
         return (u'%s - %s - %i€' % (self.member, self.start_date,
-                                     self.amount))
+                                    self.amount))
 
     class Meta:
         verbose_name = 'cotisation'
@@ -224,6 +212,7 @@ class LdapUser(ldapdb.models.Model):
 
 
 class LdapGroup(ldapdb.models.Model):
+
     """
     Class for representing an LDAP group entry.
     """
@@ -242,10 +231,20 @@ class LdapGroup(ldapdb.models.Model):
     class Meta:
         managed = False  # Indique à South de ne pas gérer le model LdapGroup
 
-#Indique à South de ne pas gérer les models LdapUser et LdapGroup
+# Indique à South de ne pas gérer les models LdapUser et LdapGroup
 add_ignored_fields(["^ldapdb\.models\.fields"])
 
 
+@receiver(pre_save, sender=Member)
+def define_ldap_cn(sender, instance, **kwargs):
+    """
+    Lors de la sauvegarde d'un membre. Si le champ ldap_cn n'est pas définit,
+    le calcul automatiquement en fonction du nom et du prénom
+    """
+    if not instance.ldap_cn:
+        instance.ldap_cn = instance.get_automatic_ldap_cn()
+
+
 @receiver(pre_save, sender=LdapUser)
 def change_password(sender, instance, **kwargs):
     """
@@ -274,8 +273,8 @@ def define_display_name(sender, instance, **kwargs):
 
 @receiver(post_delete, sender=Member)
 def remove_ldap_user_from_coin_group_when_deleting_member(sender,
-                                                                 instance,
-                                                                 **kwargs):
+                                                          instance,
+                                                          **kwargs):
     """
     Lorsqu'un membre est supprimé du SI, son utilisateur LDAP correspondant est
     sorti du groupe "coin"

+ 41 - 0
coin/models.py

@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+from django.db import models
+from django.db import transaction
+
+
+class CoinLdapSyncModel(models.Model):
+
+    """
+    Ce modèle abstrait est à utiliser lorsqu'il s'agit de définir un modèle
+    à synchroniser avec le LDAP. Le modèle doit définir la methode sync_to_ldap
+    qui s'occupe du transfert vers le LDAP.
+    L'avantage de ce modèle est que si cette méthode échoue, la sauvegarde en
+    base de données échoue a son tour et rien n'est sauvegardé afin de conservé
+    l'intégrité.
+    """
+
+    def sync_to_ldap(self, creation):
+        raise NotImplementedError('Using CoinLdapSyncModel require '
+                                  'sync_to_ldap method being implemented')
+
+    @transaction.commit_manually
+    def save(self, *args, **kwargs):
+        # Détermine si on est dans une création ou une mise à jour
+        creation = (self.pk == None)
+
+        # Sauvegarde en base de donnée (mais sans commit, cf decorator)
+        super(CoinLdapSyncModel, self).save(*args, **kwargs)
+
+        # Sauvegarde dans le LDAP
+        # Si la sauvegarde LDAP échoue, Rollback la sauvegarde en base, sinon
+        # commit
+        try:
+            self.sync_with_ldap(creation)
+        except:
+            transaction.rollback()
+            raise
+        else:
+            transaction.commit()
+
+    class Meta:
+        abstract = True