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