123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- # -*- 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 django.contrib.auth.models import AbstractUser
- from django.conf import settings
- from ldapdb.models.fields import CharField, IntegerField, ListField
- from south.modelsinspector import add_ignored_fields
- from coin.offers.models import OfferSubscription
- from coin.mixins import CoinLdapSyncMixin
- from coin import utils
- class Member(CoinLdapSyncMixin, AbstractUser):
- # USERNAME_FIELD = 'login'
- REQUIRED_FIELDS = ['first_name', 'last_name', 'email', ]
- MEMBER_TYPE_CHOICES = (
- ('natural_person', 'Personne physique'),
- ('legal_entity', 'Personne morale'),
- )
- MEMBER_STATUS_CHOICES = (
- ('member', 'Adhérent'),
- ('not_member', 'Non adhérent'),
- ('pending', "Demande d'adhésion"),
- )
- status = models.CharField(max_length=50, choices=MEMBER_STATUS_CHOICES,
- default='pending')
- type = models.CharField(max_length=20, choices=MEMBER_TYPE_CHOICES,
- default='natural_person')
- organization_name = models.CharField(max_length=200, blank=True,
- verbose_name='Nom de l\'organisme',
- help_text='Pour une personne morale')
- 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', blank=True, null=True)
- postal_code = models.CharField(max_length=15, blank=True, null=True,
- verbose_name=u'Code postal')
- city = models.CharField(max_length=200, blank=True, null=True,
- verbose_name=u'Commune')
- country = models.CharField(max_length=200, blank=True, null=True,
- 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')
- # Following fields are managed by the parent class AbstractUser :
- # username, first_name, last_name, email
- # This property is used to change password in LDAP. Used in sync_to_ldap.
- # Should not be defined manually. Prefer use set_password method that hash
- # passwords for both ldap and local db
- _password_ldap = None
- def __unicode__(self):
- name = self.first_name + ' ' + self.last_name
- if self.organization_name:
- name += ' (%s)' % self.organization_name
- return name
- def get_full_name(self):
- return '%s %s' % (self.first_name, self.last_name)
- def get_short_name(self):
- return self.username
- # 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 set_password(self, new_password, *args, **kwargs):
- """
- Définit le mot de passe a sauvegarder en base et dans le LDAP
- """
- super(Member, self).set_password(new_password, *args, **kwargs)
- self._password_ldap = utils.ldap_hash(new_password)
-
- def get_active_subscriptions(self, date=datetime.date.today()):
- """
- Return list of OfferSubscription which are active 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_username(self):
- """
- Calcul le username / 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
- username = ('%s%s' % (first_name_letters, self.last_name))
- # Remplacer ou enlever les caractères non ascii
- username = unicodedata.normalize('NFD', username)\
- .encode('ascii', 'ignore')
- # Enlever ponctuation et espace
- username = username.translate(None, string.punctuation + ' ')
- # En minuscule
- username = username.lower()
- return username
- def sync_to_ldap(self, creation, update_fields, *args, **kwargs):
- """
- Update LDAP data when a member is saved
- """
- # Do not perform LDAP query if no usefull fields to update are specified
- # in update_fields
- # Ex : at login, last_login field is updated by django auth module.
- if update_fields and set(['username', 'last_name', 'first_name']).isdisjoint(set(update_fields)):
- return
- # Fail if no username specified
- assert self.username, ('Can\'t sync with LDAP because missing username '
- 'value for the Member : %s' % self)
- if not creation:
- ldap_user = LdapUser.objects.get(pk=self.username)
- if creation:
- max_uid_number = LdapUser.objects.order_by('-uidNumber')[0].uidNumber
- ldap_user = LdapUser()
- ldap_user.pk = self.username
- ldap_user.uid = self.username
- ldap_user.nick_name = self.username
- ldap_user.uidNumber = max_uid_number + 1
- ldap_user.last_name = self.last_name
- ldap_user.first_name = self.first_name
-
- # If a password is definied in _password_ldap, change it in LDAP
- if self._password_ldap:
- # Make sure password is hashed
- ldap_user.password = utils.ldap_hash(self._password_ldap)
- 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.username, ('Can\'t delete from LDAP because missing '
- 'username value for the Member : %s' % self)
- #Delete user from LDAP
- ldap_user = LdapUser.objects.get(pk=self.username)
- ldap_user.delete()
- # 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.username in ldap_group.members:
- # ldap_group.members.remove(self.username)
- # 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')
- #TODO: config: valeur par défaut à externaliser dans la configuration
- 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):
- base_dn = settings.LDAP_USER_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):
- base_dn = settings.LDAP_GROUP_BASE_DN #"ou=groups,ou=unix,o=ILLYSE,l=Villeurbanne,st=RHA,c=FR"
- object_classes = ['posixGroup']
- 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_username(sender, instance, **kwargs):
- """
- Lors de la sauvegarde d'un membre. Si le champ username n'est pas définit,
- le calcul automatiquement en fonction du nom et du prénom
- """
- if not instance.username and not instance.pk:
- instance.username = instance.get_automatic_username()
- @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)
|