Optimisation de la requête des ressources valides
[auf_savoirs_en_partage_django.git] / auf_savoirs_en_partage / savoirs / models.py
CommitLineData
92c7413b 1# -*- encoding: utf-8 -*-
88585b02 2
db2999fa
EMS
3import caldav
4import datetime
5import feedparser
db2999fa
EMS
6import os
7import pytz
8import random
4b89a7df 9import textwrap
db2999fa
EMS
10import uuid
11import vobject
5ffde8a4 12from pytz.tzinfo import AmbiguousTimeError, NonExistentTimeError
fdcf5874
EMS
13from urllib import urlencode
14
5212238e 15from backend_config import RESOURCES
86983865 16from babel.dates import get_timezone_name
5212238e 17from caldav.lib import error
6d885e0c 18from django.contrib.auth.models import User
fdcf5874 19from django.contrib.contenttypes.models import ContentType
4b89a7df 20from django.core.mail import EmailMultiAlternatives
fdcf5874 21from django.core.urlresolvers import reverse
d15017b2 22from django.db import models
88585b02 23from django.db.models import Q
b7a741ad 24from django.db.models.signals import pre_delete
4b89a7df 25from django.utils.encoding import smart_unicode, smart_str
122c4c3d 26from djangosphinx.models import SphinxQuerySet, SearchError
4b89a7df 27from markdown2 import markdown
fdcf5874 28
693c606b 29from auf.django.references.models import Region, Pays, Thematique
a83b8efb 30from settings import CALENDRIER_URL, SITE_ROOT_URL, CONTACT_EMAIL
88585b02
EMS
31from lib.calendrier import combine
32from lib.recherche import google_search
33
5212238e
EMS
34
35# Fonctionnalités communes à tous les query sets
d15017b2 36
15261361
EMS
37class RandomQuerySetMixin(object):
38 """Mixin pour les modèles.
88585b02 39
15261361
EMS
40 ORDER BY RAND() est très lent sous MySQL. On a besoin d'une autre
41 méthode pour récupérer des objets au hasard.
42 """
43
44 def random(self, n=1):
45 """Récupère aléatoirement un nombre donné d'objets."""
bae03b7b
EMS
46 count = self.count()
47 positions = random.sample(xrange(count), min(n, count))
48 return [self[p] for p in positions]
15261361 49
88585b02 50
5212238e 51class SEPQuerySet(models.query.QuerySet, RandomQuerySetMixin):
230671ff
EMS
52
53 def _filter_date(self, field, min=None, max=None):
54 """Limite les résultats à ceux dont le champ ``field`` tombe entre
55 les dates ``min`` et ``max``."""
56 qs = self
57 if min:
58 qs = qs.filter(**{field + '__gte': min})
59 if max:
88585b02
EMS
60 qs = qs.filter(**{
61 field + '__lt': max + datetime.timedelta(days=1)
62 })
230671ff 63 return qs
5212238e 64
88585b02 65
5212238e
EMS
66class SEPSphinxQuerySet(SphinxQuerySet, RandomQuerySetMixin):
67 """Fonctionnalités communes aux query sets de Sphinx."""
68
69 def __init__(self, model=None, index=None, weights=None):
70 SphinxQuerySet.__init__(self, model=model, index=index,
71 mode='SPH_MATCH_EXTENDED2',
72 rankmode='SPH_RANK_PROXIMITY_BM25',
73 weights=weights)
74
75 def add_to_query(self, query):
76 """Ajoute une partie à la requête texte."""
6c78c673
EMS
77
78 # Assurons-nous qu'il y a un nombre pair de guillemets
79 if query.count('"') % 2 != 0:
80 # Sinon, on enlève le dernier (faut choisir...)
81 i = query.rindex('"')
88585b02 82 query = query[:i] + query[i + 1:]
6c78c673 83
88585b02
EMS
84 new_query = smart_unicode(self._query) + ' ' + query \
85 if self._query else query
5212238e
EMS
86 return self.query(new_query)
87
88 def search(self, text):
89 """Recherche ``text`` dans tous les champs."""
90 return self.add_to_query('@* ' + text)
91
92 def filter_discipline(self, discipline):
93 """Par défaut, le filtre par discipline cherche le nom de la
94 discipline dans tous les champs."""
95 return self.search('"%s"' % discipline.nom)
96
97 def filter_region(self, region):
98 """Par défaut, le filtre par région cherche le nom de la région dans
99 tous les champs."""
100 return self.search('"%s"' % region.nom)
101
230671ff
EMS
102 def _filter_date(self, field, min=None, max=None):
103 """Limite les résultats à ceux dont le champ ``field`` tombe entre
104 les dates ``min`` et ``max``."""
105 qs = self
106 if min:
88585b02 107 qs = qs.filter(**{field + '__gte': min.toordinal() + 365})
230671ff 108 if max:
88585b02 109 qs = qs.filter(**{field + '__lte': max.toordinal() + 365})
230671ff
EMS
110 return qs
111
122c4c3d
EMS
112 def _get_sphinx_results(self):
113 try:
114 return SphinxQuerySet._get_sphinx_results(self)
115 except SearchError:
116 # Essayons d'enlever les caractères qui peuvent poser problème.
117 for c in '|!@()~/<=^$':
118 self._query = self._query.replace(c, ' ')
119 try:
120 return SphinxQuerySet._get_sphinx_results(self)
121 except SearchError:
122 # Ça ne marche toujours pas. Enlevons les guillemets et les
123 # tirets.
124 for c in '"-':
125 self._query = self._query.replace(c, ' ')
126 return SphinxQuerySet._get_sphinx_results(self)
127
88585b02 128
5212238e
EMS
129class SEPManager(models.Manager):
130 """Lorsque les méthodes ``search``, ``filter_region`` et
131 ``filter_discipline`` sont appelées sur ce manager, le query set
132 Sphinx est créé, sinon, c'est le query set Django qui est créé."""
133
134 def query(self, query):
135 return self.get_sphinx_query_set().query(query)
136
137 def add_to_query(self, query):
138 return self.get_sphinx_query_set().add_to_query(query)
139
140 def search(self, text):
141 return self.get_sphinx_query_set().search(text)
142
143 def filter_region(self, region):
144 return self.get_sphinx_query_set().filter_region(region)
145
146 def filter_discipline(self, discipline):
147 return self.get_sphinx_query_set().filter_discipline(discipline)
148
88585b02 149
5212238e
EMS
150# Disciplines
151
d15017b2
CR
152class Discipline(models.Model):
153 id = models.IntegerField(primary_key=True, db_column='id_discipline')
154 nom = models.CharField(max_length=765, db_column='nom_discipline')
6ef8ead4 155
88585b02 156 def __unicode__(self):
92c7413b 157 return self.nom
6ef8ead4 158
d15017b2
CR
159 class Meta:
160 db_table = u'discipline'
88585b02
EMS
161 ordering = ["nom"]
162
d15017b2 163
5212238e
EMS
164# Actualités
165
79c398f6 166class SourceActualite(models.Model):
011804bb
EMS
167 TYPE_CHOICES = (
168 ('actu', 'Actualités'),
169 ('appels', "Appels d'offres"),
170 )
171
79c398f6 172 nom = models.CharField(max_length=255)
011804bb 173 url = models.CharField(max_length=255, verbose_name='URL', blank=True)
88585b02
EMS
174 type = models.CharField(
175 max_length=10, default='actu', choices=TYPE_CHOICES
176 )
ac45d923 177 region = models.ForeignKey(Region, null=True, blank=True, verbose_name='région')
88585b02 178
db2999fa
EMS
179 class Meta:
180 verbose_name = u'fil RSS syndiqué'
181 verbose_name_plural = u'fils RSS syndiqués'
182
ccbc4363 183 def __unicode__(self,):
011804bb 184 return u"%s (%s)" % (self.nom, self.get_type_display())
79c398f6 185
db2999fa
EMS
186 def update(self):
187 """Mise à jour du fil RSS."""
011804bb
EMS
188 if not self.url:
189 return
db2999fa
EMS
190 feed = feedparser.parse(self.url)
191 for entry in feed.entries:
b9220afb
EMS
192 if not all(
193 x in entry for x in ('link', 'published_parsed', 'title')
194 ):
195 continue
db2999fa 196 if Actualite.all_objects.filter(url=entry.link).count() == 0:
baca417f 197 ts = entry.get('published_parsed')
7b93080b
EMS
198 date = datetime.date(ts.tm_year, ts.tm_mon, ts.tm_mday) \
199 if ts else datetime.date.today()
b9220afb
EMS
200 texte = entry.summary_detail.value \
201 if 'summary_detail' in entry else ''
ac45d923 202 actualite = self.actualites.create(
b9220afb
EMS
203 titre=entry.title, texte=texte, url=entry.link,
204 date=date
88585b02 205 )
ac45d923
PP
206 if self.region:
207 actualite.regions.add(self.region)
88585b02 208
db2999fa 209
5212238e 210class ActualiteQuerySet(SEPQuerySet):
2f9c4d6c 211
5212238e 212 def filter_date(self, min=None, max=None):
230671ff 213 return self._filter_date('date', min=min, max=max)
da44ce68 214
011804bb
EMS
215 def filter_type(self, type):
216 return self.filter(source__type=type)
217
88585b02 218
5212238e 219class ActualiteSphinxQuerySet(SEPSphinxQuerySet):
88585b02 220 TYPE_CODES = {'actu': 1, 'appels': 2}
c1b134f8 221
5212238e 222 def __init__(self, model=None):
88585b02
EMS
223 SEPSphinxQuerySet.__init__(
224 self, model=model, index='savoirsenpartage_actualites',
225 weights=dict(titre=3)
226 )
c1b134f8 227
7fb6bd61 228 def filter_date(self, min=None, max=None):
230671ff
EMS
229 return self._filter_date('date', min=min, max=max)
230
011804bb
EMS
231 def filter_type(self, type):
232 return self.filter(type=self.TYPE_CODES[type])
233
27effd89
PP
234 def filter_region(self, region):
235 return self.filter(region_ids=region.id)
236
237 def filter_discipline(self, discipline):
238 return self.filter(discipline_ids=discipline.id)
239
88585b02 240
5212238e 241class ActualiteManager(SEPManager):
88585b02 242
5212238e
EMS
243 def get_query_set(self):
244 return ActualiteQuerySet(self.model).filter(visible=True)
2f9c4d6c 245
5212238e
EMS
246 def get_sphinx_query_set(self):
247 return ActualiteSphinxQuerySet(self.model).order_by('-date')
2f9c4d6c 248
5212238e
EMS
249 def filter_date(self, min=None, max=None):
250 return self.get_query_set().filter_date(min=min, max=max)
bae03b7b 251
011804bb
EMS
252 def filter_type(self, type):
253 return self.get_query_set().filter_type(type)
254
88585b02 255
d15017b2 256class Actualite(models.Model):
4f262f90 257 id = models.AutoField(primary_key=True, db_column='id_actualite')
d15017b2
CR
258 titre = models.CharField(max_length=765, db_column='titre_actualite')
259 texte = models.TextField(db_column='texte_actualite')
260 url = models.CharField(max_length=765, db_column='url_actualite')
d15017b2 261 date = models.DateField(db_column='date_actualite')
db2999fa 262 visible = models.BooleanField(db_column='visible_actualite', default=False)
88585b02
EMS
263 ancienid = models.IntegerField(
264 db_column='ancienId_actualite', blank=True, null=True
265 )
011804bb 266 source = models.ForeignKey(SourceActualite, related_name='actualites')
88585b02
EMS
267 disciplines = models.ManyToManyField(
268 Discipline, blank=True, related_name="actualites"
269 )
270 regions = models.ManyToManyField(
271 Region, blank=True, related_name="actualites",
272 verbose_name='régions'
273 )
6ef8ead4 274
2f9c4d6c 275 objects = ActualiteManager()
c59dba82 276 all_objects = models.Manager()
2f9c4d6c 277
d15017b2
CR
278 class Meta:
279 db_table = u'actualite'
7020ea3d 280 ordering = ["-date"]
92c7413b 281
88585b02 282 def __unicode__(self):
264a3210
EMS
283 return "%s" % (self.titre)
284
230671ff
EMS
285 def get_absolute_url(self):
286 return reverse('actualite', kwargs={'id': self.id})
287
264a3210
EMS
288 def assigner_disciplines(self, disciplines):
289 self.disciplines.add(*disciplines)
290
291 def assigner_regions(self, regions):
292 self.regions.add(*regions)
293
81fe476e
PP
294
295class ActualiteVoir(Actualite):
296
297 class Meta:
298 proxy = True
429222f1
PP
299 verbose_name = 'actualité (visualisation)'
300 verbose_name_plural = 'actualités (visualisation)'
81fe476e 301
88585b02 302
5212238e 303# Agenda
4101cfc0 304
5212238e 305class EvenementQuerySet(SEPQuerySet):
4101cfc0 306
5212238e
EMS
307 def filter_type(self, type):
308 return self.filter(type=type)
c1b134f8 309
5212238e 310 def filter_debut(self, min=None, max=None):
a83b8efb
EMS
311 return self._filter_date('debut', min=min, max=max)
312
313 def filter_date_modification(self, min=None, max=None):
314 return self._filter_date('date_modification', min=min, max=max)
c1b134f8 315
88585b02 316
5212238e 317class EvenementSphinxQuerySet(SEPSphinxQuerySet):
4101cfc0 318
5212238e 319 def __init__(self, model=None):
88585b02
EMS
320 SEPSphinxQuerySet.__init__(
321 self, model=model, index='savoirsenpartage_evenements',
322 weights=dict(titre=3)
323 )
116db1fd 324
5212238e
EMS
325 def filter_type(self, type):
326 return self.add_to_query('@type "%s"' % type)
88585b02 327
5212238e 328 def filter_debut(self, min=None, max=None):
230671ff 329 return self._filter_date('debut', min=min, max=max)
7bbf600c 330
a83b8efb
EMS
331 def filter_date_modification(self, min=None, max=None):
332 return self._filter_date('date_modification', min=min, max=max)
333
27effd89
PP
334 def filter_region(self, region):
335 return self.add_to_query('@regions "%s"' % region.nom)
336
337 def filter_discipline(self, discipline):
338 return self.add_to_query('@disciplines "%s"' % discipline.nom)
339
88585b02 340
5212238e 341class EvenementManager(SEPManager):
5212238e 342
5212238e
EMS
343 def get_query_set(self):
344 return EvenementQuerySet(self.model).filter(approuve=True)
345
346 def get_sphinx_query_set(self):
347 return EvenementSphinxQuerySet(self.model).order_by('-debut')
348
349 def filter_type(self, type):
350 return self.get_query_set().filter_type(type)
351
352 def filter_debut(self, min=None, max=None):
353 return self.get_query_set().filter_debut(min=min, max=max)
bae03b7b 354
a83b8efb
EMS
355 def filter_date_modification(self, min=None, max=None):
356 return self.get_query_set().filter_date_modification(min=min, max=max)
357
88585b02 358
1719bf4e 359def build_time_zone_choices(pays=None):
1719bf4e
EMS
360 timezones = pytz.country_timezones[pays] if pays else pytz.common_timezones
361 result = []
86983865 362 now = datetime.datetime.now()
1719bf4e 363 for tzname in timezones:
86983865
EMS
364 tz = pytz.timezone(tzname)
365 fr_name = get_timezone_name(tz, locale='fr_FR')
5ffde8a4
EMS
366 try:
367 offset = tz.utcoffset(now)
368 except (AmbiguousTimeError, NonExistentTimeError):
369 # oups. On est en train de changer d'heure. Ça devrait être fini
370 # demain
371 offset = tz.utcoffset(now + datetime.timedelta(days=1))
86983865
EMS
372 seconds = offset.seconds + offset.days * 86400
373 (hours, minutes) = divmod(seconds // 60, 60)
88585b02
EMS
374 offset_str = 'UTC%+d:%d' % (hours, minutes) \
375 if minutes else 'UTC%+d' % hours
1719bf4e
EMS
376 result.append((seconds, tzname, '%s - %s' % (offset_str, fr_name)))
377 result.sort()
378 return [(x[1], x[2]) for x in result]
86983865 379
88585b02 380
92c7413b 381class Evenement(models.Model):
7bbf600c
EMS
382 TYPE_CHOICES = ((u'Colloque', u'Colloque'),
383 (u'Conférence', u'Conférence'),
384 (u'Appel à contribution', u'Appel à contribution'),
385 (u'Journée d\'étude', u'Journée d\'étude'),
ec81ec66 386 (u'Autre', u'Autre'))
86983865
EMS
387 TIME_ZONE_CHOICES = build_time_zone_choices()
388
74b087e5 389 uid = models.CharField(max_length=255, default=str(uuid.uuid1()))
a5f76eb4 390 approuve = models.BooleanField(default=False, verbose_name=u'approuvé')
92c7413b 391 titre = models.CharField(max_length=255)
88585b02
EMS
392 discipline = models.ForeignKey(
393 'Discipline', related_name="discipline",
394 blank=True, null=True
395 )
396 discipline_secondaire = models.ForeignKey(
397 'Discipline', related_name="discipline_secondaire",
398 verbose_name=u"discipline secondaire", blank=True, null=True
399 )
74b087e5 400 mots_cles = models.TextField('Mots-Clés', blank=True, null=True)
7bbf600c 401 type = models.CharField(max_length=255, choices=TYPE_CHOICES)
731ef7ab
EMS
402 adresse = models.TextField()
403 ville = models.CharField(max_length=100)
31249cf3
EMS
404 pays = models.ForeignKey(
405 Pays, null=True, related_name='evenements', to_field='code'
406 )
74b087e5
EMS
407 debut = models.DateTimeField(default=datetime.datetime.now)
408 fin = models.DateTimeField(default=datetime.datetime.now)
88585b02
EMS
409 fuseau = models.CharField(
410 max_length=100, choices=TIME_ZONE_CHOICES,
411 verbose_name='fuseau horaire'
412 )
fe254ccc 413 description = models.TextField()
731ef7ab
EMS
414 contact = models.TextField(null=True) # champ obsolète
415 prenom = models.CharField('prénom', max_length=100)
416 nom = models.CharField(max_length=100)
417 courriel = models.EmailField()
74b087e5 418 url = models.CharField(max_length=255, blank=True, null=True)
88585b02
EMS
419 piece_jointe = models.FileField(
420 upload_to='agenda/pj', blank=True, verbose_name='pièce jointe'
421 )
422 regions = models.ManyToManyField(
423 Region, blank=True, related_name="evenements",
424 verbose_name='régions additionnelles',
425 help_text="On considère d'emblée que l'évènement se déroule dans la "
426 "région dans laquelle se trouve le pays indiqué plus haut. Il est "
427 "possible de désigner ici des régions additionnelles."
428 )
429 date_modification = models.DateTimeField(
430 editable=False, auto_now=True, null=True
431 )
92c7413b 432
4101cfc0 433 objects = EvenementManager()
c59dba82 434 all_objects = models.Manager()
4101cfc0
EMS
435
436 class Meta:
437 ordering = ['-debut']
fe254ccc
EMS
438 verbose_name = u'évènement'
439 verbose_name_plural = u'évènements'
4101cfc0 440
230671ff 441 def __unicode__(self):
020f79a9 442 return "[%s] %s" % (self.uid, self.titre)
443
230671ff
EMS
444 def get_absolute_url(self):
445 return reverse('evenement', kwargs={'id': self.id})
446
8dfe5efa
EMS
447 def duration_display(self):
448 delta = self.fin - self.debut
449 minutes, seconds = divmod(delta.seconds, 60)
450 hours, minutes = divmod(minutes, 60)
451 days = delta.days
452 parts = []
453 if days == 1:
454 parts.append('1 jour')
455 elif days > 1:
456 parts.append('%d jours' % days)
457 if hours == 1:
458 parts.append('1 heure')
459 elif hours > 1:
460 parts.append('%d heures' % hours)
461 if minutes == 1:
462 parts.append('1 minute')
463 elif minutes > 1:
464 parts.append('%d minutes' % minutes)
465 return ' '.join(parts)
466
27fe0d70
EMS
467 def piece_jointe_display(self):
468 return self.piece_jointe and os.path.basename(self.piece_jointe.name)
469
fe254ccc
EMS
470 def courriel_display(self):
471 return self.courriel.replace(u'@', u' (à) ')
472
a83b8efb
EMS
473 @property
474 def lieu(self):
475 bits = []
476 if self.adresse:
477 bits.append(self.adresse)
478 if self.ville:
479 bits.append(self.ville)
480 if self.pays:
481 bits.append(self.pays.nom)
482 return ', '.join(bits)
483
73309469 484 def clean(self):
485 from django.core.exceptions import ValidationError
486 if self.debut > self.fin:
88585b02
EMS
487 raise ValidationError(
488 'La date de fin ne doit pas être antérieure à la date de début'
489 )
73309469 490
b7a741ad 491 def save(self, *args, **kwargs):
88585b02
EMS
492 """
493 Sauvegarde l'objet dans django et le synchronise avec caldav s'il a
494 été approuvé.
495 """
496 self.contact = '' # Vider ce champ obsolète à la première occasion...
73309469 497 self.clean()
b7a741ad 498 super(Evenement, self).save(*args, **kwargs)
acd5cd8f 499 self.update_vevent()
b7a741ad 500
501 # methodes de commnunications avec CALDAV
502 def as_ical(self,):
503 """Retourne l'evenement django sous forme d'objet icalendar"""
504 cal = vobject.iCalendar()
505 cal.add('vevent')
506
507 # fournit son propre uid
7f56d0d4 508 if self.uid in [None, ""]:
b7a741ad 509 self.uid = str(uuid.uuid1())
510
511 cal.vevent.add('uid').value = self.uid
88585b02 512
b7a741ad 513 cal.vevent.add('summary').value = self.titre
88585b02 514
b7a741ad 515 if self.mots_cles is None:
516 kw = []
517 else:
518 kw = self.mots_cles.split(",")
519
520 try:
521 kw.append(self.discipline.nom)
522 kw.append(self.discipline_secondaire.nom)
523 kw.append(self.type)
88585b02
EMS
524 except:
525 pass
b7a741ad 526
79b400f0 527 kw = [x.strip() for x in kw if len(x.strip()) > 0 and x is not None]
b7a741ad 528 for k in kw:
529 cal.vevent.add('x-auf-keywords').value = k
530
531 description = self.description
532 if len(kw) > 0:
533 if len(self.description) > 0:
534 description += "\n"
028f548f 535 description += u"Mots-clés: " + ", ".join(kw)
b7a741ad 536
88585b02
EMS
537 cal.vevent.add('dtstart').value = \
538 combine(self.debut, pytz.timezone(self.fuseau))
539 cal.vevent.add('dtend').value = \
540 combine(self.fin, pytz.timezone(self.fuseau))
541 cal.vevent.add('created').value = \
542 combine(datetime.datetime.now(), "UTC")
543 cal.vevent.add('dtstamp').value = \
544 combine(datetime.datetime.now(), "UTC")
79b400f0 545 if len(description) > 0:
b7a741ad 546 cal.vevent.add('description').value = description
547 if len(self.contact) > 0:
548 cal.vevent.add('contact').value = self.contact
549 if len(self.url) > 0:
550 cal.vevent.add('url').value = self.url
88585b02
EMS
551 cal.vevent.add('location').value = ', '.join(
552 x for x in [self.adresse, self.ville, self.pays.nom] if x
553 )
74b087e5
EMS
554 if self.piece_jointe:
555 url = self.piece_jointe.url
556 if not url.startswith('http://'):
557 url = SITE_ROOT_URL + url
558 cal.vevent.add('attach').value = url
b7a741ad 559 return cal
560
561 def update_vevent(self,):
562 """Essaie de créer l'évènement sur le serveur ical.
563 En cas de succès, l'évènement local devient donc inactif et approuvé"""
564 try:
565 if self.approuve:
566 event = self.as_ical()
567 client = caldav.DAVClient(CALENDRIER_URL)
88585b02
EMS
568 cal = caldav.Calendar(client, url=CALENDRIER_URL)
569 e = caldav.Event(
570 client, parent=cal, data=event.serialize(), id=self.uid
571 )
b7a741ad 572 e.save()
573 except:
574 self.approuve = False
575
576 def delete_vevent(self,):
577 """Supprime l'evenement sur le serveur caldav"""
578 try:
579 if self.approuve:
b7a741ad 580 client = caldav.DAVClient(CALENDRIER_URL)
88585b02 581 cal = caldav.Calendar(client, url=CALENDRIER_URL)
b7a741ad 582 e = cal.event(self.uid)
583 e.delete()
584 except error.NotFoundError:
585 pass
586
264a3210
EMS
587 def assigner_regions(self, regions):
588 self.regions.add(*regions)
589
590 def assigner_disciplines(self, disciplines):
591 if len(disciplines) == 1:
592 if self.discipline:
593 self.discipline_secondaire = disciplines[0]
594 else:
595 self.discipline = disciplines[0]
596 elif len(disciplines) >= 2:
597 self.discipline = disciplines[0]
598 self.discipline_secondaire = disciplines[1]
599
88585b02 600
b7a741ad 601def delete_vevent(sender, instance, *args, **kwargs):
5212238e 602 # Surcharge du comportement de suppression
88585b02
EMS
603 # La méthode de connexion par signals est préférable à surcharger la
604 # méthode delete() car dans le cas de la suppression par lots, cell-ci
605 # n'est pas invoquée
b7a741ad 606 instance.delete_vevent()
88585b02
EMS
607pre_delete.connect(delete_vevent, sender=Evenement)
608
b7a741ad 609
81fe476e
PP
610class EvenementVoir(Evenement):
611
612 class Meta:
613 proxy = True
429222f1
PP
614 verbose_name = 'événement (visualisation)'
615 verbose_name_plural = 'événement (visualisation)'
81fe476e 616
88585b02 617
5212238e 618# Ressources
b7a741ad 619
d972b61d 620class ListSet(models.Model):
88585b02
EMS
621 spec = models.CharField(primary_key=True, max_length=255)
622 name = models.CharField(max_length=255)
623 server = models.CharField(max_length=255)
624 validated = models.BooleanField(default=True)
d972b61d 625
10d37e44 626 def __unicode__(self,):
627 return self.name
628
88585b02 629
656b9c0f
PP
630class RecordCategorie(models.Model):
631 nom = models.CharField(max_length=255)
632
633 class Meta:
634 verbose_name = 'catégorie ressource'
635 verbose_name_plural = 'catégories ressources'
636
637 def __unicode__(self):
638 return self.nom
639
640
230671ff
EMS
641class RecordQuerySet(SEPQuerySet):
642
643 def filter_modified(self, min=None, max=None):
7ed3ee8f 644 return self._filter_date('modified', min=min, max=max)
230671ff 645
88585b02 646
5212238e 647class RecordSphinxQuerySet(SEPSphinxQuerySet):
f153be1b 648
5212238e 649 def __init__(self, model=None):
88585b02
EMS
650 SEPSphinxQuerySet.__init__(
651 self, model=model, index='savoirsenpartage_ressources',
652 weights=dict(title=3)
653 )
c1b134f8 654
230671ff 655 def filter_modified(self, min=None, max=None):
7ed3ee8f 656 return self._filter_date('modified', min=min, max=max)
230671ff 657
27effd89
PP
658 def filter_region(self, region):
659 return self.filter(region_ids=region.id)
660
661 def filter_discipline(self, discipline):
662 return self.filter(discipline_ids=discipline.id)
663
664
5212238e 665class RecordManager(SEPManager):
f12cc7fb 666
5212238e 667 def get_query_set(self):
f153be1b
EMS
668 """Ne garder que les ressources validées et qui sont soit dans aucun
669 listset ou au moins dans un listset validé."""
e46d6207
EMS
670 ids_valides = ListSet.objects.filter(validated=True) \
671 .values('record__id')
672 ids_invalides = ListSet.objects.filter(validated=False) \
673 .values('record__id')
230671ff 674 qs = RecordQuerySet(self.model)
5212238e 675 qs = qs.filter(validated=True)
e46d6207
EMS
676 qs = qs.filter(Q(id__in=ids_valides) | ~Q(id__in=ids_invalides))
677 return qs
f153be1b 678
5212238e
EMS
679 def get_sphinx_query_set(self):
680 return RecordSphinxQuerySet(self.model)
77b0fac0 681
230671ff
EMS
682 def filter_modified(self, min=None, max=None):
683 return self.get_query_set().filter_modified(min=min, max=max)
684
88585b02 685
0cc5f772 686class Record(models.Model):
88585b02 687
23b5b3d5 688 #fonctionnement interne
88585b02
EMS
689 id = models.AutoField(primary_key=True)
690 server = models.CharField(max_length=255, verbose_name=u'serveur')
691 last_update = models.CharField(max_length=255)
692 last_checksum = models.CharField(max_length=255)
a5f76eb4 693 validated = models.BooleanField(default=True, verbose_name=u'validé')
23b5b3d5 694
695 #OAI
18dbd2cf
EMS
696 title = models.TextField(null=True, blank=True, verbose_name=u'titre')
697 creator = models.TextField(null=True, blank=True, verbose_name=u'auteur')
698 description = models.TextField(null=True, blank=True)
699 modified = models.CharField(max_length=255, null=True, blank=True)
88585b02
EMS
700 identifier = models.CharField(
701 max_length=255, null=True, blank=True, unique=True
702 )
703 uri = models.CharField(max_length=255, null=True, blank=True, unique=True)
704 source = models.TextField(null=True, blank=True)
705 contributor = models.TextField(null=True, blank=True)
18dbd2cf 706 subject = models.TextField(null=True, blank=True, verbose_name='sujet')
88585b02
EMS
707 publisher = models.TextField(null=True, blank=True)
708 type = models.TextField(null=True, blank=True)
709 format = models.TextField(null=True, blank=True)
710 language = models.TextField(null=True, blank=True)
da9020f3 711
88585b02 712 listsets = models.ManyToManyField(ListSet, null=True, blank=True)
d972b61d 713
da9020f3 714 #SEP 2 (aucune données récoltées)
88585b02
EMS
715 alt_title = models.TextField(null=True, blank=True)
716 abstract = models.TextField(null=True, blank=True)
717 creation = models.CharField(max_length=255, null=True, blank=True)
718 issued = models.CharField(max_length=255, null=True, blank=True)
719 isbn = models.TextField(null=True, blank=True)
720 orig_lang = models.TextField(null=True, blank=True)
721
722 categorie = models.ForeignKey(
723 RecordCategorie, blank=True, null=True, verbose_name='catégorie'
724 )
656b9c0f 725
da9020f3 726 # Metadata AUF multivaluées
a342f93a 727 disciplines = models.ManyToManyField(Discipline, blank=True)
88585b02
EMS
728 thematiques = models.ManyToManyField(
729 Thematique, blank=True, verbose_name='thématiques'
730 )
a342f93a 731 pays = models.ManyToManyField(Pays, blank=True)
88585b02
EMS
732 regions = models.ManyToManyField(
733 Region, blank=True, verbose_name='régions'
734 )
0cc5f772 735
5212238e 736 # Managers
da44ce68 737 objects = RecordManager()
c59dba82 738 all_objects = models.Manager()
da44ce68 739
18dbd2cf
EMS
740 class Meta:
741 verbose_name = 'ressource'
742
264a3210
EMS
743 def __unicode__(self):
744 return "[%s] %s" % (self.server, self.title)
745
230671ff
EMS
746 def get_absolute_url(self):
747 return reverse('ressource', kwargs={'id': self.id})
748
264a3210 749 def getServeurURL(self):
f98ad449 750 """Retourne l'URL du serveur de provenance"""
88451b0f
EMS
751 return RESOURCES[self.server]['url'] \
752 if self.server in RESOURCES else ''
f98ad449 753
264a3210 754 def est_complet(self):
6d885e0c 755 """teste si le record à toutes les données obligatoires"""
756 return self.disciplines.count() > 0 and \
757 self.thematiques.count() > 0 and \
758 self.pays.count() > 0 and \
759 self.regions.count() > 0
760
264a3210
EMS
761 def assigner_regions(self, regions):
762 self.regions.add(*regions)
da9020f3 763
264a3210
EMS
764 def assigner_disciplines(self, disciplines):
765 self.disciplines.add(*disciplines)
88585b02
EMS
766
767
927764f9
PP
768class RecordEdit(Record):
769
770 class Meta:
771 proxy = True
772 verbose_name = 'ressource (édition)'
773 verbose_name_plural = 'ressources (édition)'
774
88585b02 775
6d885e0c 776class Serveur(models.Model):
b7a741ad 777 """Identification d'un serveur d'ou proviennent les références"""
88585b02 778 nom = models.CharField(primary_key=True, max_length=255)
6d885e0c 779
780 def __unicode__(self,):
781 return self.nom
782
783 def conf_2_db(self,):
784 for k in RESOURCES.keys():
785 s, created = Serveur.objects.get_or_create(nom=k)
786 s.nom = k
787 s.save()
788
88585b02 789
6d885e0c 790class Profile(models.Model):
791 user = models.ForeignKey(User, unique=True)
88585b02
EMS
792 serveurs = models.ManyToManyField(Serveur, null=True, blank=True)
793
0cc5f772
CR
794
795class HarvestLog(models.Model):
88585b02
EMS
796 context = models.CharField(max_length=255)
797 name = models.CharField(max_length=255)
798 date = models.DateTimeField(auto_now=True)
799 added = models.IntegerField(null=True, blank=True)
800 updated = models.IntegerField(null=True, blank=True)
801 processed = models.IntegerField(null=True, blank=True)
802 record = models.ForeignKey(Record, null=True, blank=True)
23b5b3d5 803
804 @staticmethod
805 def add(message):
806 logger = HarvestLog()
88585b02 807 if 'record_id' in message:
d566e9c1 808 message['record'] = Record.all_objects.get(id=message['record_id'])
23b5b3d5 809 del(message['record_id'])
810
88585b02 811 for k, v in message.items():
23b5b3d5 812 setattr(logger, k, v)
813 logger.save()
f09bc1c6 814
88585b02 815
f09bc1c6
EMS
816# Pages statiques
817
818class PageStatique(models.Model):
20430b8a 819 id = models.CharField(max_length=32, primary_key=True)
f09bc1c6
EMS
820 titre = models.CharField(max_length=100)
821 contenu = models.TextField()
822
823 class Meta:
824 verbose_name_plural = 'pages statiques'
fdcf5874 825
88585b02 826
fdcf5874
EMS
827# Recherches
828
829class GlobalSearchResults(object):
830
88585b02 831 def __init__(self, actualites=None, appels=None, evenements=None,
057c3327
PP
832 ressources=None, chercheurs=None, groupes=None,
833 sites=None, sites_auf=None):
fdcf5874
EMS
834 self.actualites = actualites
835 self.appels = appels
836 self.evenements = evenements
837 self.ressources = ressources
838 self.chercheurs = chercheurs
057c3327 839 self.groupes = groupes
fdcf5874
EMS
840 self.sites = sites
841 self.sites_auf = sites_auf
842
4b89a7df 843 def __nonzero__(self):
88585b02 844 return bool(self.actualites or self.appels or self.evenements or
057c3327
PP
845 self.ressources or self.chercheurs or self.groupes or
846 self.sites or self.sites_auf)
4b89a7df 847
88585b02 848
fdcf5874
EMS
849class Search(models.Model):
850 user = models.ForeignKey(User, editable=False)
851 content_type = models.ForeignKey(ContentType, editable=False)
852 nom = models.CharField(max_length=100, verbose_name="nom de la recherche")
88585b02
EMS
853 alerte_courriel = models.BooleanField(
854 verbose_name="Envoyer une alerte courriel"
855 )
856 derniere_alerte = models.DateField(
857 verbose_name="Date d'envoi de la dernière alerte courriel",
858 null=True, editable=False
859 )
860 q = models.CharField(
861 max_length=255, blank=True, verbose_name="dans tous les champs"
862 )
fdcf5874 863 discipline = models.ForeignKey(Discipline, blank=True, null=True)
88585b02
EMS
864 region = models.ForeignKey(
865 Region, blank=True, null=True, verbose_name='région',
866 help_text="La région est ici définie au sens, non strictement "
867 "géographique, du Bureau régional de l'AUF de référence."
868 )
fdcf5874
EMS
869
870 def query_string(self):
871 params = dict()
872 for field in self._meta.fields:
88585b02
EMS
873 if field.name in ['id', 'user', 'nom', 'search_ptr',
874 'content_type']:
fdcf5874
EMS
875 continue
876 value = getattr(self, field.column)
877 if value:
878 if isinstance(value, datetime.date):
879 params[field.name] = value.strftime('%d/%m/%Y')
880 else:
4b89a7df 881 params[field.name] = smart_str(value)
fdcf5874 882 return urlencode(params)
88585b02 883
fdcf5874
EMS
884 class Meta:
885 verbose_name = 'recherche transversale'
886 verbose_name_plural = "recherches transversales"
887
2094c7e5
PP
888 def __unicode__(self):
889 return self.nom
890
fdcf5874 891 def save(self):
a83b8efb
EMS
892 if self.alerte_courriel:
893 try:
894 original_search = Search.objects.get(id=self.id)
895 if not original_search.alerte_courriel:
896 # On a nouvellement activé l'alerte courriel. Notons la
897 # date.
88585b02
EMS
898 self.derniere_alerte = \
899 datetime.date.today() - datetime.timedelta(days=1)
a83b8efb 900 except Search.DoesNotExist:
88585b02
EMS
901 self.derniere_alerte = \
902 datetime.date.today() - datetime.timedelta(days=1)
fdcf5874 903 if (not self.content_type_id):
88585b02
EMS
904 self.content_type = ContentType.objects.get_for_model(
905 self.__class__
906 )
fdcf5874
EMS
907 super(Search, self).save()
908
909 def as_leaf_class(self):
910 content_type = self.content_type
911 model = content_type.model_class()
912 if(model == Search):
913 return self
914 return model.objects.get(id=self.id)
88585b02 915
4b89a7df 916 def run(self, min_date=None, max_date=None):
057c3327 917 from chercheurs.models import Chercheur, Groupe
fdcf5874
EMS
918 from sitotheque.models import Site
919
fdcf5874
EMS
920 actualites = Actualite.objects
921 evenements = Evenement.objects
922 ressources = Record.objects
923 chercheurs = Chercheur.objects
057c3327 924 groupes = Groupe.objects
fdcf5874
EMS
925 sites = Site.objects
926 if self.q:
927 actualites = actualites.search(self.q)
928 evenements = evenements.search(self.q)
929 ressources = ressources.search(self.q)
930 chercheurs = chercheurs.search(self.q)
057c3327 931 groupes = groupes.search(self.q)
fdcf5874
EMS
932 sites = sites.search(self.q)
933 if self.discipline:
934 actualites = actualites.filter_discipline(self.discipline)
935 evenements = evenements.filter_discipline(self.discipline)
936 ressources = ressources.filter_discipline(self.discipline)
937 chercheurs = chercheurs.filter_discipline(self.discipline)
938 sites = sites.filter_discipline(self.discipline)
939 if self.region:
940 actualites = actualites.filter_region(self.region)
941 evenements = evenements.filter_region(self.region)
942 ressources = ressources.filter_region(self.region)
943 chercheurs = chercheurs.filter_region(self.region)
944 sites = sites.filter_region(self.region)
4b89a7df
EMS
945 if min_date:
946 actualites = actualites.filter_date(min=min_date)
a83b8efb 947 evenements = evenements.filter_date_modification(min=min_date)
4b89a7df
EMS
948 ressources = ressources.filter_modified(min=min_date)
949 chercheurs = chercheurs.filter_date_modification(min=min_date)
950 sites = sites.filter_date_maj(min=min_date)
951 if max_date:
952 actualites = actualites.filter_date(max=max_date)
a83b8efb 953 evenements = evenements.filter_date_modification(max=max_date)
4b89a7df
EMS
954 ressources = ressources.filter_modified(max=max_date)
955 chercheurs = chercheurs.filter_date_modification(max=max_date)
956 sites = sites.filter_date_maj(max=max_date)
957
fdcf5874
EMS
958 try:
959 sites_auf = google_search(0, self.q)['results']
960 except:
961 sites_auf = []
962
963 return GlobalSearchResults(
964 actualites=actualites.order_by('-date').filter_type('actu'),
965 appels=actualites.order_by('-date').filter_type('appels'),
966 evenements=evenements.order_by('-debut'),
a2aa3978 967 ressources=ressources.order_by('-modified'),
fdcf5874 968 chercheurs=chercheurs.order_by('-date_modification'),
057c3327 969 groupes=groupes.order_by('nom'),
fdcf5874
EMS
970 sites=sites.order_by('-date_maj'),
971 sites_auf=sites_auf
972 )
973
974 def url(self):
c956b333
PP
975
976 if self.content_type.model != 'search':
977 obj = self.content_type.get_object_for_this_type(pk=self.pk)
978 return obj.url()
979
fdcf5874
EMS
980 url = ''
981 if self.discipline:
982 url += '/discipline/%d' % self.discipline.id
983 if self.region:
984 url += '/region/%d' % self.region.id
985 url += '/recherche/'
986 if self.q:
4b89a7df 987 url += '?' + urlencode({'q': smart_str(self.q)})
fdcf5874
EMS
988 return url
989
da5bf7e9
EMS
990 def rss_url(self):
991 return None
992
4b89a7df
EMS
993 def send_email_alert(self):
994 """Envoie une alerte courriel correspondant à cette recherche"""
995 yesterday = datetime.date.today() - datetime.timedelta(days=1)
996 if self.derniere_alerte is not None:
88585b02
EMS
997 results = self.as_leaf_class().run(
998 min_date=self.derniere_alerte, max_date=yesterday
999 )
4b89a7df
EMS
1000 if results:
1001 subject = 'Savoirs en partage - ' + self.nom
a83b8efb 1002 from_email = CONTACT_EMAIL
4b89a7df 1003 to_email = self.user.email
88585b02
EMS
1004 text_content = u'Voici les derniers résultats ' \
1005 u'correspondant à votre recherche sauvegardée.\n\n'
1006 text_content += self.as_leaf_class() \
1007 .get_email_alert_content(results)
4b89a7df 1008 text_content += u'''
88585b02 1009
4b89a7df 1010Pour modifier votre abonnement aux alertes courriel de Savoirs en partage,
88585b02
EMS
1011rendez-vous sur le [gestionnaire de recherches sauvegardées](%s%s)''' % (
1012 SITE_ROOT_URL, reverse('recherches')
1013 )
1014 html_content = \
1015 '<div style="font-family: Arial, sans-serif">\n' + \
1016 markdown(smart_str(text_content)) + '</div>\n'
1017 msg = EmailMultiAlternatives(
1018 subject, text_content, from_email, [to_email]
1019 )
4b89a7df
EMS
1020 msg.attach_alternative(html_content, "text/html")
1021 msg.send()
1022 self.derniere_alerte = yesterday
1023 self.save()
4b89a7df
EMS
1024
1025 def get_email_alert_content(self, results):
1026 content = ''
1027 if results.chercheurs:
1028 content += u'\n### Nouveaux chercheurs\n\n'
1029 for chercheur in results.chercheurs:
88585b02
EMS
1030 content += u'- [%s %s](%s%s) \n' % (
1031 chercheur.nom.upper(), chercheur.prenom, SITE_ROOT_URL,
1032 chercheur.get_absolute_url()
1033 )
4b89a7df
EMS
1034 content += u' %s\n\n' % chercheur.etablissement_display
1035 if results.ressources:
1036 content += u'\n### Nouvelles ressources\n\n'
1037 for ressource in results.ressources:
88585b02
EMS
1038 content += u'- [%s](%s%s)\n\n' % (
1039 ressource.title, SITE_ROOT_URL,
1040 ressource.get_absolute_url()
1041 )
4b89a7df
EMS
1042 if ressource.description:
1043 content += '\n'
88585b02
EMS
1044 content += ''.join(
1045 ' %s\n' % line
1046 for line in textwrap.wrap(ressource.description)
1047 )
4b89a7df
EMS
1048 content += '\n'
1049
1050 if results.actualites:
1051 content += u'\n### Nouvelles actualités\n\n'
1052 for actualite in results.actualites:
88585b02
EMS
1053 content += u'- [%s](%s%s)\n\n' % (
1054 actualite.titre, SITE_ROOT_URL,
1055 actualite.get_absolute_url()
1056 )
4b89a7df
EMS
1057 if actualite.texte:
1058 content += '\n'
88585b02
EMS
1059 content += ''.join(
1060 ' %s\n' % line
1061 for line in textwrap.wrap(actualite.texte)
1062 )
4b89a7df
EMS
1063 content += '\n'
1064 if results.appels:
1065 content += u"\n### Nouveaux appels d'offres\n\n"
1066 for appel in results.appels:
1067 content += u'- [%s](%s%s)\n\n' % (appel.titre,
1068 SITE_ROOT_URL,
1069 appel.get_absolute_url())
1070 if appel.texte:
1071 content += '\n'
88585b02
EMS
1072 content += ''.join(
1073 ' %s\n' % line
1074 for line in textwrap.wrap(appel.texte)
1075 )
4b89a7df
EMS
1076 content += '\n'
1077 if results.evenements:
1078 content += u"\n### Nouveaux évènements\n\n"
1079 for evenement in results.evenements:
88585b02
EMS
1080 content += u'- [%s](%s%s) \n' % (
1081 evenement.titre, SITE_ROOT_URL,
1082 evenement.get_absolute_url()
1083 )
a83b8efb 1084 content += u' où ? : %s \n' % evenement.lieu
88585b02
EMS
1085 content += evenement.debut.strftime(
1086 ' quand ? : %d/%m/%Y %H:%M \n'
1087 )
1088 content += u' durée ? : %s\n\n' % \
1089 evenement.duration_display()
4b89a7df 1090 content += u' quoi ? : '
88585b02
EMS
1091 content += '\n '.join(
1092 textwrap.wrap(evenement.description)
1093 )
4b89a7df
EMS
1094 content += '\n\n'
1095 if results.sites:
1096 content += u"\n### Nouveaux sites\n\n"
1097 for site in results.sites:
1098 content += u'- [%s](%s%s)\n\n' % (site.titre,
1099 SITE_ROOT_URL,
1100 site.get_absolute_url())
1101 if site.description:
1102 content += '\n'
88585b02
EMS
1103 content += ''.join(
1104 ' %s\n' % line
1105 for line in textwrap.wrap(site.description)
1106 )
4b89a7df
EMS
1107 content += '\n'
1108 return content
1109
88585b02 1110
fdcf5874 1111class RessourceSearch(Search):
88585b02
EMS
1112 auteur = models.CharField(
1113 max_length=100, blank=True, verbose_name="auteur ou contributeur"
1114 )
fdcf5874
EMS
1115 titre = models.CharField(max_length=100, blank=True)
1116 sujet = models.CharField(max_length=100, blank=True)
88585b02
EMS
1117 publisher = models.CharField(
1118 max_length=100, blank=True, verbose_name="éditeur"
1119 )
1120 categorie = models.ForeignKey(
1121 RecordCategorie, blank=True, null=True, verbose_name='catégorie'
1122 )
fdcf5874
EMS
1123
1124 class Meta:
1125 verbose_name = 'recherche de ressources'
1126 verbose_name_plural = "recherches de ressources"
1127
4b89a7df 1128 def run(self, min_date=None, max_date=None):
fdcf5874
EMS
1129 results = Record.objects
1130 if self.q:
1131 results = results.search(self.q)
1132 if self.auteur:
88585b02
EMS
1133 results = results.add_to_query(
1134 '@(creator,contributor) ' + self.auteur
1135 )
fdcf5874
EMS
1136 if self.titre:
1137 results = results.add_to_query('@title ' + self.titre)
1138 if self.sujet:
1139 results = results.add_to_query('@subject ' + self.sujet)
1140 if self.publisher:
1141 results = results.add_to_query('@publisher ' + self.publisher)
ede02173
PP
1142 if self.categorie:
1143 results = results.add_to_query('@categorie %s' % self.categorie.id)
fdcf5874
EMS
1144 if self.discipline:
1145 results = results.filter_discipline(self.discipline)
1146 if self.region:
1147 results = results.filter_region(self.region)
4b89a7df
EMS
1148 if min_date:
1149 results = results.filter_modified(min=min_date)
1150 if max_date:
1151 results = results.filter_modified(max=max_date)
fdcf5874
EMS
1152 if not self.q:
1153 """Montrer les résultats les plus récents si on n'a pas fait
1154 une recherche par mots-clés."""
230671ff 1155 results = results.order_by('-modified')
fdcf5874
EMS
1156 return results.all()
1157
1158 def url(self):
1159 qs = self.query_string()
1160 return reverse('ressources') + ('?' + qs if qs else '')
1161
da5bf7e9
EMS
1162 def rss_url(self):
1163 qs = self.query_string()
1164 return reverse('rss_ressources') + ('?' + qs if qs else '')
1165
4b89a7df
EMS
1166 def get_email_alert_content(self, results):
1167 content = ''
1168 for ressource in results:
1169 content += u'- [%s](%s%s)\n\n' % (ressource.title,
1170 SITE_ROOT_URL,
1171 ressource.get_absolute_url())
1172 if ressource.description:
1173 content += '\n'
88585b02
EMS
1174 content += ''.join(
1175 ' %s\n' % line
1176 for line in textwrap.wrap(ressource.description)
1177 )
4b89a7df
EMS
1178 content += '\n'
1179 return content
1180
88585b02 1181
fdcf5874 1182class ActualiteSearchBase(Search):
88585b02
EMS
1183 date_min = models.DateField(
1184 blank=True, null=True, verbose_name="depuis le"
1185 )
1186 date_max = models.DateField(
1187 blank=True, null=True, verbose_name="jusqu'au"
1188 )
fdcf5874
EMS
1189
1190 class Meta:
1191 abstract = True
1192
4b89a7df 1193 def run(self, min_date=None, max_date=None):
fdcf5874
EMS
1194 results = Actualite.objects
1195 if self.q:
1196 results = results.search(self.q)
1197 if self.discipline:
1198 results = results.filter_discipline(self.discipline)
1199 if self.region:
1200 results = results.filter_region(self.region)
1201 if self.date_min:
1202 results = results.filter_date(min=self.date_min)
1203 if self.date_max:
1204 results = results.filter_date(max=self.date_max)
4b89a7df
EMS
1205 if min_date:
1206 results = results.filter_date(min=min_date)
1207 if max_date:
1208 results = results.filter_date(max=max_date)
fdcf5874
EMS
1209 return results.all()
1210
4b89a7df
EMS
1211 def get_email_alert_content(self, results):
1212 content = ''
1213 for actualite in results:
1214 content += u'- [%s](%s%s)\n\n' % (actualite.titre,
1215 SITE_ROOT_URL,
1216 actualite.get_absolute_url())
1217 if actualite.texte:
1218 content += '\n'
88585b02
EMS
1219 content += ''.join(
1220 ' %s\n' % line
1221 for line in textwrap.wrap(actualite.texte)
1222 )
4b89a7df
EMS
1223 content += '\n'
1224 return content
1225
88585b02 1226
fdcf5874
EMS
1227class ActualiteSearch(ActualiteSearchBase):
1228
1229 class Meta:
1230 verbose_name = "recherche d'actualités"
1231 verbose_name_plural = "recherches d'actualités"
88585b02 1232
4b89a7df 1233 def run(self, min_date=None, max_date=None):
88585b02
EMS
1234 return super(ActualiteSearch, self) \
1235 .run(min_date=min_date, max_date=max_date) \
1236 .filter_type('actu')
fdcf5874
EMS
1237
1238 def url(self):
1239 qs = self.query_string()
1240 return reverse('actualites') + ('?' + qs if qs else '')
1241
da5bf7e9
EMS
1242 def rss_url(self):
1243 qs = self.query_string()
1244 return reverse('rss_actualites') + ('?' + qs if qs else '')
88585b02
EMS
1245
1246
fdcf5874
EMS
1247class AppelSearch(ActualiteSearchBase):
1248
1249 class Meta:
1250 verbose_name = "recherche d'appels d'offres"
1251 verbose_name_plural = "recherches d'appels d'offres"
1252
4b89a7df 1253 def run(self, min_date=None, max_date=None):
88585b02
EMS
1254 return super(AppelSearch, self) \
1255 .run(min_date=min_date, max_date=max_date) \
1256 .filter_type('appels')
fdcf5874
EMS
1257
1258 def url(self):
1259 qs = self.query_string()
1260 return reverse('appels') + ('?' + qs if qs else '')
1261
da5bf7e9
EMS
1262 def rss_url(self):
1263 qs = self.query_string()
1264 return reverse('rss_appels') + ('?' + qs if qs else '')
1265
88585b02 1266
fdcf5874 1267class EvenementSearch(Search):
88585b02
EMS
1268 titre = models.CharField(
1269 max_length=100, blank=True, verbose_name="Intitulé"
1270 )
1271 type = models.CharField(
1272 max_length=100, blank=True, choices=Evenement.TYPE_CHOICES
1273 )
1274 date_min = models.DateField(
1275 blank=True, null=True, verbose_name="depuis le"
1276 )
1277 date_max = models.DateField(
1278 blank=True, null=True, verbose_name="jusqu'au"
1279 )
fdcf5874
EMS
1280
1281 class Meta:
1282 verbose_name = "recherche d'évènements"
1283 verbose_name_plural = "recherches d'évènements"
1284
4b89a7df 1285 def run(self, min_date=None, max_date=None):
fdcf5874
EMS
1286 results = Evenement.objects
1287 if self.q:
1288 results = results.search(self.q)
1289 if self.titre:
1290 results = results.add_to_query('@titre ' + self.titre)
1291 if self.discipline:
1292 results = results.filter_discipline(self.discipline)
1293 if self.region:
1294 results = results.filter_region(self.region)
1295 if self.type:
1296 results = results.filter_type(self.type)
1297 if self.date_min:
1298 results = results.filter_debut(min=self.date_min)
1299 if self.date_max:
1300 results = results.filter_debut(max=self.date_max)
4b89a7df 1301 if min_date:
a83b8efb 1302 results = results.filter_date_modification(min=min_date)
4b89a7df 1303 if max_date:
a83b8efb 1304 results = results.filter_date_modification(max=max_date)
fdcf5874
EMS
1305 return results.all()
1306
1307 def url(self):
1308 qs = self.query_string()
1309 return reverse('agenda') + ('?' + qs if qs else '')
1310
da5bf7e9
EMS
1311 def rss_url(self):
1312 qs = self.query_string()
1313 return reverse('rss_agenda') + ('?' + qs if qs else '')
4b89a7df
EMS
1314
1315 def get_email_alert_content(self, results):
1316 content = ''
1317 for evenement in results:
1318 content += u'- [%s](%s%s) \n' % (evenement.titre,
1319 SITE_ROOT_URL,
1320 evenement.get_absolute_url())
a83b8efb 1321 content += u' où ? : %s \n' % evenement.lieu
88585b02
EMS
1322 content += evenement.debut.strftime(
1323 ' quand ? : %d/%m/%Y %H:%M \n'
1324 )
4b89a7df
EMS
1325 content += u' durée ? : %s\n\n' % evenement.duration_display()
1326 content += u' quoi ? : '
88585b02
EMS
1327 content += '\n '.join(
1328 textwrap.wrap(evenement.description)
1329 )
4b89a7df
EMS
1330 content += '\n\n'
1331 return content