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