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