Merge branch 'dev' into test
[auf_savoirs_en_partage_django.git] / auf_savoirs_en_partage / chercheurs / models.py
1 # -*- encoding: utf-8 -*-
2 import hashlib
3
4 from datamaster_modeles.models import *
5 from django.conf import settings
6 from django.contrib.auth.models import User
7 from django.core.urlresolvers import reverse as url
8 from django.db import models
9 from django.db.models import Q
10 from django.utils.encoding import smart_str
11 from django.utils.hashcompat import sha_constructor
12 from djangosphinx.models import SphinxSearch
13
14 from savoirs.models import Discipline, SEPManager, SEPSphinxQuerySet, SEPQuerySet, Search
15
16 GENRE_CHOICES = (('m', 'Homme'), ('f', 'Femme'))
17 class Personne(models.Model):
18 salutation = models.CharField(max_length=128, null=True, blank=True)
19 nom = models.CharField(max_length=255)
20 prenom = models.CharField(max_length=128, verbose_name='prénom')
21 courriel = models.EmailField(max_length=128, verbose_name="courriel")
22 afficher_courriel = models.BooleanField(default=True)
23 fonction = models.CharField(max_length=128, null=True, blank=True)
24 date_naissance = models.DateField(null=True, blank=True)
25 sousfonction = models.CharField(max_length=128, null=True, blank=True, verbose_name='sous-fonction')
26 telephone = models.CharField(max_length=32, null=True, blank=True, verbose_name='numéro de téléphone')
27 adresse_postale = models.TextField(blank=True)
28 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
29 commentaire = models.TextField(verbose_name='commentaires', null=True, blank=True)
30 actif = models.BooleanField(editable=False, default=False)
31
32 def __unicode__(self):
33 return u"%s %s, %s" % (self.prenom, self.nom, self.courriel)
34
35 class Meta:
36 ordering = ["nom", "prenom"]
37
38 @property
39 def civilite(self):
40 if self.genre == 'm':
41 return 'M.'
42 elif self.genre == 'f':
43 return 'Mme'
44 else:
45 return ''
46
47 def courriel_display(self):
48 return self.courriel.replace(u'@', u' (à) ')
49
50 class ChercheurQuerySet(SEPQuerySet):
51
52 def filter_groupe(self, groupe):
53 return self.filter(groupes=groupe)
54
55 def filter_pays(self, pays):
56 return self.filter(Q(etablissement__pays=pays) | Q(etablissement_autre_pays=pays))
57
58 def filter_region(self, region):
59 return self.filter(Q(etablissement__pays__region=region) | Q(etablissement_autre_pays__region=region))
60
61 def filter_nord_sud(self, nord_sud):
62 return self.filter(Q(etablissement__pays__nord_sud=nord_sud) | Q(etablissement_autre_pays__nord_sud=nord_sud))
63
64 def filter_genre(self, genre):
65 return self.filter(genre=genre)
66
67 def filter_statut(self, statut):
68 return self.filter(statut=statut)
69
70 def filter_expert(self):
71 return self.exclude(expertises=None)
72
73 def order_by_nom(self, direction=''):
74 return self.order_by(direction + 'nom', direction + 'prenom', '-date_modification')
75
76 def order_by_etablissement(self, direction=''):
77 return self.extra(select=dict(etablissement_nom='IFNULL(ref_etablissement.nom, chercheurs_chercheur.etablissement_autre_nom)'),
78 order_by=[direction + 'etablissement_nom', '-date_modification'])
79
80 def order_by_pays(self, direction=''):
81 return self.extra(select=dict(
82 pays_etablissement='''(SELECT nom FROM ref_pays
83 WHERE ref_pays.code = IFNULL(ref_etablissement.pays, chercheurs_chercheur.etablissement_autre_pays))'''
84 ), order_by=[direction + 'pays_etablissement', '-date_modification'])
85
86 class ChercheurSphinxQuerySet(SEPSphinxQuerySet):
87
88 def __init__(self, model=None):
89 return SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_chercheurs',
90 weights=dict(nom=2, prenom=2))
91
92 def filter_region(self, region):
93 return self.filter(region_id=region.id)
94
95 def filter_groupe(self, groupe):
96 return self.filter(groupe_ids=groupe.id)
97
98 def filter_pays(self, pays):
99 return self.filter(pays_id=pays.id)
100
101 NORD_SUD_CODES = {'Nord': 1, 'Sud': 2}
102 def filter_nord_sud(self, nord_sud):
103 return self.filter(nord_sud=self.NORD_SUD_CODES[nord_sud])
104
105 GENRE_CODES = dict([(k, i+1) for i, (k, v) in enumerate(GENRE_CHOICES)])
106 def filter_genre(self, genre):
107 return self.filter(genre=self.GENRE_CODES[genre])
108
109 STATUT_CODES = {'enseignant': 1, 'etudiant': 2, 'independant': 3}
110 def filter_statut(self, statut):
111 return self.filter(statut=self.STATUT_CODES[statut])
112
113 def filter_expert(self):
114 return self.filter(expert=True)
115
116 def order_by_nom(self, direction=''):
117 return self.order_by(direction + 'nom_complet', '-date_modification')
118
119 def order_by_etablissement(self, direction=''):
120 return self.order_by(direction + 'etablissement_attr', '-date_modification')
121
122 def order_by_pays(self, direction=''):
123 return self.order_by(direction + 'pays_attr', '-date_modification')
124
125 class ChercheurManager(SEPManager):
126
127 def get_query_set(self):
128 return ChercheurQuerySet(self.model).filter(actif=True)
129
130 def get_sphinx_query_set(self):
131 return ChercheurSphinxQuerySet(self.model).order_by('-date_modification')
132
133 def filter_region(self, region):
134 """Le filtrage de chercheurs par région n'est pas une recherche texte."""
135 return self.get_query_set().filter_region(region)
136
137 def filter_groupe(self, groupe):
138 return self.get_query_set().filter_groupe(groupe)
139
140 def filter_pays(self, pays):
141 return self.get_query_set().filter_pays(pays)
142
143 def filter_nord_sud(self, nord_sud):
144 return self.get_query_set().filter_nord_sud(nord_sud)
145
146 def filter_genre(self, genre):
147 return self.get_query_set().filter_genre(genre=genre)
148
149 def filter_statut(self, statut):
150 return self.get_query_set().filter_statut(statut)
151
152 def filter_expert(self):
153 return self.get_query_set().filter_expert()
154
155 def order_by_nom(self, direction=''):
156 return self.get_query_set().order_by_nom(self, direction=direction)
157
158 def order_by_etablissement(self, direction=''):
159 return self.get_query_set().order_by_etablissement(self, direction=direction)
160
161 def order_by_pays(self, direction=''):
162 return self.get_query_set().order_by_pays(self, direction=direction)
163
164 STATUT_CHOICES = (
165 ('enseignant', 'Enseignant-chercheur dans un établissement'),
166 ('etudiant', 'Étudiant-chercheur doctorant'),
167 ('independant', 'Chercheur indépendant docteur')
168 )
169
170 class Chercheur(Personne):
171 RESEAU_INSTITUTIONNEL_CHOICES = (
172 ('AFELSH', 'Association des facultés ou établissements de lettres et sciences humaines des universités d’expression française (AFELSH)'),
173 ('CIDEGEF', 'Conférence internationale des dirigeants des institutions d’enseignement supérieur et de recherche de gestion d’expression française (CIDEGEF)'),
174 ('RIFEFF', 'Réseau international francophone des établissements de formation de formateurs (RIFEFF)'),
175 ('CIDMEF', 'Conférence internationale des doyens des facultés de médecine d’expression française (CIDMEF)'),
176 ('CIDCDF', 'Conférence internationale des doyens des facultés de chirurgie dentaire d’expression totalement ou partiellement française (CIDCDF)'),
177 ('CIFDUF', 'Conférence internationale des facultés de droit ayant en commun l’usage du français (CIFDUF)'),
178 ('CIRUISEF', 'Conférence internationale des responsables des universités et institutions à dominante scientifique et technique d’expression française (CIRUISEF)'),
179 ('Theophraste', 'Réseau Théophraste (Réseau de centres francophones de formation au journalisme)'),
180 ('CIDPHARMEF', 'Conférence internationale des doyens des facultés de pharmacie d’expression française (CIDPHARMEF)'),
181 ('CIDEFA', 'Conférence internationale des directeurs et doyens des établissements supérieurs d’expression française des sciences de l’agriculture et de l’alimentation (CIDEFA)'),
182 ('CITEF', 'Conférence internationale des formations d’ingénieurs et techniciens d’expression française (CITEF)'),
183 ('APERAU', 'Association pour la promotion de l’enseignement et de la recherche en aménagement et urbanisme (APERAU)'),
184 )
185 INSTANCE_AUF_CHOICES = (
186 ('CASSOC', 'Conseil associatif'),
187 ('CA', "Conseil d'administration"),
188 ('CS', 'Conseil scientifique'),
189 ('CRE', "Commission régionale d'experts"),
190 ('CR', 'Conférence des recteurs'),
191 ('CNO', "Conseil national d'orientation")
192 )
193
194 nationalite = models.ForeignKey(Pays, null = True, db_column='nationalite', to_field='code',
195 verbose_name = 'nationalité', related_name='nationalite')
196 statut = models.CharField(max_length=36, choices=STATUT_CHOICES)
197 diplome = models.CharField(max_length=255, null=True, verbose_name = 'diplôme le plus élevé')
198 etablissement = models.ForeignKey(Etablissement, db_column='etablissement', null=True, blank=True)
199 etablissement_autre_nom = models.CharField(max_length=255, null=True, blank=True, verbose_name = 'autre établissement')
200 etablissement_autre_pays = models.ForeignKey(Pays, null = True, blank=True, db_column='etablissement_autre_pays',
201 to_field='code', related_name='etablissement_autre_pays',
202 verbose_name = "pays de l'établissement")
203 attestation = models.BooleanField()
204
205 #Domaine
206 thematique = models.ForeignKey(Thematique, db_column='thematique', null=True, verbose_name='thematique')
207 mots_cles = models.CharField(max_length=255, null=True, verbose_name='mots-clés')
208 discipline = models.ForeignKey(Discipline, db_column='discipline', null=True, verbose_name='Discipline')
209 theme_recherche = models.TextField(null=True, blank=True, verbose_name='thèmes de recherche')
210 groupe_recherche = models.CharField(max_length=255, blank=True, verbose_name='groupe de recherche')
211 url_site_web = models.URLField(max_length=255, null=True, blank=True,
212 verbose_name='adresse site Internet', verify_exists=False)
213 url_blog = models.URLField(max_length=255, null=True, blank=True, verbose_name='blog',
214 verify_exists=False)
215 url_reseau_social = models.URLField(
216 max_length=255, null=True, blank=True, verbose_name='Réseau social',
217 verify_exists=False,
218 help_text=u"Vous pouvez indiquer ici l'adresse de votre page personnelle dans votre réseau social préféré (e.g. Facebook, LinkedIn, Twitter, Identica, ...)"
219 )
220
221 groupes = models.ManyToManyField('Groupe', through='ChercheurGroupe', blank=True, verbose_name='Domaines de recherche')
222
223 # Activités en francophonie
224 membre_instance_auf = models.NullBooleanField(verbose_name="est ou a déjà été membre d'une instance de l'AUF")
225 membre_instance_auf_nom = models.CharField(max_length=10, blank=True, choices=INSTANCE_AUF_CHOICES, verbose_name="instance")
226 membre_instance_auf_fonction = models.CharField(max_length=255, blank=True, verbose_name="fonction")
227 membre_instance_auf_dates = models.CharField(max_length=255, blank=True, verbose_name="dates")
228 expert_oif = models.NullBooleanField(verbose_name="a été sollicité par l'OIF")
229 expert_oif_details = models.CharField(max_length=255, blank=True, verbose_name="détails")
230 expert_oif_dates = models.CharField(max_length=255, blank=True, verbose_name="dates")
231 membre_association_francophone = models.NullBooleanField(verbose_name="est membre d'une association francophone")
232 membre_association_francophone_details = models.CharField(max_length=255, blank=True, verbose_name="nom de l'association")
233 membre_reseau_institutionnel = models.NullBooleanField(
234 verbose_name="est membre des instances d'un réseau institutionnel de l'AUF"
235 )
236 membre_reseau_institutionnel_nom = models.CharField(
237 max_length=15, choices=RESEAU_INSTITUTIONNEL_CHOICES, blank=True,
238 verbose_name="réseau institutionnel"
239 )
240 membre_reseau_institutionnel_fonction = models.CharField(
241 max_length=255, blank=True, verbose_name="fonction"
242 )
243 membre_reseau_institutionnel_dates = models.CharField(
244 max_length=255, blank=True, verbose_name="dates"
245 )
246
247 # Expertises
248 expertises_auf = models.NullBooleanField(verbose_name="est disposé à réaliser des expertises pour l'AUF")
249
250 #meta
251 date_creation = models.DateField(auto_now_add=True, db_column='date_creation')
252 date_modification = models.DateField(auto_now=True, db_column='date_modification')
253
254 # Manager
255 objects = ChercheurManager()
256 all_objects = models.Manager()
257
258 def __unicode__(self):
259 return u"%s %s" % (self.nom.upper(), self.prenom.title())
260
261 def statut_display(self):
262 for s in STATUT_CHOICES:
263 if self.statut == s[0]:
264 return s[1]
265 return "-"
266
267 @property
268 def etablissement_display(self):
269 return self.nom_etablissement + ', ' + self.pays
270
271 @property
272 def pays(self):
273 return self.etablissement.pays if self.etablissement else self.etablissement_autre_pays
274
275 @property
276 def nom_etablissement(self):
277 return self.etablissement.nom if self.etablissement else self.etablissement_autre_nom
278
279 @property
280 def region(self):
281 return self.pays.region
282
283 def save(self):
284 """Si on a donné un établissement membre, on laisse tomber l'autre établissement."""
285 if self.etablissement:
286 self.etablissement_autre_nom = None
287 self.etablissement_autre_pays = None
288 super(Chercheur, self).save()
289
290 def activation_token(self):
291 return sha_constructor(settings.SECRET_KEY + unicode(self.id)).hexdigest()[::2]
292
293 class Publication(models.Model):
294 chercheur = models.ForeignKey(Chercheur, related_name='publications')
295 auteurs = models.CharField(max_length=255, blank=True, verbose_name='auteur(s)')
296 titre = models.CharField(max_length=255, null=True, blank=True, verbose_name='titre')
297 revue = models.CharField(max_length=255, null=True, blank=True, verbose_name='revue')
298 annee = models.IntegerField(null=True, blank=True, verbose_name='année de publication')
299 editeur = models.CharField(max_length=255, null=True, blank=True, verbose_name='éditeur')
300 lieu_edition = models.CharField(max_length=255, null=True, blank=True, verbose_name="lieu d'édition")
301 nb_pages = models.CharField(max_length=255, null=True, blank=True, verbose_name='nombre de pages')
302 url = models.URLField(max_length=255, null=True, blank=True, verbose_name='lien vers la publication', verify_exists=False)
303 #Migration des publications depuis l'ancien repertoire de chercheurs
304 publication_affichage = models.TextField(verbose_name='publication', null=True, blank=True)
305 actif = models.BooleanField(editable=False)
306
307 def __unicode__(self):
308 return self.titre or '(Aucun)'
309
310 def save(self):
311 if self.publication_affichage and (self.auteurs or self.titre or
312 self.revue or self.annee or
313 self.editeur or self.lieu_edition
314 or self.nb_pages or self.url):
315 self.publication_affichage = ''
316 super(Publication, self).save()
317
318 class These(models.Model):
319 chercheur = models.OneToOneField(Chercheur, primary_key=True)
320 titre = models.CharField(max_length=255, verbose_name='Titre')
321 annee = models.IntegerField(verbose_name='Année de soutenance (réalisée ou prévue)')
322 directeur = models.CharField(max_length=255, verbose_name='Directeur')
323 etablissement = models.CharField(max_length=255, verbose_name='Établissement de soutenance')
324 nb_pages = models.IntegerField(verbose_name='Nombre de pages', blank=True, null=True)
325 url = models.URLField(max_length=255, verbose_name='Lien vers la publication', blank=True, verify_exists=False)
326
327 def __unicode__(self):
328 return self.titre
329
330 class Expertise(models.Model):
331 id = models.AutoField(primary_key=True, db_column='id')
332 chercheur = models.ForeignKey(Chercheur, related_name='expertises')
333 nom = models.CharField(max_length=255, verbose_name = "Objet de l'expertise")
334 date = models.CharField(max_length=255, blank=True)
335 lieu = models.CharField(max_length=255, null=True, blank=True, verbose_name = "Lieu de l'expertise")
336 organisme_demandeur = models.CharField(max_length=255, null=True, blank=True, verbose_name = 'Organisme demandeur')
337 organisme_demandeur_visible = models.BooleanField(verbose_name="Afficher l'organisme demandeur")
338 actif = models.BooleanField(editable = False, db_column='actif')
339
340 def __unicode__(self):
341 return u"%s" % (self.nom)
342
343 class Groupe(models.Model):
344 id = models.AutoField(primary_key=True, db_column='id')
345 nom = models.CharField(max_length=255, db_column='nom')
346 url = models.URLField(max_length=255, null=True, blank=True,
347 verbose_name='Site web')
348 liste_diffusion = models.URLField(max_length=255, null=True, blank=True,
349 verbose_name='Liste de diffusion')
350 bulletin = models.URLField(max_length=255, null=True, blank=True,
351 verbose_name='Bulletin')
352 actif = models.BooleanField(editable = False, db_column='actif')
353
354 class Meta:
355 verbose_name = 'domaine de recherche'
356 verbose_name_plural = 'domaines de recherche'
357
358 def __unicode__(self):
359 return u"%s" % (self.nom)
360
361 class ChercheurGroupe(models.Model):
362 id = models.AutoField(primary_key=True, db_column='id')
363 chercheur = models.ForeignKey('Chercheur', db_column='chercheur', editable=False)
364 groupe = models.ForeignKey('Groupe', db_column='groupe')
365 date_inscription = models.DateField(auto_now_add=True)
366 date_modification = models.DateField(auto_now=True)
367 actif = models.BooleanField(editable = False, db_column='actif')
368
369 class Meta:
370 verbose_name = 'adhésion'
371
372 def __unicode__(self):
373 return u"%s - %s" % (self.chercheur, self.groupe)
374
375 class ChercheurSearch(Search):
376 nom_chercheur = models.CharField(max_length=100, blank=True, verbose_name='nom')
377 domaine = models.ForeignKey(Groupe, blank=True, null=True, verbose_name='domaine de recherche')
378 groupe_recherche = models.CharField(max_length=100, blank=True, null=True,
379 verbose_name='groupe de recherche',
380 help_text='ou Laboratoire, ou Groupement inter-universitaire')
381 statut = models.CharField(max_length=100, blank=True, choices=STATUT_CHOICES + (('expert', 'Expert'),))
382 pays = models.ForeignKey(Pays, blank=True, null=True)
383 nord_sud = models.CharField(max_length=4, blank=True, choices=(('Nord', 'Nord'), ('Sud', 'Sud')),
384 verbose_name='Nord/Sud',
385 help_text="Distinction d'ordre géopolitique et économique, non géographique, qui conditionne souvent l'attribution de soutiens par les agences internationales: on entend par Nord les pays développés, par Sud les pays en développement (pays les moins avancés, pays émergents et pays à économies en transition)")
386 activites_francophonie = models.CharField(
387 max_length=25, blank=True, verbose_name='activités en Francophonie',
388 choices=(('instance_auf', "Membre d'une instance de l'AUF"),
389 ('expert_oif', "Sollicité par l'OIF"),
390 ('association_francophone', "Membre d'une association ou d'une société savante francophone"),
391 ('reseau_institutionnel', "Membre des instances d'un réseau institutionnel de l'AUF"))
392 )
393 genre = models.CharField(max_length=1, blank=True, choices=GENRE_CHOICES)
394
395 class Meta:
396 verbose_name = 'recherche de chercheurs'
397 verbose_name_plural = 'recherches de chercheurs'
398
399 def run(self):
400 results = Chercheur.objects
401 if self.q:
402 results = results.search(self.q)
403 if self.nom_chercheur:
404 results = results.add_to_query('@(nom,prenom) ' + self.nom_chercheur)
405 if self.groupe_recherche:
406 results = results.add_to_query('@groupe_recherche ' + self.groupe_recherche)
407 if self.discipline:
408 results = results.filter_discipline(self.discipline)
409 if self.region:
410 results = results.filter_region(self.region)
411 if self.statut:
412 if self.statut == "expert":
413 results = results.filter_expert()
414 else:
415 results = results.filter_statut(self.statut)
416 if self.domaine:
417 results = results.filter_groupe(self.domaine)
418 if self.pays:
419 results = results.filter_pays(self.pays)
420 if self.nord_sud:
421 results = results.filter_nord_sud(self.nord_sud)
422 if self.genre:
423 results = results.filter_genre(self.genre)
424 if self.activites_francophonie == 'instance_auf':
425 results = results.filter(membre_instance_auf=True)
426 elif self.activites_francophonie == 'expert_oif':
427 results = results.filter(expert_oif=True)
428 elif self.activites_francophonie == 'association_francophone':
429 results = results.filter(membre_association_francophone=True)
430 elif self.activites_francophonie == 'reseau_institutionnel':
431 results = results.filter(membre_reseau_institutionnel=True)
432 return results.all()
433
434 def url(self):
435 qs = self.query_string()
436 return url('chercheurs') + ('?' + qs if qs else '')