Assigner les disciplines et régions en bloc dans l'admin
[auf_savoirs_en_partage_django.git] / auf_savoirs_en_partage / savoirs / models.py
CommitLineData
92c7413b 1# -*- encoding: utf-8 -*-
b7a741ad 2import simplejson, uuid, datetime, caldav, vobject, uuid
6d885e0c 3from django.contrib.auth.models import User
d15017b2 4from django.db import models
f12cc7fb 5from django.db.models import Q
b7a741ad 6from django.db.models.signals import pre_delete
92c7413b 7from timezones.fields import TimeZoneField
6d885e0c 8from auf_savoirs_en_partage.backend_config import RESOURCES
da9020f3 9from savoirs.globals import META
b7a741ad 10from settings import CALENDRIER_URL
e3c3296e 11from datamaster_modeles.models import Thematique, Pays, Region
b7a741ad 12from lib.calendrier import combine
13from caldav.lib import error
d15017b2
CR
14
15class Discipline(models.Model):
16 id = models.IntegerField(primary_key=True, db_column='id_discipline')
17 nom = models.CharField(max_length=765, db_column='nom_discipline')
6ef8ead4
CR
18
19 def __unicode__ (self):
92c7413b 20 return self.nom
6ef8ead4 21
d15017b2
CR
22 class Meta:
23 db_table = u'discipline'
24 ordering = ["nom",]
25
79c398f6
CR
26class SourceActualite(models.Model):
27 nom = models.CharField(max_length=255)
28 url = models.CharField(max_length=255)
ccbc4363 29
30 def __unicode__(self,):
31 return u"%s" % self.nom
79c398f6 32
2f9c4d6c
EMS
33class ActualiteManager(models.Manager):
34
35 def get_query_set(self):
36 return ActualiteQuerySet(self.model)
37
da44ce68
EMS
38 def search(self, text):
39 return self.get_query_set().search(text)
40
2f9c4d6c
EMS
41class ActualiteQuerySet(models.query.QuerySet):
42
43 def search(self, text):
c9cbc784 44 return self.filter(Q(titre__icontains=text) | Q(texte__icontains=text))
2f9c4d6c 45
d15017b2 46class Actualite(models.Model):
4f262f90 47 id = models.AutoField(primary_key=True, db_column='id_actualite')
d15017b2
CR
48 titre = models.CharField(max_length=765, db_column='titre_actualite')
49 texte = models.TextField(db_column='texte_actualite')
50 url = models.CharField(max_length=765, db_column='url_actualite')
d15017b2 51 date = models.DateField(db_column='date_actualite')
f554ef70 52 visible = models.BooleanField(db_column='visible_actualite', default = False)
3b6456b0 53 ancienid = models.IntegerField(db_column='ancienId_actualite', blank = True, null = True)
ccbc4363 54 source = models.ForeignKey(SourceActualite, blank = True, null = True)
3a45eb64
EMS
55 disciplines = models.ManyToManyField(Discipline, blank=True, related_name="actualites")
56 regions = models.ManyToManyField(Region, blank=True, related_name="actualites", verbose_name='Régions')
6ef8ead4 57
2f9c4d6c
EMS
58 objects = ActualiteManager()
59
d15017b2
CR
60 class Meta:
61 db_table = u'actualite'
62 ordering = ["-date",]
92c7413b 63
264a3210
EMS
64 def __unicode__ (self):
65 return "%s" % (self.titre)
66
67 def assigner_disciplines(self, disciplines):
68 self.disciplines.add(*disciplines)
69
70 def assigner_regions(self, regions):
71 self.regions.add(*regions)
72
4101cfc0
EMS
73class EvenementManager(models.Manager):
74
75 def get_query_set(self):
76 return EvenementQuerySet(self.model)
77
78 def search(self, text):
79 return self.get_query_set().search(text)
80
81class EvenementQuerySet(models.query.QuerySet):
82
83 def search(self, text):
84 qs = self
0b1ddc11
EMS
85 q = None
86 for word in text.split():
87 part = (Q(titre__icontains=word) |
88 Q(mots_cles__icontains=word) |
89 Q(discipline__nom__icontains=word) |
90 Q(discipline_secondaire__nom__icontains=word) |
91 Q(type__icontains=word) |
92 Q(lieu__icontains=word) |
93 Q(description__icontains=word) |
94 Q(contact__icontains=word))
95 if q is None:
96 q = part
97 else:
98 q = q & part
3c23982e 99 return qs.filter(q) if q is not None else qs
4101cfc0 100
7bbf600c
EMS
101 def search_titre(self, text):
102 qs = self
103 for word in text.split():
104 qs = qs.filter(titre__icontains=word)
105 return qs
106
92c7413b 107class Evenement(models.Model):
7bbf600c
EMS
108 TYPE_CHOICES = ((u'Colloque', u'Colloque'),
109 (u'Conférence', u'Conférence'),
110 (u'Appel à contribution', u'Appel à contribution'),
111 (u'Journée d\'étude', u'Journée d\'étude'),
112 (None, u'Autre'))
113
c5b3da8b 114 uid = models.CharField(max_length = 255, default = str(uuid.uuid1()))
92c7413b
CR
115 approuve = models.BooleanField(default = False)
116 titre = models.CharField(max_length=255)
117 discipline = models.ForeignKey('Discipline', related_name = "discipline",
118 blank = True, null = True)
119 discipline_secondaire = models.ForeignKey('Discipline', related_name = \
120 "discipline_secondaire",
121 verbose_name = \
122 "Discipline secondaire",
123 blank = True, null = True)
124 mots_cles = models.TextField('Mots-Clés', blank = True, null = True)
7bbf600c 125 type = models.CharField(max_length=255, choices=TYPE_CHOICES)
92c7413b
CR
126 fuseau = TimeZoneField(verbose_name = 'Fuseau horaire')
127 debut = models.DateTimeField(default = datetime.datetime.now)
128 fin = models.DateTimeField(default = datetime.datetime.now)
129 lieu = models.TextField()
130 description = models.TextField(blank = True, null = True)
131 #fichiers = TODO?
132 contact = models.TextField(blank = True, null = True)
133 url = models.CharField(max_length=255, blank = True, null = True)
3a45eb64 134 regions = models.ManyToManyField(Region, blank=True, related_name="evenements", verbose_name='Régions')
92c7413b 135
4101cfc0
EMS
136 objects = EvenementManager()
137
138 class Meta:
139 ordering = ['-debut']
140
020f79a9 141 def __unicode__(self,):
142 return "[%s] %s" % (self.uid, self.titre)
143
73309469 144 def clean(self):
145 from django.core.exceptions import ValidationError
146 if self.debut > self.fin:
147 raise ValidationError('La date de fin ne doit pas être antérieure à la date de début')
148
b7a741ad 149 def save(self, *args, **kwargs):
150 """Sauvegarde l'objet dans django et le synchronise avec caldav s'il a été
151 approuvé"""
73309469 152 self.clean()
b7a741ad 153 self.update_vevent()
154 super(Evenement, self).save(*args, **kwargs)
155
156 # methodes de commnunications avec CALDAV
157 def as_ical(self,):
158 """Retourne l'evenement django sous forme d'objet icalendar"""
159 cal = vobject.iCalendar()
160 cal.add('vevent')
161
162 # fournit son propre uid
7f56d0d4 163 if self.uid in [None, ""]:
b7a741ad 164 self.uid = str(uuid.uuid1())
165
166 cal.vevent.add('uid').value = self.uid
167
168 cal.vevent.add('summary').value = self.titre
169
170 if self.mots_cles is None:
171 kw = []
172 else:
173 kw = self.mots_cles.split(",")
174
175 try:
176 kw.append(self.discipline.nom)
177 kw.append(self.discipline_secondaire.nom)
178 kw.append(self.type)
179 except: pass
180
79b400f0 181 kw = [x.strip() for x in kw if len(x.strip()) > 0 and x is not None]
b7a741ad 182 for k in kw:
183 cal.vevent.add('x-auf-keywords').value = k
184
185 description = self.description
186 if len(kw) > 0:
187 if len(self.description) > 0:
188 description += "\n"
028f548f 189 description += u"Mots-clés: " + ", ".join(kw)
b7a741ad 190
191 cal.vevent.add('dtstart').value = combine(self.debut, self.fuseau)
192 cal.vevent.add('dtend').value = combine(self.fin, self.fuseau)
193 cal.vevent.add('created').value = combine(datetime.datetime.now(), "UTC")
194 cal.vevent.add('dtstamp').value = combine(datetime.datetime.now(), "UTC")
79b400f0 195 if len(description) > 0:
b7a741ad 196 cal.vevent.add('description').value = description
197 if len(self.contact) > 0:
198 cal.vevent.add('contact').value = self.contact
199 if len(self.url) > 0:
200 cal.vevent.add('url').value = self.url
201 if len(self.lieu) > 0:
202 cal.vevent.add('location').value = self.lieu
203 return cal
204
205 def update_vevent(self,):
206 """Essaie de créer l'évènement sur le serveur ical.
207 En cas de succès, l'évènement local devient donc inactif et approuvé"""
208 try:
209 if self.approuve:
210 event = self.as_ical()
211 client = caldav.DAVClient(CALENDRIER_URL)
212 cal = caldav.Calendar(client, url = CALENDRIER_URL)
213 e = caldav.Event(client, parent = cal, data = event.serialize(), id=self.uid)
214 e.save()
215 except:
216 self.approuve = False
217
218 def delete_vevent(self,):
219 """Supprime l'evenement sur le serveur caldav"""
220 try:
221 if self.approuve:
222 event = self.as_ical()
223 client = caldav.DAVClient(CALENDRIER_URL)
224 cal = caldav.Calendar(client, url = CALENDRIER_URL)
225 e = cal.event(self.uid)
226 e.delete()
227 except error.NotFoundError:
228 pass
229
264a3210
EMS
230 def assigner_regions(self, regions):
231 self.regions.add(*regions)
232
233 def assigner_disciplines(self, disciplines):
234 if len(disciplines) == 1:
235 if self.discipline:
236 self.discipline_secondaire = disciplines[0]
237 else:
238 self.discipline = disciplines[0]
239 elif len(disciplines) >= 2:
240 self.discipline = disciplines[0]
241 self.discipline_secondaire = disciplines[1]
242
b7a741ad 243
244# Surcharge du comportement de suppression
245# La méthode de connexion par signals est préférable à surcharger la méthode delete()
246# car dans le cas de la suppression par lots, cell-ci n'est pas invoquée
247def delete_vevent(sender, instance, *args, **kwargs):
248 instance.delete_vevent()
249
250pre_delete.connect(delete_vevent, sender = Evenement)
251
252
d972b61d 253class ListSet(models.Model):
254 spec = models.CharField(primary_key = True, max_length = 255)
255 name = models.CharField(max_length = 255)
256 server = models.CharField(max_length = 255)
9eda5d6c 257 validated = models.BooleanField(default = True)
d972b61d 258
10d37e44 259 def __unicode__(self,):
260 return self.name
261
da44ce68
EMS
262class RecordManager(models.Manager):
263
264 def get_query_set(self):
265 return RecordQuerySet(self.model)
266
267 def search(self, text):
268 return self.get_query_set().search(text)
269
f153be1b
EMS
270 def validated(self):
271 return self.get_query_set().validated()
272
da44ce68
EMS
273class RecordQuerySet(models.query.QuerySet):
274
275 def search(self, text):
f12cc7fb 276 qs = self
da44ce68 277 words = text.split()
da44ce68 278
f12cc7fb
EMS
279 # Ne garder que les ressources qui contiennent tous les mots
280 # demandés.
0b1ddc11 281 q = None
f12cc7fb 282 for word in words:
0b1ddc11
EMS
283 part = (Q(title__icontains=word) | Q(description__icontains=word) |
284 Q(creator__icontains=word) | Q(contributor__icontains=word) |
285 Q(subject__icontains=word) | Q(disciplines__nom__icontains=word) |
ad0600bc 286 Q(regions__nom__icontains=word) | Q(pays__nom__icontains=word))
0b1ddc11
EMS
287 if q is None:
288 q = part
289 else:
290 q = q & part
3c23982e
EMS
291 if q is not None:
292 qs = qs.filter(q).distinct()
f12cc7fb
EMS
293
294 # On donne un point pour chaque mot présent dans le titre.
3c23982e
EMS
295 if words:
296 score_expr = ' + '.join(['(title LIKE %s)'] * len(words))
297 score_params = ['%' + word + '%' for word in words]
298 qs = qs.extra(
299 select={'score': score_expr},
300 select_params=score_params
301 ).order_by('-score')
302
303 return qs
f12cc7fb
EMS
304
305 def search_auteur(self, text):
306 qs = self
307 for word in text.split():
308 qs = qs.filter(Q(creator__icontains=word) | Q(contributor__icontains=word))
309 return qs
310
311 def search_sujet(self, text):
312 qs = self
313 for word in text.split():
314 qs = qs.filter(subject__icontains=word)
315 return qs
316
317 def search_titre(self, text):
318 qs = self
319 for word in text.split():
320 qs = qs.filter(title__icontains=word)
321 return qs
322
f153be1b
EMS
323 def validated(self):
324 """Ne garder que les ressources validées et qui sont soit dans aucun
325 listset ou au moins dans un listset validé."""
326 qs = self.filter(validated=True)
327 qs = qs.extra(where=['''((savoirs_record.id NOT IN (SELECT record_id FROM savoirs_record_listsets)) OR
328 ((SELECT MAX(l.validated) FROM savoirs_listset l
329 INNER JOIN savoirs_record_listsets rl ON rl.listset_id = l.spec
330 WHERE rl.record_id = savoirs_record.id) = TRUE))'''])
331 return qs
332
0cc5f772 333class Record(models.Model):
23b5b3d5 334
335 #fonctionnement interne
0cc5f772 336 id = models.AutoField(primary_key = True)
23b5b3d5 337 server = models.CharField(max_length = 255)
338 last_update = models.CharField(max_length = 255)
339 last_checksum = models.CharField(max_length = 255)
c88d78dc 340 validated = models.BooleanField(default = True)
23b5b3d5 341
342 #OAI
343 title = models.TextField(null = True, blank = True)
344 creator = models.TextField(null = True, blank = True)
345 description = models.TextField(null = True, blank = True)
346 modified = models.CharField(max_length = 255, null = True, blank = True)
347 identifier = models.CharField(max_length = 255, null = True, blank = True, unique = True)
348 uri = models.CharField(max_length = 255, null = True, blank = True, unique = True)
349 source = models.TextField(null = True, blank = True)
350 contributor = models.TextField(null = True, blank = True)
351 subject = models.TextField(null = True, blank = True)
352 publisher = models.TextField(null = True, blank = True)
353 type = models.TextField(null = True, blank = True)
354 format = models.TextField(null = True, blank = True)
355 language = models.TextField(null = True, blank = True)
da9020f3 356
c88d78dc 357 listsets = models.ManyToManyField(ListSet, null = True, blank = True)
d972b61d 358
da9020f3 359 #SEP 2 (aucune données récoltées)
23b5b3d5 360 alt_title = models.TextField(null = True, blank = True)
361 abstract = models.TextField(null = True, blank = True)
362 creation = models.CharField(max_length = 255, null = True, blank = True)
363 issued = models.CharField(max_length = 255, null = True, blank = True)
364 isbn = models.TextField(null = True, blank = True)
365 orig_lang = models.TextField(null = True, blank = True)
da9020f3 366
367 # Metadata AUF multivaluées
368 disciplines = models.ManyToManyField(Discipline)
369 thematiques = models.ManyToManyField(Thematique)
e3c3296e 370 pays = models.ManyToManyField(Pays)
3a45eb64 371 regions = models.ManyToManyField(Region, verbose_name='Régions')
0cc5f772 372
da44ce68
EMS
373 # Manager
374 objects = RecordManager()
375
264a3210
EMS
376 def __unicode__(self):
377 return "[%s] %s" % (self.server, self.title)
378
379 def getServeurURL(self):
f98ad449 380 """Retourne l'URL du serveur de provenance"""
381 return RESOURCES[self.server]['url']
382
264a3210 383 def est_complet(self):
6d885e0c 384 """teste si le record à toutes les données obligatoires"""
385 return self.disciplines.count() > 0 and \
386 self.thematiques.count() > 0 and \
387 self.pays.count() > 0 and \
388 self.regions.count() > 0
389
264a3210
EMS
390 def assigner_regions(self, regions):
391 self.regions.add(*regions)
da9020f3 392
264a3210
EMS
393 def assigner_disciplines(self, disciplines):
394 self.disciplines.add(*disciplines)
395
396
6d885e0c 397class Serveur(models.Model):
b7a741ad 398 """Identification d'un serveur d'ou proviennent les références"""
6d885e0c 399 nom = models.CharField(primary_key = True, max_length = 255)
400
401 def __unicode__(self,):
402 return self.nom
403
404 def conf_2_db(self,):
405 for k in RESOURCES.keys():
406 s, created = Serveur.objects.get_or_create(nom=k)
407 s.nom = k
408 s.save()
409
410class Profile(models.Model):
411 user = models.ForeignKey(User, unique=True)
412 serveurs = models.ManyToManyField(Serveur, null = True, blank = True)
0cc5f772
CR
413
414class HarvestLog(models.Model):
23b5b3d5 415 context = models.CharField(max_length = 255)
416 name = models.CharField(max_length = 255)
0cc5f772 417 date = models.DateTimeField(auto_now = True)
23b5b3d5 418 added = models.IntegerField(null = True, blank = True)
419 updated = models.IntegerField(null = True, blank = True)
a85ba76e 420 processed = models.IntegerField(null = True, blank = True)
23b5b3d5 421 record = models.ForeignKey(Record, null = True, blank = True)
422
423 @staticmethod
424 def add(message):
425 logger = HarvestLog()
426 if message.has_key('record_id'):
427 message['record'] = Record.objects.get(id=message['record_id'])
428 del(message['record_id'])
429
430 for k,v in message.items():
431 setattr(logger, k, v)
432 logger.save()