123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- # -*- coding: utf-8 -*-
- import ldapdb.models
- import unicodedata
- import string
- import datetime
- from django.db import models
- from django.db.models import Q
- from django.db.models.signals import pre_save
- from django.dispatch import receiver
- 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 CoinLdapSyncModel
- from coin import utils
- from django.contrib.auth.signals import user_logged_in
- from django.conf import settings
- class Member(CoinLdapSyncModel):
- MEMBER_TYPE_CHOICES = (
- ('personne_physique', 'Personne physique'),
- ('personne_morale', 'Personne morale'),
- )
- MEMBER_STATUS_CHOICES = (
- ('adherent', 'Adhérent'),
- ('non_adherent', 'Non adhérent'),
- ('demande_adhesion', "Demande d'adhésion"),
- )
- user = models.OneToOneField(settings.AUTH_USER_MODEL,
- null=True,
- default=None,
- verbose_name='Utilisateur Django',
- on_delete=models.SET_NULL)
- status = models.CharField(max_length=50, choices=MEMBER_STATUS_CHOICES,
- default='non_adherent')
- type = models.CharField(max_length=20, choices=MEMBER_TYPE_CHOICES,
- default='personne_physique')
- 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')
- organization_name = models.CharField(max_length=200, blank=True,
- 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')
- mobile_phone_number = models.CharField(max_length=25, blank=True,
- verbose_name=u'Téléphone mobile')
- # TODO: use a django module that provides an address model? (would
- # support more countries and address types)
- address = models.TextField(verbose_name=u'Adresse')
- postal_code = models.CharField(max_length=15,
- verbose_name=u'Code postal')
- city = models.CharField(max_length=200,
- verbose_name=u'Commune')
- country = models.CharField(max_length=200,
- default='France',
- verbose_name=u'Pays')
- entry_date = models.DateField(null=False,
- blank=False,
- default=datetime.date.today,
- verbose_name='Date de première adhésion')
- # TODO: for data retention, prevent deletion of a user object while
- # the resign date is recent enough (e.g. one year in France).
- resign_date = models.DateField(null=True, blank=True,
- verbose_name='Date de départ de '
- 'l\'association')
- def __unicode__(self):
- name = self.first_name + ' ' + self.last_name
- if self.organization_name:
- name += ' (%s)' % self.organization_name
- return name
- # Renvoie la date de fin de la dernière cotisation du membre
- def end_date_of_membership(self):
- try:
- return self.membership_fees.order_by('-end_date')[0].end_date
- #TODO: bad practice de tout matcher comme ca
- except:
- return None
- def is_paid_up(self):
- """
- True si le membre est à jour de cotisation. False sinon
- """
- if self.end_date_of_membership() and self.end_date_of_membership() >= datetime.date.today():
- return True
- else:
- return False
- def change_password(self, new_password):
- ldap_user = LdapUser.objects.get(pk=self.ldap_cn)
- ldap_user.password = new_password
- ldap_user.save()
- def get_active_subscriptions(self, date=datetime.date.today()):
- return OfferSubscription.objects.filter(
- Q(member__exact=self.pk),
- Q(subscription_date__lte=date),
- Q(resign_date__isnull=True) | Q(resign_date__gte=date))
- def get_automatic_ldap_cn(self):
- """
- Calcul le login / ldap_cn automatiquement en fonction
- du nom et du prénom
- """
- # Première lettre de chaque partie du prénom
- first_name_letters = ''.join(
- [c[0] for c in self.first_name.split('-')]
- )
- # Concaténer avec nom de famille
- ldap_cn = ('%s%s' % (first_name_letters, self.last_name))
- # Remplacer ou enlever les caractères non ascii
- ldap_cn = unicodedata.normalize('NFD', ldap_cn)\
- .encode('ascii', 'ignore')
- # Enlever ponctuation et espace
- ldap_cn = ldap_cn.translate(None, string.punctuation + ' ')
- # En minuscule
- ldap_cn = ldap_cn.lower()
- return ldap_cn
- def sync_to_ldap(self, creation):
- """
- Update LDAP data when a member is saved
- """
- assert self.ldap_cn, ('Can\'t sync with LDAP because missing ldap_cn '
- 'value for the Member : %s' % self)
- if not creation:
- ldap_user = LdapUser.objects.get(pk=self.ldap_cn)
- if creation:
- max_uid_number = LdapUser.objects.order_by('-uidNumber')[0].uidNumber
- ldap_user = LdapUser()
- ldap_user.pk = self.ldap_cn
- ldap_user.uid = self.ldap_cn
- ldap_user.nick_name = self.ldap_cn
- ldap_user.uidNumber = max_uid_number + 1
- ldap_user.last_name = self.last_name
- ldap_user.first_name = self.first_name
- ldap_user.save()
- if creation:
- ldap_group = LdapGroup.objects.get(pk='coin')
- ldap_group.members.append(ldap_user.pk)
- ldap_group.save()
- def delete_from_ldap(self):
- """
- Delete member from the LDAP
- """
- assert self.ldap_cn, ('Can\'t delete from LDAP because missing '
- 'ldap_cn value for the Member : %s' % self)
- # Lorsqu'un membre est supprimé du SI, son utilisateur LDAP
- # correspondant est sorti du groupe "coin" afin qu'il n'ait plus
- # accès au SI
- ldap_group = LdapGroup.objects.get(pk='coin')
- if self.ldap_cn in ldap_group.members:
- ldap_group.members.remove(self.ldap_cn)
- ldap_group.save()
- class Meta:
- verbose_name = 'membre'
- class CryptoKey(models.Model):
- KEY_TYPE_CHOICES = (('RSA', 'RSA'), ('GPG', 'GPG'))
- type = models.CharField(max_length=3, choices=KEY_TYPE_CHOICES)
- key = models.TextField(verbose_name=u'Clé')
- member = models.ForeignKey('Member', verbose_name=u'Membre')
- def __unicode__(self):
- return u'Clé %s de %s' % (self.type, self.member)
- class Meta:
- verbose_name = 'clé'
- class MembershipFee(models.Model):
- member = models.ForeignKey('Member', related_name='membership_fees',
- verbose_name=u'Membre')
- amount = models.IntegerField(null=False, default='20', help_text='en €',
- verbose_name=u'Montant')
- start_date = models.DateField(
- null=False,
- blank=False,
- default=datetime.date.today,
- verbose_name='Date de début de cotisation')
- end_date = models.DateField(
- null=False,
- blank=False,
- default=datetime.date.today() + datetime.timedelta(365),
- verbose_name='Date de fin de cotisation')
- def __unicode__(self):
- return u'%s - %s - %i€' % (self.member, self.start_date, self.amount)
- class Meta:
- verbose_name = 'cotisation'
- class LdapUser(ldapdb.models.Model):
- # TODO: déplacer ligne suivante dans settings.py
- base_dn = "ou=users,ou=unix,o=ILLYSE,l=Villeurbanne,st=RHA,c=FR"
- object_classes = ['inetOrgPerson', 'organizationalPerson', 'person',
- 'top', 'posixAccount']
- uid = CharField(db_column='uid', unique=True, max_length=255)
- nick_name = CharField(db_column='cn', unique=True, primary_key=True,
- max_length=255)
- first_name = CharField(db_column='givenName', max_length=255)
- last_name = CharField(db_column='sn', max_length=255)
- display_name = CharField(db_column='displayName', max_length=255,
- blank=True)
- password = CharField(db_column='userPassword', max_length=255)
- uidNumber = IntegerField(db_column='uidNumber', unique=True)
- gidNumber = IntegerField(db_column='gidNumber', default=2000)
- homeDirectory = CharField(db_column='homeDirectory', max_length=255,
- default='/tmp')
- def __unicode__(self):
- return self.display_name
- class Meta:
- managed = False # Indique à South de ne pas gérer le model LdapUser
- class LdapGroup(ldapdb.models.Model):
- """
- Class for representing an LDAP group entry.
- """
- #TODO: config à externaliser
- # LDAP meta-data
- base_dn = "ou=groups,ou=unix,o=ILLYSE,l=Villeurbanne,st=RHA,c=FR"
- object_classes = ['posixGroup']
- # posixGroup attributes
- gid = IntegerField(db_column='gidNumber', unique=True)
- name = CharField(db_column='cn', max_length=200, primary_key=True)
- members = ListField(db_column='memberUid')
- def __unicode__(self):
- return self.name
- 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
- 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 and not instance.pk:
- instance.ldap_cn = instance.get_automatic_ldap_cn()
- @receiver(pre_save, sender=LdapUser)
- def change_password(sender, instance, **kwargs):
- """
- Lors de la sauvegarde d'un utilisateur Ldap, cette fonction est exécutée
- avant la sauvegarde pour chiffrer le mot de passe s'il est définit
- et s'il n'est pas déjà chiffré
- """
- instance.password = utils.ldap_hash(instance.password)
- @receiver(pre_save, sender=LdapUser)
- def define_display_name(sender, instance, **kwargs):
- """
- Lors de la sauvegarde d'un utilisateur Ldap, le champ display_name est la
- concaténation de first_name et last_name
- """
- if not instance.display_name:
- instance.display_name = '%s %s' % (instance.first_name,
- instance.last_name)
- @receiver(user_logged_in)
- def define_member_user(sender, request, user, **kwargs):
- """
- Lorsqu'un utilisateur se connect avec succes, fait le lien entre le membre
- et l'utilisateur en définissant le champ user du model membre ayant le
- ldap_cn utilisé pour la connexion
- """
- try:
- member = Member.objects.get(ldap_cn=user.username)
- if not member.user:
- member.user = user
- member.save()
- elif member.user.username != user.username:
- raise Exception('Un membre avec cet ldap_cn existe en base de '
- 'donnée mais l\'utilisateur auquel il est rattaché '
- 'ne correspond pas.')
- except Member.DoesNotExist:
- if not user.is_superuser:
- raise
- #==============================================================================
- # @receiver(pre_save, sender = LdapUser)
- # def ssha_password(sender, **kwargs):
- # if not kwargs['instance'].password.startswith('{SSHA}'):
- # salt = os.urandom(8).encode('hex')
- # kwargs['instance'].password = '{SSHA}' + base64.b64encode(
- # hashlib.sha1(obj.password + salt).digest() + salt)
- #==============================================================================
|