close #311, moderation a posteriori, filtrage dans l'admin par validation
[auf_savoirs_en_partage_django.git] / auf_savoirs_en_partage / savoirs / lib / sep.py
CommitLineData
0cc5f772 1# -*- encoding: utf-8 -*-
23b5b3d5 2import simplejson, re, datetime, operator, hashlib
ecc62927 3from savoirs.globals import *
d972b61d 4from savoirs.models import Record, ListSet
0cc5f772 5
23b5b3d5 6class SEPEncoder:
7 """
8 Classe permettant de d'encoder et de décoder les données moissonnées.
9 """
10 separator = ", "
11
12 def encode(self, field, data):
13 if field in META.keys() and META[field]['type'] == 'array':
14 return self.separator.join(data)
15 else:
16 return data
17
18 def decode(self, field, data):
19 if field in META.keys() and META[field]['type'] == 'array':
20 return data.split(self.separator)
21 else:
22 return data
23
24 #def migrate(self,):
25 # for r in Record.objects.all():
26 # for f in META.keys():
27 # json = getattr(r, f)
28 # if json is not None:
29 # normal = simplejson.loads(json)
30 # new = self.encode(f, normal)
31 # setattr(r, f, new)
32 # r.save()
0cc5f772
CR
33
34class SEP:
35 """
23b5b3d5 36 Classe utilisée pour réaliser manipuler les données moisonnées.
0cc5f772 37 """
0cc5f772 38
23b5b3d5 39 encoder = SEPEncoder()
0cc5f772 40
23b5b3d5 41 ############################################################################
42 # MÉTHODES INTERNES
43 ############################################################################
0cc5f772 44
23b5b3d5 45 def _load (self, id):
46 """Recupérer la structure de métadonnées pour un record selon un `id`."""
47 r = Record.objects.get(id = id)
48 meta = {}
49 for k in META.keys ():
50 if hasattr (r, k):
51 v = getattr (r, k)
52 if v is not None:
53 meta[k] = self.encoder.decode(k, v)
54 return meta
55
d972b61d 56 # traitement spécial pour certaines clef de la structure
57 def listsets(self, record, value):
58
59 # doit avoir un id pour créer les relations multivaluées
60 record.save()
61 for set in [ls for ls in ListSet.objects.all() if ls.spec in value]:
62 record.listsets.add(set)
63
c88d78dc 64 def _update_record(self, r, metadata):
23b5b3d5 65 for k in metadata.keys ():
d972b61d 66 if hasattr(self, k):
67 method = getattr(self, k)
68 method(r, metadata[k])
69 else:
70 setattr (r, k, self.encoder.encode(k, metadata[k]))
71
23b5b3d5 72 r.last_checksum = hashlib.md5(str(metadata)).hexdigest()
73 r.last_update = datetime.datetime.today()
74 r.save()
c88d78dc 75
76
77 def _save (self, metadata):
78 r = Record ()
79 self._update_record(r, metadata)
23b5b3d5 80 return r.id
81
82 def _modify (self, id, metadata):
83 r = Record.objects.get(id = id)
84
85 # test si le fichier a été modifié
86 if hashlib.md5(str(metadata)).hexdigest() == r.last_checksum:
87 return False
23b5b3d5 88
c88d78dc 89 self._update_record(r, metadata)
90
23b5b3d5 91 return True
92
93 def _combine (self, result_lists, op):
94 scores = {}
95 simple_sets = []
96
97 for list in result_lists:
98 simple_sets.append (set([x[0] for x in list]))
99 for (id, score) in list:
100 if scores.get (id) is None:
101 scores[id] = 0
102 scores[id] += score
103
104 matches = []
105 for s in simple_sets:
106 if op == "|":
107 matches = set(matches) | s
108 elif op == "&":
109 if len (matches) == 0:
110 matches = s
111 else:
112 matches = set(matches) & s
113 #print "EE", matches
114
115 return [(x, scores[x]) for x in matches]
116
117
118 def _text_search (self, q, fields = None):
119 if fields is None:
120 fields = [x for x in META.keys() if META[x].get("text_search", False)]
121
122 w = re.compile (r'\W+', re.U)
123 words = w.split (q)
124
125 matches = []
126 suffix = ""
127 if len(fields)==1 and fields[0] == "subject":
128 suffix = " IN BOOLEAN MODE"
129
130 for k in fields:
131 matches.append ("MATCH(`%s`) AGAINST ('%s'%s)" % (k, " ".join(words), suffix))
132 m = "+".join (matches)
133
9eda5d6c 134 q = "SELECT r.id, (%s) AS score FROM savoirs_record AS r \
135 LEFT JOIN savoirs_record_listsets AS rl ON r.id = rl.record_id \
136 JOIN savoirs_listset AS l ON rl.listset_id = l.spec \
137 WHERE (%s) AND r.validated = 1 AND l.validated = 1 \
a3a69209 138 HAVING score > 0 ORDER BY score DESC" % (m, m)
23b5b3d5 139
140 from django.db import connection, transaction
141 cursor = connection.cursor()
142 cursor.execute(q)
143 rc = cursor.fetchall()
0cc5f772
CR
144 return rc
145
23b5b3d5 146 ############################################################################
147 # API
148 ############################################################################
149
0cc5f772
CR
150 def add (self, metadata):
151 """Ajouter la ressource définie par `metadata`. Si on trouve une
152 ressource avec le même `identifier`, on le met a jour.
153
154 Retourne l'id de la ressource créée ou mise à jour.
155 """
23b5b3d5 156 added = updated = False
0cc5f772
CR
157 exists = self.search (q = {URI: metadata[URI]})
158 if len (exists) > 0:
8b95ddc9 159 id = exists[0][0]
23b5b3d5 160 updated = self.update (int(id), metadata)
0cc5f772 161 else:
23b5b3d5 162 added = True
163 id = self._save (metadata)
164 return {'record_id': id, 'added':added, 'updated':updated}
165
166 def delete (self, id):
167 """Supprime la ressource identifiée par `id`.
168 """
169 r = Record.objects.get(id = id)
170 r.delete()
0cc5f772
CR
171
172 def update (self, id, metadata):
173 """Met a jour la ressource identifiée par `id`, avec les données de
174 `metadata`. Une exception est levée si elle n'existe pas.
175 """
176 if self.get (int(id)) is not None:
23b5b3d5 177 return self._modify (int(id), metadata)
0cc5f772
CR
178 else:
179 raise Exception ("Objet inexistant")
23b5b3d5 180 return False
0cc5f772 181
23b5b3d5 182 def get (self, id):
183 """Recupérer la structure de métadonnées pour la ressource identifiée
184 par `id`. `id` peut être une liste si on veut les structures de
185 plusieurs ressources.
0cc5f772 186 """
23b5b3d5 187 if isinstance (id, tuple) or isinstance (id, list):
188 rc = []
189 for i in id:
190 try:
191 i = i[0]
192 except: pass
193 rc.append (self._load (int(i)))
194 else:
195 rc = self._load (int(id))
196 return rc
0cc5f772 197
23b5b3d5 198 def ids (self):
199 """ Retourner la liste complète des ids des ressources."""
200 return [x.id for x in Record.objects.all()]
0cc5f772 201
23b5b3d5 202 def search (self, q):
203 """Effectue une recherche multi-critères, en fonction du dictionnaire
204 `q`. Retourne une list d'`id`s uniquement. Les données pour chaque
205 résultat doivent être chargées ulterieurement.
206 """
207 rc = []
208 sets = []
0cc5f772 209
23b5b3d5 210 if len (q) > 0:
211 # Recherche "simple"
f991eb01 212 ww = q.get ("q", "").strip()
23b5b3d5 213 if len (ww) > 0:
214 s = self._text_search (ww)
215 if len(s) > 0:
8f17344b 216 rc.extend(s)
23b5b3d5 217 # Recherche URL
218 elif q.get (URI) is not None:
219 s = []
220 try:
221 s.append((Record.objects.get(uri__iexact = q.get(URI)).id, 1))
222 rc.append(s)
223 except: pass
224 # Recherche avancée
225 else:
f991eb01 226 creator = q.get ("creator", "")
227 title = q.get ("title", "")
228 description = q.get ("description", "")
229 subject = q.get ("subject", "")
23b5b3d5 230
231 if len (creator) > 0:
232 sets.append (self._text_search (creator, [CREATOR, CONTRIBUTOR]))
233 if len (title) > 0:
234 sets.append (self._text_search (title, [TITLE, ALT_TITLE]))
235 if len (description) > 0:
236 sets.append (self._text_search (description, [DESCRIPTION, ABSTRACT]))
237 if len (subject) > 0:
238 sets.append (self._text_search (subject, [SUBJECT,]))
239 rc = self._combine (sets, q.get ("operator", "|"))
240 rc.sort (key = operator.itemgetter(1), reverse = True)
241
242 if len(rc) > 0:
243 rc = [x[0] for x in rc]
244
245 else:
246 rc = self.ids()
23b5b3d5 247 return rc