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 | |
9 | import uuid | |
10 | import vobject | |
86983865 | 11 | from babel.dates import get_timezone_name |
6d885e0c | 12 | from django.contrib.auth.models import User |
d15017b2 | 13 | from django.db import models |
15261361 | 14 | from django.db.models import Q, Max |
b7a741ad | 15 | from django.db.models.signals import pre_delete |
116db1fd | 16 | from auf_savoirs_en_partage.backend_config import RESOURCES |
da9020f3 | 17 | from savoirs.globals import META |
74b087e5 | 18 | from settings import CALENDRIER_URL, SITE_ROOT_URL |
116db1fd EMS |
19 | from datamaster_modeles.models import Thematique, Pays, Region |
20 | from lib.calendrier import combine | |
21 | from caldav.lib import error | |
d15017b2 | 22 | |
15261361 EMS |
23 | class RandomQuerySetMixin(object): |
24 | """Mixin pour les modèles. | |
25 | ||
26 | ORDER BY RAND() est très lent sous MySQL. On a besoin d'une autre | |
27 | méthode pour récupérer des objets au hasard. | |
28 | """ | |
29 | ||
30 | def random(self, n=1): | |
31 | """Récupère aléatoirement un nombre donné d'objets.""" | |
bae03b7b EMS |
32 | count = self.count() |
33 | positions = random.sample(xrange(count), min(n, count)) | |
34 | return [self[p] for p in positions] | |
15261361 | 35 | |
d15017b2 CR |
36 | class Discipline(models.Model): |
37 | id = models.IntegerField(primary_key=True, db_column='id_discipline') | |
38 | nom = models.CharField(max_length=765, db_column='nom_discipline') | |
6ef8ead4 CR |
39 | |
40 | def __unicode__ (self): | |
92c7413b | 41 | return self.nom |
6ef8ead4 | 42 | |
d15017b2 CR |
43 | class Meta: |
44 | db_table = u'discipline' | |
45 | ordering = ["nom",] | |
46 | ||
79c398f6 CR |
47 | class SourceActualite(models.Model): |
48 | nom = models.CharField(max_length=255) | |
db2999fa | 49 | url = models.CharField(max_length=255, verbose_name='URL') |
ccbc4363 | 50 | |
51 | def __unicode__(self,): | |
52 | return u"%s" % self.nom | |
79c398f6 | 53 | |
db2999fa EMS |
54 | class Meta: |
55 | verbose_name = u'fil RSS syndiqué' | |
56 | verbose_name_plural = u'fils RSS syndiqués' | |
57 | ||
58 | def update(self): | |
59 | """Mise à jour du fil RSS.""" | |
60 | feed = feedparser.parse(self.url) | |
61 | for entry in feed.entries: | |
62 | if Actualite.all_objects.filter(url=entry.link).count() == 0: | |
63 | ts = entry.updated_parsed | |
64 | date = datetime.date(ts.tm_year, ts.tm_mon, ts.tm_mday) | |
65 | a = self.actualites.create(titre=entry.title, | |
66 | texte=entry.summary_detail.value, | |
67 | url=entry.link, date=date) | |
68 | ||
116db1fd EMS |
69 | class ActualiteManager(models.Manager): |
70 | ||
71 | def get_query_set(self): | |
c59dba82 | 72 | return ActualiteQuerySet(self.model).filter(visible=True) |
2f9c4d6c | 73 | |
116db1fd EMS |
74 | def search(self, text): |
75 | return self.get_query_set().search(text) | |
da44ce68 | 76 | |
116db1fd EMS |
77 | def filter_region(self, region): |
78 | return self.get_query_set().filter_region(region) | |
c1b134f8 | 79 | |
116db1fd EMS |
80 | def filter_discipline(self, discipline): |
81 | return self.get_query_set().filter_discipline(discipline) | |
c1b134f8 | 82 | |
116db1fd | 83 | class ActualiteQuerySet(models.query.QuerySet, RandomQuerySetMixin): |
2f9c4d6c | 84 | |
116db1fd EMS |
85 | def search(self, text): |
86 | q = None | |
87 | for word in text.split(): | |
88 | part = (Q(titre__icontains=word) | Q(texte__icontains=word) | | |
89 | Q(regions__nom__icontains=word) | Q(disciplines__nom__icontains=word)) | |
90 | if q is None: | |
91 | q = part | |
92 | else: | |
93 | q = q & part | |
94 | return self.filter(q).distinct() if q is not None else self | |
2f9c4d6c | 95 | |
116db1fd EMS |
96 | def filter_discipline(self, discipline): |
97 | """Ne conserve que les actualités dans la discipline donnée. | |
98 | ||
99 | Si ``disicipline`` est None, ce filtre n'a aucun effet.""" | |
100 | if discipline is None: | |
101 | return self | |
102 | if not isinstance(discipline, Discipline): | |
103 | discipline = Discipline.objects.get(pk=discipline) | |
104 | return self.filter(Q(disciplines=discipline) | | |
105 | Q(titre__icontains=discipline.nom) | | |
106 | Q(texte__icontains=discipline.nom)).distinct() | |
bae03b7b | 107 | |
116db1fd EMS |
108 | def filter_region(self, region): |
109 | """Ne conserve que les actualités dans la région donnée. | |
110 | ||
111 | Si ``region`` est None, ce filtre n'a aucun effet.""" | |
112 | if region is None: | |
113 | return self | |
114 | if not isinstance(region, Region): | |
115 | region = Region.objects.get(pk=region) | |
116 | return self.filter(Q(regions=region) | | |
117 | Q(titre__icontains=region.nom) | | |
118 | Q(texte__icontains=region.nom)).distinct() | |
bae03b7b | 119 | |
d15017b2 | 120 | class Actualite(models.Model): |
4f262f90 | 121 | id = models.AutoField(primary_key=True, db_column='id_actualite') |
d15017b2 CR |
122 | titre = models.CharField(max_length=765, db_column='titre_actualite') |
123 | texte = models.TextField(db_column='texte_actualite') | |
124 | url = models.CharField(max_length=765, db_column='url_actualite') | |
d15017b2 | 125 | date = models.DateField(db_column='date_actualite') |
db2999fa EMS |
126 | visible = models.BooleanField(db_column='visible_actualite', default=False) |
127 | ancienid = models.IntegerField(db_column='ancienId_actualite', blank=True, null=True) | |
128 | source = models.ForeignKey(SourceActualite, blank=True, null=True) | |
3a45eb64 | 129 | disciplines = models.ManyToManyField(Discipline, blank=True, related_name="actualites") |
a5f76eb4 | 130 | regions = models.ManyToManyField(Region, blank=True, related_name="actualites", verbose_name='régions') |
6ef8ead4 | 131 | |
2f9c4d6c | 132 | objects = ActualiteManager() |
c59dba82 | 133 | all_objects = models.Manager() |
2f9c4d6c | 134 | |
d15017b2 CR |
135 | class Meta: |
136 | db_table = u'actualite' | |
7020ea3d | 137 | ordering = ["-date"] |
92c7413b | 138 | |
264a3210 EMS |
139 | def __unicode__ (self): |
140 | return "%s" % (self.titre) | |
141 | ||
142 | def assigner_disciplines(self, disciplines): | |
143 | self.disciplines.add(*disciplines) | |
144 | ||
145 | def assigner_regions(self, regions): | |
146 | self.regions.add(*regions) | |
147 | ||
116db1fd | 148 | class EvenementManager(models.Manager): |
4101cfc0 | 149 | |
116db1fd | 150 | def get_query_set(self): |
c59dba82 | 151 | return EvenementQuerySet(self.model).filter(approuve=True) |
4101cfc0 | 152 | |
116db1fd EMS |
153 | def search(self, text): |
154 | return self.get_query_set().search(text) | |
4101cfc0 | 155 | |
116db1fd EMS |
156 | def filter_region(self, region): |
157 | return self.get_query_set().filter_region(region) | |
c1b134f8 | 158 | |
116db1fd EMS |
159 | def filter_discipline(self, discipline): |
160 | return self.get_query_set().filter_discipline(discipline) | |
c1b134f8 | 161 | |
116db1fd | 162 | class EvenementQuerySet(models.query.QuerySet, RandomQuerySetMixin): |
4101cfc0 | 163 | |
116db1fd EMS |
164 | def search(self, text): |
165 | q = None | |
166 | for word in text.split(): | |
167 | part = (Q(titre__icontains=word) | | |
168 | Q(mots_cles__icontains=word) | | |
169 | Q(discipline__nom__icontains=word) | | |
170 | Q(discipline_secondaire__nom__icontains=word) | | |
171 | Q(type__icontains=word) | | |
172 | Q(lieu__icontains=word) | | |
173 | Q(description__icontains=word) | | |
174 | Q(contact__icontains=word) | | |
175 | Q(regions__nom__icontains=word)) | |
176 | if q is None: | |
177 | q = part | |
178 | else: | |
179 | q = q & part | |
180 | return self.filter(q).distinct() if q is not None else self | |
181 | ||
182 | def search_titre(self, text): | |
7bbf600c | 183 | qs = self |
116db1fd EMS |
184 | for word in text.split(): |
185 | qs = qs.filter(titre__icontains=word) | |
7bbf600c EMS |
186 | return qs |
187 | ||
116db1fd EMS |
188 | def filter_discipline(self, discipline): |
189 | """Ne conserve que les évènements dans la discipline donnée. | |
190 | ||
191 | Si ``disicipline`` est None, ce filtre n'a aucun effet.""" | |
192 | if discipline is None: | |
193 | return self | |
194 | if not isinstance(discipline, Discipline): | |
195 | discipline = Discipline.objects.get(pk=discipline) | |
196 | return self.filter(Q(discipline=discipline) | | |
197 | Q(discipline_secondaire=discipline) | | |
198 | Q(titre__icontains=discipline.nom) | | |
199 | Q(mots_cles__icontains=discipline.nom) | | |
200 | Q(description__icontains=discipline.nom)) | |
5212238e | 201 | |
116db1fd EMS |
202 | def filter_region(self, region): |
203 | """Ne conserve que les évènements dans la région donnée. | |
204 | ||
205 | Si ``region`` est None, ce filtre n'a aucun effet.""" | |
206 | if region is None: | |
207 | return self | |
208 | if not isinstance(region, Region): | |
209 | region = Region.objects.get(pk=region) | |
210 | return self.filter(Q(regions=region) | | |
211 | Q(titre__icontains=region.nom) | | |
212 | Q(mots_cles__icontains=region.nom) | | |
213 | Q(description__icontains=region.nom) | | |
1719bf4e | 214 | Q(pays__region=region) | |
116db1fd | 215 | Q(lieu__icontains=region.nom)).distinct() |
bae03b7b | 216 | |
1719bf4e | 217 | def build_time_zone_choices(pays=None): |
86983865 | 218 | fr_names = set() |
1719bf4e EMS |
219 | timezones = pytz.country_timezones[pays] if pays else pytz.common_timezones |
220 | result = [] | |
86983865 | 221 | now = datetime.datetime.now() |
1719bf4e | 222 | for tzname in timezones: |
86983865 EMS |
223 | tz = pytz.timezone(tzname) |
224 | fr_name = get_timezone_name(tz, locale='fr_FR') | |
225 | if fr_name in fr_names: | |
226 | continue | |
227 | fr_names.add(fr_name) | |
228 | offset = tz.utcoffset(now) | |
229 | seconds = offset.seconds + offset.days * 86400 | |
230 | (hours, minutes) = divmod(seconds // 60, 60) | |
231 | offset_str = 'UTC%+d:%d' % (hours, minutes) if minutes else 'UTC%+d' % hours | |
1719bf4e EMS |
232 | result.append((seconds, tzname, '%s - %s' % (offset_str, fr_name))) |
233 | result.sort() | |
234 | return [(x[1], x[2]) for x in result] | |
86983865 | 235 | |
92c7413b | 236 | class Evenement(models.Model): |
7bbf600c EMS |
237 | TYPE_CHOICES = ((u'Colloque', u'Colloque'), |
238 | (u'Conférence', u'Conférence'), | |
239 | (u'Appel à contribution', u'Appel à contribution'), | |
240 | (u'Journée d\'étude', u'Journée d\'étude'), | |
116db1fd | 241 | (None, u'Autre')) |
86983865 EMS |
242 | TIME_ZONE_CHOICES = build_time_zone_choices() |
243 | ||
74b087e5 | 244 | uid = models.CharField(max_length=255, default=str(uuid.uuid1())) |
a5f76eb4 | 245 | approuve = models.BooleanField(default=False, verbose_name=u'approuvé') |
92c7413b CR |
246 | titre = models.CharField(max_length=255) |
247 | discipline = models.ForeignKey('Discipline', related_name = "discipline", | |
248 | blank = True, null = True) | |
a5f76eb4 EMS |
249 | discipline_secondaire = models.ForeignKey('Discipline', related_name="discipline_secondaire", |
250 | verbose_name=u"discipline secondaire", | |
251 | blank=True, null=True) | |
74b087e5 | 252 | mots_cles = models.TextField('Mots-Clés', blank=True, null=True) |
7bbf600c | 253 | type = models.CharField(max_length=255, choices=TYPE_CHOICES) |
86983865 | 254 | lieu = models.TextField() |
74b087e5 EMS |
255 | debut = models.DateTimeField(default=datetime.datetime.now) |
256 | fin = models.DateTimeField(default=datetime.datetime.now) | |
1719bf4e | 257 | pays = models.ForeignKey(Pays, related_name='evenements', null=True, blank=True) |
86983865 | 258 | fuseau = models.CharField(max_length=100, choices=TIME_ZONE_CHOICES, verbose_name='fuseau horaire') |
74b087e5 EMS |
259 | description = models.TextField(blank=True, null=True) |
260 | contact = models.TextField(blank=True, null=True) | |
261 | url = models.CharField(max_length=255, blank=True, null=True) | |
262 | piece_jointe = models.FileField(upload_to='agenda/pj', blank=True, verbose_name='pièce jointe') | |
a5f76eb4 | 263 | regions = models.ManyToManyField(Region, blank=True, related_name="evenements", verbose_name='régions') |
92c7413b | 264 | |
4101cfc0 | 265 | objects = EvenementManager() |
c59dba82 | 266 | all_objects = models.Manager() |
4101cfc0 EMS |
267 | |
268 | class Meta: | |
269 | ordering = ['-debut'] | |
270 | ||
020f79a9 | 271 | def __unicode__(self,): |
272 | return "[%s] %s" % (self.uid, self.titre) | |
273 | ||
27fe0d70 EMS |
274 | def piece_jointe_display(self): |
275 | return self.piece_jointe and os.path.basename(self.piece_jointe.name) | |
276 | ||
73309469 | 277 | def clean(self): |
278 | from django.core.exceptions import ValidationError | |
279 | if self.debut > self.fin: | |
280 | raise ValidationError('La date de fin ne doit pas être antérieure à la date de début') | |
281 | ||
b7a741ad | 282 | def save(self, *args, **kwargs): |
283 | """Sauvegarde l'objet dans django et le synchronise avec caldav s'il a été | |
284 | approuvé""" | |
73309469 | 285 | self.clean() |
b7a741ad | 286 | self.update_vevent() |
287 | super(Evenement, self).save(*args, **kwargs) | |
288 | ||
289 | # methodes de commnunications avec CALDAV | |
290 | def as_ical(self,): | |
291 | """Retourne l'evenement django sous forme d'objet icalendar""" | |
292 | cal = vobject.iCalendar() | |
293 | cal.add('vevent') | |
294 | ||
295 | # fournit son propre uid | |
7f56d0d4 | 296 | if self.uid in [None, ""]: |
b7a741ad | 297 | self.uid = str(uuid.uuid1()) |
298 | ||
299 | cal.vevent.add('uid').value = self.uid | |
300 | ||
301 | cal.vevent.add('summary').value = self.titre | |
302 | ||
303 | if self.mots_cles is None: | |
304 | kw = [] | |
305 | else: | |
306 | kw = self.mots_cles.split(",") | |
307 | ||
308 | try: | |
309 | kw.append(self.discipline.nom) | |
310 | kw.append(self.discipline_secondaire.nom) | |
311 | kw.append(self.type) | |
312 | except: pass | |
313 | ||
79b400f0 | 314 | kw = [x.strip() for x in kw if len(x.strip()) > 0 and x is not None] |
b7a741ad | 315 | for k in kw: |
316 | cal.vevent.add('x-auf-keywords').value = k | |
317 | ||
318 | description = self.description | |
319 | if len(kw) > 0: | |
320 | if len(self.description) > 0: | |
321 | description += "\n" | |
028f548f | 322 | description += u"Mots-clés: " + ", ".join(kw) |
b7a741ad | 323 | |
7f214e0f EMS |
324 | cal.vevent.add('dtstart').value = combine(self.debut, pytz.timezone(self.fuseau)) |
325 | cal.vevent.add('dtend').value = combine(self.fin, pytz.timezone(self.fuseau)) | |
b7a741ad | 326 | cal.vevent.add('created').value = combine(datetime.datetime.now(), "UTC") |
327 | cal.vevent.add('dtstamp').value = combine(datetime.datetime.now(), "UTC") | |
79b400f0 | 328 | if len(description) > 0: |
b7a741ad | 329 | cal.vevent.add('description').value = description |
330 | if len(self.contact) > 0: | |
331 | cal.vevent.add('contact').value = self.contact | |
332 | if len(self.url) > 0: | |
333 | cal.vevent.add('url').value = self.url | |
334 | if len(self.lieu) > 0: | |
335 | cal.vevent.add('location').value = self.lieu | |
74b087e5 EMS |
336 | if self.piece_jointe: |
337 | url = self.piece_jointe.url | |
338 | if not url.startswith('http://'): | |
339 | url = SITE_ROOT_URL + url | |
340 | cal.vevent.add('attach').value = url | |
b7a741ad | 341 | return cal |
342 | ||
343 | def update_vevent(self,): | |
344 | """Essaie de créer l'évènement sur le serveur ical. | |
345 | En cas de succès, l'évènement local devient donc inactif et approuvé""" | |
346 | try: | |
347 | if self.approuve: | |
348 | event = self.as_ical() | |
349 | client = caldav.DAVClient(CALENDRIER_URL) | |
350 | cal = caldav.Calendar(client, url = CALENDRIER_URL) | |
351 | e = caldav.Event(client, parent = cal, data = event.serialize(), id=self.uid) | |
352 | e.save() | |
353 | except: | |
354 | self.approuve = False | |
355 | ||
356 | def delete_vevent(self,): | |
357 | """Supprime l'evenement sur le serveur caldav""" | |
358 | try: | |
359 | if self.approuve: | |
360 | event = self.as_ical() | |
361 | client = caldav.DAVClient(CALENDRIER_URL) | |
362 | cal = caldav.Calendar(client, url = CALENDRIER_URL) | |
363 | e = cal.event(self.uid) | |
364 | e.delete() | |
365 | except error.NotFoundError: | |
366 | pass | |
367 | ||
264a3210 EMS |
368 | def assigner_regions(self, regions): |
369 | self.regions.add(*regions) | |
370 | ||
371 | def assigner_disciplines(self, disciplines): | |
372 | if len(disciplines) == 1: | |
373 | if self.discipline: | |
374 | self.discipline_secondaire = disciplines[0] | |
375 | else: | |
376 | self.discipline = disciplines[0] | |
377 | elif len(disciplines) >= 2: | |
378 | self.discipline = disciplines[0] | |
379 | self.discipline_secondaire = disciplines[1] | |
380 | ||
116db1fd EMS |
381 | |
382 | # Surcharge du comportement de suppression | |
383 | # La méthode de connexion par signals est préférable à surcharger la méthode delete() | |
384 | # car dans le cas de la suppression par lots, cell-ci n'est pas invoquée | |
b7a741ad | 385 | def delete_vevent(sender, instance, *args, **kwargs): |
386 | instance.delete_vevent() | |
387 | ||
116db1fd EMS |
388 | pre_delete.connect(delete_vevent, sender = Evenement) |
389 | ||
b7a741ad | 390 | |
d972b61d | 391 | class ListSet(models.Model): |
392 | spec = models.CharField(primary_key = True, max_length = 255) | |
393 | name = models.CharField(max_length = 255) | |
394 | server = models.CharField(max_length = 255) | |
9eda5d6c | 395 | validated = models.BooleanField(default = True) |
d972b61d | 396 | |
10d37e44 | 397 | def __unicode__(self,): |
398 | return self.name | |
399 | ||
116db1fd EMS |
400 | class RecordManager(models.Manager): |
401 | ||
402 | def get_query_set(self): | |
c59dba82 | 403 | return RecordQuerySet(self.model).filter(validated=True) |
f153be1b | 404 | |
116db1fd EMS |
405 | def search(self, text): |
406 | return self.get_query_set().search(text) | |
c1b134f8 | 407 | |
116db1fd EMS |
408 | def validated(self): |
409 | return self.get_query_set().validated() | |
f12cc7fb | 410 | |
116db1fd EMS |
411 | def filter_region(self, region): |
412 | return self.get_query_set().filter_region(region) | |
413 | ||
414 | def filter_discipline(self, discipline): | |
415 | return self.get_query_set().filter_discipline(discipline) | |
416 | ||
417 | class RecordQuerySet(models.query.QuerySet, RandomQuerySetMixin): | |
418 | ||
419 | def search(self, text): | |
420 | qs = self | |
421 | words = text.split() | |
422 | ||
423 | # Ne garder que les ressources qui contiennent tous les mots | |
424 | # demandés. | |
425 | q = None | |
426 | for word in words: | |
427 | matching_pays = list(Pays.objects.filter(Q(nom__icontains=word) | Q(region__nom__icontains=word)).values_list('pk', flat=True)) | |
428 | part = (Q(title__icontains=word) | Q(description__icontains=word) | | |
429 | Q(creator__icontains=word) | Q(contributor__icontains=word) | | |
430 | Q(subject__icontains=word) | Q(disciplines__nom__icontains=word) | | |
431 | Q(regions__nom__icontains=word) | Q(pays__in=matching_pays) | | |
432 | Q(publisher__icontains=word)) | |
433 | if q is None: | |
434 | q = part | |
435 | else: | |
436 | q = q & part | |
437 | if q is not None: | |
438 | qs = qs.filter(q).distinct() | |
439 | ||
440 | # On donne un point pour chaque mot présent dans le titre. | |
441 | if words: | |
442 | score_expr = ' + '.join(['(title LIKE %s)'] * len(words)) | |
443 | score_params = ['%' + word + '%' for word in words] | |
444 | qs = qs.extra( | |
445 | select={'score': score_expr}, | |
446 | select_params=score_params | |
447 | ).order_by('-score') | |
448 | return qs | |
449 | ||
450 | def search_auteur(self, text): | |
451 | qs = self | |
452 | for word in text.split(): | |
453 | qs = qs.filter(Q(creator__icontains=word) | Q(contributor__icontains=word)) | |
454 | return qs | |
455 | ||
456 | def search_sujet(self, text): | |
457 | qs = self | |
458 | for word in text.split(): | |
459 | qs = qs.filter(subject__icontains=word) | |
460 | return qs | |
461 | ||
462 | def search_titre(self, text): | |
463 | qs = self | |
464 | for word in text.split(): | |
465 | qs = qs.filter(title__icontains=word) | |
466 | return qs | |
467 | ||
468 | def filter_discipline(self, discipline): | |
469 | """Ne conserve que les ressources dans la discipline donnée. | |
470 | ||
471 | Si ``disicipline`` est None, ce filtre n'a aucun effet.""" | |
472 | if discipline is None: | |
473 | return self | |
474 | if not isinstance(discipline, Discipline): | |
475 | discipline = Discipline.objects.get(pk=discipline) | |
476 | return self.filter(Q(disciplines=discipline) | | |
477 | Q(title__icontains=discipline.nom) | | |
478 | Q(description__icontains=discipline.nom) | | |
479 | Q(subject__icontains=discipline.nom)).distinct() | |
480 | ||
481 | def filter_region(self, region): | |
482 | """Ne conserve que les ressources dans la région donnée. | |
483 | ||
484 | Si ``region`` est None, ce filtre n'a aucun effet.""" | |
485 | if region is None: | |
486 | return self | |
487 | if not isinstance(region, Region): | |
488 | region = Region.objects.get(pk=region) | |
489 | return self.filter(Q(pays__region=region) | | |
490 | Q(regions=region) | | |
491 | Q(title__icontains=region.nom) | | |
492 | Q(description__icontains=region.nom) | | |
493 | Q(subject__icontains=region.nom)).distinct() | |
494 | ||
495 | def validated(self): | |
f153be1b EMS |
496 | """Ne garder que les ressources validées et qui sont soit dans aucun |
497 | listset ou au moins dans un listset validé.""" | |
116db1fd | 498 | qs = self.filter(validated=True) |
82f25472 EMS |
499 | qs = qs.filter(Q(listsets__isnull=True) | Q(listsets__validated=True)) |
500 | return qs.distinct() | |
f153be1b | 501 | |
116db1fd EMS |
502 | def filter(self, *args, **kwargs): |
503 | """Gère des filtres supplémentaires pour l'admin. | |
504 | ||
505 | C'est la seule façon que j'ai trouvée de contourner les mécanismes | |
506 | de recherche de l'admin.""" | |
507 | search = kwargs.pop('admin_search', None) | |
508 | search_titre = kwargs.pop('admin_search_titre', None) | |
509 | search_sujet = kwargs.pop('admin_search_sujet', None) | |
510 | search_description = kwargs.pop('admin_search_description', None) | |
511 | search_auteur = kwargs.pop('admin_search_auteur', None) | |
512 | ||
513 | if search: | |
514 | qs = self | |
515 | search_all = not (search_titre or search_description or search_sujet or search_auteur) | |
516 | fields = [] | |
517 | if search_titre or search_all: | |
518 | fields += ['title', 'alt_title'] | |
519 | if search_description or search_all: | |
520 | fields += ['description', 'abstract'] | |
521 | if search_sujet or search_all: | |
522 | fields += ['subject'] | |
523 | if search_auteur or search_all: | |
524 | fields += ['creator', 'contributor'] | |
525 | ||
526 | for bit in search.split(): | |
527 | or_queries = [Q(**{field + '__icontains': bit}) for field in fields] | |
528 | qs = qs.filter(reduce(operator.or_, or_queries)) | |
529 | ||
530 | if args or kwargs: | |
531 | qs = super(RecordQuerySet, qs).filter(*args, **kwargs) | |
532 | return qs | |
533 | else: | |
534 | return super(RecordQuerySet, self).filter(*args, **kwargs) | |
77b0fac0 | 535 | |
0cc5f772 | 536 | class Record(models.Model): |
23b5b3d5 | 537 | |
538 | #fonctionnement interne | |
0cc5f772 | 539 | id = models.AutoField(primary_key = True) |
a5f76eb4 | 540 | server = models.CharField(max_length = 255, verbose_name=u'serveur') |
23b5b3d5 | 541 | last_update = models.CharField(max_length = 255) |
542 | last_checksum = models.CharField(max_length = 255) | |
a5f76eb4 | 543 | validated = models.BooleanField(default=True, verbose_name=u'validé') |
23b5b3d5 | 544 | |
545 | #OAI | |
18dbd2cf EMS |
546 | title = models.TextField(null=True, blank=True, verbose_name=u'titre') |
547 | creator = models.TextField(null=True, blank=True, verbose_name=u'auteur') | |
548 | description = models.TextField(null=True, blank=True) | |
549 | modified = models.CharField(max_length=255, null=True, blank=True) | |
23b5b3d5 | 550 | identifier = models.CharField(max_length = 255, null = True, blank = True, unique = True) |
551 | uri = models.CharField(max_length = 255, null = True, blank = True, unique = True) | |
552 | source = models.TextField(null = True, blank = True) | |
553 | contributor = models.TextField(null = True, blank = True) | |
18dbd2cf | 554 | subject = models.TextField(null=True, blank=True, verbose_name='sujet') |
23b5b3d5 | 555 | publisher = models.TextField(null = True, blank = True) |
556 | type = models.TextField(null = True, blank = True) | |
557 | format = models.TextField(null = True, blank = True) | |
558 | language = models.TextField(null = True, blank = True) | |
da9020f3 | 559 | |
c88d78dc | 560 | listsets = models.ManyToManyField(ListSet, null = True, blank = True) |
d972b61d | 561 | |
da9020f3 | 562 | #SEP 2 (aucune données récoltées) |
23b5b3d5 | 563 | alt_title = models.TextField(null = True, blank = True) |
564 | abstract = models.TextField(null = True, blank = True) | |
565 | creation = models.CharField(max_length = 255, null = True, blank = True) | |
566 | issued = models.CharField(max_length = 255, null = True, blank = True) | |
567 | isbn = models.TextField(null = True, blank = True) | |
568 | orig_lang = models.TextField(null = True, blank = True) | |
da9020f3 | 569 | |
570 | # Metadata AUF multivaluées | |
a342f93a EMS |
571 | disciplines = models.ManyToManyField(Discipline, blank=True) |
572 | thematiques = models.ManyToManyField(Thematique, blank=True, verbose_name='thématiques') | |
573 | pays = models.ManyToManyField(Pays, blank=True) | |
574 | regions = models.ManyToManyField(Region, blank=True, verbose_name='régions') | |
0cc5f772 | 575 | |
116db1fd | 576 | # Manager |
da44ce68 | 577 | objects = RecordManager() |
c59dba82 | 578 | all_objects = models.Manager() |
da44ce68 | 579 | |
18dbd2cf EMS |
580 | class Meta: |
581 | verbose_name = 'ressource' | |
582 | ||
264a3210 EMS |
583 | def __unicode__(self): |
584 | return "[%s] %s" % (self.server, self.title) | |
585 | ||
586 | def getServeurURL(self): | |
f98ad449 | 587 | """Retourne l'URL du serveur de provenance""" |
588 | return RESOURCES[self.server]['url'] | |
589 | ||
264a3210 | 590 | def est_complet(self): |
6d885e0c | 591 | """teste si le record à toutes les données obligatoires""" |
592 | return self.disciplines.count() > 0 and \ | |
593 | self.thematiques.count() > 0 and \ | |
594 | self.pays.count() > 0 and \ | |
595 | self.regions.count() > 0 | |
596 | ||
264a3210 EMS |
597 | def assigner_regions(self, regions): |
598 | self.regions.add(*regions) | |
da9020f3 | 599 | |
264a3210 EMS |
600 | def assigner_disciplines(self, disciplines): |
601 | self.disciplines.add(*disciplines) | |
264a3210 | 602 | |
6d885e0c | 603 | class Serveur(models.Model): |
b7a741ad | 604 | """Identification d'un serveur d'ou proviennent les références""" |
6d885e0c | 605 | nom = models.CharField(primary_key = True, max_length = 255) |
606 | ||
607 | def __unicode__(self,): | |
608 | return self.nom | |
609 | ||
610 | def conf_2_db(self,): | |
611 | for k in RESOURCES.keys(): | |
612 | s, created = Serveur.objects.get_or_create(nom=k) | |
613 | s.nom = k | |
614 | s.save() | |
615 | ||
616 | class Profile(models.Model): | |
617 | user = models.ForeignKey(User, unique=True) | |
618 | serveurs = models.ManyToManyField(Serveur, null = True, blank = True) | |
0cc5f772 CR |
619 | |
620 | class HarvestLog(models.Model): | |
23b5b3d5 | 621 | context = models.CharField(max_length = 255) |
622 | name = models.CharField(max_length = 255) | |
0cc5f772 | 623 | date = models.DateTimeField(auto_now = True) |
23b5b3d5 | 624 | added = models.IntegerField(null = True, blank = True) |
625 | updated = models.IntegerField(null = True, blank = True) | |
a85ba76e | 626 | processed = models.IntegerField(null = True, blank = True) |
23b5b3d5 | 627 | record = models.ForeignKey(Record, null = True, blank = True) |
628 | ||
629 | @staticmethod | |
630 | def add(message): | |
631 | logger = HarvestLog() | |
632 | if message.has_key('record_id'): | |
d566e9c1 | 633 | message['record'] = Record.all_objects.get(id=message['record_id']) |
23b5b3d5 | 634 | del(message['record_id']) |
635 | ||
636 | for k,v in message.items(): | |
637 | setattr(logger, k, v) | |
638 | logger.save() |