Bugfix: les sélecteurs de région et de discipline étaient brisés lorsqu'on les
[auf_savoirs_en_partage_django.git] / auf_savoirs_en_partage / savoirs / models.py
CommitLineData
92c7413b 1# -*- encoding: utf-8 -*-
db2999fa
EMS
2import caldav
3import datetime
4import feedparser
5import operator
6import os
7import pytz
8import random
9import uuid
10import vobject
fdcf5874
EMS
11from urllib import urlencode
12
5212238e 13from backend_config import RESOURCES
86983865 14from babel.dates import get_timezone_name
5212238e 15from caldav.lib import error
6d885e0c 16from django.contrib.auth.models import User
fdcf5874
EMS
17from django.contrib.contenttypes.models import ContentType
18from django.core.urlresolvers import reverse
d15017b2 19from django.db import models
15261361 20from django.db.models import Q, Max
b7a741ad 21from django.db.models.signals import pre_delete
5212238e 22from django.utils.encoding import smart_unicode
122c4c3d 23from djangosphinx.models import SphinxQuerySet, SearchError
fdcf5874
EMS
24
25from datamaster_modeles.models import Region, Pays, Thematique
da9020f3 26from savoirs.globals import META
74b087e5 27from settings import CALENDRIER_URL, SITE_ROOT_URL
5212238e
EMS
28
29# Fonctionnalités communes à tous les query sets
d15017b2 30
15261361
EMS
31class RandomQuerySetMixin(object):
32 """Mixin pour les modèles.
33
34 ORDER BY RAND() est très lent sous MySQL. On a besoin d'une autre
35 méthode pour récupérer des objets au hasard.
36 """
37
38 def random(self, n=1):
39 """Récupère aléatoirement un nombre donné d'objets."""
bae03b7b
EMS
40 count = self.count()
41 positions = random.sample(xrange(count), min(n, count))
42 return [self[p] for p in positions]
15261361 43
5212238e
EMS
44class SEPQuerySet(models.query.QuerySet, RandomQuerySetMixin):
45 pass
46
47class SEPSphinxQuerySet(SphinxQuerySet, RandomQuerySetMixin):
48 """Fonctionnalités communes aux query sets de Sphinx."""
49
50 def __init__(self, model=None, index=None, weights=None):
51 SphinxQuerySet.__init__(self, model=model, index=index,
52 mode='SPH_MATCH_EXTENDED2',
53 rankmode='SPH_RANK_PROXIMITY_BM25',
54 weights=weights)
55
56 def add_to_query(self, query):
57 """Ajoute une partie à la requête texte."""
6c78c673
EMS
58
59 # Assurons-nous qu'il y a un nombre pair de guillemets
60 if query.count('"') % 2 != 0:
61 # Sinon, on enlève le dernier (faut choisir...)
62 i = query.rindex('"')
63 query = query[:i] + query[i+1:]
64
5212238e
EMS
65 new_query = smart_unicode(self._query) + ' ' + query if self._query else query
66 return self.query(new_query)
67
68 def search(self, text):
69 """Recherche ``text`` dans tous les champs."""
70 return self.add_to_query('@* ' + text)
71
72 def filter_discipline(self, discipline):
73 """Par défaut, le filtre par discipline cherche le nom de la
74 discipline dans tous les champs."""
75 return self.search('"%s"' % discipline.nom)
76
77 def filter_region(self, region):
78 """Par défaut, le filtre par région cherche le nom de la région dans
79 tous les champs."""
80 return self.search('"%s"' % region.nom)
81
122c4c3d
EMS
82 def _get_sphinx_results(self):
83 try:
84 return SphinxQuerySet._get_sphinx_results(self)
85 except SearchError:
86 # Essayons d'enlever les caractères qui peuvent poser problème.
87 for c in '|!@()~/<=^$':
88 self._query = self._query.replace(c, ' ')
89 try:
90 return SphinxQuerySet._get_sphinx_results(self)
91 except SearchError:
92 # Ça ne marche toujours pas. Enlevons les guillemets et les
93 # tirets.
94 for c in '"-':
95 self._query = self._query.replace(c, ' ')
96 return SphinxQuerySet._get_sphinx_results(self)
97
5212238e
EMS
98class SEPManager(models.Manager):
99 """Lorsque les méthodes ``search``, ``filter_region`` et
100 ``filter_discipline`` sont appelées sur ce manager, le query set
101 Sphinx est créé, sinon, c'est le query set Django qui est créé."""
102
103 def query(self, query):
104 return self.get_sphinx_query_set().query(query)
105
106 def add_to_query(self, query):
107 return self.get_sphinx_query_set().add_to_query(query)
108
109 def search(self, text):
110 return self.get_sphinx_query_set().search(text)
111
112 def filter_region(self, region):
113 return self.get_sphinx_query_set().filter_region(region)
114
115 def filter_discipline(self, discipline):
116 return self.get_sphinx_query_set().filter_discipline(discipline)
117
118# Disciplines
119
d15017b2
CR
120class Discipline(models.Model):
121 id = models.IntegerField(primary_key=True, db_column='id_discipline')
122 nom = models.CharField(max_length=765, db_column='nom_discipline')
6ef8ead4
CR
123
124 def __unicode__ (self):
92c7413b 125 return self.nom
6ef8ead4 126
d15017b2
CR
127 class Meta:
128 db_table = u'discipline'
129 ordering = ["nom",]
130
5212238e
EMS
131# Actualités
132
79c398f6 133class SourceActualite(models.Model):
011804bb
EMS
134 TYPE_CHOICES = (
135 ('actu', 'Actualités'),
136 ('appels', "Appels d'offres"),
137 )
138
79c398f6 139 nom = models.CharField(max_length=255)
011804bb
EMS
140 url = models.CharField(max_length=255, verbose_name='URL', blank=True)
141 type = models.CharField(max_length=10, default='actu', choices=TYPE_CHOICES)
ccbc4363 142
db2999fa
EMS
143 class Meta:
144 verbose_name = u'fil RSS syndiqué'
145 verbose_name_plural = u'fils RSS syndiqués'
146
ccbc4363 147 def __unicode__(self,):
011804bb 148 return u"%s (%s)" % (self.nom, self.get_type_display())
79c398f6 149
db2999fa
EMS
150 def update(self):
151 """Mise à jour du fil RSS."""
011804bb
EMS
152 if not self.url:
153 return
db2999fa
EMS
154 feed = feedparser.parse(self.url)
155 for entry in feed.entries:
156 if Actualite.all_objects.filter(url=entry.link).count() == 0:
157 ts = entry.updated_parsed
158 date = datetime.date(ts.tm_year, ts.tm_mon, ts.tm_mday)
159 a = self.actualites.create(titre=entry.title,
160 texte=entry.summary_detail.value,
161 url=entry.link, date=date)
162
5212238e 163class ActualiteQuerySet(SEPQuerySet):
2f9c4d6c 164
5212238e
EMS
165 def filter_date(self, min=None, max=None):
166 qs = self
167 if min:
168 qs = qs.filter(date__gte=min)
169 if max:
170 qs = qs.filter(date__lte=max)
171 return qs
da44ce68 172
011804bb
EMS
173 def filter_type(self, type):
174 return self.filter(source__type=type)
175
5212238e 176class ActualiteSphinxQuerySet(SEPSphinxQuerySet):
c1b134f8 177
5212238e 178 def __init__(self, model=None):
4134daa0 179 SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_actualites',
5212238e 180 weights=dict(titre=3))
c1b134f8 181
7fb6bd61
EMS
182 def filter_date(self, min=None, max=None):
183 qs = self
184 if min:
185 qs = qs.filter(date__gte=min.toordinal()+365)
186 if max:
187 qs = qs.filter(date__lte=max.toordinal()+365)
188 return qs
189
011804bb
EMS
190 TYPE_CODES = {'actu': 1, 'appels': 2}
191 def filter_type(self, type):
192 return self.filter(type=self.TYPE_CODES[type])
193
5212238e
EMS
194class ActualiteManager(SEPManager):
195
196 def get_query_set(self):
197 return ActualiteQuerySet(self.model).filter(visible=True)
2f9c4d6c 198
5212238e
EMS
199 def get_sphinx_query_set(self):
200 return ActualiteSphinxQuerySet(self.model).order_by('-date')
2f9c4d6c 201
5212238e
EMS
202 def filter_date(self, min=None, max=None):
203 return self.get_query_set().filter_date(min=min, max=max)
bae03b7b 204
011804bb
EMS
205 def filter_type(self, type):
206 return self.get_query_set().filter_type(type)
207
d15017b2 208class Actualite(models.Model):
4f262f90 209 id = models.AutoField(primary_key=True, db_column='id_actualite')
d15017b2
CR
210 titre = models.CharField(max_length=765, db_column='titre_actualite')
211 texte = models.TextField(db_column='texte_actualite')
212 url = models.CharField(max_length=765, db_column='url_actualite')
d15017b2 213 date = models.DateField(db_column='date_actualite')
db2999fa
EMS
214 visible = models.BooleanField(db_column='visible_actualite', default=False)
215 ancienid = models.IntegerField(db_column='ancienId_actualite', blank=True, null=True)
011804bb 216 source = models.ForeignKey(SourceActualite, related_name='actualites')
3a45eb64 217 disciplines = models.ManyToManyField(Discipline, blank=True, related_name="actualites")
a5f76eb4 218 regions = models.ManyToManyField(Region, blank=True, related_name="actualites", verbose_name='régions')
6ef8ead4 219
2f9c4d6c 220 objects = ActualiteManager()
c59dba82 221 all_objects = models.Manager()
2f9c4d6c 222
d15017b2
CR
223 class Meta:
224 db_table = u'actualite'
7020ea3d 225 ordering = ["-date"]
92c7413b 226
264a3210
EMS
227 def __unicode__ (self):
228 return "%s" % (self.titre)
229
230 def assigner_disciplines(self, disciplines):
231 self.disciplines.add(*disciplines)
232
233 def assigner_regions(self, regions):
234 self.regions.add(*regions)
235
5212238e 236# Agenda
4101cfc0 237
5212238e 238class EvenementQuerySet(SEPQuerySet):
4101cfc0 239
5212238e
EMS
240 def filter_type(self, type):
241 return self.filter(type=type)
c1b134f8 242
5212238e
EMS
243 def filter_debut(self, min=None, max=None):
244 qs = self
245 if min:
246 qs = qs.filter(debut__gte=min)
247 if max:
248 qs = qs.filter(debut__lt=max+datetime.timedelta(days=1))
249 return qs
c1b134f8 250
5212238e 251class EvenementSphinxQuerySet(SEPSphinxQuerySet):
4101cfc0 252
5212238e 253 def __init__(self, model=None):
4134daa0 254 SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_evenements',
5212238e 255 weights=dict(titre=3))
116db1fd 256
5212238e
EMS
257 def filter_type(self, type):
258 return self.add_to_query('@type "%s"' % type)
259
260 def filter_debut(self, min=None, max=None):
7bbf600c 261 qs = self
5212238e
EMS
262 if min:
263 qs = qs.filter(debut__gte=min.toordinal()+365)
264 if max:
265 qs = qs.filter(debut__lte=max.toordinal()+365)
7bbf600c
EMS
266 return qs
267
5212238e 268class EvenementManager(SEPManager):
5212238e 269
5212238e
EMS
270 def get_query_set(self):
271 return EvenementQuerySet(self.model).filter(approuve=True)
272
273 def get_sphinx_query_set(self):
274 return EvenementSphinxQuerySet(self.model).order_by('-debut')
275
276 def filter_type(self, type):
277 return self.get_query_set().filter_type(type)
278
279 def filter_debut(self, min=None, max=None):
280 return self.get_query_set().filter_debut(min=min, max=max)
bae03b7b 281
1719bf4e 282def build_time_zone_choices(pays=None):
1719bf4e
EMS
283 timezones = pytz.country_timezones[pays] if pays else pytz.common_timezones
284 result = []
86983865 285 now = datetime.datetime.now()
1719bf4e 286 for tzname in timezones:
86983865
EMS
287 tz = pytz.timezone(tzname)
288 fr_name = get_timezone_name(tz, locale='fr_FR')
86983865
EMS
289 offset = tz.utcoffset(now)
290 seconds = offset.seconds + offset.days * 86400
291 (hours, minutes) = divmod(seconds // 60, 60)
292 offset_str = 'UTC%+d:%d' % (hours, minutes) if minutes else 'UTC%+d' % hours
1719bf4e
EMS
293 result.append((seconds, tzname, '%s - %s' % (offset_str, fr_name)))
294 result.sort()
295 return [(x[1], x[2]) for x in result]
86983865 296
92c7413b 297class Evenement(models.Model):
7bbf600c
EMS
298 TYPE_CHOICES = ((u'Colloque', u'Colloque'),
299 (u'Conférence', u'Conférence'),
300 (u'Appel à contribution', u'Appel à contribution'),
301 (u'Journée d\'étude', u'Journée d\'étude'),
ec81ec66 302 (u'Autre', u'Autre'))
86983865
EMS
303 TIME_ZONE_CHOICES = build_time_zone_choices()
304
74b087e5 305 uid = models.CharField(max_length=255, default=str(uuid.uuid1()))
a5f76eb4 306 approuve = models.BooleanField(default=False, verbose_name=u'approuvé')
92c7413b
CR
307 titre = models.CharField(max_length=255)
308 discipline = models.ForeignKey('Discipline', related_name = "discipline",
309 blank = True, null = True)
a5f76eb4
EMS
310 discipline_secondaire = models.ForeignKey('Discipline', related_name="discipline_secondaire",
311 verbose_name=u"discipline secondaire",
312 blank=True, null=True)
74b087e5 313 mots_cles = models.TextField('Mots-Clés', blank=True, null=True)
7bbf600c 314 type = models.CharField(max_length=255, choices=TYPE_CHOICES)
731ef7ab
EMS
315 adresse = models.TextField()
316 ville = models.CharField(max_length=100)
317 pays = models.ForeignKey(Pays, null=True, related_name='evenements')
74b087e5
EMS
318 debut = models.DateTimeField(default=datetime.datetime.now)
319 fin = models.DateTimeField(default=datetime.datetime.now)
86983865 320 fuseau = models.CharField(max_length=100, choices=TIME_ZONE_CHOICES, verbose_name='fuseau horaire')
fe254ccc 321 description = models.TextField()
731ef7ab
EMS
322 contact = models.TextField(null=True) # champ obsolète
323 prenom = models.CharField('prénom', max_length=100)
324 nom = models.CharField(max_length=100)
325 courriel = models.EmailField()
74b087e5
EMS
326 url = models.CharField(max_length=255, blank=True, null=True)
327 piece_jointe = models.FileField(upload_to='agenda/pj', blank=True, verbose_name='pièce jointe')
fe254ccc
EMS
328 regions = models.ManyToManyField(Region, blank=True, related_name="evenements", verbose_name='régions additionnelles',
329 help_text="On considère d'emblée que l'évènement se déroule dans la région "
330 "dans laquelle se trouve le pays indiqué plus haut. Il est possible "
331 "de désigner ici des régions additionnelles.")
92c7413b 332
4101cfc0 333 objects = EvenementManager()
c59dba82 334 all_objects = models.Manager()
4101cfc0
EMS
335
336 class Meta:
337 ordering = ['-debut']
fe254ccc
EMS
338 verbose_name = u'évènement'
339 verbose_name_plural = u'évènements'
4101cfc0 340
020f79a9 341 def __unicode__(self,):
342 return "[%s] %s" % (self.uid, self.titre)
343
8dfe5efa
EMS
344 def duration_display(self):
345 delta = self.fin - self.debut
346 minutes, seconds = divmod(delta.seconds, 60)
347 hours, minutes = divmod(minutes, 60)
348 days = delta.days
349 parts = []
350 if days == 1:
351 parts.append('1 jour')
352 elif days > 1:
353 parts.append('%d jours' % days)
354 if hours == 1:
355 parts.append('1 heure')
356 elif hours > 1:
357 parts.append('%d heures' % hours)
358 if minutes == 1:
359 parts.append('1 minute')
360 elif minutes > 1:
361 parts.append('%d minutes' % minutes)
362 return ' '.join(parts)
363
27fe0d70
EMS
364 def piece_jointe_display(self):
365 return self.piece_jointe and os.path.basename(self.piece_jointe.name)
366
fe254ccc
EMS
367 def courriel_display(self):
368 return self.courriel.replace(u'@', u' (à) ')
369
73309469 370 def clean(self):
371 from django.core.exceptions import ValidationError
372 if self.debut > self.fin:
373 raise ValidationError('La date de fin ne doit pas être antérieure à la date de début')
374
b7a741ad 375 def save(self, *args, **kwargs):
376 """Sauvegarde l'objet dans django et le synchronise avec caldav s'il a été
377 approuvé"""
731ef7ab 378 self.contact = '' # Vider ce champ obsolète à la première occasion...
73309469 379 self.clean()
b7a741ad 380 super(Evenement, self).save(*args, **kwargs)
acd5cd8f 381 self.update_vevent()
b7a741ad 382
383 # methodes de commnunications avec CALDAV
384 def as_ical(self,):
385 """Retourne l'evenement django sous forme d'objet icalendar"""
386 cal = vobject.iCalendar()
387 cal.add('vevent')
388
389 # fournit son propre uid
7f56d0d4 390 if self.uid in [None, ""]:
b7a741ad 391 self.uid = str(uuid.uuid1())
392
393 cal.vevent.add('uid').value = self.uid
394
395 cal.vevent.add('summary').value = self.titre
396
397 if self.mots_cles is None:
398 kw = []
399 else:
400 kw = self.mots_cles.split(",")
401
402 try:
403 kw.append(self.discipline.nom)
404 kw.append(self.discipline_secondaire.nom)
405 kw.append(self.type)
406 except: pass
407
79b400f0 408 kw = [x.strip() for x in kw if len(x.strip()) > 0 and x is not None]
b7a741ad 409 for k in kw:
410 cal.vevent.add('x-auf-keywords').value = k
411
412 description = self.description
413 if len(kw) > 0:
414 if len(self.description) > 0:
415 description += "\n"
028f548f 416 description += u"Mots-clés: " + ", ".join(kw)
b7a741ad 417
7f214e0f
EMS
418 cal.vevent.add('dtstart').value = combine(self.debut, pytz.timezone(self.fuseau))
419 cal.vevent.add('dtend').value = combine(self.fin, pytz.timezone(self.fuseau))
b7a741ad 420 cal.vevent.add('created').value = combine(datetime.datetime.now(), "UTC")
421 cal.vevent.add('dtstamp').value = combine(datetime.datetime.now(), "UTC")
79b400f0 422 if len(description) > 0:
b7a741ad 423 cal.vevent.add('description').value = description
424 if len(self.contact) > 0:
425 cal.vevent.add('contact').value = self.contact
426 if len(self.url) > 0:
427 cal.vevent.add('url').value = self.url
fe254ccc 428 cal.vevent.add('location').value = ', '.join([x for x in [self.adresse, self.ville, self.pays.nom] if x])
74b087e5
EMS
429 if self.piece_jointe:
430 url = self.piece_jointe.url
431 if not url.startswith('http://'):
432 url = SITE_ROOT_URL + url
433 cal.vevent.add('attach').value = url
b7a741ad 434 return cal
435
436 def update_vevent(self,):
437 """Essaie de créer l'évènement sur le serveur ical.
438 En cas de succès, l'évènement local devient donc inactif et approuvé"""
439 try:
440 if self.approuve:
441 event = self.as_ical()
442 client = caldav.DAVClient(CALENDRIER_URL)
443 cal = caldav.Calendar(client, url = CALENDRIER_URL)
444 e = caldav.Event(client, parent = cal, data = event.serialize(), id=self.uid)
445 e.save()
446 except:
447 self.approuve = False
448
449 def delete_vevent(self,):
450 """Supprime l'evenement sur le serveur caldav"""
451 try:
452 if self.approuve:
453 event = self.as_ical()
454 client = caldav.DAVClient(CALENDRIER_URL)
455 cal = caldav.Calendar(client, url = CALENDRIER_URL)
456 e = cal.event(self.uid)
457 e.delete()
458 except error.NotFoundError:
459 pass
460
264a3210
EMS
461 def assigner_regions(self, regions):
462 self.regions.add(*regions)
463
464 def assigner_disciplines(self, disciplines):
465 if len(disciplines) == 1:
466 if self.discipline:
467 self.discipline_secondaire = disciplines[0]
468 else:
469 self.discipline = disciplines[0]
470 elif len(disciplines) >= 2:
471 self.discipline = disciplines[0]
472 self.discipline_secondaire = disciplines[1]
473
b7a741ad 474def delete_vevent(sender, instance, *args, **kwargs):
5212238e
EMS
475 # Surcharge du comportement de suppression
476 # La méthode de connexion par signals est préférable à surcharger la méthode delete()
477 # car dans le cas de la suppression par lots, cell-ci n'est pas invoquée
b7a741ad 478 instance.delete_vevent()
5212238e 479pre_delete.connect(delete_vevent, sender=Evenement)
b7a741ad 480
5212238e 481# Ressources
b7a741ad 482
d972b61d 483class ListSet(models.Model):
484 spec = models.CharField(primary_key = True, max_length = 255)
485 name = models.CharField(max_length = 255)
486 server = models.CharField(max_length = 255)
9eda5d6c 487 validated = models.BooleanField(default = True)
d972b61d 488
10d37e44 489 def __unicode__(self,):
490 return self.name
491
5212238e 492class RecordSphinxQuerySet(SEPSphinxQuerySet):
f153be1b 493
5212238e 494 def __init__(self, model=None):
4134daa0 495 SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_ressources',
5212238e 496 weights=dict(title=3))
c1b134f8 497
5212238e 498class RecordManager(SEPManager):
f12cc7fb 499
5212238e 500 def get_query_set(self):
f153be1b
EMS
501 """Ne garder que les ressources validées et qui sont soit dans aucun
502 listset ou au moins dans un listset validé."""
5212238e
EMS
503 qs = SEPQuerySet(self.model)
504 qs = qs.filter(validated=True)
82f25472
EMS
505 qs = qs.filter(Q(listsets__isnull=True) | Q(listsets__validated=True))
506 return qs.distinct()
f153be1b 507
5212238e
EMS
508 def get_sphinx_query_set(self):
509 return RecordSphinxQuerySet(self.model)
77b0fac0 510
0cc5f772 511class Record(models.Model):
23b5b3d5 512
513 #fonctionnement interne
0cc5f772 514 id = models.AutoField(primary_key = True)
a5f76eb4 515 server = models.CharField(max_length = 255, verbose_name=u'serveur')
23b5b3d5 516 last_update = models.CharField(max_length = 255)
517 last_checksum = models.CharField(max_length = 255)
a5f76eb4 518 validated = models.BooleanField(default=True, verbose_name=u'validé')
23b5b3d5 519
520 #OAI
18dbd2cf
EMS
521 title = models.TextField(null=True, blank=True, verbose_name=u'titre')
522 creator = models.TextField(null=True, blank=True, verbose_name=u'auteur')
523 description = models.TextField(null=True, blank=True)
524 modified = models.CharField(max_length=255, null=True, blank=True)
23b5b3d5 525 identifier = models.CharField(max_length = 255, null = True, blank = True, unique = True)
526 uri = models.CharField(max_length = 255, null = True, blank = True, unique = True)
527 source = models.TextField(null = True, blank = True)
528 contributor = models.TextField(null = True, blank = True)
18dbd2cf 529 subject = models.TextField(null=True, blank=True, verbose_name='sujet')
23b5b3d5 530 publisher = models.TextField(null = True, blank = True)
531 type = models.TextField(null = True, blank = True)
532 format = models.TextField(null = True, blank = True)
533 language = models.TextField(null = True, blank = True)
da9020f3 534
c88d78dc 535 listsets = models.ManyToManyField(ListSet, null = True, blank = True)
d972b61d 536
da9020f3 537 #SEP 2 (aucune données récoltées)
23b5b3d5 538 alt_title = models.TextField(null = True, blank = True)
539 abstract = models.TextField(null = True, blank = True)
540 creation = models.CharField(max_length = 255, null = True, blank = True)
541 issued = models.CharField(max_length = 255, null = True, blank = True)
542 isbn = models.TextField(null = True, blank = True)
543 orig_lang = models.TextField(null = True, blank = True)
da9020f3 544
545 # Metadata AUF multivaluées
a342f93a
EMS
546 disciplines = models.ManyToManyField(Discipline, blank=True)
547 thematiques = models.ManyToManyField(Thematique, blank=True, verbose_name='thématiques')
548 pays = models.ManyToManyField(Pays, blank=True)
549 regions = models.ManyToManyField(Region, blank=True, verbose_name='régions')
0cc5f772 550
5212238e 551 # Managers
da44ce68 552 objects = RecordManager()
c59dba82 553 all_objects = models.Manager()
da44ce68 554
18dbd2cf
EMS
555 class Meta:
556 verbose_name = 'ressource'
557
264a3210
EMS
558 def __unicode__(self):
559 return "[%s] %s" % (self.server, self.title)
560
561 def getServeurURL(self):
f98ad449 562 """Retourne l'URL du serveur de provenance"""
563 return RESOURCES[self.server]['url']
564
264a3210 565 def est_complet(self):
6d885e0c 566 """teste si le record à toutes les données obligatoires"""
567 return self.disciplines.count() > 0 and \
568 self.thematiques.count() > 0 and \
569 self.pays.count() > 0 and \
570 self.regions.count() > 0
571
264a3210
EMS
572 def assigner_regions(self, regions):
573 self.regions.add(*regions)
da9020f3 574
264a3210
EMS
575 def assigner_disciplines(self, disciplines):
576 self.disciplines.add(*disciplines)
264a3210 577
6d885e0c 578class Serveur(models.Model):
b7a741ad 579 """Identification d'un serveur d'ou proviennent les références"""
6d885e0c 580 nom = models.CharField(primary_key = True, max_length = 255)
581
582 def __unicode__(self,):
583 return self.nom
584
585 def conf_2_db(self,):
586 for k in RESOURCES.keys():
587 s, created = Serveur.objects.get_or_create(nom=k)
588 s.nom = k
589 s.save()
590
591class Profile(models.Model):
592 user = models.ForeignKey(User, unique=True)
593 serveurs = models.ManyToManyField(Serveur, null = True, blank = True)
0cc5f772
CR
594
595class HarvestLog(models.Model):
23b5b3d5 596 context = models.CharField(max_length = 255)
597 name = models.CharField(max_length = 255)
0cc5f772 598 date = models.DateTimeField(auto_now = True)
23b5b3d5 599 added = models.IntegerField(null = True, blank = True)
600 updated = models.IntegerField(null = True, blank = True)
a85ba76e 601 processed = models.IntegerField(null = True, blank = True)
23b5b3d5 602 record = models.ForeignKey(Record, null = True, blank = True)
603
604 @staticmethod
605 def add(message):
606 logger = HarvestLog()
607 if message.has_key('record_id'):
d566e9c1 608 message['record'] = Record.all_objects.get(id=message['record_id'])
23b5b3d5 609 del(message['record_id'])
610
611 for k,v in message.items():
612 setattr(logger, k, v)
613 logger.save()
f09bc1c6
EMS
614
615# Pages statiques
616
617class PageStatique(models.Model):
20430b8a 618 id = models.CharField(max_length=32, primary_key=True)
f09bc1c6
EMS
619 titre = models.CharField(max_length=100)
620 contenu = models.TextField()
621
622 class Meta:
623 verbose_name_plural = 'pages statiques'
fdcf5874
EMS
624
625# Recherches
626
627class GlobalSearchResults(object):
628
629 def __init__(self, actualites=None, appels=None, evenements=None,
630 ressources=None, chercheurs=None, sites=None, sites_auf=None):
631 self.actualites = actualites
632 self.appels = appels
633 self.evenements = evenements
634 self.ressources = ressources
635 self.chercheurs = chercheurs
636 self.sites = sites
637 self.sites_auf = sites_auf
638
639class Search(models.Model):
640 user = models.ForeignKey(User, editable=False)
641 content_type = models.ForeignKey(ContentType, editable=False)
642 nom = models.CharField(max_length=100, verbose_name="nom de la recherche")
643 q = models.CharField(max_length=100, blank=True, verbose_name="rechercher dans tous les champs")
644 discipline = models.ForeignKey(Discipline, blank=True, null=True)
645 region = models.ForeignKey(Region, blank=True, null=True, verbose_name='région',
646 help_text="La région est ici définie au sens, non strictement géographique, du Bureau régional de l'AUF de référence.")
647
648 def query_string(self):
649 params = dict()
650 for field in self._meta.fields:
651 if field.name in ['id', 'user', 'nom', 'search_ptr', 'content_type']:
652 continue
653 value = getattr(self, field.column)
654 if value:
655 if isinstance(value, datetime.date):
656 params[field.name] = value.strftime('%d/%m/%Y')
657 else:
658 params[field.name] = value
659 return urlencode(params)
660
661 class Meta:
662 verbose_name = 'recherche transversale'
663 verbose_name_plural = "recherches transversales"
664
665 def save(self):
666 if (not self.content_type_id):
667 self.content_type = ContentType.objects.get_for_model(self.__class__)
668 super(Search, self).save()
669
670 def as_leaf_class(self):
671 content_type = self.content_type
672 model = content_type.model_class()
673 if(model == Search):
674 return self
675 return model.objects.get(id=self.id)
676
677 def run(self):
678 from chercheurs.models import Chercheur
679 from sitotheque.models import Site
680
681 results = object()
682 actualites = Actualite.objects
683 evenements = Evenement.objects
684 ressources = Record.objects
685 chercheurs = Chercheur.objects
686 sites = Site.objects
687 if self.q:
688 actualites = actualites.search(self.q)
689 evenements = evenements.search(self.q)
690 ressources = ressources.search(self.q)
691 chercheurs = chercheurs.search(self.q)
692 sites = sites.search(self.q)
693 if self.discipline:
694 actualites = actualites.filter_discipline(self.discipline)
695 evenements = evenements.filter_discipline(self.discipline)
696 ressources = ressources.filter_discipline(self.discipline)
697 chercheurs = chercheurs.filter_discipline(self.discipline)
698 sites = sites.filter_discipline(self.discipline)
699 if self.region:
700 actualites = actualites.filter_region(self.region)
701 evenements = evenements.filter_region(self.region)
702 ressources = ressources.filter_region(self.region)
703 chercheurs = chercheurs.filter_region(self.region)
704 sites = sites.filter_region(self.region)
705 try:
706 sites_auf = google_search(0, self.q)['results']
707 except:
708 sites_auf = []
709
710 return GlobalSearchResults(
711 actualites=actualites.order_by('-date').filter_type('actu'),
712 appels=actualites.order_by('-date').filter_type('appels'),
713 evenements=evenements.order_by('-debut'),
714 ressources=ressources.order_by('-id'),
715 chercheurs=chercheurs.order_by('-date_modification'),
716 sites=sites.order_by('-date_maj'),
717 sites_auf=sites_auf
718 )
719
720 def url(self):
721 url = ''
722 if self.discipline:
723 url += '/discipline/%d' % self.discipline.id
724 if self.region:
725 url += '/region/%d' % self.region.id
726 url += '/recherche/'
727 if self.q:
728 url += '?' + urlencode({'q': self.q})
729 return url
730
731class RessourceSearch(Search):
732 auteur = models.CharField(max_length=100, blank=True, verbose_name="auteur ou contributeur")
733 titre = models.CharField(max_length=100, blank=True)
734 sujet = models.CharField(max_length=100, blank=True)
735 publisher = models.CharField(max_length=100, blank=True, verbose_name="éditeur")
736
737 class Meta:
738 verbose_name = 'recherche de ressources'
739 verbose_name_plural = "recherches de ressources"
740
741 def run(self):
742 results = Record.objects
743 if self.q:
744 results = results.search(self.q)
745 if self.auteur:
746 results = results.add_to_query('@(creator,contributor) ' + self.auteur)
747 if self.titre:
748 results = results.add_to_query('@title ' + self.titre)
749 if self.sujet:
750 results = results.add_to_query('@subject ' + self.sujet)
751 if self.publisher:
752 results = results.add_to_query('@publisher ' + self.publisher)
753 if self.discipline:
754 results = results.filter_discipline(self.discipline)
755 if self.region:
756 results = results.filter_region(self.region)
757 if not self.q:
758 """Montrer les résultats les plus récents si on n'a pas fait
759 une recherche par mots-clés."""
760 results = results.order_by('-id')
761 return results.all()
762
763 def url(self):
764 qs = self.query_string()
765 return reverse('ressources') + ('?' + qs if qs else '')
766
767class ActualiteSearchBase(Search):
768 date_min = models.DateField(blank=True, null=True, verbose_name="depuis le")
769 date_max = models.DateField(blank=True, null=True, verbose_name="jusqu'au")
770
771 class Meta:
772 abstract = True
773
774 def run(self):
775 results = Actualite.objects
776 if self.q:
777 results = results.search(self.q)
778 if self.discipline:
779 results = results.filter_discipline(self.discipline)
780 if self.region:
781 results = results.filter_region(self.region)
782 if self.date_min:
783 results = results.filter_date(min=self.date_min)
784 if self.date_max:
785 results = results.filter_date(max=self.date_max)
786 return results.all()
787
788class ActualiteSearch(ActualiteSearchBase):
789
790 class Meta:
791 verbose_name = "recherche d'actualités"
792 verbose_name_plural = "recherches d'actualités"
793
794 def run(self):
795 return super(ActualiteSearch, self).run().filter_type('actu')
796
797 def url(self):
798 qs = self.query_string()
799 return reverse('actualites') + ('?' + qs if qs else '')
800
801class AppelSearch(ActualiteSearchBase):
802
803 class Meta:
804 verbose_name = "recherche d'appels d'offres"
805 verbose_name_plural = "recherches d'appels d'offres"
806
807 def run(self):
808 return super(AppelSearch, self).run().filter_type('appel')
809
810 def url(self):
811 qs = self.query_string()
812 return reverse('appels') + ('?' + qs if qs else '')
813
814class EvenementSearch(Search):
815 titre = models.CharField(max_length=100, blank=True, verbose_name="Intitulé")
816 type = models.CharField(max_length=100, blank=True, choices=Evenement.TYPE_CHOICES)
817 date_min = models.DateField(blank=True, null=True, verbose_name="depuis le")
818 date_max = models.DateField(blank=True, null=True, verbose_name="jusqu'au")
819
820 class Meta:
821 verbose_name = "recherche d'évènements"
822 verbose_name_plural = "recherches d'évènements"
823
824 def run(self):
825 results = Evenement.objects
826 if self.q:
827 results = results.search(self.q)
828 if self.titre:
829 results = results.add_to_query('@titre ' + self.titre)
830 if self.discipline:
831 results = results.filter_discipline(self.discipline)
832 if self.region:
833 results = results.filter_region(self.region)
834 if self.type:
835 results = results.filter_type(self.type)
836 if self.date_min:
837 results = results.filter_debut(min=self.date_min)
838 if self.date_max:
839 results = results.filter_debut(max=self.date_max)
840 return results.all()
841
842 def url(self):
843 qs = self.query_string()
844 return reverse('agenda') + ('?' + qs if qs else '')
845