1 # -*- encoding: utf-8 -*-
11 from urllib
import urlencode
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
25 from datamaster_modeles
.models
import Region
, Pays
, Thematique
26 from savoirs
.globals import META
27 from settings
import CALENDRIER_URL
, SITE_ROOT_URL
29 # Fonctionnalités communes à tous les query sets
31 class RandomQuerySetMixin(object):
32 """Mixin pour les modèles.
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.
38 def random(self
, n
=1):
39 """Récupère aléatoirement un nombre donné d'objets."""
41 positions
= random
.sample(xrange(count
), min(n
, count
))
42 return [self
[p
] for p
in positions
]
44 class SEPQuerySet(models
.query
.QuerySet
, RandomQuerySetMixin
):
46 def _filter_date(self
, field
, min=None, max=None):
47 """Limite les résultats à ceux dont le champ ``field`` tombe entre
48 les dates ``min`` et ``max``."""
51 qs
= qs
.filter(**{field
+ '__gte': min})
53 qs
= qs
.filter(**{field
+ '__lte': max})
56 class SEPSphinxQuerySet(SphinxQuerySet
, RandomQuerySetMixin
):
57 """Fonctionnalités communes aux query sets de Sphinx."""
59 def __init__(self
, model
=None, index
=None, weights
=None):
60 SphinxQuerySet
.__init__(self
, model
=model
, index
=index
,
61 mode
='SPH_MATCH_EXTENDED2',
62 rankmode
='SPH_RANK_PROXIMITY_BM25',
65 def add_to_query(self
, query
):
66 """Ajoute une partie à la requête texte."""
68 # Assurons-nous qu'il y a un nombre pair de guillemets
69 if query
.count('"') % 2 != 0:
70 # Sinon, on enlève le dernier (faut choisir...)
72 query
= query
[:i
] + query
[i
+1:]
74 new_query
= smart_unicode(self
._query
) + ' ' + query
if self
._query
else query
75 return self
.query(new_query
)
77 def search(self
, text
):
78 """Recherche ``text`` dans tous les champs."""
79 return self
.add_to_query('@* ' + text
)
81 def filter_discipline(self
, discipline
):
82 """Par défaut, le filtre par discipline cherche le nom de la
83 discipline dans tous les champs."""
84 return self
.search('"%s"' % discipline
.nom
)
86 def filter_region(self
, region
):
87 """Par défaut, le filtre par région cherche le nom de la région dans
89 return self
.search('"%s"' % region
.nom
)
91 def _filter_date(self
, field
, min=None, max=None):
92 """Limite les résultats à ceux dont le champ ``field`` tombe entre
93 les dates ``min`` et ``max``."""
96 qs
= qs
.filter(**{field
+ '__gte': min.toordinal()+365})
98 qs
= qs
.filter(**{field
+ '__lte': max.toordinal()+365})
101 def _get_sphinx_results(self
):
103 return SphinxQuerySet
._get_sphinx_results(self
)
105 # Essayons d'enlever les caractères qui peuvent poser problème.
106 for c
in '|!@()~/<=^$':
107 self
._query
= self
._query
.replace(c
, ' ')
109 return SphinxQuerySet
._get_sphinx_results(self
)
111 # Ça ne marche toujours pas. Enlevons les guillemets et les
114 self
._query
= self
._query
.replace(c
, ' ')
115 return SphinxQuerySet
._get_sphinx_results(self
)
117 class SEPManager(models
.Manager
):
118 """Lorsque les méthodes ``search``, ``filter_region`` et
119 ``filter_discipline`` sont appelées sur ce manager, le query set
120 Sphinx est créé, sinon, c'est le query set Django qui est créé."""
122 def query(self
, query
):
123 return self
.get_sphinx_query_set().query(query
)
125 def add_to_query(self
, query
):
126 return self
.get_sphinx_query_set().add_to_query(query
)
128 def search(self
, text
):
129 return self
.get_sphinx_query_set().search(text
)
131 def filter_region(self
, region
):
132 return self
.get_sphinx_query_set().filter_region(region
)
134 def filter_discipline(self
, discipline
):
135 return self
.get_sphinx_query_set().filter_discipline(discipline
)
139 class Discipline(models
.Model
):
140 id = models
.IntegerField(primary_key
=True, db_column
='id_discipline')
141 nom
= models
.CharField(max_length
=765, db_column
='nom_discipline')
143 def __unicode__ (self
):
147 db_table
= u
'discipline'
152 class SourceActualite(models
.Model
):
154 ('actu', 'Actualités'),
155 ('appels', "Appels d'offres"),
158 nom
= models
.CharField(max_length
=255)
159 url
= models
.CharField(max_length
=255, verbose_name
='URL', blank
=True)
160 type = models
.CharField(max_length
=10, default
='actu', choices
=TYPE_CHOICES
)
163 verbose_name
= u
'fil RSS syndiqué'
164 verbose_name_plural
= u
'fils RSS syndiqués'
166 def __unicode__(self
,):
167 return u
"%s (%s)" % (self
.nom
, self
.get_type_display())
170 """Mise à jour du fil RSS."""
173 feed
= feedparser
.parse(self
.url
)
174 for entry
in feed
.entries
:
175 if Actualite
.all_objects
.filter(url
=entry
.link
).count() == 0:
176 ts
= entry
.updated_parsed
177 date
= datetime
.date(ts
.tm_year
, ts
.tm_mon
, ts
.tm_mday
)
178 a
= self
.actualites
.create(titre
=entry
.title
,
179 texte
=entry
.summary_detail
.value
,
180 url
=entry
.link
, date
=date
)
182 class ActualiteQuerySet(SEPQuerySet
):
184 def filter_date(self
, min=None, max=None):
185 return self
._filter_date('date', min=min, max=max)
187 def filter_type(self
, type):
188 return self
.filter(source__type
=type)
190 class ActualiteSphinxQuerySet(SEPSphinxQuerySet
):
192 def __init__(self
, model
=None):
193 SEPSphinxQuerySet
.__init__(self
, model
=model
, index
='savoirsenpartage_actualites',
194 weights
=dict(titre
=3))
196 def filter_date(self
, min=None, max=None):
197 return self
._filter_date('date', min=min, max=max)
199 TYPE_CODES
= {'actu': 1, 'appels': 2}
200 def filter_type(self
, type):
201 return self
.filter(type=self
.TYPE_CODES
[type])
203 class ActualiteManager(SEPManager
):
205 def get_query_set(self
):
206 return ActualiteQuerySet(self
.model
).filter(visible
=True)
208 def get_sphinx_query_set(self
):
209 return ActualiteSphinxQuerySet(self
.model
).order_by('-date')
211 def filter_date(self
, min=None, max=None):
212 return self
.get_query_set().filter_date(min=min, max=max)
214 def filter_type(self
, type):
215 return self
.get_query_set().filter_type(type)
217 class Actualite(models
.Model
):
218 id = models
.AutoField(primary_key
=True, db_column
='id_actualite')
219 titre
= models
.CharField(max_length
=765, db_column
='titre_actualite')
220 texte
= models
.TextField(db_column
='texte_actualite')
221 url
= models
.CharField(max_length
=765, db_column
='url_actualite')
222 date
= models
.DateField(db_column
='date_actualite')
223 visible
= models
.BooleanField(db_column
='visible_actualite', default
=False)
224 ancienid
= models
.IntegerField(db_column
='ancienId_actualite', blank
=True, null
=True)
225 source
= models
.ForeignKey(SourceActualite
, related_name
='actualites')
226 disciplines
= models
.ManyToManyField(Discipline
, blank
=True, related_name
="actualites")
227 regions
= models
.ManyToManyField(Region
, blank
=True, related_name
="actualites", verbose_name
='régions')
229 objects
= ActualiteManager()
230 all_objects
= models
.Manager()
233 db_table
= u
'actualite'
236 def __unicode__ (self
):
237 return "%s" % (self
.titre
)
239 def get_absolute_url(self
):
240 return reverse('actualite', kwargs
={'id': self
.id})
242 def assigner_disciplines(self
, disciplines
):
243 self
.disciplines
.add(*disciplines
)
245 def assigner_regions(self
, regions
):
246 self
.regions
.add(*regions
)
250 class EvenementQuerySet(SEPQuerySet
):
252 def filter_type(self
, type):
253 return self
.filter(type=type)
255 def filter_debut(self
, min=None, max=None):
258 qs
= qs
.filter(debut__gte
=min)
260 qs
= qs
.filter(debut__lt
=max+datetime
.timedelta(days
=1))
263 class EvenementSphinxQuerySet(SEPSphinxQuerySet
):
265 def __init__(self
, model
=None):
266 SEPSphinxQuerySet
.__init__(self
, model
=model
, index
='savoirsenpartage_evenements',
267 weights
=dict(titre
=3))
269 def filter_type(self
, type):
270 return self
.add_to_query('@type "%s"' % type)
272 def filter_debut(self
, min=None, max=None):
273 return self
._filter_date('debut', min=min, max=max)
275 class EvenementManager(SEPManager
):
277 def get_query_set(self
):
278 return EvenementQuerySet(self
.model
).filter(approuve
=True)
280 def get_sphinx_query_set(self
):
281 return EvenementSphinxQuerySet(self
.model
).order_by('-debut')
283 def filter_type(self
, type):
284 return self
.get_query_set().filter_type(type)
286 def filter_debut(self
, min=None, max=None):
287 return self
.get_query_set().filter_debut(min=min, max=max)
289 def build_time_zone_choices(pays
=None):
290 timezones
= pytz
.country_timezones
[pays
] if pays
else pytz
.common_timezones
292 now
= datetime
.datetime
.now()
293 for tzname
in timezones
:
294 tz
= pytz
.timezone(tzname
)
295 fr_name
= get_timezone_name(tz
, locale
='fr_FR')
296 offset
= tz
.utcoffset(now
)
297 seconds
= offset
.seconds
+ offset
.days
* 86400
298 (hours
, minutes
) = divmod(seconds
// 60, 60)
299 offset_str
= 'UTC%+d:%d' % (hours
, minutes
) if minutes
else 'UTC%+d' % hours
300 result
.append((seconds
, tzname
, '%s - %s' % (offset_str
, fr_name
)))
302 return [(x
[1], x
[2]) for x
in result
]
304 class Evenement(models
.Model
):
305 TYPE_CHOICES
= ((u
'Colloque', u
'Colloque'),
306 (u
'Conférence', u
'Conférence'),
307 (u
'Appel à contribution', u
'Appel à contribution'),
308 (u
'Journée d\'étude', u
'Journée d\'étude'),
309 (u
'Autre', u
'Autre'))
310 TIME_ZONE_CHOICES
= build_time_zone_choices()
312 uid
= models
.CharField(max_length
=255, default
=str(uuid
.uuid1()))
313 approuve
= models
.BooleanField(default
=False, verbose_name
=u
'approuvé')
314 titre
= models
.CharField(max_length
=255)
315 discipline
= models
.ForeignKey('Discipline', related_name
= "discipline",
316 blank
= True, null
= True)
317 discipline_secondaire
= models
.ForeignKey('Discipline', related_name
="discipline_secondaire",
318 verbose_name
=u
"discipline secondaire",
319 blank
=True, null
=True)
320 mots_cles
= models
.TextField('Mots-Clés', blank
=True, null
=True)
321 type = models
.CharField(max_length
=255, choices
=TYPE_CHOICES
)
322 adresse
= models
.TextField()
323 ville
= models
.CharField(max_length
=100)
324 pays
= models
.ForeignKey(Pays
, null
=True, related_name
='evenements')
325 debut
= models
.DateTimeField(default
=datetime
.datetime
.now
)
326 fin
= models
.DateTimeField(default
=datetime
.datetime
.now
)
327 fuseau
= models
.CharField(max_length
=100, choices
=TIME_ZONE_CHOICES
, verbose_name
='fuseau horaire')
328 description
= models
.TextField()
329 contact
= models
.TextField(null
=True) # champ obsolète
330 prenom
= models
.CharField('prénom', max_length
=100)
331 nom
= models
.CharField(max_length
=100)
332 courriel
= models
.EmailField()
333 url
= models
.CharField(max_length
=255, blank
=True, null
=True)
334 piece_jointe
= models
.FileField(upload_to
='agenda/pj', blank
=True, verbose_name
='pièce jointe')
335 regions
= models
.ManyToManyField(Region
, blank
=True, related_name
="evenements", verbose_name
='régions additionnelles',
336 help_text
="On considère d'emblée que l'évènement se déroule dans la région "
337 "dans laquelle se trouve le pays indiqué plus haut. Il est possible "
338 "de désigner ici des régions additionnelles.")
340 objects
= EvenementManager()
341 all_objects
= models
.Manager()
344 ordering
= ['-debut']
345 verbose_name
= u
'évènement'
346 verbose_name_plural
= u
'évènements'
348 def __unicode__(self
):
349 return "[%s] %s" % (self
.uid
, self
.titre
)
351 def get_absolute_url(self
):
352 return reverse('evenement', kwargs
={'id': self
.id})
354 def duration_display(self
):
355 delta
= self
.fin
- self
.debut
356 minutes
, seconds
= divmod(delta
.seconds
, 60)
357 hours
, minutes
= divmod(minutes
, 60)
361 parts
.append('1 jour')
363 parts
.append('%d jours' % days
)
365 parts
.append('1 heure')
367 parts
.append('%d heures' % hours
)
369 parts
.append('1 minute')
371 parts
.append('%d minutes' % minutes
)
372 return ' '.join(parts
)
374 def piece_jointe_display(self
):
375 return self
.piece_jointe
and os
.path
.basename(self
.piece_jointe
.name
)
377 def courriel_display(self
):
378 return self
.courriel
.replace(u
'@', u
' (à) ')
381 from django
.core
.exceptions
import ValidationError
382 if self
.debut
> self
.fin
:
383 raise ValidationError('La date de fin ne doit pas être antérieure à la date de début')
385 def save(self
, *args
, **kwargs
):
386 """Sauvegarde l'objet dans django et le synchronise avec caldav s'il a été
388 self
.contact
= '' # Vider ce champ obsolète à la première occasion...
390 super(Evenement
, self
).save(*args
, **kwargs
)
393 # methodes de commnunications avec CALDAV
395 """Retourne l'evenement django sous forme d'objet icalendar"""
396 cal
= vobject
.iCalendar()
399 # fournit son propre uid
400 if self
.uid
in [None, ""]:
401 self
.uid
= str(uuid
.uuid1())
403 cal
.vevent
.add('uid').value
= self
.uid
405 cal
.vevent
.add('summary').value
= self
.titre
407 if self
.mots_cles
is None:
410 kw
= self
.mots_cles
.split(",")
413 kw
.append(self
.discipline
.nom
)
414 kw
.append(self
.discipline_secondaire
.nom
)
418 kw
= [x
.strip() for x
in kw
if len(x
.strip()) > 0 and x
is not None]
420 cal
.vevent
.add('x-auf-keywords').value
= k
422 description
= self
.description
424 if len(self
.description
) > 0:
426 description
+= u
"Mots-clés: " + ", ".join(kw
)
428 cal
.vevent
.add('dtstart').value
= combine(self
.debut
, pytz
.timezone(self
.fuseau
))
429 cal
.vevent
.add('dtend').value
= combine(self
.fin
, pytz
.timezone(self
.fuseau
))
430 cal
.vevent
.add('created').value
= combine(datetime
.datetime
.now(), "UTC")
431 cal
.vevent
.add('dtstamp').value
= combine(datetime
.datetime
.now(), "UTC")
432 if len(description
) > 0:
433 cal
.vevent
.add('description').value
= description
434 if len(self
.contact
) > 0:
435 cal
.vevent
.add('contact').value
= self
.contact
436 if len(self
.url
) > 0:
437 cal
.vevent
.add('url').value
= self
.url
438 cal
.vevent
.add('location').value
= ', '.join([x
for x
in [self
.adresse
, self
.ville
, self
.pays
.nom
] if x
])
439 if self
.piece_jointe
:
440 url
= self
.piece_jointe
.url
441 if not url
.startswith('http://'):
442 url
= SITE_ROOT_URL
+ url
443 cal
.vevent
.add('attach').value
= url
446 def update_vevent(self
,):
447 """Essaie de créer l'évènement sur le serveur ical.
448 En cas de succès, l'évènement local devient donc inactif et approuvé"""
451 event
= self
.as_ical()
452 client
= caldav
.DAVClient(CALENDRIER_URL
)
453 cal
= caldav
.Calendar(client
, url
= CALENDRIER_URL
)
454 e
= caldav
.Event(client
, parent
= cal
, data
= event
.serialize(), id=self
.uid
)
457 self
.approuve
= False
459 def delete_vevent(self
,):
460 """Supprime l'evenement sur le serveur caldav"""
463 event
= self
.as_ical()
464 client
= caldav
.DAVClient(CALENDRIER_URL
)
465 cal
= caldav
.Calendar(client
, url
= CALENDRIER_URL
)
466 e
= cal
.event(self
.uid
)
468 except error
.NotFoundError
:
471 def assigner_regions(self
, regions
):
472 self
.regions
.add(*regions
)
474 def assigner_disciplines(self
, disciplines
):
475 if len(disciplines
) == 1:
477 self
.discipline_secondaire
= disciplines
[0]
479 self
.discipline
= disciplines
[0]
480 elif len(disciplines
) >= 2:
481 self
.discipline
= disciplines
[0]
482 self
.discipline_secondaire
= disciplines
[1]
484 def delete_vevent(sender
, instance
, *args
, **kwargs
):
485 # Surcharge du comportement de suppression
486 # La méthode de connexion par signals est préférable à surcharger la méthode delete()
487 # car dans le cas de la suppression par lots, cell-ci n'est pas invoquée
488 instance
.delete_vevent()
489 pre_delete
.connect(delete_vevent
, sender
=Evenement
)
493 class ListSet(models
.Model
):
494 spec
= models
.CharField(primary_key
= True, max_length
= 255)
495 name
= models
.CharField(max_length
= 255)
496 server
= models
.CharField(max_length
= 255)
497 validated
= models
.BooleanField(default
= True)
499 def __unicode__(self
,):
502 class RecordQuerySet(SEPQuerySet
):
504 def filter_modified(self
, min=None, max=None):
505 return self
._filter_date(self
, 'modified', min=min, max=max)
507 class RecordSphinxQuerySet(SEPSphinxQuerySet
):
509 def __init__(self
, model
=None):
510 SEPSphinxQuerySet
.__init__(self
, model
=model
, index
='savoirsenpartage_ressources',
511 weights
=dict(title
=3))
513 def filter_modified(self
, min=None, max=None):
514 return self
._filter_date(self
, 'modified', min=min, max=max)
516 class RecordManager(SEPManager
):
518 def get_query_set(self
):
519 """Ne garder que les ressources validées et qui sont soit dans aucun
520 listset ou au moins dans un listset validé."""
521 qs
= RecordQuerySet(self
.model
)
522 qs
= qs
.filter(validated
=True)
523 qs
= qs
.filter(Q(listsets__isnull
=True) |
Q(listsets__validated
=True))
526 def get_sphinx_query_set(self
):
527 return RecordSphinxQuerySet(self
.model
)
529 def filter_modified(self
, min=None, max=None):
530 return self
.get_query_set().filter_modified(min=min, max=max)
532 class Record(models
.Model
):
534 #fonctionnement interne
535 id = models
.AutoField(primary_key
= True)
536 server
= models
.CharField(max_length
= 255, verbose_name
=u
'serveur')
537 last_update
= models
.CharField(max_length
= 255)
538 last_checksum
= models
.CharField(max_length
= 255)
539 validated
= models
.BooleanField(default
=True, verbose_name
=u
'validé')
542 title
= models
.TextField(null
=True, blank
=True, verbose_name
=u
'titre')
543 creator
= models
.TextField(null
=True, blank
=True, verbose_name
=u
'auteur')
544 description
= models
.TextField(null
=True, blank
=True)
545 modified
= models
.CharField(max_length
=255, null
=True, blank
=True)
546 identifier
= models
.CharField(max_length
= 255, null
= True, blank
= True, unique
= True)
547 uri
= models
.CharField(max_length
= 255, null
= True, blank
= True, unique
= True)
548 source
= models
.TextField(null
= True, blank
= True)
549 contributor
= models
.TextField(null
= True, blank
= True)
550 subject
= models
.TextField(null
=True, blank
=True, verbose_name
='sujet')
551 publisher
= models
.TextField(null
= True, blank
= True)
552 type = models
.TextField(null
= True, blank
= True)
553 format
= models
.TextField(null
= True, blank
= True)
554 language
= models
.TextField(null
= True, blank
= True)
556 listsets
= models
.ManyToManyField(ListSet
, null
= True, blank
= True)
558 #SEP 2 (aucune données récoltées)
559 alt_title
= models
.TextField(null
= True, blank
= True)
560 abstract
= models
.TextField(null
= True, blank
= True)
561 creation
= models
.CharField(max_length
= 255, null
= True, blank
= True)
562 issued
= models
.CharField(max_length
= 255, null
= True, blank
= True)
563 isbn
= models
.TextField(null
= True, blank
= True)
564 orig_lang
= models
.TextField(null
= True, blank
= True)
566 # Metadata AUF multivaluées
567 disciplines
= models
.ManyToManyField(Discipline
, blank
=True)
568 thematiques
= models
.ManyToManyField(Thematique
, blank
=True, verbose_name
='thématiques')
569 pays
= models
.ManyToManyField(Pays
, blank
=True)
570 regions
= models
.ManyToManyField(Region
, blank
=True, verbose_name
='régions')
573 objects
= RecordManager()
574 all_objects
= models
.Manager()
577 verbose_name
= 'ressource'
579 def __unicode__(self
):
580 return "[%s] %s" % (self
.server
, self
.title
)
582 def get_absolute_url(self
):
583 return reverse('ressource', kwargs
={'id': self
.id})
585 def getServeurURL(self
):
586 """Retourne l'URL du serveur de provenance"""
587 return RESOURCES
[self
.server
]['url']
589 def est_complet(self
):
590 """teste si le record à toutes les données obligatoires"""
591 return self
.disciplines
.count() > 0 and \
592 self
.thematiques
.count() > 0 and \
593 self
.pays
.count() > 0 and \
594 self
.regions
.count() > 0
596 def assigner_regions(self
, regions
):
597 self
.regions
.add(*regions
)
599 def assigner_disciplines(self
, disciplines
):
600 self
.disciplines
.add(*disciplines
)
602 class Serveur(models
.Model
):
603 """Identification d'un serveur d'ou proviennent les références"""
604 nom
= models
.CharField(primary_key
= True, max_length
= 255)
606 def __unicode__(self
,):
609 def conf_2_db(self
,):
610 for k
in RESOURCES
.keys():
611 s
, created
= Serveur
.objects
.get_or_create(nom
=k
)
615 class Profile(models
.Model
):
616 user
= models
.ForeignKey(User
, unique
=True)
617 serveurs
= models
.ManyToManyField(Serveur
, null
= True, blank
= True)
619 class HarvestLog(models
.Model
):
620 context
= models
.CharField(max_length
= 255)
621 name
= models
.CharField(max_length
= 255)
622 date
= models
.DateTimeField(auto_now
= True)
623 added
= models
.IntegerField(null
= True, blank
= True)
624 updated
= models
.IntegerField(null
= True, blank
= True)
625 processed
= models
.IntegerField(null
= True, blank
= True)
626 record
= models
.ForeignKey(Record
, null
= True, blank
= True)
630 logger
= HarvestLog()
631 if message
.has_key('record_id'):
632 message
['record'] = Record
.all_objects
.get(id=message
['record_id'])
633 del(message
['record_id'])
635 for k
,v
in message
.items():
636 setattr(logger
, k
, v
)
641 class PageStatique(models
.Model
):
642 id = models
.CharField(max_length
=32, primary_key
=True)
643 titre
= models
.CharField(max_length
=100)
644 contenu
= models
.TextField()
647 verbose_name_plural
= 'pages statiques'
651 class GlobalSearchResults(object):
653 def __init__(self
, actualites
=None, appels
=None, evenements
=None,
654 ressources
=None, chercheurs
=None, sites
=None, sites_auf
=None):
655 self
.actualites
= actualites
657 self
.evenements
= evenements
658 self
.ressources
= ressources
659 self
.chercheurs
= chercheurs
661 self
.sites_auf
= sites_auf
663 class Search(models
.Model
):
664 user
= models
.ForeignKey(User
, editable
=False)
665 content_type
= models
.ForeignKey(ContentType
, editable
=False)
666 nom
= models
.CharField(max_length
=100, verbose_name
="nom de la recherche")
667 q
= models
.CharField(max_length
=100, blank
=True, verbose_name
="rechercher dans tous les champs")
668 discipline
= models
.ForeignKey(Discipline
, blank
=True, null
=True)
669 region
= models
.ForeignKey(Region
, blank
=True, null
=True, verbose_name
='région',
670 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.")
672 def query_string(self
):
674 for field
in self
._meta
.fields
:
675 if field
.name
in ['id', 'user', 'nom', 'search_ptr', 'content_type']:
677 value
= getattr(self
, field
.column
)
679 if isinstance(value
, datetime
.date
):
680 params
[field
.name
] = value
.strftime('%d/%m/%Y')
682 params
[field
.name
] = value
683 return urlencode(params
)
686 verbose_name
= 'recherche transversale'
687 verbose_name_plural
= "recherches transversales"
690 if (not self
.content_type_id
):
691 self
.content_type
= ContentType
.objects
.get_for_model(self
.__class__
)
692 super(Search
, self
).save()
694 def as_leaf_class(self
):
695 content_type
= self
.content_type
696 model
= content_type
.model_class()
699 return model
.objects
.get(id=self
.id)
702 from chercheurs
.models
import Chercheur
703 from sitotheque
.models
import Site
706 actualites
= Actualite
.objects
707 evenements
= Evenement
.objects
708 ressources
= Record
.objects
709 chercheurs
= Chercheur
.objects
712 actualites
= actualites
.search(self
.q
)
713 evenements
= evenements
.search(self
.q
)
714 ressources
= ressources
.search(self
.q
)
715 chercheurs
= chercheurs
.search(self
.q
)
716 sites
= sites
.search(self
.q
)
718 actualites
= actualites
.filter_discipline(self
.discipline
)
719 evenements
= evenements
.filter_discipline(self
.discipline
)
720 ressources
= ressources
.filter_discipline(self
.discipline
)
721 chercheurs
= chercheurs
.filter_discipline(self
.discipline
)
722 sites
= sites
.filter_discipline(self
.discipline
)
724 actualites
= actualites
.filter_region(self
.region
)
725 evenements
= evenements
.filter_region(self
.region
)
726 ressources
= ressources
.filter_region(self
.region
)
727 chercheurs
= chercheurs
.filter_region(self
.region
)
728 sites
= sites
.filter_region(self
.region
)
730 sites_auf
= google_search(0, self
.q
)['results']
734 return GlobalSearchResults(
735 actualites
=actualites
.order_by('-date').filter_type('actu'),
736 appels
=actualites
.order_by('-date').filter_type('appels'),
737 evenements
=evenements
.order_by('-debut'),
738 ressources
=ressources
.order_by('-id'),
739 chercheurs
=chercheurs
.order_by('-date_modification'),
740 sites
=sites
.order_by('-date_maj'),
747 url
+= '/discipline/%d' % self
.discipline
.id
749 url
+= '/region/%d' % self
.region
.id
752 url
+= '?' + urlencode({'q': self
.q
})
755 class RessourceSearch(Search
):
756 auteur
= models
.CharField(max_length
=100, blank
=True, verbose_name
="auteur ou contributeur")
757 titre
= models
.CharField(max_length
=100, blank
=True)
758 sujet
= models
.CharField(max_length
=100, blank
=True)
759 publisher
= models
.CharField(max_length
=100, blank
=True, verbose_name
="éditeur")
762 verbose_name
= 'recherche de ressources'
763 verbose_name_plural
= "recherches de ressources"
766 results
= Record
.objects
768 results
= results
.search(self
.q
)
770 results
= results
.add_to_query('@(creator,contributor) ' + self
.auteur
)
772 results
= results
.add_to_query('@title ' + self
.titre
)
774 results
= results
.add_to_query('@subject ' + self
.sujet
)
776 results
= results
.add_to_query('@publisher ' + self
.publisher
)
778 results
= results
.filter_discipline(self
.discipline
)
780 results
= results
.filter_region(self
.region
)
782 """Montrer les résultats les plus récents si on n'a pas fait
783 une recherche par mots-clés."""
784 results
= results
.order_by('-modified')
788 qs
= self
.query_string()
789 return reverse('ressources') + ('?' + qs
if qs
else '')
791 class ActualiteSearchBase(Search
):
792 date_min
= models
.DateField(blank
=True, null
=True, verbose_name
="depuis le")
793 date_max
= models
.DateField(blank
=True, null
=True, verbose_name
="jusqu'au")
799 results
= Actualite
.objects
801 results
= results
.search(self
.q
)
803 results
= results
.filter_discipline(self
.discipline
)
805 results
= results
.filter_region(self
.region
)
807 results
= results
.filter_date(min=self
.date_min
)
809 results
= results
.filter_date(max=self
.date_max
)
812 class ActualiteSearch(ActualiteSearchBase
):
815 verbose_name
= "recherche d'actualités"
816 verbose_name_plural
= "recherches d'actualités"
819 return super(ActualiteSearch
, self
).run().filter_type('actu')
822 qs
= self
.query_string()
823 return reverse('actualites') + ('?' + qs
if qs
else '')
825 class AppelSearch(ActualiteSearchBase
):
828 verbose_name
= "recherche d'appels d'offres"
829 verbose_name_plural
= "recherches d'appels d'offres"
832 return super(AppelSearch
, self
).run().filter_type('appel')
835 qs
= self
.query_string()
836 return reverse('appels') + ('?' + qs
if qs
else '')
838 class EvenementSearch(Search
):
839 titre
= models
.CharField(max_length
=100, blank
=True, verbose_name
="Intitulé")
840 type = models
.CharField(max_length
=100, blank
=True, choices
=Evenement
.TYPE_CHOICES
)
841 date_min
= models
.DateField(blank
=True, null
=True, verbose_name
="depuis le")
842 date_max
= models
.DateField(blank
=True, null
=True, verbose_name
="jusqu'au")
845 verbose_name
= "recherche d'évènements"
846 verbose_name_plural
= "recherches d'évènements"
849 results
= Evenement
.objects
851 results
= results
.search(self
.q
)
853 results
= results
.add_to_query('@titre ' + self
.titre
)
855 results
= results
.filter_discipline(self
.discipline
)
857 results
= results
.filter_region(self
.region
)
859 results
= results
.filter_type(self
.type)
861 results
= results
.filter_debut(min=self
.date_min
)
863 results
= results
.filter_debut(max=self
.date_max
)
867 qs
= self
.query_string()
868 return reverse('agenda') + ('?' + qs
if qs
else '')