Fils RSS pour toutes les briques
[auf_savoirs_en_partage_django.git] / auf_savoirs_en_partage / savoirs / models.py
CommitLineData
92c7413b 1# -*- encoding: utf-8 -*-
db2999fa
EMS
2import caldav
3import datetime
4import feedparser
5import operator
6import os
7import pytz
8import random
9import uuid
10import vobject
fdcf5874
EMS
11from urllib import urlencode
12
5212238e 13from backend_config import RESOURCES
86983865 14from babel.dates import get_timezone_name
5212238e 15from caldav.lib import error
6d885e0c 16from django.contrib.auth.models import User
fdcf5874
EMS
17from django.contrib.contenttypes.models import ContentType
18from django.core.urlresolvers import reverse
d15017b2 19from django.db import models
15261361 20from django.db.models import Q, Max
b7a741ad 21from django.db.models.signals import pre_delete
5212238e 22from django.utils.encoding import smart_unicode
122c4c3d 23from djangosphinx.models import SphinxQuerySet, SearchError
fdcf5874
EMS
24
25from datamaster_modeles.models import Region, Pays, Thematique
da9020f3 26from savoirs.globals import META
74b087e5 27from settings import CALENDRIER_URL, SITE_ROOT_URL
5212238e
EMS
28
29# Fonctionnalités communes à tous les query sets
d15017b2 30
15261361
EMS
31class RandomQuerySetMixin(object):
32 """Mixin pour les modèles.
33
34 ORDER BY RAND() est très lent sous MySQL. On a besoin d'une autre
35 méthode pour récupérer des objets au hasard.
36 """
37
38 def random(self, n=1):
39 """Récupère aléatoirement un nombre donné d'objets."""
bae03b7b
EMS
40 count = self.count()
41 positions = random.sample(xrange(count), min(n, count))
42 return [self[p] for p in positions]
15261361 43
5212238e 44class SEPQuerySet(models.query.QuerySet, RandomQuerySetMixin):
230671ff
EMS
45
46 def _filter_date(self, field, min=None, max=None):
47 """Limite les résultats à ceux dont le champ ``field`` tombe entre
48 les dates ``min`` et ``max``."""
49 qs = self
50 if min:
51 qs = qs.filter(**{field + '__gte': min})
52 if max:
53 qs = qs.filter(**{field + '__lte': max})
54 return qs
5212238e
EMS
55
56class SEPSphinxQuerySet(SphinxQuerySet, RandomQuerySetMixin):
57 """Fonctionnalités communes aux query sets de Sphinx."""
58
59 def __init__(self, model=None, index=None, weights=None):
60 SphinxQuerySet.__init__(self, model=model, index=index,
61 mode='SPH_MATCH_EXTENDED2',
62 rankmode='SPH_RANK_PROXIMITY_BM25',
63 weights=weights)
64
65 def add_to_query(self, query):
66 """Ajoute une partie à la requête texte."""
6c78c673
EMS
67
68 # Assurons-nous qu'il y a un nombre pair de guillemets
69 if query.count('"') % 2 != 0:
70 # Sinon, on enlève le dernier (faut choisir...)
71 i = query.rindex('"')
72 query = query[:i] + query[i+1:]
73
5212238e
EMS
74 new_query = smart_unicode(self._query) + ' ' + query if self._query else query
75 return self.query(new_query)
76
77 def search(self, text):
78 """Recherche ``text`` dans tous les champs."""
79 return self.add_to_query('@* ' + text)
80
81 def filter_discipline(self, discipline):
82 """Par défaut, le filtre par discipline cherche le nom de la
83 discipline dans tous les champs."""
84 return self.search('"%s"' % discipline.nom)
85
86 def filter_region(self, region):
87 """Par défaut, le filtre par région cherche le nom de la région dans
88 tous les champs."""
89 return self.search('"%s"' % region.nom)
90
230671ff
EMS
91 def _filter_date(self, field, min=None, max=None):
92 """Limite les résultats à ceux dont le champ ``field`` tombe entre
93 les dates ``min`` et ``max``."""
94 qs = self
95 if min:
96 qs = qs.filter(**{field + '__gte': min.toordinal()+365})
97 if max:
98 qs = qs.filter(**{field + '__lte': max.toordinal()+365})
99 return qs
100
122c4c3d
EMS
101 def _get_sphinx_results(self):
102 try:
103 return SphinxQuerySet._get_sphinx_results(self)
104 except SearchError:
105 # Essayons d'enlever les caractères qui peuvent poser problème.
106 for c in '|!@()~/<=^$':
107 self._query = self._query.replace(c, ' ')
108 try:
109 return SphinxQuerySet._get_sphinx_results(self)
110 except SearchError:
111 # Ça ne marche toujours pas. Enlevons les guillemets et les
112 # tirets.
113 for c in '"-':
114 self._query = self._query.replace(c, ' ')
115 return SphinxQuerySet._get_sphinx_results(self)
116
5212238e
EMS
117class SEPManager(models.Manager):
118 """Lorsque les méthodes ``search``, ``filter_region`` et
119 ``filter_discipline`` sont appelées sur ce manager, le query set
120 Sphinx est créé, sinon, c'est le query set Django qui est créé."""
121
122 def query(self, query):
123 return self.get_sphinx_query_set().query(query)
124
125 def add_to_query(self, query):
126 return self.get_sphinx_query_set().add_to_query(query)
127
128 def search(self, text):
129 return self.get_sphinx_query_set().search(text)
130
131 def filter_region(self, region):
132 return self.get_sphinx_query_set().filter_region(region)
133
134 def filter_discipline(self, discipline):
135 return self.get_sphinx_query_set().filter_discipline(discipline)
136
137# Disciplines
138
d15017b2
CR
139class Discipline(models.Model):
140 id = models.IntegerField(primary_key=True, db_column='id_discipline')
141 nom = models.CharField(max_length=765, db_column='nom_discipline')
6ef8ead4
CR
142
143 def __unicode__ (self):
92c7413b 144 return self.nom
6ef8ead4 145
d15017b2
CR
146 class Meta:
147 db_table = u'discipline'
148 ordering = ["nom",]
149
5212238e
EMS
150# Actualités
151
79c398f6 152class SourceActualite(models.Model):
011804bb
EMS
153 TYPE_CHOICES = (
154 ('actu', 'Actualités'),
155 ('appels', "Appels d'offres"),
156 )
157
79c398f6 158 nom = models.CharField(max_length=255)
011804bb
EMS
159 url = models.CharField(max_length=255, verbose_name='URL', blank=True)
160 type = models.CharField(max_length=10, default='actu', choices=TYPE_CHOICES)
ccbc4363 161
db2999fa
EMS
162 class Meta:
163 verbose_name = u'fil RSS syndiqué'
164 verbose_name_plural = u'fils RSS syndiqués'
165
ccbc4363 166 def __unicode__(self,):
011804bb 167 return u"%s (%s)" % (self.nom, self.get_type_display())
79c398f6 168
db2999fa
EMS
169 def update(self):
170 """Mise à jour du fil RSS."""
011804bb
EMS
171 if not self.url:
172 return
db2999fa
EMS
173 feed = feedparser.parse(self.url)
174 for entry in feed.entries:
175 if Actualite.all_objects.filter(url=entry.link).count() == 0:
176 ts = entry.updated_parsed
177 date = datetime.date(ts.tm_year, ts.tm_mon, ts.tm_mday)
178 a = self.actualites.create(titre=entry.title,
179 texte=entry.summary_detail.value,
180 url=entry.link, date=date)
181
5212238e 182class ActualiteQuerySet(SEPQuerySet):
2f9c4d6c 183
5212238e 184 def filter_date(self, min=None, max=None):
230671ff 185 return self._filter_date('date', min=min, max=max)
da44ce68 186
011804bb
EMS
187 def filter_type(self, type):
188 return self.filter(source__type=type)
189
5212238e 190class ActualiteSphinxQuerySet(SEPSphinxQuerySet):
c1b134f8 191
5212238e 192 def __init__(self, model=None):
4134daa0 193 SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_actualites',
5212238e 194 weights=dict(titre=3))
c1b134f8 195
7fb6bd61 196 def filter_date(self, min=None, max=None):
230671ff
EMS
197 return self._filter_date('date', min=min, max=max)
198
011804bb
EMS
199 TYPE_CODES = {'actu': 1, 'appels': 2}
200 def filter_type(self, type):
201 return self.filter(type=self.TYPE_CODES[type])
202
5212238e
EMS
203class ActualiteManager(SEPManager):
204
205 def get_query_set(self):
206 return ActualiteQuerySet(self.model).filter(visible=True)
2f9c4d6c 207
5212238e
EMS
208 def get_sphinx_query_set(self):
209 return ActualiteSphinxQuerySet(self.model).order_by('-date')
2f9c4d6c 210
5212238e
EMS
211 def filter_date(self, min=None, max=None):
212 return self.get_query_set().filter_date(min=min, max=max)
bae03b7b 213
011804bb
EMS
214 def filter_type(self, type):
215 return self.get_query_set().filter_type(type)
216
d15017b2 217class Actualite(models.Model):
4f262f90 218 id = models.AutoField(primary_key=True, db_column='id_actualite')
d15017b2
CR
219 titre = models.CharField(max_length=765, db_column='titre_actualite')
220 texte = models.TextField(db_column='texte_actualite')
221 url = models.CharField(max_length=765, db_column='url_actualite')
d15017b2 222 date = models.DateField(db_column='date_actualite')
db2999fa
EMS
223 visible = models.BooleanField(db_column='visible_actualite', default=False)
224 ancienid = models.IntegerField(db_column='ancienId_actualite', blank=True, null=True)
011804bb 225 source = models.ForeignKey(SourceActualite, related_name='actualites')
3a45eb64 226 disciplines = models.ManyToManyField(Discipline, blank=True, related_name="actualites")
a5f76eb4 227 regions = models.ManyToManyField(Region, blank=True, related_name="actualites", verbose_name='régions')
6ef8ead4 228
2f9c4d6c 229 objects = ActualiteManager()
c59dba82 230 all_objects = models.Manager()
2f9c4d6c 231
d15017b2
CR
232 class Meta:
233 db_table = u'actualite'
7020ea3d 234 ordering = ["-date"]
92c7413b 235
264a3210
EMS
236 def __unicode__ (self):
237 return "%s" % (self.titre)
238
230671ff
EMS
239 def get_absolute_url(self):
240 return reverse('actualite', kwargs={'id': self.id})
241
264a3210
EMS
242 def assigner_disciplines(self, disciplines):
243 self.disciplines.add(*disciplines)
244
245 def assigner_regions(self, regions):
246 self.regions.add(*regions)
247
5212238e 248# Agenda
4101cfc0 249
5212238e 250class EvenementQuerySet(SEPQuerySet):
4101cfc0 251
5212238e
EMS
252 def filter_type(self, type):
253 return self.filter(type=type)
c1b134f8 254
5212238e
EMS
255 def filter_debut(self, min=None, max=None):
256 qs = self
257 if min:
258 qs = qs.filter(debut__gte=min)
259 if max:
260 qs = qs.filter(debut__lt=max+datetime.timedelta(days=1))
261 return qs
c1b134f8 262
5212238e 263class EvenementSphinxQuerySet(SEPSphinxQuerySet):
4101cfc0 264
5212238e 265 def __init__(self, model=None):
4134daa0 266 SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_evenements',
5212238e 267 weights=dict(titre=3))
116db1fd 268
5212238e
EMS
269 def filter_type(self, type):
270 return self.add_to_query('@type "%s"' % type)
271
272 def filter_debut(self, min=None, max=None):
230671ff 273 return self._filter_date('debut', min=min, max=max)
7bbf600c 274
5212238e 275class EvenementManager(SEPManager):
5212238e 276
5212238e
EMS
277 def get_query_set(self):
278 return EvenementQuerySet(self.model).filter(approuve=True)
279
280 def get_sphinx_query_set(self):
281 return EvenementSphinxQuerySet(self.model).order_by('-debut')
282
283 def filter_type(self, type):
284 return self.get_query_set().filter_type(type)
285
286 def filter_debut(self, min=None, max=None):
287 return self.get_query_set().filter_debut(min=min, max=max)
bae03b7b 288
1719bf4e 289def build_time_zone_choices(pays=None):
1719bf4e
EMS
290 timezones = pytz.country_timezones[pays] if pays else pytz.common_timezones
291 result = []
86983865 292 now = datetime.datetime.now()
1719bf4e 293 for tzname in timezones:
86983865
EMS
294 tz = pytz.timezone(tzname)
295 fr_name = get_timezone_name(tz, locale='fr_FR')
86983865
EMS
296 offset = tz.utcoffset(now)
297 seconds = offset.seconds + offset.days * 86400
298 (hours, minutes) = divmod(seconds // 60, 60)
299 offset_str = 'UTC%+d:%d' % (hours, minutes) if minutes else 'UTC%+d' % hours
1719bf4e
EMS
300 result.append((seconds, tzname, '%s - %s' % (offset_str, fr_name)))
301 result.sort()
302 return [(x[1], x[2]) for x in result]
86983865 303
92c7413b 304class Evenement(models.Model):
7bbf600c
EMS
305 TYPE_CHOICES = ((u'Colloque', u'Colloque'),
306 (u'Conférence', u'Conférence'),
307 (u'Appel à contribution', u'Appel à contribution'),
308 (u'Journée d\'étude', u'Journée d\'étude'),
ec81ec66 309 (u'Autre', u'Autre'))
86983865
EMS
310 TIME_ZONE_CHOICES = build_time_zone_choices()
311
74b087e5 312 uid = models.CharField(max_length=255, default=str(uuid.uuid1()))
a5f76eb4 313 approuve = models.BooleanField(default=False, verbose_name=u'approuvé')
92c7413b
CR
314 titre = models.CharField(max_length=255)
315 discipline = models.ForeignKey('Discipline', related_name = "discipline",
316 blank = True, null = True)
a5f76eb4
EMS
317 discipline_secondaire = models.ForeignKey('Discipline', related_name="discipline_secondaire",
318 verbose_name=u"discipline secondaire",
319 blank=True, null=True)
74b087e5 320 mots_cles = models.TextField('Mots-Clés', blank=True, null=True)
7bbf600c 321 type = models.CharField(max_length=255, choices=TYPE_CHOICES)
731ef7ab
EMS
322 adresse = models.TextField()
323 ville = models.CharField(max_length=100)
324 pays = models.ForeignKey(Pays, null=True, related_name='evenements')
74b087e5
EMS
325 debut = models.DateTimeField(default=datetime.datetime.now)
326 fin = models.DateTimeField(default=datetime.datetime.now)
86983865 327 fuseau = models.CharField(max_length=100, choices=TIME_ZONE_CHOICES, verbose_name='fuseau horaire')
fe254ccc 328 description = models.TextField()
731ef7ab
EMS
329 contact = models.TextField(null=True) # champ obsolète
330 prenom = models.CharField('prénom', max_length=100)
331 nom = models.CharField(max_length=100)
332 courriel = models.EmailField()
74b087e5
EMS
333 url = models.CharField(max_length=255, blank=True, null=True)
334 piece_jointe = models.FileField(upload_to='agenda/pj', blank=True, verbose_name='pièce jointe')
fe254ccc
EMS
335 regions = models.ManyToManyField(Region, blank=True, related_name="evenements", verbose_name='régions additionnelles',
336 help_text="On considère d'emblée que l'évènement se déroule dans la région "
337 "dans laquelle se trouve le pays indiqué plus haut. Il est possible "
338 "de désigner ici des régions additionnelles.")
92c7413b 339
4101cfc0 340 objects = EvenementManager()
c59dba82 341 all_objects = models.Manager()
4101cfc0
EMS
342
343 class Meta:
344 ordering = ['-debut']
fe254ccc
EMS
345 verbose_name = u'évènement'
346 verbose_name_plural = u'évènements'
4101cfc0 347
230671ff 348 def __unicode__(self):
020f79a9 349 return "[%s] %s" % (self.uid, self.titre)
350
230671ff
EMS
351 def get_absolute_url(self):
352 return reverse('evenement', kwargs={'id': self.id})
353
8dfe5efa
EMS
354 def duration_display(self):
355 delta = self.fin - self.debut
356 minutes, seconds = divmod(delta.seconds, 60)
357 hours, minutes = divmod(minutes, 60)
358 days = delta.days
359 parts = []
360 if days == 1:
361 parts.append('1 jour')
362 elif days > 1:
363 parts.append('%d jours' % days)
364 if hours == 1:
365 parts.append('1 heure')
366 elif hours > 1:
367 parts.append('%d heures' % hours)
368 if minutes == 1:
369 parts.append('1 minute')
370 elif minutes > 1:
371 parts.append('%d minutes' % minutes)
372 return ' '.join(parts)
373
27fe0d70
EMS
374 def piece_jointe_display(self):
375 return self.piece_jointe and os.path.basename(self.piece_jointe.name)
376
fe254ccc
EMS
377 def courriel_display(self):
378 return self.courriel.replace(u'@', u' (à) ')
379
73309469 380 def clean(self):
381 from django.core.exceptions import ValidationError
382 if self.debut > self.fin:
383 raise ValidationError('La date de fin ne doit pas être antérieure à la date de début')
384
b7a741ad 385 def save(self, *args, **kwargs):
386 """Sauvegarde l'objet dans django et le synchronise avec caldav s'il a été
387 approuvé"""
731ef7ab 388 self.contact = '' # Vider ce champ obsolète à la première occasion...
73309469 389 self.clean()
b7a741ad 390 super(Evenement, self).save(*args, **kwargs)
acd5cd8f 391 self.update_vevent()
b7a741ad 392
393 # methodes de commnunications avec CALDAV
394 def as_ical(self,):
395 """Retourne l'evenement django sous forme d'objet icalendar"""
396 cal = vobject.iCalendar()
397 cal.add('vevent')
398
399 # fournit son propre uid
7f56d0d4 400 if self.uid in [None, ""]:
b7a741ad 401 self.uid = str(uuid.uuid1())
402
403 cal.vevent.add('uid').value = self.uid
404
405 cal.vevent.add('summary').value = self.titre
406
407 if self.mots_cles is None:
408 kw = []
409 else:
410 kw = self.mots_cles.split(",")
411
412 try:
413 kw.append(self.discipline.nom)
414 kw.append(self.discipline_secondaire.nom)
415 kw.append(self.type)
416 except: pass
417
79b400f0 418 kw = [x.strip() for x in kw if len(x.strip()) > 0 and x is not None]
b7a741ad 419 for k in kw:
420 cal.vevent.add('x-auf-keywords').value = k
421
422 description = self.description
423 if len(kw) > 0:
424 if len(self.description) > 0:
425 description += "\n"
028f548f 426 description += u"Mots-clés: " + ", ".join(kw)
b7a741ad 427
7f214e0f
EMS
428 cal.vevent.add('dtstart').value = combine(self.debut, pytz.timezone(self.fuseau))
429 cal.vevent.add('dtend').value = combine(self.fin, pytz.timezone(self.fuseau))
b7a741ad 430 cal.vevent.add('created').value = combine(datetime.datetime.now(), "UTC")
431 cal.vevent.add('dtstamp').value = combine(datetime.datetime.now(), "UTC")
79b400f0 432 if len(description) > 0:
b7a741ad 433 cal.vevent.add('description').value = description
434 if len(self.contact) > 0:
435 cal.vevent.add('contact').value = self.contact
436 if len(self.url) > 0:
437 cal.vevent.add('url').value = self.url
fe254ccc 438 cal.vevent.add('location').value = ', '.join([x for x in [self.adresse, self.ville, self.pays.nom] if x])
74b087e5
EMS
439 if self.piece_jointe:
440 url = self.piece_jointe.url
441 if not url.startswith('http://'):
442 url = SITE_ROOT_URL + url
443 cal.vevent.add('attach').value = url
b7a741ad 444 return cal
445
446 def update_vevent(self,):
447 """Essaie de créer l'évènement sur le serveur ical.
448 En cas de succès, l'évènement local devient donc inactif et approuvé"""
449 try:
450 if self.approuve:
451 event = self.as_ical()
452 client = caldav.DAVClient(CALENDRIER_URL)
453 cal = caldav.Calendar(client, url = CALENDRIER_URL)
454 e = caldav.Event(client, parent = cal, data = event.serialize(), id=self.uid)
455 e.save()
456 except:
457 self.approuve = False
458
459 def delete_vevent(self,):
460 """Supprime l'evenement sur le serveur caldav"""
461 try:
462 if self.approuve:
463 event = self.as_ical()
464 client = caldav.DAVClient(CALENDRIER_URL)
465 cal = caldav.Calendar(client, url = CALENDRIER_URL)
466 e = cal.event(self.uid)
467 e.delete()
468 except error.NotFoundError:
469 pass
470
264a3210
EMS
471 def assigner_regions(self, regions):
472 self.regions.add(*regions)
473
474 def assigner_disciplines(self, disciplines):
475 if len(disciplines) == 1:
476 if self.discipline:
477 self.discipline_secondaire = disciplines[0]
478 else:
479 self.discipline = disciplines[0]
480 elif len(disciplines) >= 2:
481 self.discipline = disciplines[0]
482 self.discipline_secondaire = disciplines[1]
483
b7a741ad 484def delete_vevent(sender, instance, *args, **kwargs):
5212238e
EMS
485 # Surcharge du comportement de suppression
486 # La méthode de connexion par signals est préférable à surcharger la méthode delete()
487 # car dans le cas de la suppression par lots, cell-ci n'est pas invoquée
b7a741ad 488 instance.delete_vevent()
5212238e 489pre_delete.connect(delete_vevent, sender=Evenement)
b7a741ad 490
5212238e 491# Ressources
b7a741ad 492
d972b61d 493class ListSet(models.Model):
494 spec = models.CharField(primary_key = True, max_length = 255)
495 name = models.CharField(max_length = 255)
496 server = models.CharField(max_length = 255)
9eda5d6c 497 validated = models.BooleanField(default = True)
d972b61d 498
10d37e44 499 def __unicode__(self,):
500 return self.name
501
230671ff
EMS
502class RecordQuerySet(SEPQuerySet):
503
504 def filter_modified(self, min=None, max=None):
505 return self._filter_date(self, 'modified', min=min, max=max)
506
5212238e 507class RecordSphinxQuerySet(SEPSphinxQuerySet):
f153be1b 508
5212238e 509 def __init__(self, model=None):
4134daa0 510 SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_ressources',
5212238e 511 weights=dict(title=3))
c1b134f8 512
230671ff
EMS
513 def filter_modified(self, min=None, max=None):
514 return self._filter_date(self, 'modified', min=min, max=max)
515
5212238e 516class RecordManager(SEPManager):
f12cc7fb 517
5212238e 518 def get_query_set(self):
f153be1b
EMS
519 """Ne garder que les ressources validées et qui sont soit dans aucun
520 listset ou au moins dans un listset validé."""
230671ff 521 qs = RecordQuerySet(self.model)
5212238e 522 qs = qs.filter(validated=True)
82f25472
EMS
523 qs = qs.filter(Q(listsets__isnull=True) | Q(listsets__validated=True))
524 return qs.distinct()
f153be1b 525
5212238e
EMS
526 def get_sphinx_query_set(self):
527 return RecordSphinxQuerySet(self.model)
77b0fac0 528
230671ff
EMS
529 def filter_modified(self, min=None, max=None):
530 return self.get_query_set().filter_modified(min=min, max=max)
531
0cc5f772 532class Record(models.Model):
23b5b3d5 533
534 #fonctionnement interne
0cc5f772 535 id = models.AutoField(primary_key = True)
a5f76eb4 536 server = models.CharField(max_length = 255, verbose_name=u'serveur')
23b5b3d5 537 last_update = models.CharField(max_length = 255)
538 last_checksum = models.CharField(max_length = 255)
a5f76eb4 539 validated = models.BooleanField(default=True, verbose_name=u'validé')
23b5b3d5 540
541 #OAI
18dbd2cf
EMS
542 title = models.TextField(null=True, blank=True, verbose_name=u'titre')
543 creator = models.TextField(null=True, blank=True, verbose_name=u'auteur')
544 description = models.TextField(null=True, blank=True)
545 modified = models.CharField(max_length=255, null=True, blank=True)
23b5b3d5 546 identifier = models.CharField(max_length = 255, null = True, blank = True, unique = True)
547 uri = models.CharField(max_length = 255, null = True, blank = True, unique = True)
548 source = models.TextField(null = True, blank = True)
549 contributor = models.TextField(null = True, blank = True)
18dbd2cf 550 subject = models.TextField(null=True, blank=True, verbose_name='sujet')
23b5b3d5 551 publisher = models.TextField(null = True, blank = True)
552 type = models.TextField(null = True, blank = True)
553 format = models.TextField(null = True, blank = True)
554 language = models.TextField(null = True, blank = True)
da9020f3 555
c88d78dc 556 listsets = models.ManyToManyField(ListSet, null = True, blank = True)
d972b61d 557
da9020f3 558 #SEP 2 (aucune données récoltées)
23b5b3d5 559 alt_title = models.TextField(null = True, blank = True)
560 abstract = models.TextField(null = True, blank = True)
561 creation = models.CharField(max_length = 255, null = True, blank = True)
562 issued = models.CharField(max_length = 255, null = True, blank = True)
563 isbn = models.TextField(null = True, blank = True)
564 orig_lang = models.TextField(null = True, blank = True)
da9020f3 565
566 # Metadata AUF multivaluées
a342f93a
EMS
567 disciplines = models.ManyToManyField(Discipline, blank=True)
568 thematiques = models.ManyToManyField(Thematique, blank=True, verbose_name='thématiques')
569 pays = models.ManyToManyField(Pays, blank=True)
570 regions = models.ManyToManyField(Region, blank=True, verbose_name='régions')
0cc5f772 571
5212238e 572 # Managers
da44ce68 573 objects = RecordManager()
c59dba82 574 all_objects = models.Manager()
da44ce68 575
18dbd2cf
EMS
576 class Meta:
577 verbose_name = 'ressource'
578
264a3210
EMS
579 def __unicode__(self):
580 return "[%s] %s" % (self.server, self.title)
581
230671ff
EMS
582 def get_absolute_url(self):
583 return reverse('ressource', kwargs={'id': self.id})
584
264a3210 585 def getServeurURL(self):
f98ad449 586 """Retourne l'URL du serveur de provenance"""
587 return RESOURCES[self.server]['url']
588
264a3210 589 def est_complet(self):
6d885e0c 590 """teste si le record à toutes les données obligatoires"""
591 return self.disciplines.count() > 0 and \
592 self.thematiques.count() > 0 and \
593 self.pays.count() > 0 and \
594 self.regions.count() > 0
595
264a3210
EMS
596 def assigner_regions(self, regions):
597 self.regions.add(*regions)
da9020f3 598
264a3210
EMS
599 def assigner_disciplines(self, disciplines):
600 self.disciplines.add(*disciplines)
264a3210 601
6d885e0c 602class Serveur(models.Model):
b7a741ad 603 """Identification d'un serveur d'ou proviennent les références"""
6d885e0c 604 nom = models.CharField(primary_key = True, max_length = 255)
605
606 def __unicode__(self,):
607 return self.nom
608
609 def conf_2_db(self,):
610 for k in RESOURCES.keys():
611 s, created = Serveur.objects.get_or_create(nom=k)
612 s.nom = k
613 s.save()
614
615class Profile(models.Model):
616 user = models.ForeignKey(User, unique=True)
617 serveurs = models.ManyToManyField(Serveur, null = True, blank = True)
0cc5f772
CR
618
619class HarvestLog(models.Model):
23b5b3d5 620 context = models.CharField(max_length = 255)
621 name = models.CharField(max_length = 255)
0cc5f772 622 date = models.DateTimeField(auto_now = True)
23b5b3d5 623 added = models.IntegerField(null = True, blank = True)
624 updated = models.IntegerField(null = True, blank = True)
a85ba76e 625 processed = models.IntegerField(null = True, blank = True)
23b5b3d5 626 record = models.ForeignKey(Record, null = True, blank = True)
627
628 @staticmethod
629 def add(message):
630 logger = HarvestLog()
631 if message.has_key('record_id'):
d566e9c1 632 message['record'] = Record.all_objects.get(id=message['record_id'])
23b5b3d5 633 del(message['record_id'])
634
635 for k,v in message.items():
636 setattr(logger, k, v)
637 logger.save()
f09bc1c6
EMS
638
639# Pages statiques
640
641class PageStatique(models.Model):
20430b8a 642 id = models.CharField(max_length=32, primary_key=True)
f09bc1c6
EMS
643 titre = models.CharField(max_length=100)
644 contenu = models.TextField()
645
646 class Meta:
647 verbose_name_plural = 'pages statiques'
fdcf5874
EMS
648
649# Recherches
650
651class GlobalSearchResults(object):
652
653 def __init__(self, actualites=None, appels=None, evenements=None,
654 ressources=None, chercheurs=None, sites=None, sites_auf=None):
655 self.actualites = actualites
656 self.appels = appels
657 self.evenements = evenements
658 self.ressources = ressources
659 self.chercheurs = chercheurs
660 self.sites = sites
661 self.sites_auf = sites_auf
662
663class Search(models.Model):
664 user = models.ForeignKey(User, editable=False)
665 content_type = models.ForeignKey(ContentType, editable=False)
666 nom = models.CharField(max_length=100, verbose_name="nom de la recherche")
667 q = models.CharField(max_length=100, blank=True, verbose_name="rechercher dans tous les champs")
668 discipline = models.ForeignKey(Discipline, blank=True, null=True)
669 region = models.ForeignKey(Region, blank=True, null=True, verbose_name='région',
670 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.")
671
672 def query_string(self):
673 params = dict()
674 for field in self._meta.fields:
675 if field.name in ['id', 'user', 'nom', 'search_ptr', 'content_type']:
676 continue
677 value = getattr(self, field.column)
678 if value:
679 if isinstance(value, datetime.date):
680 params[field.name] = value.strftime('%d/%m/%Y')
681 else:
682 params[field.name] = value
683 return urlencode(params)
684
685 class Meta:
686 verbose_name = 'recherche transversale'
687 verbose_name_plural = "recherches transversales"
688
689 def save(self):
690 if (not self.content_type_id):
691 self.content_type = ContentType.objects.get_for_model(self.__class__)
692 super(Search, self).save()
693
694 def as_leaf_class(self):
695 content_type = self.content_type
696 model = content_type.model_class()
697 if(model == Search):
698 return self
699 return model.objects.get(id=self.id)
700
701 def run(self):
702 from chercheurs.models import Chercheur
703 from sitotheque.models import Site
704
705 results = object()
706 actualites = Actualite.objects
707 evenements = Evenement.objects
708 ressources = Record.objects
709 chercheurs = Chercheur.objects
710 sites = Site.objects
711 if self.q:
712 actualites = actualites.search(self.q)
713 evenements = evenements.search(self.q)
714 ressources = ressources.search(self.q)
715 chercheurs = chercheurs.search(self.q)
716 sites = sites.search(self.q)
717 if self.discipline:
718 actualites = actualites.filter_discipline(self.discipline)
719 evenements = evenements.filter_discipline(self.discipline)
720 ressources = ressources.filter_discipline(self.discipline)
721 chercheurs = chercheurs.filter_discipline(self.discipline)
722 sites = sites.filter_discipline(self.discipline)
723 if self.region:
724 actualites = actualites.filter_region(self.region)
725 evenements = evenements.filter_region(self.region)
726 ressources = ressources.filter_region(self.region)
727 chercheurs = chercheurs.filter_region(self.region)
728 sites = sites.filter_region(self.region)
729 try:
730 sites_auf = google_search(0, self.q)['results']
731 except:
732 sites_auf = []
733
734 return GlobalSearchResults(
735 actualites=actualites.order_by('-date').filter_type('actu'),
736 appels=actualites.order_by('-date').filter_type('appels'),
737 evenements=evenements.order_by('-debut'),
738 ressources=ressources.order_by('-id'),
739 chercheurs=chercheurs.order_by('-date_modification'),
740 sites=sites.order_by('-date_maj'),
741 sites_auf=sites_auf
742 )
743
744 def url(self):
745 url = ''
746 if self.discipline:
747 url += '/discipline/%d' % self.discipline.id
748 if self.region:
749 url += '/region/%d' % self.region.id
750 url += '/recherche/'
751 if self.q:
752 url += '?' + urlencode({'q': self.q})
753 return url
754
755class RessourceSearch(Search):
756 auteur = models.CharField(max_length=100, blank=True, verbose_name="auteur ou contributeur")
757 titre = models.CharField(max_length=100, blank=True)
758 sujet = models.CharField(max_length=100, blank=True)
759 publisher = models.CharField(max_length=100, blank=True, verbose_name="éditeur")
760
761 class Meta:
762 verbose_name = 'recherche de ressources'
763 verbose_name_plural = "recherches de ressources"
764
765 def run(self):
766 results = Record.objects
767 if self.q:
768 results = results.search(self.q)
769 if self.auteur:
770 results = results.add_to_query('@(creator,contributor) ' + self.auteur)
771 if self.titre:
772 results = results.add_to_query('@title ' + self.titre)
773 if self.sujet:
774 results = results.add_to_query('@subject ' + self.sujet)
775 if self.publisher:
776 results = results.add_to_query('@publisher ' + self.publisher)
777 if self.discipline:
778 results = results.filter_discipline(self.discipline)
779 if self.region:
780 results = results.filter_region(self.region)
781 if not self.q:
782 """Montrer les résultats les plus récents si on n'a pas fait
783 une recherche par mots-clés."""
230671ff 784 results = results.order_by('-modified')
fdcf5874
EMS
785 return results.all()
786
787 def url(self):
788 qs = self.query_string()
789 return reverse('ressources') + ('?' + qs if qs else '')
790
791class ActualiteSearchBase(Search):
792 date_min = models.DateField(blank=True, null=True, verbose_name="depuis le")
793 date_max = models.DateField(blank=True, null=True, verbose_name="jusqu'au")
794
795 class Meta:
796 abstract = True
797
798 def run(self):
799 results = Actualite.objects
800 if self.q:
801 results = results.search(self.q)
802 if self.discipline:
803 results = results.filter_discipline(self.discipline)
804 if self.region:
805 results = results.filter_region(self.region)
806 if self.date_min:
807 results = results.filter_date(min=self.date_min)
808 if self.date_max:
809 results = results.filter_date(max=self.date_max)
810 return results.all()
811
812class ActualiteSearch(ActualiteSearchBase):
813
814 class Meta:
815 verbose_name = "recherche d'actualités"
816 verbose_name_plural = "recherches d'actualités"
817
818 def run(self):
819 return super(ActualiteSearch, self).run().filter_type('actu')
820
821 def url(self):
822 qs = self.query_string()
823 return reverse('actualites') + ('?' + qs if qs else '')
824
825class AppelSearch(ActualiteSearchBase):
826
827 class Meta:
828 verbose_name = "recherche d'appels d'offres"
829 verbose_name_plural = "recherches d'appels d'offres"
830
831 def run(self):
832 return super(AppelSearch, self).run().filter_type('appel')
833
834 def url(self):
835 qs = self.query_string()
836 return reverse('appels') + ('?' + qs if qs else '')
837
838class EvenementSearch(Search):
839 titre = models.CharField(max_length=100, blank=True, verbose_name="Intitulé")
840 type = models.CharField(max_length=100, blank=True, choices=Evenement.TYPE_CHOICES)
841 date_min = models.DateField(blank=True, null=True, verbose_name="depuis le")
842 date_max = models.DateField(blank=True, null=True, verbose_name="jusqu'au")
843
844 class Meta:
845 verbose_name = "recherche d'évènements"
846 verbose_name_plural = "recherches d'évènements"
847
848 def run(self):
849 results = Evenement.objects
850 if self.q:
851 results = results.search(self.q)
852 if self.titre:
853 results = results.add_to_query('@titre ' + self.titre)
854 if self.discipline:
855 results = results.filter_discipline(self.discipline)
856 if self.region:
857 results = results.filter_region(self.region)
858 if self.type:
859 results = results.filter_type(self.type)
860 if self.date_min:
861 results = results.filter_debut(min=self.date_min)
862 if self.date_max:
863 results = results.filter_debut(max=self.date_max)
864 return results.all()
865
866 def url(self):
867 qs = self.query_string()
868 return reverse('agenda') + ('?' + qs if qs else '')
869