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