|
@@ -7,6 +7,7 @@ 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 AbstractBaseUser, PermissionsMixin, BaseUserManager
|
|
|
from ldapdb.models.fields import CharField, IntegerField, ListField
|
|
|
from south.modelsinspector import add_ignored_fields
|
|
|
from coin.offers.models import OfferSubscription
|
|
@@ -15,33 +16,79 @@ from coin import utils
|
|
|
from django.contrib.auth.signals import user_logged_in
|
|
|
from django.conf import settings
|
|
|
|
|
|
+class CoinUserManager(BaseUserManager):
|
|
|
+ def create_user(self, login, first_name, last_name, email, password=None):
|
|
|
+ """
|
|
|
+ """
|
|
|
+ if not login:
|
|
|
+ raise ValueError('Users must have a login')
|
|
|
+ if not email:
|
|
|
+ raise ValueError('Users must have an email address')
|
|
|
+ if not first_name:
|
|
|
+ raise ValueError('Users must have a first name')
|
|
|
+ if not last_name:
|
|
|
+ raise ValueError('Users must have a last name')
|
|
|
+
|
|
|
+ user = self.model(
|
|
|
+ login=login,
|
|
|
+ email=self.normalize_email(email),
|
|
|
+ first_name=first_name,
|
|
|
+ last_name=last_name,
|
|
|
+ )
|
|
|
+
|
|
|
+ user.set_password(password, ldap=False)
|
|
|
+ user.save(using=self._db)
|
|
|
+ user.set_ldap_password(password)
|
|
|
+ return user
|
|
|
+
|
|
|
+ def create_superuser(self, login, first_name, last_name, email, password):
|
|
|
+ """
|
|
|
+ Creates and saves a superuser
|
|
|
+ """
|
|
|
+ user = self.create_user(
|
|
|
+ login=login,
|
|
|
+ email=email,
|
|
|
+ first_name=first_name,
|
|
|
+ last_name=last_name,
|
|
|
+ password=password
|
|
|
+ )
|
|
|
+ user.status = 'member'
|
|
|
+ user.is_superuser = True
|
|
|
+ user.is_staff = True
|
|
|
+ user.save(using=self._db)
|
|
|
+ return user
|
|
|
+
|
|
|
|
|
|
-class Member(CoinLdapSyncMixin, models.Model):
|
|
|
+class Member(CoinLdapSyncMixin, AbstractBaseUser, PermissionsMixin):
|
|
|
+
|
|
|
+ USERNAME_FIELD = 'login'
|
|
|
+ REQUIRED_FIELDS = ['first_name', 'last_name', 'email', ]
|
|
|
|
|
|
MEMBER_TYPE_CHOICES = (
|
|
|
- ('personne_physique', 'Personne physique'),
|
|
|
- ('personne_morale', 'Personne morale'),
|
|
|
+ ('natural_person', 'Personne physique'),
|
|
|
+ ('legal_entity', 'Personne morale'),
|
|
|
)
|
|
|
MEMBER_STATUS_CHOICES = (
|
|
|
- ('adherent', 'Adhérent'),
|
|
|
- ('non_adherent', 'Non adhérent'),
|
|
|
- ('demande_adhesion', "Demande d'adhésion"),
|
|
|
+ ('member', 'Adhérent'),
|
|
|
+ ('not_member', 'Non adhérent'),
|
|
|
+ ('pending', "Demande d'adhésion"),
|
|
|
)
|
|
|
|
|
|
- user = models.OneToOneField(settings.AUTH_USER_MODEL,
|
|
|
- null=True,
|
|
|
- default=None,
|
|
|
- verbose_name='Utilisateur Django',
|
|
|
- on_delete=models.SET_NULL)
|
|
|
+ objects = CoinUserManager()
|
|
|
+ is_active = models.BooleanField(default=True)
|
|
|
+ is_staff = models.BooleanField(default=False)
|
|
|
+
|
|
|
status = models.CharField(max_length=50, choices=MEMBER_STATUS_CHOICES,
|
|
|
- default='non_adherent')
|
|
|
+ default='pending')
|
|
|
type = models.CharField(max_length=20, choices=MEMBER_TYPE_CHOICES,
|
|
|
- default='personne_physique')
|
|
|
+ default='natural_person')
|
|
|
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')
|
|
|
+ login = models.CharField(max_length=200, unique=True, null=True,
|
|
|
+ blank=True,
|
|
|
+ verbose_name='login',
|
|
|
+ help_text='Login. Clé avec le LDAP. Laisser vide pour '
|
|
|
+ 'le 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')
|
|
@@ -52,12 +99,12 @@ class Member(CoinLdapSyncMixin, models.Model):
|
|
|
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,
|
|
|
+ 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,
|
|
|
+ city = models.CharField(max_length=200, blank=True, null=True,
|
|
|
verbose_name=u'Commune')
|
|
|
- country = models.CharField(max_length=200,
|
|
|
+ country = models.CharField(max_length=200, blank=True, null=True,
|
|
|
default='France',
|
|
|
verbose_name=u'Pays')
|
|
|
entry_date = models.DateField(null=False,
|
|
@@ -70,12 +117,19 @@ class Member(CoinLdapSyncMixin, models.Model):
|
|
|
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
|
|
|
|
|
|
+ def get_full_name(self):
|
|
|
+ return '%s %s' % (self.first_name, self.last_name)
|
|
|
+
|
|
|
+ def get_short_name(self):
|
|
|
+ return '%s' % self.login
|
|
|
+
|
|
|
# Renvoie la date de fin de la dernière cotisation du membre
|
|
|
def end_date_of_membership(self):
|
|
|
try:
|
|
@@ -94,11 +148,22 @@ class Member(CoinLdapSyncMixin, models.Model):
|
|
|
else:
|
|
|
return False
|
|
|
|
|
|
- def change_password(self, new_password):
|
|
|
- ldap_user = LdapUser.objects.get(pk=self.ldap_cn)
|
|
|
+ def set_password(self, new_password, ldap=True, *args, **kwargs):
|
|
|
+ """
|
|
|
+ Override set_password in order to change password in ldap too
|
|
|
+ """
|
|
|
+ super(Member, self).set_password(new_password, *args, **kwargs)
|
|
|
+ if ldap:
|
|
|
+ self.set_ldap_password(new_password)
|
|
|
+
|
|
|
+ def set_ldap_password(self, new_password):
|
|
|
+ """
|
|
|
+ Change password in LDAP
|
|
|
+ """
|
|
|
+ ldap_user = LdapUser.objects.get(pk=self.login)
|
|
|
ldap_user.password = new_password
|
|
|
ldap_user.save()
|
|
|
-
|
|
|
+
|
|
|
def get_active_subscriptions(self, date=datetime.date.today()):
|
|
|
"""
|
|
|
Return list of OfferSubscription which are active today
|
|
@@ -108,9 +173,9 @@ class Member(CoinLdapSyncMixin, models.Model):
|
|
|
Q(subscription_date__lte=date),
|
|
|
Q(resign_date__isnull=True) | Q(resign_date__gte=date))
|
|
|
|
|
|
- def get_automatic_ldap_cn(self):
|
|
|
+ def get_automatic_login(self):
|
|
|
"""
|
|
|
- Calcul le login / ldap_cn automatiquement en fonction
|
|
|
+ Calcul le login / ldap cn automatiquement en fonction
|
|
|
du nom et du prénom
|
|
|
"""
|
|
|
# Première lettre de chaque partie du prénom
|
|
@@ -118,35 +183,35 @@ class Member(CoinLdapSyncMixin, models.Model):
|
|
|
[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))
|
|
|
+ login = ('%s%s' % (first_name_letters, self.last_name))
|
|
|
# Remplacer ou enlever les caractères non ascii
|
|
|
- ldap_cn = unicodedata.normalize('NFD', ldap_cn)\
|
|
|
+ login = unicodedata.normalize('NFD', login)\
|
|
|
.encode('ascii', 'ignore')
|
|
|
# Enlever ponctuation et espace
|
|
|
- ldap_cn = ldap_cn.translate(None, string.punctuation + ' ')
|
|
|
+ login = login.translate(None, string.punctuation + ' ')
|
|
|
# En minuscule
|
|
|
- ldap_cn = ldap_cn.lower()
|
|
|
+ login = login.lower()
|
|
|
|
|
|
- return ldap_cn
|
|
|
+ return login
|
|
|
|
|
|
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 '
|
|
|
+ assert self.login, ('Can\'t sync with LDAP because missing login '
|
|
|
'value for the Member : %s' % self)
|
|
|
|
|
|
if not creation:
|
|
|
- ldap_user = LdapUser.objects.get(pk=self.ldap_cn)
|
|
|
+ ldap_user = LdapUser.objects.get(pk=self.login)
|
|
|
|
|
|
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.pk = self.login
|
|
|
+ ldap_user.uid = self.login
|
|
|
+ ldap_user.nick_name = self.login
|
|
|
ldap_user.uidNumber = max_uid_number + 1
|
|
|
|
|
|
ldap_user.last_name = self.last_name
|
|
@@ -162,17 +227,27 @@ class Member(CoinLdapSyncMixin, models.Model):
|
|
|
"""
|
|
|
Delete member from the LDAP
|
|
|
"""
|
|
|
- assert self.ldap_cn, ('Can\'t delete from LDAP because missing '
|
|
|
- 'ldap_cn value for the Member : %s' % self)
|
|
|
+ assert self.login, ('Can\'t delete from LDAP because missing '
|
|
|
+ 'login 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)
|
|
|
+ if self.login in ldap_group.members:
|
|
|
+ ldap_group.members.remove(self.login)
|
|
|
ldap_group.save()
|
|
|
|
|
|
+ def has_perm(self, perm, obj=None):
|
|
|
+ "Does the user have a specific permission?"
|
|
|
+ # Simplest possible answer: Yes, always
|
|
|
+ return True
|
|
|
+
|
|
|
+ def has_module_perms(self, app_label):
|
|
|
+ "Does the user have permissions to view the app `app_label`?"
|
|
|
+ # Simplest possible answer: Yes, always
|
|
|
+ return True
|
|
|
+
|
|
|
class Meta:
|
|
|
verbose_name = 'membre'
|
|
|
|
|
@@ -267,13 +342,13 @@ add_ignored_fields(["^ldapdb\.models\.fields"])
|
|
|
|
|
|
|
|
|
@receiver(pre_save, sender=Member)
|
|
|
-def define_ldap_cn(sender, instance, **kwargs):
|
|
|
+def define_login(sender, instance, **kwargs):
|
|
|
"""
|
|
|
- Lors de la sauvegarde d'un membre. Si le champ ldap_cn n'est pas définit,
|
|
|
+ Lors de la sauvegarde d'un membre. Si le champ login 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()
|
|
|
+ if not instance.login and not instance.pk:
|
|
|
+ instance.login = instance.get_automatic_login()
|
|
|
|
|
|
|
|
|
@receiver(pre_save, sender=LdapUser)
|
|
@@ -297,25 +372,25 @@ def define_display_name(sender, instance, **kwargs):
|
|
|
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(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
|
|
|
|
|
|
|
|
|
#==============================================================================
|