|
@@ -4,11 +4,20 @@
|
4
|
4
|
# @brief This package contains the abstract class representing Lodel Editorial components
|
5
|
5
|
#
|
6
|
6
|
|
|
7
|
+import warnings
|
7
|
8
|
import importlib
|
8
|
9
|
import re
|
9
|
10
|
|
|
11
|
+import leapi.leobject
|
10
|
12
|
import EditorialModel.fieldtypes.generic
|
11
|
13
|
|
|
14
|
+## @brief When an error concern a query
|
|
15
|
+class LeApiQueryError(EditorialModel.fieldtypes.generic.FieldTypeDataCheckError): pass
|
|
16
|
+
|
|
17
|
+## @brief When an error concerns a datas
|
|
18
|
+class LeApiDataCheckError(EditorialModel.fieldtypes.generic.FieldTypeDataCheckError): pass
|
|
19
|
+
|
|
20
|
+
|
12
|
21
|
## @brief Main class to handler lodel editorial components (relations and objects)
|
13
|
22
|
class _LeCrud(object):
|
14
|
23
|
## @brief The datasource
|
|
@@ -38,7 +47,8 @@ class _LeCrud(object):
|
38
|
47
|
return getattr(mod, name)
|
39
|
48
|
except AttributeError:
|
40
|
49
|
return False
|
41
|
|
-
|
|
50
|
+
|
|
51
|
+ ## @return LeObject class
|
42
|
52
|
@classmethod
|
43
|
53
|
def leobject(cls):
|
44
|
54
|
return cls.name2class('LeObject')
|
|
@@ -53,16 +63,49 @@ class _LeCrud(object):
|
53
|
63
|
def fieldtypes_internal(self):
|
54
|
64
|
return { fname: ft for fname, ft in cls.fieldtypes().items() if hasattr(ft, 'internal') and ft.internal }
|
55
|
65
|
|
|
66
|
+ ## @return A list of field name
|
|
67
|
+ @classmethod
|
|
68
|
+ def fieldlist(cls):
|
|
69
|
+ return cls.fieldtypes().keys()
|
|
70
|
+
|
|
71
|
+ ## @brief Update a component in DB
|
|
72
|
+ # @param datas dict : If None use instance attributes to update de DB
|
|
73
|
+ # @return True if success
|
|
74
|
+ # @todo better error handling
|
|
75
|
+ def update(self, datas = None):
|
|
76
|
+ datas = self.datas(internal=False) if datas is None else datas
|
|
77
|
+ upd_datas = self.prepare_datas(datas, complete = False, allow_internal = False)
|
|
78
|
+ filters = [self._id_filter()]
|
|
79
|
+ rel_filters = []
|
|
80
|
+ ret = self._datasource.update(self.__class__, filters, rel_filters, upd_datas)
|
|
81
|
+ if ret == 1:
|
|
82
|
+ return True
|
|
83
|
+ else:
|
|
84
|
+ #ERROR HANDLING
|
|
85
|
+ return False
|
|
86
|
+
|
|
87
|
+ ## @brief Delete a component
|
|
88
|
+ # @return True if success
|
|
89
|
+ # @todo better error handling
|
|
90
|
+ def delete(self):
|
|
91
|
+ filters = [self._id_filter()]
|
|
92
|
+ rel_filters = []
|
|
93
|
+ ret = self._datasource.delete(self.__class__, filters, rel_filters)
|
|
94
|
+ if ret == 1:
|
|
95
|
+ return True
|
|
96
|
+ else:
|
|
97
|
+ #ERROR HANDLING
|
|
98
|
+ return False
|
|
99
|
+
|
56
|
100
|
## @brief Check that datas are valid for this type
|
57
|
101
|
# @param datas dict : key == field name value are field values
|
58
|
102
|
# @param complete bool : if True expect that datas provide values for all non internal fields
|
59
|
103
|
# @param allow_internal bool : if True don't raise an error if a field is internal
|
60
|
|
- # @throw AttributeError if datas provides values for automatic fields
|
61
|
|
- # @throw AttributeError if datas provides values for fields that doesn't exists
|
|
104
|
+ # @return Checked datas
|
|
105
|
+ # @throw LeObjectError if errors reported during check
|
62
|
106
|
@classmethod
|
63
|
107
|
def check_datas_value(cls, datas, complete = False, allow_internal = True):
|
64
|
|
-
|
65
|
|
- err_l = []
|
|
108
|
+ err_l = [] #Stores errors
|
66
|
109
|
excepted = set()
|
67
|
110
|
internal = set() #Only used if not allow_internal
|
68
|
111
|
if not allow_internal:
|
|
@@ -77,7 +120,6 @@ class _LeCrud(object):
|
77
|
120
|
internal = set(internal)
|
78
|
121
|
else:
|
79
|
122
|
excepted = set( cls.fieldtypes().keys() )
|
80
|
|
-
|
81
|
123
|
provided = set(datas.keys())
|
82
|
124
|
|
83
|
125
|
#Searching unknown fields
|
|
@@ -94,7 +136,6 @@ class _LeCrud(object):
|
94
|
136
|
wrong_internal = provided & internal
|
95
|
137
|
for wint in wrong_internal:
|
96
|
138
|
err_l.append(AttributeError("A value was provided for the field '%s' but it is marked as internal"%wint))
|
97
|
|
-
|
98
|
139
|
#Datas check
|
99
|
140
|
checked_datas = dict()
|
100
|
141
|
for name, value in [ (name, value) for name, value in datas.items() if name in (excepted | internal) ]:
|
|
@@ -104,13 +145,8 @@ class _LeCrud(object):
|
104
|
145
|
|
105
|
146
|
if len(missing_data) > 0:
|
106
|
147
|
raise LeObjectError("The argument complete was True but some fields are missing : %s" % (missing_data))
|
107
|
|
-
|
108
|
148
|
return checked_datas
|
109
|
149
|
|
110
|
|
- @classmethod
|
111
|
|
- def fieldlist(cls):
|
112
|
|
- return cls.fieldtypes().keys()
|
113
|
|
-
|
114
|
150
|
## @brief Retrieve a collection of lodel editorial components
|
115
|
151
|
#
|
116
|
152
|
# @param query_filters list : list of string of query filters (or tuple (FIELD, OPERATOR, VALUE) ) see @ref leobject_filters
|
|
@@ -135,7 +171,84 @@ class _LeCrud(object):
|
135
|
171
|
db_datas = cls._datasource.get(cls, filters, relational_filters)
|
136
|
172
|
|
137
|
173
|
return [ cls(**datas) for datas in db_datas]
|
138
|
|
-
|
|
174
|
+
|
|
175
|
+ ## @brief Given filters delete components
|
|
176
|
+ # @param filters list : list of string of query filters (or tuple (FIELD, OPERATOR, VALUE) ) see @ref leobject_filters
|
|
177
|
+ # @return the number of deleted components
|
|
178
|
+ # @todo Check for Abstract calls (with cls == LeCrud)
|
|
179
|
+ @classmethod
|
|
180
|
+ def delete_multi(cls, filters):
|
|
181
|
+ filters, rel_filters = cls._prepare_filters(filters)
|
|
182
|
+ return cls._datasource.delete(cls, filters, rel_filters)
|
|
183
|
+
|
|
184
|
+ ## @brief Insert a new component
|
|
185
|
+ # @param datas dict : The value of object we want to insert
|
|
186
|
+ # @return A new id if success else False
|
|
187
|
+ @classmethod
|
|
188
|
+ def insert(cls, datas):
|
|
189
|
+ insert_datas = self.prepare_datas(datas, complete = True, allow_internal = False)
|
|
190
|
+ return cls._datasource.insert(cls, insert_datas)
|
|
191
|
+
|
|
192
|
+ ## @brief Check and prepare datas
|
|
193
|
+ #
|
|
194
|
+ # @warning when complete = False we are not able to make construct_datas() and _check_data_consistency()
|
|
195
|
+ #
|
|
196
|
+ # @param datas dict : {fieldname : fieldvalue, ...}
|
|
197
|
+ # @param complete bool : If True you MUST give all the datas
|
|
198
|
+ # @param allow_internal : Wether or not interal fields are expected in datas
|
|
199
|
+ # @return Datas ready for use
|
|
200
|
+ @classmethod
|
|
201
|
+ def prepare_datas(cls, datas, complete = False, allow_internal = True):
|
|
202
|
+ if not complete:
|
|
203
|
+ warnings.warn("Actual implementation can make datas construction and consitency checks fails when datas are not complete")
|
|
204
|
+ ret_dats = self.check_datas_value(cls, datas, complete, allow_internal)
|
|
205
|
+ ret_datas = self._construct_datas(cls, ret_datas)
|
|
206
|
+ ret_datas = self._check_data_consistency(cls, ret_datas)
|
|
207
|
+ return ret_datas
|
|
208
|
+
|
|
209
|
+ #-###################-#
|
|
210
|
+ # Private methods #
|
|
211
|
+ #-###################-#
|
|
212
|
+
|
|
213
|
+ ## @brief Build a filter to select an object with a specific ID
|
|
214
|
+ # @warning assert that the uid is not composed with multiple fieldtypes
|
|
215
|
+ # @return A filter of the form tuple(UID, '=', self.UID)
|
|
216
|
+ def _id_filter(self):
|
|
217
|
+ id_name = self._uid_fieldtype.keys()[0]
|
|
218
|
+ return ( id_name, '=', getattr(self, id_name) )
|
|
219
|
+
|
|
220
|
+ ## @brief Construct datas values
|
|
221
|
+ #
|
|
222
|
+ # @warning assert that datas is complete
|
|
223
|
+ #
|
|
224
|
+ # @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods
|
|
225
|
+ # @return A new dict of datas
|
|
226
|
+ # @todo Decide wether or not the datas are modifed inplace or returned in a new dict (second solution for the moment)
|
|
227
|
+ @classmethod
|
|
228
|
+ def _construct_datas(cls, datas):
|
|
229
|
+ res_datas = dict()
|
|
230
|
+ for fname, ftype in cls.fieldtypes().items():
|
|
231
|
+ if fname in datas:
|
|
232
|
+ res_datas[fname] = ftype.construct_data(datas)
|
|
233
|
+ return res_datas
|
|
234
|
+ ## @brief Check datas consistency
|
|
235
|
+ #
|
|
236
|
+ # @warning assert that datas is complete
|
|
237
|
+ #
|
|
238
|
+ # @param datas dict : Datas that have been returned by LeCrud._construct_datas() method
|
|
239
|
+ # @throw LeApiDataCheckError if fails
|
|
240
|
+ @classmethod
|
|
241
|
+ def _check_datas_consistency(cls, datas):
|
|
242
|
+ err_l = []
|
|
243
|
+ for fname, ftype in cls.fieldtypes().items():
|
|
244
|
+ ret = ftype.check_data_consistency(datas)
|
|
245
|
+ if isinstance(ret, Exception):
|
|
246
|
+ err_l.append(ret)
|
|
247
|
+
|
|
248
|
+ if len(err_l) > 0:
|
|
249
|
+ raise LeApiDataCheckError("Datas consistency checks fails", err_l)
|
|
250
|
+
|
|
251
|
+
|
139
|
252
|
## @brief Prepare a field_list
|
140
|
253
|
# @param field_list list : List of string representing fields
|
141
|
254
|
# @return A well formated field list
|
|
@@ -174,13 +287,6 @@ class _LeCrud(object):
|
174
|
287
|
return ValueError("No such field '%s' in %s"%(field, cls.__name__))
|
175
|
288
|
return field
|
176
|
289
|
|
177
|
|
- ## @brief Check if a field is relational or not
|
178
|
|
- # @param field str : the field to test
|
179
|
|
- # @return True if the field is relational else False
|
180
|
|
- @staticmethod
|
181
|
|
- def _field_is_relational(field):
|
182
|
|
- return field.startswith('superior.') or field.startswith('subordinate')
|
183
|
|
-
|
184
|
290
|
## @brief Prepare filters for datasource
|
185
|
291
|
#
|
186
|
292
|
# This method divide filters in two categories :
|
|
@@ -193,7 +299,6 @@ class _LeCrud(object):
|
193
|
299
|
# @return a tuple(FILTERS, RELATIONNAL_FILTERS
|
194
|
300
|
#
|
195
|
301
|
# @see @ref datasource_side
|
196
|
|
-
|
197
|
302
|
@classmethod
|
198
|
303
|
def _prepare_filters(cls, filters_l):
|
199
|
304
|
filters = list()
|
|
@@ -258,11 +363,12 @@ class _LeCrud(object):
|
258
|
363
|
cls._query_re = re.compile('^\s*(?P<field>(((superior)|(subordinate))\.)?[a-z_][a-z0-9\-_]*)\s*'+op_re_piece+'\s*(?P<value>[^<>=!].*)\s*$', flags=re.IGNORECASE)
|
259
|
364
|
pass
|
260
|
365
|
|
|
366
|
+ ## @brief Check if a field is relational or not
|
|
367
|
+ # @param field str : the field to test
|
|
368
|
+ # @return True if the field is relational else False
|
|
369
|
+ @staticmethod
|
|
370
|
+ def _field_is_relational(field):
|
|
371
|
+ return field.startswith('superior.') or field.startswith('subordinate')
|
|
372
|
+
|
261
|
373
|
|
262
|
|
-
|
263
|
|
-class LeApiQueryError(EditorialModel.fieldtypes.generic.FieldTypeDataCheckError):
|
264
|
|
- pass
|
265
|
374
|
|
266
|
|
-class LeApiDataCheckError(EditorialModel.fieldtypes.generic.FieldTypeDataCheckError):
|
267
|
|
- pass
|
268
|
|
-
|