Parcourir la source

Merge branch 'nom_usage' into demo_ffdn

Conflicts:
	coin/members/admin.py
	coin/members/models.py
Baptiste Jonglez il y a 10 ans
Parent
commit
f32bd0b959

+ 6 - 3
coin/members/admin.py

@@ -32,7 +32,8 @@ class MembershipFeeInline(admin.TabularInline):
 
 
 class MemberAdmin(UserAdmin):
 class MemberAdmin(UserAdmin):
     list_display = ('id', 'status', 'username', 'first_name', 'last_name',
     list_display = ('id', 'status', 'username', 'first_name', 'last_name',
-                    'organization_name', 'email', 'end_date_of_membership')
+                    'nickname', 'organization_name', 'email',
+                    'end_date_of_membership')
     list_display_links = ('id', 'username', 'first_name', 'last_name')
     list_display_links = ('id', 'username', 'first_name', 'last_name')
     list_filter = ('status', MembershipFeeFilter)
     list_filter = ('status', MembershipFeeFilter)
     search_fields = ['username', 'first_name', 'last_name', 'email']
     search_fields = ['username', 'first_name', 'last_name', 'email']
@@ -47,7 +48,8 @@ class MemberAdmin(UserAdmin):
         ('Adhérent', {'fields': (
         ('Adhérent', {'fields': (
             ('status', 'resign_date'),
             ('status', 'resign_date'),
             'type',
             'type',
-            ('first_name', 'last_name', 'organization_name'))}),
+            ('first_name', 'last_name', 'nickname'),
+            'organization_name')}),
         ('Coordonnées', {'fields': (
         ('Coordonnées', {'fields': (
             'email',
             'email',
             ('home_phone_number', 'mobile_phone_number'),
             ('home_phone_number', 'mobile_phone_number'),
@@ -63,7 +65,8 @@ class MemberAdmin(UserAdmin):
         ('Adhérent', {'fields': (
         ('Adhérent', {'fields': (
             'status',
             'status',
             'type',
             'type',
-            ('first_name', 'last_name', 'organization_name'))}),
+            ('first_name', 'last_name', 'nickname'),
+            'organization_name')}),
         ('Coordonnées', {'fields': (
         ('Coordonnées', {'fields': (
             'email',
             'email',
             ('home_phone_number', 'mobile_phone_number'),
             ('home_phone_number', 'mobile_phone_number'),

+ 1 - 1
coin/members/forms.py

@@ -16,7 +16,7 @@ class MemberCreationForm(forms.ModelForm):
     username = forms.RegexField(required=False,
     username = forms.RegexField(required=False,
                                 label="Nom d'utilisateur", max_length=30, regex=r"^[\w.@+-]+$",
                                 label="Nom d'utilisateur", max_length=30, regex=r"^[\w.@+-]+$",
                                 help_text="Laisser vide pour le générer automatiquement à partir du "
                                 help_text="Laisser vide pour le générer automatiquement à partir du "
-                                "nom et du prénom")
+                                "nom d'usage, nom et prénom, ou nom de l'organisme")
     password = forms.CharField(
     password = forms.CharField(
         required=False, label='Mot de passe', widget=forms.PasswordInput)
         required=False, label='Mot de passe', widget=forms.PasswordInput)
 
 

+ 20 - 0
coin/members/migrations/0008_member_nickname.py

@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('members', '0007_auto_20141008_1107'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='member',
+            name='nickname',
+            field=models.CharField(default='', help_text='Pseudonyme, \u2026', max_length=64, verbose_name="nom d'usage", blank=True),
+            preserve_default=False,
+        ),
+    ]

+ 24 - 0
coin/members/migrations/0009_auto_20141008_2244.py

@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('members', '0008_member_nickname'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='member',
+            name='first_name',
+            field=models.CharField(max_length=30, verbose_name='first name', blank=True),
+        ),
+        migrations.AlterField(
+            model_name='member',
+            name='last_name',
+            field=models.CharField(max_length=30, verbose_name='last name', blank=True),
+        ),
+    ]

+ 19 - 0
coin/members/migrations/0010_auto_20141008_2246.py

@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('members', '0009_auto_20141008_2244'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='member',
+            name='status',
+            field=models.CharField(default='member', max_length=50, verbose_name='statut', choices=[('member', 'Adh\xe9rent'), ('not_member', 'Non adh\xe9rent'), ('pending', "Demande d'adh\xe9sion")]),
+        ),
+    ]

+ 51 - 26
coin/members/models.py

@@ -3,7 +3,6 @@ from __future__ import unicode_literals
 
 
 import ldapdb.models
 import ldapdb.models
 import unicodedata
 import unicodedata
-import string
 import datetime
 import datetime
 from django.db import models
 from django.db import models
 from django.db.models import Q
 from django.db.models import Q
@@ -12,6 +11,7 @@ from django.dispatch import receiver
 from django.contrib.auth.models import AbstractUser
 from django.contrib.auth.models import AbstractUser
 from django.conf import settings
 from django.conf import settings
 from django.core.validators import RegexValidator
 from django.core.validators import RegexValidator
+from django.core.exceptions import ValidationError
 from ldapdb.models.fields import CharField, IntegerField, ListField
 from ldapdb.models.fields import CharField, IntegerField, ListField
 
 
 from coin.offers.models import OfferSubscription
 from coin.offers.models import OfferSubscription
@@ -35,10 +35,13 @@ class Member(CoinLdapSyncMixin, AbstractUser):
     )
     )
 
 
     status = models.CharField(max_length=50, choices=MEMBER_STATUS_CHOICES,
     status = models.CharField(max_length=50, choices=MEMBER_STATUS_CHOICES,
-                              default='pending', verbose_name='statut')
+                              default='member', verbose_name='statut')
     type = models.CharField(max_length=20, choices=MEMBER_TYPE_CHOICES,
     type = models.CharField(max_length=20, choices=MEMBER_TYPE_CHOICES,
                             default='natural_person', verbose_name='type')
                             default='natural_person', verbose_name='type')
 
 
+    nickname = models.CharField(max_length=64, blank=True,
+                                verbose_name="nom d'usage",
+                                help_text='Pseudonyme, …')
     organization_name = models.CharField(max_length=200, blank=True,
     organization_name = models.CharField(max_length=200, blank=True,
                                          verbose_name="nom de l'organisme",
                                          verbose_name="nom de l'organisme",
                                          help_text='Pour une personne morale')
                                          help_text='Pour une personne morale')
@@ -75,14 +78,26 @@ class Member(CoinLdapSyncMixin, AbstractUser):
     # passwords for both ldap and local db
     # passwords for both ldap and local db
     _password_ldap = None
     _password_ldap = None
 
 
+    def clean(self):
+        if self.type == 'legal_entity':
+            if not self.organization_name:
+                raise ValidationError("Le nom de l'organisme est obligatoire "
+                                      "pour une personne morale")
+        elif self.type == 'natural_person':
+            if not (self.first_name and self.last_name):
+                raise ValidationError("Le nom et prénom sont obligatoires "
+                                      "pour une personne physique")
+
     def __unicode__(self):
     def __unicode__(self):
-        name = self.first_name + ' ' + self.last_name
-        if self.organization_name:
-            name += ' (%s)' % self.organization_name
-        return name
+        if self.type == 'legal_entity':
+            return self.organization_name
+        elif self.nickname:
+            return self.nickname
+        else:
+            return self.first_name + ' ' + self.last_name
 
 
     def get_full_name(self):
     def get_full_name(self):
-        return '%s %s' % (self.first_name, self.last_name)
+        return str(self)
 
 
     def get_short_name(self):
     def get_short_name(self):
         return self.username
         return self.username
@@ -172,8 +187,12 @@ class Member(CoinLdapSyncMixin, AbstractUser):
             ldap_user.nick_name = self.username
             ldap_user.nick_name = self.username
             ldap_user.uidNumber = uid_number
             ldap_user.uidNumber = uid_number
 
 
-        ldap_user.last_name = self.last_name
-        ldap_user.first_name = self.first_name
+        if self.type == 'natural_person':
+            ldap_user.last_name = self.last_name
+            ldap_user.first_name = self.first_name
+        elif self.type == 'legal_entity':
+            ldap_user.last_name = self.organization_name
+            ldap_user.first_name = ""
 
 
         # If a password is definied in _password_ldap, change it in LDAP
         # If a password is definied in _password_ldap, change it in LDAP
         if self._password_ldap:
         if self._password_ldap:
@@ -216,37 +235,44 @@ class Member(CoinLdapSyncMixin, AbstractUser):
     class Meta:
     class Meta:
         verbose_name = 'membre'
         verbose_name = 'membre'
 
 
-# Hack to force email, first_name ans last_name to be required by Member model
+# Hack to force email to be required by Member model
 Member._meta.get_field('email')._unique = True
 Member._meta.get_field('email')._unique = True
 Member._meta.get_field('email').blank = False
 Member._meta.get_field('email').blank = False
 Member._meta.get_field('email').null = False
 Member._meta.get_field('email').null = False
-Member._meta.get_field('first_name').blank = False
-Member._meta.get_field('first_name').null = False
-Member._meta.get_field('last_name').blank = False
-Member._meta.get_field('last_name').null = False
 
 
 
 
 def count_active_members():
 def count_active_members():
     return Member.objects.filter(status='member').count()
     return Member.objects.filter(status='member').count()
 
 
 
 
-def get_automatic_username(first_name, last_name):
+def get_automatic_username(member):
     """
     """
     Calcul le username automatiquement en fonction
     Calcul le username automatiquement en fonction
     du nom et du prénom
     du nom et du prénom
     """
     """
 
 
-    # Première lettre de chaque partie du prénom
-    first_name_letters = ''.join(
-        [c[0] for c in first_name.split('-')]
-    )
-    # Concaténer avec nom de famille
-    username = ('%s%s' % (first_name_letters, last_name))
+    # S'il s'agit d'une entreprise, utilise son nom:
+    if member.type == 'legal_entity' and member.organization_name:
+        username = member.organization_name
+    # Sinon, si un pseudo est définit, l'utilise
+    elif member.nickname:
+        username = member.nickname
+    # Sinon, utilise nom et prenom
+    elif member.first_name and member.last_name:
+        # Première lettre de chaque partie du prénom
+        first_name_letters = ''.join(
+            [c[0] for c in member.first_name.split('-')]
+        )
+        # Concaténer avec nom de famille
+        username = ('%s%s' % (first_name_letters, member.last_name))
+    else:
+        raise Exception('Il n\'y a pas sufissement d\'informations pour déterminer un login automatiquement')
+
     # Remplacer ou enlever les caractères non ascii
     # Remplacer ou enlever les caractères non ascii
     username = unicodedata.normalize('NFD', username)\
     username = unicodedata.normalize('NFD', username)\
         .encode('ascii', 'ignore')
         .encode('ascii', 'ignore')
-    # Enlever ponctuation et espace
-    punctuation = (string.punctuation + ' ').encode('ascii')
+    # Enlever ponctuation (sauf _-.) et espace
+    punctuation = ('!"#$%&\'()*+,/:;<=>?@[\\]^`{|}~ ').encode('ascii')
     username = username.translate(None, punctuation)
     username = username.translate(None, punctuation)
     # En minuscule
     # En minuscule
     username = username.lower()
     username = username.lower()
@@ -257,7 +283,7 @@ def get_automatic_username(first_name, last_name):
     member = Member.objects.filter(username=username)
     member = Member.objects.filter(username=username)
     base_username = username
     base_username = username
     incr = 2
     incr = 2
-    # Tant qu'un membre est trouvé, incrément un entier à la fin
+    # Tant qu'un membre est trouvé, incrémente un entier à la fin
     while member:
     while member:
         if len(base_username) >= 30:
         if len(base_username) >= 30:
             username = base_username[30 - len(str(incr)):]
             username = base_username[30 - len(str(incr)):]
@@ -379,8 +405,7 @@ def define_username(sender, instance, **kwargs):
     le calcul automatiquement en fonction du nom et du prénom
     le calcul automatiquement en fonction du nom et du prénom
     """
     """
     if not instance.username and not instance.pk:
     if not instance.username and not instance.pk:
-        instance.username = get_automatic_username(instance.first_name,
-                                                   instance.last_name)
+        instance.username = get_automatic_username(instance)
 
 
 
 
 @receiver(pre_save, sender=LdapUser)
 @receiver(pre_save, sender=LdapUser)

+ 10 - 1
coin/members/templates/members/detail.html

@@ -12,8 +12,17 @@
             <h3>Me joindre</h3>
             <h3>Me joindre</h3>
             <table class="full-width">
             <table class="full-width">
                 <tr>
                 <tr>
+                  {% if user.type == 'natural_person' %}
                     <td class="center"><span class="label">Prénom - Nom</span></td>
                     <td class="center"><span class="label">Prénom - Nom</span></td>
-                    <td>{{user.first_name}} {{user.last_name}}</td>
+                    <td>{{user.first_name}} {{user.last_name}}
+                      {% if user.nickname %}
+                      ({{ user.nickname }})
+                      {% endif %}
+                    </td>
+                  {% else %}
+                    <td class="center"><span class="label">Nom de la structure</span></td>
+                    <td>{{ user.organization_name }}</td>
+                  {% endif %}
                 </tr>
                 </tr>
                 <tr>
                 <tr>
                     <td class="center"><span class="label">Adresse</span></td>
                     <td class="center"><span class="label">Adresse</span></td>

+ 34 - 1
coin/members/tests.py

@@ -202,6 +202,28 @@ class MemberTests(TestCase):
         member2.delete()
         member2.delete()
         member3.delete()
         member3.delete()
 
 
+    def test_when_creating_legal_entity_organization_name_is_used_for_username(self):
+        """
+        Lors de la créatio d'une entreprise, son nom doit être utilisée lors de
+        la détermination automatique du username
+        """
+        random = os.urandom(4).encode('hex')
+        member = Member(type='legal_entity', organization_name='ILLYSE' + random, email='illyse@coin.org')
+        member.save()
+        self.assertEqual(member.username, 'illyse' + random)
+        member.delete()
+
+    def test_when_creating_member_with_nickname_it_is_used_for_username(self):
+        """
+        Lors de la création d'une personne, qui a un pseudo, celui-ci est utilisé en priorité
+        """
+        random = os.urandom(4).encode('hex')
+        member = Member(first_name='Richard', last_name='Stallman', nickname='rms' + random, email='illyse@coin.org')
+        member.save()
+        self.assertEqual(member.username, 'rms' + random)
+
+        member.delete()
+
     def test_when_saving_member_and_ldap_fail_dont_save(self):
     def test_when_saving_member_and_ldap_fail_dont_save(self):
         """
         """
         Test que lors de la sauvegarde d'un membre et que la sauvegarde en LDAP
         Test que lors de la sauvegarde d'un membre et que la sauvegarde en LDAP
@@ -291,7 +313,7 @@ class MemberTests(TestCase):
 
 
     def test_member_is_paid_up(self):
     def test_member_is_paid_up(self):
         """
         """
-        Test que end_date_of_membership d'un membre envoi bien la date de fin d'adhésion
+        Test l'état "a jour de cotisation" d'un adhérent.
         """
         """
         # Créé un membre
         # Créé un membre
         first_name = 'Capitain'
         first_name = 'Capitain'
@@ -327,6 +349,17 @@ class MemberTests(TestCase):
         # de cotisation
         # de cotisation
         self.assertEqual(member.is_paid_up(), True)
         self.assertEqual(member.is_paid_up(), True)
 
 
+    def test_member_cant_be_created_without_names(self):
+        """
+        Test qu'un membre ne peut pas être créé sans "noms"
+        (prenom, nom) ou pseudo ou nom d'organization
+        """
+        member = Member(username='blop')
+        self.assertRaises(Exception, member.save)
+
+        member = Member()
+        self.assertRaises(Exception, member.save)
+
 
 
 class MemberAdminTests(TestCase):
 class MemberAdminTests(TestCase):