models.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. # -*- coding: utf-8 -*-
  2. import ldapdb.models
  3. import pprint
  4. import os
  5. import base64
  6. import hashlib
  7. import unicodedata
  8. import string
  9. import datetime
  10. from django.db import models
  11. from django.db.models import Q
  12. from django.db.models.signals import post_save, pre_save, post_delete
  13. from django.dispatch import receiver
  14. from django.core import exceptions
  15. from ldapdb.models.fields import CharField, IntegerField, ListField
  16. from south.modelsinspector import add_ignored_fields
  17. from coin.offers.models import OfferSubscription
  18. class Member(models.Model):
  19. MEMBER_TYPE_CHOICES = (
  20. ('personne_physique', 'Personne physique'),
  21. ('personne_morale', 'Personne morale'),
  22. )
  23. MEMBER_STATUS_CHOICES = (
  24. ('adherent', 'Adhérent'),
  25. ('non_adherent', 'Non adhérent'),
  26. ('demande_adhesion', "Demande d'adhésion"),
  27. )
  28. status = models.CharField(max_length=50, choices=MEMBER_STATUS_CHOICES,
  29. default='non_adherent')
  30. type = models.CharField(max_length=20, choices=MEMBER_TYPE_CHOICES,
  31. default='personne_physique')
  32. first_name = models.CharField(max_length=200, verbose_name=u'Prénom')
  33. last_name = models.CharField(max_length=200, verbose_name=u'Nom')
  34. ldap_cn = models.CharField(max_length=200, blank=True,
  35. help_text='Clé avec le LDAP. Laisser vide pour la générer'
  36. 'automatiquement')
  37. organization_name = models.CharField(max_length=200, blank=True,
  38. verbose_name='Nom de l\'organisme',
  39. help_text='Pour une personne morale')
  40. email = models.EmailField(max_length=254, verbose_name=u'Courriel')
  41. home_phone_number = models.CharField(max_length=25, blank=True,
  42. verbose_name=u'Téléphone fixe')
  43. mobile_phone_number = models.CharField(max_length=25, blank=True,
  44. verbose_name=u'Téléphone mobile')
  45. address = models.TextField(verbose_name=u'Adresse')
  46. postal_code = models.CharField(max_length=15,
  47. verbose_name=u'Code postal')
  48. city = models.CharField(max_length=200,
  49. verbose_name=u'Commune')
  50. country = models.CharField(max_length=200,
  51. default='France',
  52. verbose_name=u'Pays')
  53. entry_date = models.DateField(null=False,
  54. blank=False,
  55. default=datetime.date.today,
  56. verbose_name='Date de première adhésion')
  57. resign_date = models.DateField(null=True, blank=True,
  58. verbose_name='Date de départ de l\'association')
  59. def __unicode__(self):
  60. name = self.first_name + ' ' + self.last_name
  61. if (self.organization_name):
  62. name += ' (%s)' % self.organization_name
  63. return name
  64. # Renvoi la date de fin de la dernière cotisation du membre
  65. def end_date_of_membership(self):
  66. try:
  67. return self.membership_fees.order_by('-end_date')[0].end_date
  68. except:
  69. return None
  70. def change_password(self, new_password):
  71. ldap_user = LdapUser.objects.get(pk=self.ldap_cn)
  72. ldap_user.password = new_password
  73. ldap_user.save()
  74. def get_active_subscriptions(self, date=datetime.date.today()):
  75. return OfferSubscription.objects.filter(
  76. Q(member__exact=self.pk),
  77. Q(subscription_date__lte=date),
  78. Q(resign_date__isnull=True) | Q(resign_date__gte=date))
  79. def save(self, *args, **kwargs):
  80. create = (self.pk == None)
  81. # If not ldap_cn defined and creation
  82. if not self.ldap_cn and create:
  83. self.ldap_cn = get_ldap_cn(self.first_name, self.last_name)
  84. # Save in ldap and in database
  85. try:
  86. sync_member_with_ldap_user(self, create)
  87. super(Member, self).save(*args, **kwargs)
  88. except:
  89. pass
  90. class Meta:
  91. verbose_name = 'membre'
  92. class CryptoKey(models.Model):
  93. KEY_TYPE_CHOICES = (('RSA', 'RSA'), ('GPG', 'GPG'))
  94. type = models.CharField(max_length=3, choices=KEY_TYPE_CHOICES)
  95. key = models.TextField(verbose_name=u'Clé')
  96. member = models.ForeignKey('Member', verbose_name=u'Membre')
  97. def __unicode__(self):
  98. return u'Clé %s de %s' % (self.type, self.member)
  99. class Meta:
  100. verbose_name = 'clé'
  101. class MembershipFee(models.Model):
  102. member = models.ForeignKey('Member', related_name='membership_fees',
  103. verbose_name=u'Membre')
  104. amount = models.IntegerField(null=False, default='20', help_text='en €',
  105. verbose_name=u'Montant')
  106. start_date = models.DateField(
  107. null=False,
  108. blank=False,
  109. default=datetime.date.today,
  110. verbose_name='Date de début de cotisation')
  111. end_date = models.DateField(
  112. null=False,
  113. blank=False,
  114. default=datetime.date.today() + datetime.timedelta(365),
  115. verbose_name='Date de fin de cotisation')
  116. def __unicode__(self):
  117. return (u'%s - %s - %i€' % (self.member, self.start_date,
  118. self.amount))
  119. class Meta:
  120. verbose_name = 'cotisation'
  121. class LdapUser(ldapdb.models.Model):
  122. # TODO: déplacer ligne suivante dans settings.py
  123. base_dn = "ou=users,ou=unix,o=ILLYSE,l=Villeurbanne,st=RHA,c=FR"
  124. object_classes = ['inetOrgPerson', 'organizationalPerson', 'person',
  125. 'top', 'posixAccount']
  126. uid = CharField(db_column='uid', unique=True, max_length=255)
  127. nick_name = CharField(db_column='cn', unique=True, primary_key=True,
  128. max_length=255)
  129. first_name = CharField(db_column='givenName', max_length=255)
  130. last_name = CharField(db_column='sn', max_length=255)
  131. display_name = CharField(db_column='displayName', max_length=255,
  132. blank=True)
  133. password = CharField(db_column='userPassword', max_length=255)
  134. uidNumber = IntegerField(db_column='uidNumber', unique=True)
  135. gidNumber = IntegerField(db_column='gidNumber', default=2000)
  136. homeDirectory = CharField(db_column='homeDirectory', max_length=255,
  137. default='/tmp')
  138. def __unicode__(self):
  139. return self.display_name
  140. class Meta:
  141. managed = False # Indique à South de ne pas gérer le model LdapUser
  142. class LdapGroup(ldapdb.models.Model):
  143. """
  144. Class for representing an LDAP group entry.
  145. """
  146. # LDAP meta-data
  147. base_dn = "ou=groups,ou=unix,o=ILLYSE,l=Villeurbanne,st=RHA,c=FR"
  148. object_classes = ['posixGroup']
  149. # posixGroup attributes
  150. gid = IntegerField(db_column='gidNumber', unique=True)
  151. name = CharField(db_column='cn', max_length=200, primary_key=True)
  152. members = ListField(db_column='memberUid')
  153. def __unicode__(self):
  154. return self.name
  155. class Meta:
  156. managed = False # Indique à South de ne pas gérer le model LdapGroup
  157. #Indique à South de ne pas gérer les models LdapUser et LdapGroup
  158. add_ignored_fields(["^ldapdb\.models\.fields"])
  159. @receiver(pre_save, sender=LdapUser)
  160. def change_password(sender, instance, **kwargs):
  161. """
  162. Lors de la sauvegarde d'un utilisateur Ldap, cette fonction est exécutée
  163. avant la sauvegarde pour chiffrer le mot de passe s'il est définit
  164. et s'il n'est pas déjà chiffré
  165. """
  166. # Si le mot de passe est définit et n'est pas déjà chiffré,
  167. # alors ça le chiffre
  168. if instance.password and not instance.password.startswith('{SSHA}'):
  169. salt = os.urandom(8).encode('hex')
  170. digest = hashlib.sha1(instance.password + salt).digest()
  171. instance.password = '{SSHA}' + base64.b64encode(digest + salt)
  172. @receiver(pre_save, sender=LdapUser)
  173. def define_display_name(sender, instance, **kwargs):
  174. """
  175. Lors de la sauvegarde d'un utilisateur Ldap, le champ display_name est la
  176. concaténation de first_name et last_name
  177. """
  178. if not instance.display_name:
  179. instance.display_name = '%s %s' % (instance.first_name,
  180. instance.last_name)
  181. def get_ldap_cn(first_name, last_name):
  182. """
  183. Calcul le login / ldap_cn automatiquement en fonction du nom et du prénom
  184. """
  185. # Première lettre de chaque partie du prénom
  186. first_name_letters = ''.join(
  187. [c[0] for c in first_name.split('-')]
  188. )
  189. # Concaténer avec nom de famille
  190. ldap_cn = ('%s%s' % (first_name_letters, last_name))
  191. # Remplacer ou enlever les caractères non ascii
  192. ldap_cn = unicodedata.normalize('NFD', ldap_cn)\
  193. .encode('ascii', 'ignore')
  194. # Enlever ponctuation et espace
  195. ldap_cn = ldap_cn.translate(None, string.punctuation + ' ')
  196. # En minuscule
  197. ldap_cn = ldap_cn.lower()
  198. return ldap_cn
  199. def sync_member_with_ldap_user(member, created):
  200. """
  201. Update LDAP data when a member is saved
  202. """
  203. if not created:
  204. ldap_user = LdapUser.objects.get(pk=member.ldap_cn)
  205. if created:
  206. max_uidNumber = LdapUser.objects.order_by('-uidNumber')[0].uidNumber
  207. ldap_user = LdapUser()
  208. ldap_user.pk = member.ldap_cn
  209. ldap_user.uid = member.ldap_cn
  210. ldap_user.nick_name = member.ldap_cn
  211. ldap_user.uidNumber = max_uidNumber + 1
  212. ldap_user.last_name = member.last_name
  213. ldap_user.first_name = member.first_name
  214. ldap_user.save()
  215. if created:
  216. ldap_group = LdapGroup.objects.get(pk='coin')
  217. ldap_group.members.append(ldap_user.pk)
  218. ldap_group.save()
  219. @receiver(post_delete, sender=Member)
  220. def remove_ldap_user_from_coin_group_when_deleting_member(sender,
  221. instance,
  222. **kwargs):
  223. """
  224. Lorsqu'un membre est supprimé du SI, son utilisateur LDAP correspondant est
  225. sorti du groupe "coin"
  226. """
  227. ldap_group = LdapGroup.objects.get(pk='coin')
  228. if instance.ldap_cn in ldap_group.members:
  229. ldap_group.members.remove(instance.ldap_cn)
  230. ldap_group.save()
  231. #==============================================================================
  232. # @receiver(pre_save, sender = LdapUser)
  233. # def ssha_password(sender, **kwargs):
  234. # if not kwargs['instance'].password.startswith('{SSHA}'):
  235. # salt = os.urandom(8).encode('hex')
  236. # kwargs['instance'].password = '{SSHA}' + base64.b64encode(
  237. # hashlib.sha1(obj.password + salt).digest() + salt)
  238. #==============================================================================