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