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
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')
19 def __unicode__ (self
):
23 db_table
= u
'discipline'
26 class SourceActualite(models
.Model
):
27 nom
= models
.CharField(max_length
=255)
28 url
= models
.CharField(max_length
=255)
30 def __unicode__(self
,):
31 return u
"%s" % self
.nom
33 class ActualiteManager(models
.Manager
):
35 def get_query_set(self
):
36 return ActualiteQuerySet(self
.model
)
38 def search(self
, text
):
39 return self
.get_query_set().search(text
)
41 class ActualiteQuerySet(models
.query
.QuerySet
):
43 def search(self
, text
):
44 return self
.filter(Q(titre__icontains
=text
) |
Q(texte__icontains
=text
))
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')
58 objects
= ActualiteManager()
61 db_table
= u
'actualite'
64 def __unicode__ (self
):
65 return "%s" % (self
.titre
)
67 def assigner_disciplines(self
, disciplines
):
68 self
.disciplines
.add(*disciplines
)
70 def assigner_regions(self
, regions
):
71 self
.regions
.add(*regions
)
73 class EvenementManager(models
.Manager
):
75 def get_query_set(self
):
76 return EvenementQuerySet(self
.model
)
78 def search(self
, text
):
79 return self
.get_query_set().search(text
)
81 class EvenementQuerySet(models
.query
.QuerySet
):
83 def search(self
, text
):
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
))
99 return qs
.filter(q
) if q
is not None else qs
101 def search_titre(self
, text
):
103 for word
in text
.split():
104 qs
= qs
.filter(titre__icontains
=word
)
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'),
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)
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')
134 objects
= EvenementManager()
137 ordering
= ['-debut']
139 def __unicode__(self
,):
140 return "[%s] %s" % (self
.uid
, self
.titre
)
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')
147 def save(self
, *args
, **kwargs
):
148 """Sauvegarde l'objet dans django et le synchronise avec caldav s'il a été
152 super(Evenement
, self
).save(*args
, **kwargs
)
154 # methodes de commnunications avec CALDAV
156 """Retourne l'evenement django sous forme d'objet icalendar"""
157 cal
= vobject
.iCalendar()
160 # fournit son propre uid
161 if self
.uid
in [None, ""]:
162 self
.uid
= str(uuid
.uuid1())
164 cal
.vevent
.add('uid').value
= self
.uid
166 cal
.vevent
.add('summary').value
= self
.titre
168 if self
.mots_cles
is None:
171 kw
= self
.mots_cles
.split(",")
174 kw
.append(self
.discipline
.nom
)
175 kw
.append(self
.discipline_secondaire
.nom
)
179 kw
= [x
.strip() for x
in kw
if len(x
.strip()) > 0 and x
is not None]
181 cal
.vevent
.add('x-auf-keywords').value
= k
183 description
= self
.description
185 if len(self
.description
) > 0:
187 description
+= u
"Mots-clés: " + ", ".join(kw
)
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
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é"""
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
)
214 self
.approuve
= False
216 def delete_vevent(self
,):
217 """Supprime l'evenement sur le serveur caldav"""
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
)
225 except error
.NotFoundError
:
228 def assigner_regions(self
, regions
):
229 self
.regions
.add(*regions
)
231 def assigner_disciplines(self
, disciplines
):
232 if len(disciplines
) == 1:
234 self
.discipline_secondaire
= disciplines
[0]
236 self
.discipline
= disciplines
[0]
237 elif len(disciplines
) >= 2:
238 self
.discipline
= disciplines
[0]
239 self
.discipline_secondaire
= disciplines
[1]
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()
248 pre_delete
.connect(delete_vevent
, sender
= Evenement
)
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)
257 def __unicode__(self
,):
260 class RecordManager(models
.Manager
):
262 def get_query_set(self
):
263 return RecordQuerySet(self
.model
)
265 def search(self
, text
):
266 return self
.get_query_set().search(text
)
269 return self
.get_query_set().validated()
271 class RecordQuerySet(models
.query
.QuerySet
):
273 def search(self
, text
):
277 # Ne garder que les ressources qui contiennent tous les mots
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
))
290 qs
= qs
.filter(q
).distinct()
292 # On donne un point pour chaque mot présent dans le titre.
294 score_expr
= ' + '.join(['(title LIKE %s)'] * len(words
))
295 score_params
= ['%' + word
+ '%' for word
in words
]
297 select
={'score': score_expr
},
298 select_params
=score_params
303 def search_auteur(self
, text
):
305 for word
in text
.split():
306 qs
= qs
.filter(Q(creator__icontains
=word
) |
Q(contributor__icontains
=word
))
309 def search_sujet(self
, text
):
311 for word
in text
.split():
312 qs
= qs
.filter(subject__icontains
=word
)
315 def search_titre(self
, text
):
317 for word
in text
.split():
318 qs
= qs
.filter(title__icontains
=word
)
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))'''])
331 class Record(models
.Model
):
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é')
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)
355 listsets
= models
.ManyToManyField(ListSet
, null
= True, blank
= True)
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)
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')
372 objects
= RecordManager()
374 def __unicode__(self
):
375 return "[%s] %s" % (self
.server
, self
.title
)
377 def getServeurURL(self
):
378 """Retourne l'URL du serveur de provenance"""
379 return RESOURCES
[self
.server
]['url']
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
388 def assigner_regions(self
, regions
):
389 self
.regions
.add(*regions
)
391 def assigner_disciplines(self
, disciplines
):
392 self
.disciplines
.add(*disciplines
)
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)
399 def __unicode__(self
,):
402 def conf_2_db(self
,):
403 for k
in RESOURCES
.keys():
404 s
, created
= Serveur
.objects
.get_or_create(nom
=k
)
408 class Profile(models
.Model
):
409 user
= models
.ForeignKey(User
, unique
=True)
410 serveurs
= models
.ManyToManyField(Serveur
, null
= True, blank
= True)
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)
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'])
428 for k
,v
in message
.items():
429 setattr(logger
, k
, v
)