Commit | Line | Data |
---|---|---|
92c7413b | 1 | # -*- encoding: utf-8 -*- |
88585b02 | 2 | |
db2999fa EMS |
3 | import caldav |
4 | import datetime | |
5 | import feedparser | |
db2999fa EMS |
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 |
88585b02 | 23 | from django.db.models import Q |
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 |
a83b8efb | 30 | from settings import CALENDRIER_URL, SITE_ROOT_URL, CONTACT_EMAIL |
88585b02 EMS |
31 | from lib.calendrier import combine |
32 | from lib.recherche import google_search | |
33 | ||
5212238e EMS |
34 | |
35 | # Fonctionnalités communes à tous les query sets | |
d15017b2 | 36 | |
15261361 EMS |
37 | class 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 | 51 | class 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 |
66 | class 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 |
129 | class 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 |
152 | class 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 | 166 | class 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 | 204 | class 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 | 213 | class 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 | 235 | class 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 | 250 | class 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 | |
289 | class 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 | 299 | class 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 | 311 | class 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 | 335 | class 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 | 353 | def 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 | 375 | class 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 | 595 | def 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 |
601 | pre_delete.connect(delete_vevent, sender=Evenement) |
602 | ||
b7a741ad | 603 | |
81fe476e PP |
604 | class 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 | 614 | class 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 |
624 | class 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 |
635 | class 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 | 641 | class 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 | 659 | class 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 | 676 | class 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 |
757 | class RecordEdit(Record): |
758 | ||
759 | class Meta: | |
760 | proxy = True | |
761 | verbose_name = 'ressource (édition)' | |
762 | verbose_name_plural = 'ressources (édition)' | |
763 | ||
88585b02 | 764 | |
6d885e0c | 765 | class 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 | 779 | class 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 | |
784 | class 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 | ||
807 | class 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 | ||
818 | class 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 |
838 | class 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 | 999 | Pour modifier votre abonnement aux alertes courriel de Savoirs en partage, |
88585b02 EMS |
1000 | rendez-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 | 1100 | class 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 | 1171 | class 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 |
1216 | class 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 |
1236 | class 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 | 1256 | class 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 |