Recherches sauvegardées
[auf_savoirs_en_partage_django.git] / auf_savoirs_en_partage / savoirs / models.py
1 # -*- encoding: utf-8 -*-
2 import caldav
3 import datetime
4 import feedparser
5 import operator
6 import os
7 import pytz
8 import random
9 import uuid
10 import vobject
11 from urllib import urlencode
12
13 from backend_config import RESOURCES
14 from babel.dates import get_timezone_name
15 from caldav.lib import error
16 from django.contrib.auth.models import User
17 from django.contrib.contenttypes.models import ContentType
18 from django.core.urlresolvers import reverse
19 from django.db import models
20 from django.db.models import Q, Max
21 from django.db.models.signals import pre_delete
22 from django.utils.encoding import smart_unicode
23 from djangosphinx.models import SphinxQuerySet, SearchError
24
25 from datamaster_modeles.models import Region, Pays, Thematique
26 from savoirs.globals import META
27 from settings import CALENDRIER_URL, SITE_ROOT_URL
28
29 # Fonctionnalités communes à tous les query sets
30
31 class 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."""
40 count = self.count()
41 positions = random.sample(xrange(count), min(n, count))
42 return [self[p] for p in positions]
43
44 class SEPQuerySet(models.query.QuerySet, RandomQuerySetMixin):
45 pass
46
47 class 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."""
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
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
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
98 class 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
120 class 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')
123
124 def __unicode__ (self):
125 return self.nom
126
127 class Meta:
128 db_table = u'discipline'
129 ordering = ["nom",]
130
131 # Actualités
132
133 class SourceActualite(models.Model):
134 TYPE_CHOICES = (
135 ('actu', 'Actualités'),
136 ('appels', "Appels d'offres"),
137 )
138
139 nom = models.CharField(max_length=255)
140 url = models.CharField(max_length=255, verbose_name='URL', blank=True)
141 type = models.CharField(max_length=10, default='actu', choices=TYPE_CHOICES)
142
143 class Meta:
144 verbose_name = u'fil RSS syndiqué'
145 verbose_name_plural = u'fils RSS syndiqués'
146
147 def __unicode__(self,):
148 return u"%s (%s)" % (self.nom, self.get_type_display())
149
150 def update(self):
151 """Mise à jour du fil RSS."""
152 if not self.url:
153 return
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
163 class ActualiteQuerySet(SEPQuerySet):
164
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
172
173 def filter_type(self, type):
174 return self.filter(source__type=type)
175
176 class ActualiteSphinxQuerySet(SEPSphinxQuerySet):
177
178 def __init__(self, model=None):
179 SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_actualites',
180 weights=dict(titre=3))
181
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
190 TYPE_CODES = {'actu': 1, 'appels': 2}
191 def filter_type(self, type):
192 return self.filter(type=self.TYPE_CODES[type])
193
194 class ActualiteManager(SEPManager):
195
196 def get_query_set(self):
197 return ActualiteQuerySet(self.model).filter(visible=True)
198
199 def get_sphinx_query_set(self):
200 return ActualiteSphinxQuerySet(self.model).order_by('-date')
201
202 def filter_date(self, min=None, max=None):
203 return self.get_query_set().filter_date(min=min, max=max)
204
205 def filter_type(self, type):
206 return self.get_query_set().filter_type(type)
207
208 class Actualite(models.Model):
209 id = models.AutoField(primary_key=True, db_column='id_actualite')
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')
213 date = models.DateField(db_column='date_actualite')
214 visible = models.BooleanField(db_column='visible_actualite', default=False)
215 ancienid = models.IntegerField(db_column='ancienId_actualite', blank=True, null=True)
216 source = models.ForeignKey(SourceActualite, related_name='actualites')
217 disciplines = models.ManyToManyField(Discipline, blank=True, related_name="actualites")
218 regions = models.ManyToManyField(Region, blank=True, related_name="actualites", verbose_name='régions')
219
220 objects = ActualiteManager()
221 all_objects = models.Manager()
222
223 class Meta:
224 db_table = u'actualite'
225 ordering = ["-date"]
226
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
236 # Agenda
237
238 class EvenementQuerySet(SEPQuerySet):
239
240 def filter_type(self, type):
241 return self.filter(type=type)
242
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
250
251 class EvenementSphinxQuerySet(SEPSphinxQuerySet):
252
253 def __init__(self, model=None):
254 SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_evenements',
255 weights=dict(titre=3))
256
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):
261 qs = self
262 if min:
263 qs = qs.filter(debut__gte=min.toordinal()+365)
264 if max:
265 qs = qs.filter(debut__lte=max.toordinal()+365)
266 return qs
267
268 class EvenementManager(SEPManager):
269
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)
281
282 def build_time_zone_choices(pays=None):
283 timezones = pytz.country_timezones[pays] if pays else pytz.common_timezones
284 result = []
285 now = datetime.datetime.now()
286 for tzname in timezones:
287 tz = pytz.timezone(tzname)
288 fr_name = get_timezone_name(tz, locale='fr_FR')
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
293 result.append((seconds, tzname, '%s - %s' % (offset_str, fr_name)))
294 result.sort()
295 return [(x[1], x[2]) for x in result]
296
297 class Evenement(models.Model):
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'),
302 (u'Autre', u'Autre'))
303 TIME_ZONE_CHOICES = build_time_zone_choices()
304
305 uid = models.CharField(max_length=255, default=str(uuid.uuid1()))
306 approuve = models.BooleanField(default=False, verbose_name=u'approuvé')
307 titre = models.CharField(max_length=255)
308 discipline = models.ForeignKey('Discipline', related_name = "discipline",
309 blank = True, null = True)
310 discipline_secondaire = models.ForeignKey('Discipline', related_name="discipline_secondaire",
311 verbose_name=u"discipline secondaire",
312 blank=True, null=True)
313 mots_cles = models.TextField('Mots-Clés', blank=True, null=True)
314 type = models.CharField(max_length=255, choices=TYPE_CHOICES)
315 adresse = models.TextField()
316 ville = models.CharField(max_length=100)
317 pays = models.ForeignKey(Pays, null=True, related_name='evenements')
318 debut = models.DateTimeField(default=datetime.datetime.now)
319 fin = models.DateTimeField(default=datetime.datetime.now)
320 fuseau = models.CharField(max_length=100, choices=TIME_ZONE_CHOICES, verbose_name='fuseau horaire')
321 description = models.TextField()
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()
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')
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.")
332
333 objects = EvenementManager()
334 all_objects = models.Manager()
335
336 class Meta:
337 ordering = ['-debut']
338 verbose_name = u'évènement'
339 verbose_name_plural = u'évènements'
340
341 def __unicode__(self,):
342 return "[%s] %s" % (self.uid, self.titre)
343
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
364 def piece_jointe_display(self):
365 return self.piece_jointe and os.path.basename(self.piece_jointe.name)
366
367 def courriel_display(self):
368 return self.courriel.replace(u'@', u' (à) ')
369
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
375 def save(self, *args, **kwargs):
376 """Sauvegarde l'objet dans django et le synchronise avec caldav s'il a été
377 approuvé"""
378 self.contact = '' # Vider ce champ obsolète à la première occasion...
379 self.clean()
380 super(Evenement, self).save(*args, **kwargs)
381 self.update_vevent()
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
390 if self.uid in [None, ""]:
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
408 kw = [x.strip() for x in kw if len(x.strip()) > 0 and x is not None]
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"
416 description += u"Mots-clés: " + ", ".join(kw)
417
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))
420 cal.vevent.add('created').value = combine(datetime.datetime.now(), "UTC")
421 cal.vevent.add('dtstamp').value = combine(datetime.datetime.now(), "UTC")
422 if len(description) > 0:
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
428 cal.vevent.add('location').value = ', '.join([x for x in [self.adresse, self.ville, self.pays.nom] if x])
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
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
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
474 def delete_vevent(sender, instance, *args, **kwargs):
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
478 instance.delete_vevent()
479 pre_delete.connect(delete_vevent, sender=Evenement)
480
481 # Ressources
482
483 class 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)
487 validated = models.BooleanField(default = True)
488
489 def __unicode__(self,):
490 return self.name
491
492 class RecordSphinxQuerySet(SEPSphinxQuerySet):
493
494 def __init__(self, model=None):
495 SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_ressources',
496 weights=dict(title=3))
497
498 class RecordManager(SEPManager):
499
500 def get_query_set(self):
501 """Ne garder que les ressources validées et qui sont soit dans aucun
502 listset ou au moins dans un listset validé."""
503 qs = SEPQuerySet(self.model)
504 qs = qs.filter(validated=True)
505 qs = qs.filter(Q(listsets__isnull=True) | Q(listsets__validated=True))
506 return qs.distinct()
507
508 def get_sphinx_query_set(self):
509 return RecordSphinxQuerySet(self.model)
510
511 class Record(models.Model):
512
513 #fonctionnement interne
514 id = models.AutoField(primary_key = True)
515 server = models.CharField(max_length = 255, verbose_name=u'serveur')
516 last_update = models.CharField(max_length = 255)
517 last_checksum = models.CharField(max_length = 255)
518 validated = models.BooleanField(default=True, verbose_name=u'validé')
519
520 #OAI
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)
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)
529 subject = models.TextField(null=True, blank=True, verbose_name='sujet')
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)
534
535 listsets = models.ManyToManyField(ListSet, null = True, blank = True)
536
537 #SEP 2 (aucune données récoltées)
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)
544
545 # Metadata AUF multivaluées
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')
550
551 # Managers
552 objects = RecordManager()
553 all_objects = models.Manager()
554
555 class Meta:
556 verbose_name = 'ressource'
557
558 def __unicode__(self):
559 return "[%s] %s" % (self.server, self.title)
560
561 def getServeurURL(self):
562 """Retourne l'URL du serveur de provenance"""
563 return RESOURCES[self.server]['url']
564
565 def est_complet(self):
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
572 def assigner_regions(self, regions):
573 self.regions.add(*regions)
574
575 def assigner_disciplines(self, disciplines):
576 self.disciplines.add(*disciplines)
577
578 class Serveur(models.Model):
579 """Identification d'un serveur d'ou proviennent les références"""
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
591 class Profile(models.Model):
592 user = models.ForeignKey(User, unique=True)
593 serveurs = models.ManyToManyField(Serveur, null = True, blank = True)
594
595 class HarvestLog(models.Model):
596 context = models.CharField(max_length = 255)
597 name = models.CharField(max_length = 255)
598 date = models.DateTimeField(auto_now = True)
599 added = models.IntegerField(null = True, blank = True)
600 updated = models.IntegerField(null = True, blank = True)
601 processed = models.IntegerField(null = True, blank = True)
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'):
608 message['record'] = Record.all_objects.get(id=message['record_id'])
609 del(message['record_id'])
610
611 for k,v in message.items():
612 setattr(logger, k, v)
613 logger.save()
614
615 # Pages statiques
616
617 class PageStatique(models.Model):
618 id = models.CharField(max_length=32, primary_key=True)
619 titre = models.CharField(max_length=100)
620 contenu = models.TextField()
621
622 class Meta:
623 verbose_name_plural = 'pages statiques'
624
625 # Recherches
626
627 class 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
639 class 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
731 class 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
767 class 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
788 class 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
801 class 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
814 class 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