Séparation de domaines de recherche et groupes de chercheur
[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 filter_date_modification(self, min=None, max=None):
74 return self._filter_date('date_modification', min=min, max=max)
75
76 def order_by_nom(self, direction=''):
77 return self.order_by(direction + 'nom', direction + 'prenom', '-date_modification')
78
79 def order_by_etablissement(self, direction=''):
80 return self.extra(select=dict(etablissement_nom='IFNULL(ref_etablissement.nom, chercheurs_chercheur.etablissement_autre_nom)'),
81 order_by=[direction + 'etablissement_nom', '-date_modification'])
82
83 def order_by_pays(self, direction=''):
84 return self.extra(select=dict(
85 pays_etablissement='''(SELECT nom FROM ref_pays
86 WHERE ref_pays.code = IFNULL(ref_etablissement.pays, chercheurs_chercheur.etablissement_autre_pays))'''
87 ), order_by=[direction + 'pays_etablissement', '-date_modification'])
88
89 class ChercheurSphinxQuerySet(SEPSphinxQuerySet):
90
91 def __init__(self, model=None):
92 return SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_chercheurs',
93 weights=dict(nom=2, prenom=2))
94
95 def filter_region(self, region):
96 return self.filter(region_id=region.id)
97
98 def filter_groupe(self, groupe):
99 return self.filter(groupe_ids=groupe.id)
100
101 def filter_pays(self, pays):
102 return self.filter(pays_id=pays.id)
103
104 NORD_SUD_CODES = {'Nord': 1, 'Sud': 2}
105 def filter_nord_sud(self, nord_sud):
106 return self.filter(nord_sud=self.NORD_SUD_CODES[nord_sud])
107
108 GENRE_CODES = dict([(k, i+1) for i, (k, v) in enumerate(GENRE_CHOICES)])
109 def filter_genre(self, genre):
110 return self.filter(genre=self.GENRE_CODES[genre])
111
112 STATUT_CODES = {'enseignant': 1, 'etudiant': 2, 'independant': 3}
113 def filter_statut(self, statut):
114 return self.filter(statut=self.STATUT_CODES[statut])
115
116 def filter_expert(self):
117 return self.filter(expert=True)
118
119 def filter_date_modification(self, min=None, max=None):
120 return self._filter_date('date_modification', min=min, max=max)
121
122 def order_by_nom(self, direction=''):
123 return self.order_by(direction + 'nom_complet', '-date_modification')
124
125 def order_by_etablissement(self, direction=''):
126 return self.order_by(direction + 'etablissement_attr', '-date_modification')
127
128 def order_by_pays(self, direction=''):
129 return self.order_by(direction + 'pays_attr', '-date_modification')
130
131 class ChercheurManager(SEPManager):
132
133 def get_query_set(self):
134 return ChercheurQuerySet(self.model).filter(actif=True)
135
136 def get_sphinx_query_set(self):
137 return ChercheurSphinxQuerySet(self.model).order_by('-date_modification')
138
139 def filter_region(self, region):
140 """Le filtrage de chercheurs par région n'est pas une recherche texte."""
141 return self.get_query_set().filter_region(region)
142
143 def filter_groupe(self, groupe):
144 return self.get_query_set().filter_groupe(groupe)
145
146 def filter_pays(self, pays):
147 return self.get_query_set().filter_pays(pays)
148
149 def filter_nord_sud(self, nord_sud):
150 return self.get_query_set().filter_nord_sud(nord_sud)
151
152 def filter_genre(self, genre):
153 return self.get_query_set().filter_genre(genre=genre)
154
155 def filter_statut(self, statut):
156 return self.get_query_set().filter_statut(statut)
157
158 def filter_expert(self):
159 return self.get_query_set().filter_expert()
160
161 def filter_date_modification(self, min=None, max=None):
162 return self.get_query_set().filter_date_modification(min=min, max=max)
163
164 def order_by_nom(self, direction=''):
165 return self.get_query_set().order_by_nom(self, direction=direction)
166
167 def order_by_etablissement(self, direction=''):
168 return self.get_query_set().order_by_etablissement(self, direction=direction)
169
170 def order_by_pays(self, direction=''):
171 return self.get_query_set().order_by_pays(self, direction=direction)
172
173 STATUT_CHOICES = (
174 ('enseignant', 'Enseignant-chercheur dans un établissement'),
175 ('etudiant', 'Étudiant-chercheur doctorant'),
176 ('independant', 'Chercheur indépendant docteur')
177 )
178
179 class Chercheur(Personne):
180 RESEAU_INSTITUTIONNEL_CHOICES = (
181 ('AFELSH', 'Association des facultés ou établissements de lettres et sciences humaines des universités d’expression française (AFELSH)'),
182 ('CIDEGEF', 'Conférence internationale des dirigeants des institutions d’enseignement supérieur et de recherche de gestion d’expression française (CIDEGEF)'),
183 ('RIFEFF', 'Réseau international francophone des établissements de formation de formateurs (RIFEFF)'),
184 ('CIDMEF', 'Conférence internationale des doyens des facultés de médecine d’expression française (CIDMEF)'),
185 ('CIDCDF', 'Conférence internationale des doyens des facultés de chirurgie dentaire d’expression totalement ou partiellement française (CIDCDF)'),
186 ('CIFDUF', 'Conférence internationale des facultés de droit ayant en commun l’usage du français (CIFDUF)'),
187 ('CIRUISEF', 'Conférence internationale des responsables des universités et institutions à dominante scientifique et technique d’expression française (CIRUISEF)'),
188 ('Theophraste', 'Réseau Théophraste (Réseau de centres francophones de formation au journalisme)'),
189 ('CIDPHARMEF', 'Conférence internationale des doyens des facultés de pharmacie d’expression française (CIDPHARMEF)'),
190 ('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)'),
191 ('CITEF', 'Conférence internationale des formations d’ingénieurs et techniciens d’expression française (CITEF)'),
192 ('APERAU', 'Association pour la promotion de l’enseignement et de la recherche en aménagement et urbanisme (APERAU)'),
193 )
194 INSTANCE_AUF_CHOICES = (
195 ('CASSOC', 'Conseil associatif'),
196 ('CA', "Conseil d'administration"),
197 ('CS', 'Conseil scientifique'),
198 ('CRE', "Commission régionale d'experts"),
199 ('CR', 'Conférence des recteurs'),
200 ('CNO', "Conseil national d'orientation")
201 )
202
203 nationalite = models.ForeignKey(Pays, null = True, db_column='nationalite', to_field='code',
204 verbose_name = 'nationalité', related_name='nationalite')
205 statut = models.CharField(max_length=36, choices=STATUT_CHOICES)
206 diplome = models.CharField(max_length=255, null=True, verbose_name = 'diplôme le plus élevé')
207 etablissement = models.ForeignKey(Etablissement, db_column='etablissement', null=True, blank=True)
208 etablissement_autre_nom = models.CharField(max_length=255, null=True, blank=True, verbose_name = 'autre établissement')
209 etablissement_autre_pays = models.ForeignKey(Pays, null = True, blank=True, db_column='etablissement_autre_pays',
210 to_field='code', related_name='etablissement_autre_pays',
211 verbose_name = "pays de l'établissement")
212 attestation = models.BooleanField()
213
214 #Domaine
215 thematique = models.ForeignKey(Thematique, db_column='thematique', blank=True, null=True, verbose_name='thematique')
216 mots_cles = models.CharField(max_length=255, null=True, verbose_name='mots-clés')
217 discipline = models.ForeignKey(Discipline, db_column='discipline', null=True, verbose_name='Discipline')
218 theme_recherche = models.TextField(null=True, blank=True, verbose_name='thèmes de recherche')
219 groupe_recherche = models.CharField(max_length=255, blank=True, verbose_name='groupe de recherche')
220 url_site_web = models.URLField(max_length=255, null=True, blank=True,
221 verbose_name='adresse site Internet', verify_exists=False)
222 url_blog = models.URLField(max_length=255, null=True, blank=True, verbose_name='blog',
223 verify_exists=False)
224 url_reseau_social = models.URLField(
225 max_length=255, null=True, blank=True, verbose_name='Réseau social',
226 verify_exists=False,
227 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, ...)"
228 )
229
230 groupes = models.ManyToManyField('Groupe', through='ChercheurGroupe', blank=True, verbose_name='Domaines de recherche')
231
232 # Activités en francophonie
233 membre_instance_auf = models.NullBooleanField(verbose_name="est ou a déjà été membre d'une instance de l'AUF")
234 membre_instance_auf_nom = models.CharField(max_length=10, blank=True, choices=INSTANCE_AUF_CHOICES, verbose_name="instance")
235 membre_instance_auf_fonction = models.CharField(max_length=255, blank=True, verbose_name="fonction")
236 membre_instance_auf_dates = models.CharField(max_length=255, blank=True, verbose_name="dates")
237 expert_oif = models.NullBooleanField(verbose_name="a été sollicité par l'OIF")
238 expert_oif_details = models.CharField(max_length=255, blank=True, verbose_name="détails")
239 expert_oif_dates = models.CharField(max_length=255, blank=True, verbose_name="dates")
240 membre_association_francophone = models.NullBooleanField(verbose_name="est membre d'une association francophone")
241 membre_association_francophone_details = models.CharField(max_length=255, blank=True, verbose_name="nom de l'association")
242 membre_reseau_institutionnel = models.NullBooleanField(
243 verbose_name="est membre des instances d'un réseau institutionnel de l'AUF"
244 )
245 membre_reseau_institutionnel_nom = models.CharField(
246 max_length=15, choices=RESEAU_INSTITUTIONNEL_CHOICES, blank=True,
247 verbose_name="réseau institutionnel"
248 )
249 membre_reseau_institutionnel_fonction = models.CharField(
250 max_length=255, blank=True, verbose_name="fonction"
251 )
252 membre_reseau_institutionnel_dates = models.CharField(
253 max_length=255, blank=True, verbose_name="dates"
254 )
255
256 # Expertises
257 expertises_auf = models.NullBooleanField(verbose_name="est disposé à réaliser des expertises pour l'AUF")
258
259 #meta
260 date_creation = models.DateField(auto_now_add=True, db_column='date_creation')
261 date_modification = models.DateField(auto_now=True, db_column='date_modification')
262
263 # Manager
264 objects = ChercheurManager()
265 all_objects = models.Manager()
266
267 def __unicode__(self):
268 return u"%s %s" % (self.nom.upper(), self.prenom.title())
269
270 def statut_display(self):
271 for s in STATUT_CHOICES:
272 if self.statut == s[0]:
273 return s[1]
274 return "-"
275
276 @property
277 def etablissement_display(self):
278 return (self.nom_etablissement or '') + (', ' + self.pays.nom if self.pays else '')
279
280 @property
281 def pays(self):
282 return self.etablissement.pays if self.etablissement else self.etablissement_autre_pays
283
284 @property
285 def nom_etablissement(self):
286 return self.etablissement.nom if self.etablissement else self.etablissement_autre_nom
287
288 @property
289 def region(self):
290 return self.pays.region
291
292 @property
293 def domaines_recherche(self):
294 return self.groupes.filter(groupe_chercheur=False)
295
296 @property
297 def groupes_chercheur(self):
298 return self.groupes.filter(groupe_chercheur=True)
299
300 def save(self):
301 """Si on a donné un établissement membre, on laisse tomber l'autre établissement."""
302 if self.etablissement:
303 self.etablissement_autre_nom = None
304 self.etablissement_autre_pays = None
305 super(Chercheur, self).save()
306
307 def activation_token(self):
308 return sha_constructor(settings.SECRET_KEY + unicode(self.id)).hexdigest()[::2]
309
310 def get_absolute_url(self):
311 return url('chercheur', kwargs={'id': self.id})
312
313 class Publication(models.Model):
314 chercheur = models.ForeignKey(Chercheur, related_name='publications')
315 auteurs = models.CharField(max_length=255, blank=True, verbose_name='auteur(s)')
316 titre = models.CharField(max_length=255, null=True, blank=True, verbose_name='titre')
317 revue = models.CharField(max_length=255, null=True, blank=True, verbose_name='revue')
318 annee = models.IntegerField(null=True, blank=True, verbose_name='année de publication')
319 editeur = models.CharField(max_length=255, null=True, blank=True, verbose_name='éditeur')
320 lieu_edition = models.CharField(max_length=255, null=True, blank=True, verbose_name="lieu d'édition")
321 nb_pages = models.CharField(max_length=255, null=True, blank=True, verbose_name='nombre de pages')
322 url = models.URLField(max_length=255, null=True, blank=True, verbose_name='lien vers la publication', verify_exists=False)
323 #Migration des publications depuis l'ancien repertoire de chercheurs
324 publication_affichage = models.TextField(verbose_name='publication', null=True, blank=True)
325 actif = models.BooleanField(editable=False)
326
327 def __unicode__(self):
328 return self.titre or '(Aucun)'
329
330 def save(self):
331 if self.publication_affichage and (self.auteurs or self.titre or
332 self.revue or self.annee or
333 self.editeur or self.lieu_edition
334 or self.nb_pages or self.url):
335 self.publication_affichage = ''
336 super(Publication, self).save()
337
338 class These(models.Model):
339 chercheur = models.OneToOneField(Chercheur, primary_key=True)
340 titre = models.CharField(max_length=255, verbose_name='Titre')
341 annee = models.IntegerField(verbose_name='Année de soutenance (réalisée ou prévue)')
342 directeur = models.CharField(max_length=255, verbose_name='Directeur')
343 etablissement = models.CharField(max_length=255, verbose_name='Établissement de soutenance')
344 nb_pages = models.IntegerField(verbose_name='Nombre de pages', blank=True, null=True)
345 url = models.URLField(max_length=255, verbose_name='Lien vers la publication', blank=True, verify_exists=False)
346
347 def __unicode__(self):
348 return self.titre
349
350 class Expertise(models.Model):
351 id = models.AutoField(primary_key=True, db_column='id')
352 chercheur = models.ForeignKey(Chercheur, related_name='expertises')
353 nom = models.CharField(max_length=255, verbose_name = "Objet de l'expertise")
354 date = models.CharField(max_length=255, blank=True)
355 lieu = models.CharField(max_length=255, null=True, blank=True, verbose_name = "Lieu de l'expertise")
356 organisme_demandeur = models.CharField(max_length=255, null=True, blank=True, verbose_name = 'Organisme demandeur')
357 organisme_demandeur_visible = models.BooleanField(verbose_name="Afficher l'organisme demandeur")
358 actif = models.BooleanField(editable = False, db_column='actif')
359
360 def __unicode__(self):
361 return u"%s" % (self.nom)
362
363 class GroupeChercheurManager(models.Manager):
364 def get_query_set(self):
365 return super(GroupeChercheurManager, self).get_query_set().filter(groupe_chercheur=True)
366
367 class DomaineRechercheManager(models.Manager):
368 def get_query_set(self):
369 return super(DomaineRechercheManager, self).get_query_set().filter(groupe_chercheur=False)
370
371 class Groupe(models.Model):
372 id = models.AutoField(primary_key=True, db_column='id')
373 nom = models.CharField(max_length=255, db_column='nom')
374 url = models.URLField(max_length=255, null=True, blank=True,
375 verbose_name='Site web')
376 liste_diffusion = models.URLField(max_length=255, null=True, blank=True,
377 verbose_name='Liste de diffusion')
378 bulletin = models.URLField(max_length=255, null=True, blank=True,
379 verbose_name='Bulletin')
380 actif = models.BooleanField(editable = False, db_column='actif')
381 groupe_chercheur = models.BooleanField(default=False, verbose_name='Groupe de chercheur')
382
383
384 objects = models.Manager()
385 groupe_chercheur_objects = GroupeChercheurManager()
386 domaine_recherche_objects = DomaineRechercheManager()
387
388 class Meta:
389 verbose_name = 'domaine de recherche'
390 verbose_name_plural = 'domaines de recherche'
391
392 def __unicode__(self):
393 return u"%s" % (self.nom)
394
395 class GroupeChercheur(Groupe):
396 objects = GroupeChercheurManager()
397
398 class Meta:
399 proxy = True
400 verbose_name = 'groupe de chercheur'
401 verbose_name_plural = 'groupes de chercheur'
402
403 class DomaineRecherche(Groupe):
404 objects = DomaineRechercheManager()
405
406 class Meta:
407 proxy = True
408 verbose_name = 'domaine de recherche'
409 verbose_name_plural = 'domaines de recherche'
410
411 class ChercheurGroupe(models.Model):
412 id = models.AutoField(primary_key=True, db_column='id')
413 chercheur = models.ForeignKey('Chercheur', db_column='chercheur', editable=False)
414 groupe = models.ForeignKey('Groupe', db_column='groupe')
415 date_inscription = models.DateField(auto_now_add=True)
416 date_modification = models.DateField(auto_now=True)
417 actif = models.BooleanField(editable = False, db_column='actif')
418
419 class Meta:
420 verbose_name = 'adhésion'
421
422 def __unicode__(self):
423 return u"%s - %s" % (self.chercheur, self.groupe)
424
425 class ChercheurSearch(Search):
426 nom_chercheur = models.CharField(max_length=100, blank=True, verbose_name='nom')
427 domaine = models.ForeignKey(DomaineRecherche, blank=True, null=True, verbose_name='domaine de recherche')
428 groupe_chercheur = models.ForeignKey(GroupeChercheur, blank=True, null=True, verbose_name='groupe de chercheur')
429 groupe_recherche = models.CharField(max_length=100, blank=True, null=True,
430 verbose_name='groupe de recherche',
431 help_text='ou Laboratoire, ou Groupement inter-universitaire')
432 statut = models.CharField(max_length=100, blank=True, choices=STATUT_CHOICES + (('expert', 'Expert'),))
433 pays = models.ForeignKey(Pays, blank=True, null=True)
434 nord_sud = models.CharField(max_length=4, blank=True, choices=(('Nord', 'Nord'), ('Sud', 'Sud')),
435 verbose_name='Nord/Sud',
436 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)")
437 activites_francophonie = models.CharField(
438 max_length=25, blank=True, verbose_name='activités en Francophonie',
439 choices=(('instance_auf', "Membre d'une instance de l'AUF"),
440 ('expert_oif', "Sollicité par l'OIF"),
441 ('association_francophone', "Membre d'une association ou d'une société savante francophone"),
442 ('reseau_institutionnel', "Membre des instances d'un réseau institutionnel de l'AUF"))
443 )
444 genre = models.CharField(max_length=1, blank=True, choices=GENRE_CHOICES)
445
446 class Meta:
447 verbose_name = 'recherche de chercheurs'
448 verbose_name_plural = 'recherches de chercheurs'
449
450 def run(self, min_date=None, max_date=None):
451 results = Chercheur.objects
452 if self.q:
453 results = results.search(self.q)
454 if self.nom_chercheur:
455 results = results.add_to_query('@(nom,prenom) ' + self.nom_chercheur)
456 if self.groupe_recherche:
457 results = results.add_to_query('@groupe_recherche ' + self.groupe_recherche)
458 if self.discipline:
459 results = results.filter_discipline(self.discipline)
460 if self.region:
461 results = results.filter_region(self.region)
462 if self.statut:
463 if self.statut == "expert":
464 results = results.filter_expert()
465 else:
466 results = results.filter_statut(self.statut)
467 if self.domaine:
468 results = results.filter_groupe(self.domaine)
469 if self.groupe_chercheur:
470 results = results.filter_groupe(self.groupe_chercheur)
471 if self.pays:
472 results = results.filter_pays(self.pays)
473 if self.nord_sud:
474 results = results.filter_nord_sud(self.nord_sud)
475 if self.genre:
476 results = results.filter_genre(self.genre)
477 if self.activites_francophonie == 'instance_auf':
478 results = results.filter(membre_instance_auf=True)
479 elif self.activites_francophonie == 'expert_oif':
480 results = results.filter(expert_oif=True)
481 elif self.activites_francophonie == 'association_francophone':
482 results = results.filter(membre_association_francophone=True)
483 elif self.activites_francophonie == 'reseau_institutionnel':
484 results = results.filter(membre_reseau_institutionnel=True)
485 if min_date:
486 results = results.filter_date_modification(min=min_date)
487 if max_date:
488 results = results.filter_date_modification(max=max_date)
489 return results.all()
490
491 def url(self):
492 qs = self.query_string()
493 return url('chercheurs') + ('?' + qs if qs else '')
494
495 def rss_url(self):
496 qs = self.query_string()
497 return url('rss_chercheurs') + ('?' + qs if qs else '')
498
499 def get_email_alert_content(self, results):
500 content = ''
501 for chercheur in results:
502 content += u'- [%s %s](%s%s) \n' % (chercheur.nom.upper(),
503 chercheur.prenom,
504 settings.SITE_ROOT_URL,
505 chercheur.get_absolute_url())
506 content += u' %s\n\n' % chercheur.etablissement_display
507 return content