Browse Source

Tests + bugfixes on LeCrud LeObject LeClass and LeType delete and get methods

Yann Weber 9 years ago
parent
commit
bbdee7d082
6 changed files with 198 additions and 320 deletions
  1. 30
    31
      leapi/lecrud.py
  2. 53
    85
      leapi/leobject.py
  3. 20
    39
      leapi/letype.py
  4. 60
    0
      leapi/test/test_lecrud.py
  5. 23
    135
      leapi/test/test_leobject.py
  6. 12
    30
      leapi/test/test_letype.py

+ 30
- 31
leapi/lecrud.py View File

@@ -21,9 +21,6 @@ class LeApiErrors(Exception):
21 21
             msg += " {expt_name}:{expt_msg}; ".format(expt_name=expt.__class__.__name__, expt_msg=str(expt))
22 22
         return msg
23 23
 
24
-    def __repr__(self):
25
-        return str(self)
26
-
27 24
 
28 25
 ## @brief When an error concern a query
29 26
 class LeApiQueryError(LeApiErrors): pass
@@ -82,11 +79,13 @@ class _LeCrud(object):
82 79
     def fieldlist(cls):
83 80
         return cls.fieldtypes().keys()
84 81
     
85
-    ## @return a dict of fieldname : value
86
-    def datas(self):
82
+    ## @brief Returns object datas
83
+    # @param
84
+    # @return a dict of fieldname : value
85
+    def datas(self, internal = False):
87 86
         res = dict()
88
-        for fname in self.fieldtypes().keys():
89
-            if hasattr(self, fname):
87
+        for fname, ftt in self.fieldtypes().items():
88
+            if (internal or (not internal and ftt.is_internal)) and hasattr(self, fname):
90 89
                 res[fname] = getattr(self, fname)
91 90
     
92 91
     ## @brief Update a component in DB
@@ -105,13 +104,12 @@ class _LeCrud(object):
105 104
             #ERROR HANDLING
106 105
             return False
107 106
     
108
-    ## @brief Delete a component
107
+    ## @brief Delete a component (instance method)
109 108
     # @return True if success
110 109
     # @todo better error handling
111
-    def delete(self):
110
+    def _delete(self):
112 111
         filters = [self._id_filter()]
113
-        rel_filters = []
114
-        ret = self._datasource.delete(self.__class__, filters, rel_filters)
112
+        ret = _LeCrud.delete(self.__class__, filters)
115 113
         if ret == 1:
116 114
             return True
117 115
         else:
@@ -157,6 +155,14 @@ class _LeCrud(object):
157 155
         if len(err_l) > 0:
158 156
             raise LeApiDataCheckError("Error while checking datas", err_l)
159 157
         return checked_datas
158
+    
159
+    ## @brief Given filters delete editorial components
160
+    # @param filters list : 
161
+    # @return The number of deleted components
162
+    @staticmethod
163
+    def delete(cls, filters):
164
+        filters, rel_filters = cls._prepare_filters(filters)
165
+        return cls._datasource.delete(cls, filters, rel_filters)
160 166
 
161 167
     ## @brief Retrieve a collection of lodel editorial components
162 168
     #
@@ -166,39 +172,28 @@ class _LeCrud(object):
166 172
     # @todo think about LeObject and LeClass instanciation (partial instanciation, etc)
167 173
     @classmethod
168 174
     def get(cls, query_filters, field_list = None):
169
-        if not(isinstance(cls, cls.name2class('LeObject'))) and not(isinstance(cls, cls.name2class('LeRelation'))):
170
-            raise NotImplementedError("Cannot call get with LeCrud")
171
-
172 175
         if field_list is None or len(field_list) == 0:
173 176
             #default field_list
174
-            field_list = cls.default_fieldlist()
177
+            field_list = cls.fieldlist()
175 178
 
176
-        field_list = cls.prepare_field_list(field_list) #Can raise LeApiDataCheckError
179
+        field_list = cls._prepare_field_list(field_list) #Can raise LeApiDataCheckError
177 180
 
178 181
         #preparing filters
179
-        filters, relational_filters = cls._prepare_filters(field_list)
182
+        filters, relational_filters = cls._prepare_filters(query_filters)
180 183
 
181 184
         #Fetching datas from datasource
182
-        db_datas = cls._datasource.get(cls, filters, relational_filters)
185
+        db_datas = cls._datasource.select(cls, field_list, filters, relational_filters)
183 186
 
184 187
         return [ cls(**datas) for datas in db_datas]
185 188
     
186
-    ## @brief Given filters delete components
187
-    # @param filters list : list of string of query filters (or tuple (FIELD, OPERATOR, VALUE) ) see @ref leobject_filters
188
-    # @return the number of deleted components
189
-    # @todo Check for Abstract calls (with cls == LeCrud)
190
-    @classmethod
191
-    def delete_multi(cls, filters):
192
-        filters, rel_filters = cls._prepare_filters(filters)
193
-        return cls._datasource.delete(cls, filters, rel_filters)
194
-    
195 189
     ## @brief Insert a new component
196 190
     # @param datas dict : The value of object we want to insert
197 191
     # @return A new id if success else False
198 192
     @classmethod
199
-    def insert(cls, datas):
200
-        insert_datas = cls.prepare_datas(datas, complete = True, allow_internal = False)
201
-        return cls._datasource.insert(cls, **insert_datas)
193
+    def insert(cls, datas = datas, classname = None):
194
+        callcls = cls if classname is None else cls.name2class(classname)
195
+        insert_datas = callcls.prepare_datas(datas, complete = True, allow_internal = False)
196
+        return callcls._datasource.insert(callcls, **insert_datas)
202 197
     
203 198
     ## @brief Check and prepare datas
204 199
     # 
@@ -213,6 +208,8 @@ class _LeCrud(object):
213 208
         if not complete:
214 209
             warnings.warn("Actual implementation can make datas construction and consitency checks fails when datas are not complete")
215 210
         ret_datas = cls.check_datas_value(datas, complete, allow_internal)
211
+        if isinstance(ret_datas, Exception):
212
+            raise ret_datas
216 213
         ret_datas = cls._construct_datas(ret_datas)
217 214
         cls._check_datas_consistency(ret_datas)
218 215
         return ret_datas
@@ -225,7 +222,7 @@ class _LeCrud(object):
225 222
     # @warning assert that the uid is not composed with multiple fieldtypes
226 223
     # @return A filter of the form tuple(UID, '=', self.UID)
227 224
     def _id_filter(self):
228
-        id_name = self._uid_fieldtype.keys()[0]
225
+        id_name = list(self._uid_fieldtype.keys())[0]
229 226
         return ( id_name, '=', getattr(self, id_name) )
230 227
 
231 228
     ## @brief Construct datas values
@@ -266,6 +263,7 @@ class _LeCrud(object):
266 263
     # @throw LeApiDataCheckError if invalid field given
267 264
     @classmethod
268 265
     def _prepare_field_list(cls, field_list):
266
+        err_l = list()
269 267
         ret_field_list = list()
270 268
         for field in field_list:
271 269
             if cls._field_is_relational(field):
@@ -316,6 +314,7 @@ class _LeCrud(object):
316 314
         res_filters = list()
317 315
         rel_filters = list()
318 316
         err_l = list()
317
+        #Splitting in tuple if necessary
319 318
         for fil in filters_l:
320 319
             if len(fil) == 3 and not isinstance(fil, str):
321 320
                 filters.append(tuple(fil))

+ 53
- 85
leapi/leobject.py View File

@@ -11,6 +11,7 @@
11 11
 # @note LeObject will be generated by leapi.lefactory.LeFactory
12 12
 
13 13
 import re
14
+import copy
14 15
 import warnings
15 16
 
16 17
 import leapi
@@ -56,9 +57,50 @@ class _LeObject(_LeCrud):
56 57
             cls._fieldtypes_all.update(cls._leo_fieldtypes)
57 58
         return cls._fieldtypes_all
58 59
 
60
+    @classmethod
61
+    def typefilter(cls):
62
+        if hasattr(cls, '_type_id'):
63
+            return ('type_id','=', cls._type_id)
64
+        elif hasattr(cls, '_class_id'):
65
+            return ('class_id', '=', cls._class_id)
66
+        else:
67
+            raise ValueError("Cannot generate a typefilter with %s class"%cls.__name__)
68
+    
69
+    ## @brief Delete LeObjects from db given filters and a classname
70
+    # @note if no classname given, take the caller class
71
+    # @param filters list : 
72
+    # @param classname None|str : the classname or None
73
+    # @return number of deleted LeObjects
74
+    # @see leapi.lecrud._LeCrud.delete()
75
+    @classmethod
76
+    def delete(cls, filters, classname = None):
77
+        ccls = cls if classname is None else cls.name2class(classname)
78
+        new_filters = copy.copy(filters)
79
+        new_filters.append(ccls.typefilter())
80
+        return _LeCrud.delete(ccls, new_filters)
81
+
82
+    ## @brief Check that a relational field is valid
83
+    # @param field str : a relational field
84
+    # @return a nature
85
+    @staticmethod
86
+    def _prepare_relational_field(field):
87
+        spl = field.split('.')
88
+        if len(spl) != 2:
89
+            return ValueError("The relationalfield '%s' is not valid"%field)
90
+        nature = spl[-1]
91
+        if nature not in EditorialModel.classtypes.EmNature.getall():
92
+            return ValueError("'%s' is not a valid nature in the field %s"%(nature, field))
93
+        
94
+        if spl[0] == 'superior':
95
+            return (REL_SUP, nature)
96
+        elif spl[0] == 'subordinate':
97
+            return (REL_SUB, nature)
98
+        else:
99
+            return ValueError("Invalid preffix for relationnal field : '%s'"%spl[0])
100
+
59 101
     ## @brief Check if a LeType is a hierarchy root
60 102
     @staticmethod
61
-    def is_root(leo):
103
+    def ___is_root(leo):
62 104
         if isinstance(leo, leapi.letype.LeType):
63 105
             return False
64 106
         elif isinstance(leo, LeRoot):
@@ -67,33 +109,9 @@ class _LeObject(_LeCrud):
67 109
     
68 110
     ## @brief Return a LeRoot instance
69 111
     @staticmethod
70
-    def get_root():
112
+    def ___get_root():
71 113
         return LeRoot()
72 114
 
73
-    ## @brief Delete LeObjects given filters
74
-    # @param cls
75
-    # @param letype LeType|str : LeType child class or name
76
-    # @param leclass LeClass|str : LeClass child class or name
77
-    # @param filters list : list of filters (see @ref leobject_filters)
78
-    # @return bool
79
-    @classmethod
80
-    def delete(cls, letype, filters):
81
-        filters,relationnal_filters = cls._prepare_filters(filters)
82
-        return cls._datasource.delete(letype, leclass, filters, relationnal_filters)
83
-    
84
-    ## @brief Update LeObjects given filters and datas
85
-    # @param cls
86
-    # @param letype LeType|str : LeType child class or name
87
-    # @param filters list : list of filters (see @ref leobject_filters)
88
-    @classmethod
89
-    def update(cls, letype, filters, datas):
90
-        letype, leclass = cls._prepare_targets(letype)
91
-        filters,relationnal_filters = cls._prepare_filters(filters, letype, leclass)
92
-        if letype is None:
93
-            raise ValueError("Argument letype cannot be None")
94
-        letype.prepare_data(datas, False)
95
-        return cls._datasource.update(letype, leclass, filters, relationnal_filters, datas)
96
-
97 115
     ## @brief Link two leobject together using a rel2type field
98 116
     # @param lesup LeType : LeType child class instance linked as superior
99 117
     # @param lesub LeType : LeType child class instance linked as subordinate
@@ -107,7 +125,7 @@ class _LeObject(_LeCrud):
107 125
     # @todo Code factorisation on relation check
108 126
     # @todo unit tests
109 127
     @classmethod
110
-    def link_together(cls, lesup, lesub, rank = 'last', **rel_attr):
128
+    def ___link_together(cls, lesup, lesub, rank = 'last', **rel_attr):
111 129
         if lesub.__class__ not in lesup._linked_types.keys():
112 130
             raise LeObjectError("Relation error : %s cannot be linked with %s"%(lesup.__class__.__name__, lesub.__class__.__name__))
113 131
 
@@ -135,7 +153,7 @@ class _LeObject(_LeCrud):
135 153
     # @todo Code factorisation on relation check
136 154
     # @todo unit tests
137 155
     @classmethod
138
-    def linked_together(cls, leo, letype, leo_is_superior = True):
156
+    def ___linked_together(cls, leo, letype, leo_is_superior = True):
139 157
         valid_link = letype in leo._linked_types.keys() if leo_is_superior else leo.__class__ in letype._linked_types.keys()
140 158
 
141 159
         if not valid_link:
@@ -151,13 +169,13 @@ class _LeObject(_LeCrud):
151 169
     # @return a tuple(lesup, lesub, dict_attr) or False if no relation exists with this id
152 170
     # @throw Exception if the relation is not a rel2type relation
153 171
     @classmethod
154
-    def link_get(cls, id_relation):
172
+    def ___link_get(cls, id_relation):
155 173
         return cls._datasource.get_relation(id_relation)
156 174
     
157 175
     ## @brief Fetch all relations for an objects
158 176
     # @param leo LeType : LeType child class instance
159 177
     # @return a list of tuple (lesup, lesub, dict_attr)
160
-    def links_get(cls, leo):
178
+    def ___links_get(cls, leo):
161 179
         return cls._datasource.get_relations(leo)
162 180
     
163 181
     ## @brief Remove a link (and attributes) between two LeObject
@@ -168,7 +186,7 @@ class _LeObject(_LeCrud):
168 186
     # @todo Code factorisation on relation check
169 187
     # @todo unit tests
170 188
     @classmethod
171
-    def link_remove(cls, id_relation):
189
+    def ___link_remove(cls, id_relation):
172 190
         if lesub.__class__ not in lesup._linked_types.keys():
173 191
             raise LeObjectError("Relation errorr : %s cannot be linked with %s"%(lesup.__class__.__name__, lesub.__class__.__name__))
174 192
 
@@ -183,7 +201,7 @@ class _LeObject(_LeCrud):
183 201
     # @return The relation ID or False if fails
184 202
     # @throw LeObjectQueryError replace_if_exists == False and there is a superior
185 203
     @classmethod
186
-    def hierarchy_add(cls, lesup, lesub, nature, rank = 'last', replace_if_exists = False):
204
+    def ___hierarchy_add(cls, lesup, lesub, nature, rank = 'last', replace_if_exists = False):
187 205
         #Arguments check
188 206
         if nature not in EditorialModel.classtypes.EmClassType.natures(lesub._classtype):
189 207
             raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
@@ -216,7 +234,7 @@ class _LeObject(_LeCrud):
216 234
     # @return True if deletion done successfully
217 235
     # @throw ValueError when bad arguments given
218 236
     @classmethod
219
-    def hierarchy_del(cls, lesup, lesub, nature):
237
+    def ___hierarchy_del(cls, lesup, lesub, nature):
220 238
         if nature not in EditorialModel.classtypes.EmClassType.natures(lesub._classtype):
221 239
             raise ValueError("Invalid nature '%s' for %s"%(nature, lesup.__class__.__name__))
222 240
 
@@ -239,7 +257,7 @@ class _LeObject(_LeCrud):
239 257
     # @param leo_is_sup bool : if True leo is the superior and we want to fetch the subordinates else its the oposite
240 258
     # @return A list of LeObject ordered by depth if leo_is_sup, else a list of subordinates
241 259
     @classmethod
242
-    def hierarchy_get(cls, leo, nature, leo_is_sup = True):
260
+    def ___hierarchy_get(cls, leo, nature, leo_is_sup = True):
243 261
         #Checking arguments
244 262
         if not (nature is None) and not cls.is_root(leo):
245 263
             if nature not in EditorialModel.classtypes.EmClassType.natures(leo._classtype):
@@ -262,7 +280,7 @@ class _LeObject(_LeCrud):
262 280
     # @param leclass LeClass|str|None : LeClass child instant or its name
263 281
     # @return a tuple with 2 python classes (LeTypeChild, LeClassChild)
264 282
     @classmethod
265
-    def _prepare_targets(cls, letype = None , leclass = None):
283
+    def ___prepare_targets(cls, letype = None , leclass = None):
266 284
         warnings.warn("_LeObject._prepare_targets is deprecated", DeprecationWarning)
267 285
         raise ValueError()
268 286
         if not(leclass is None):
@@ -286,56 +304,6 @@ class _LeObject(_LeCrud):
286 304
 
287 305
         return (letype, leclass)
288 306
 
289
-    ## @brief Check if a fieldname is valid
290
-    # @warning This method assume that letype and leclass are returned from _LeObject._prepare_targets() method
291
-    # @param letype LeType|None : The concerned type (or None)
292
-    # @param leclass LeClass|None : The concerned class (or None)
293
-    # @param fields list : List of string representing fields
294
-    # @throw LeObjectQueryError if their is some problems
295
-    # @throw AttributeError if letype is not from the leclass class
296
-    # @todo Delete the checks of letype and leclass and ensure that this method is called with letype and leclass arguments from _prepare_targets()
297
-    #
298
-    # @see @ref leobject_filters
299
-    # @todo delete me
300
-    @staticmethod
301
-    def _check_fields(letype, leclass, fields):
302
-        warnings.warn("deprecated", DeprecationWarning)
303
-        #Checking that fields in the query_filters are correct
304
-        if letype is None and leclass is None:
305
-            #Only fields from the object table are allowed
306
-            for field in fields:
307
-                if field not in EditorialModel.classtypes.common_fields.keys():
308
-                    raise LeObjectQueryError("Not typename and no classname given, but the field %s is not in the common_fields list"%field)
309
-        else:
310
-            if letype is None:
311
-                field_l = leclass._fieldtypes.keys()
312
-            else:
313
-                field_l = letype._fields
314
-            #Checks that fields are in this type
315
-            for field in fields:
316
-                if field not in field_l:
317
-                    raise LeObjectQueryError("No field named '%s' in '%s'"%(field, letype.__name__))
318
-        pass
319
-    
320
-    ## @brief Check that a relational field is valid
321
-    # @param field str : a relational field
322
-    # @return a nature
323
-    @staticmethod
324
-    def _prepare_relational_field(field):
325
-        spl = field.split('.')
326
-        if len(spl) != 2:
327
-            return ValueError("The relationalfield '%s' is not valid"%field)
328
-        nature = spl[-1]
329
-        if nature not in EditorialModel.classtypes.EmNature.getall():
330
-            return ValueError("'%s' is not a valid nature in the field %s"%(nature, field))
331
-        
332
-        if spl[0] == 'superior':
333
-            return (REL_SUP, nature)
334
-        elif spl[0] == 'subordinate':
335
-            return (REL_SUB, nature)
336
-        else:
337
-            return ValueError("Invalid preffix for relationnal field : '%s'"%spl[0])
338
-
339 307
 ## @brief Class designed to represent the hierarchy roots
340 308
 # @see _LeObject.get_root() _LeObject.is_root()
341 309
 class LeRoot(object):

+ 20
- 39
leapi/letype.py View File

@@ -11,6 +11,7 @@
11 11
 # @note LeObject will be generated by leapi.lefactory.LeFactory
12 12
 
13 13
 import leapi
14
+from leapi.lecrud import _LeCrud
14 15
 from leapi.leclass import _LeClass
15 16
 from leapi.leobject import LeObjectError
16 17
 
@@ -48,7 +49,7 @@ class _LeType(_LeClass):
48 49
             if name not in self._fields:
49 50
                 raise AttributeError("No such field '%s' for %s"%(name, self.__class__.__name__))
50 51
             setattr(self, name, value)
51
-    
52
+
52 53
     @classmethod
53 54
     def fieldlist(cls):
54 55
         return cls._fields
@@ -62,15 +63,15 @@ class _LeType(_LeClass):
62 63
     def populate(self, field_list=None):
63 64
         if field_list == None:
64 65
             field_list = [ fname for fname in self._fields if not hasattr(self, fname) ]
65
-        filters, rel_filters = self._prepare_filters(['lodel_id = %d'%(self.lodel_id)], self.__class__, self._leclass)
66
+        filters = [self._id_filter()]
67
+        rel_filters = []
66 68
 
67
-        fdatas = self._datasource.get(self._leclass, self.__class__, field_list, filters, rel_filters)
69
+        fdatas = self._datasource.select(self.__class__, field_list, filters, rel_filters)
68 70
         for fname, fdats in fdatas[0].items():
69 71
             setattr(self, name, value)
70 72
 
71 73
     ## @brief Get a fieldname:value dict
72 74
     # @return A dict with field name as key and the field value as value
73
-    @property
74 75
     def datas(self):
75 76
         return { fname: getattr(self, fname) for fname in self._fields if hasattr(self,fname) }
76 77
     
@@ -79,18 +80,17 @@ class _LeType(_LeClass):
79 80
     # @warning Can represent a performance issue
80 81
     def all_datas(self):
81 82
         self.populate()
82
-        return self.datas
83
-    
84
-    ## @brief Delete the LeType from Db
85
-    # @note equivalent to LeType::delete(filters=['lodel_id = %s'%self.lodel_id)
86
-    def db_delete(self):
87
-        return self.delete([('lodel_id','=',repr(self.lodel_id))])
83
+        return self.datas()
88 84
     
85
+    ## @brief Delete current instance from DB
86
+    def delete(self):
87
+        _LeCrud._delete(self)
88
+
89 89
     ## @brief Get the linked objects lodel_id
90 90
     # @param letype LeType : Filter the result with LeType child class (not instance) 
91 91
     # @return a dict with LeType instance as key and dict{attr_name:attr_val...} as value
92 92
     # @todo unit tests
93
-    def linked(self, letype):
93
+    def ___linked(self, letype):
94 94
         if leapi.letype.LeType not in letype.__bases__:
95 95
             raise ValueError("letype has to be a child class of LeType (not an instance) but %s found"%type(letype))
96 96
         
@@ -113,7 +113,7 @@ class _LeType(_LeClass):
113 113
     # @throw AttributeError if an non existing relation attribute is given as argument
114 114
     # @throw ValueError if the relation attrivute value check fails
115 115
     # @see leapi.lefactory.LeFactory.link_together()
116
-    def link(self, leo, **rel_attr):
116
+    def ___link(self, leo, **rel_attr):
117 117
         return leapi.lefactory.LeFactory.leobj_from_name('LeObject').link_together(self, leo, **rel_attr)
118 118
     
119 119
     ## @brief Returns linked subordinates in a rel2type given a wanted LeType child class
@@ -121,20 +121,20 @@ class _LeType(_LeClass):
121 121
     # @return A dict with LeType child class instance as key and dict {'id_relation':id, rel_attr_name:rel_attr_value, ...}
122 122
     # @throw LeObjectError if the relation is not possible
123 123
     # @see leapi.lefactory.LeFactory.linked_together()
124
-    def linked_subordinates(self, letype):
124
+    def ___linked_subordinates(self, letype):
125 125
         return leapi.lefactory.LeFactory.leobj_from_name('LeObject').linked_together(self, letype, True)
126 126
 
127 127
     ## @brief Remove a link with a subordinate
128 128
     # @param leo LeType : LeType child instance
129 129
     # @return True if a link has been deleted
130 130
     # @throw LeObjectError if the relation do not concern the current LeType
131
-    def unlink_subordinate(self, id_relation):
131
+    def ___unlink_subordinate(self, id_relation):
132 132
         return leapi.lefactory.LeFactory.leobj_from_name('LeObject').linked_together(self, leo)
133 133
 
134 134
     ## @brief Remove a link bewteen this object and another
135 135
     # @param leo LeType : LeType child class instance
136 136
     # @todo unit tests
137
-    def unlink(self, leo):
137
+    def ___unlink(self, leo):
138 138
         if leo.__class__ in self._linked_types.keys():
139 139
             sup = self
140 140
             sub = leo
@@ -150,46 +150,27 @@ class _LeType(_LeClass):
150 150
     # @param rank str|int :  The relation rank. Can be 'last', 'first' or an integer
151 151
     # @param replace_if_exists bool : if True delete the old superior and set the new one. If False and there is a superior raise an LeObjectQueryError
152 152
     # @return The relation ID or False if fails
153
-    def superior_add(self, leo, nature, rank = 'last', replace_if_exists = False):
153
+    def ___superior_add(self, leo, nature, rank = 'last', replace_if_exists = False):
154 154
         return leapi.lefactory.LeFactory.leobj_from_name('LeObject').hierarchy_add(leo, self, nature, rank, replace_if_exists)
155 155
 
156 156
     ## @brief Delete a superior given a relation's natue
157 157
     # @param leo LeType | LeRoot : The superior to delete
158 158
     # @param nature str : The nature of the relation @ref EditorialModel.classtypes
159 159
     # @return True if deletion is a success
160
-    def superior_del(self, leo, nature):
160
+    def ___superior_del(self, leo, nature):
161 161
         return leapi.lefactory.leobj_from_name('LeObject').hierarchy_del(leo, self, nature)
162 162
     
163 163
     ## @brief Fetch superiors by depth
164 164
     # @return A list of LeObject ordered by depth (the first is the one with the bigger depth)
165
-    def superiors(self):
165
+    def ___superiors(self):
166 166
         return leapi.lefactory.leobj_from_name('LeObject').hierarchy_get(self,nature, leo_is_sup = False)
167 167
 
168 168
     ## @brief Fetch subordinates ordered by rank
169 169
     # @return A list of LeObject ordered by rank
170
-    def subordinates(self):
170
+    def ___subordinates(self):
171 171
         return leapi.lefactory.leobj_from_name('LeObject').hierarchy_get(self,nature, leo_is_sup = True)
172 172
     
173
-    ## @brief Delete a LeType from the datasource
174
-    # @param filters list : list of filters (see @ref leobject_filters)
175
-    # @param cls
176
-    # @return True if deleted False if not existing
177
-    # @throw InvalidArgumentError if invalid parameters
178
-    # @throw Leo exception if the lodel_id identify an object from another type
179
-    @classmethod
180
-    def delete(cls, filters):
181
-        return leapi.lefactory.LeFactory.leobject().delete(cls, filters)
182
-        
183 173
     ## @brief Update a LeType in db
184
-    def db_update(self):
174
+    def ___db_update(self):
185 175
         return self.update(filters=[('lodel_id', '=', repr(self.lodel_id))], datas = self.datas)
186 176
         
187
-    @classmethod
188
-    ## @brief Update some LeType in db
189
-    # @param datas : keys are lodel_id and value are dict of fieldname => value
190
-    # @param filters list : list of filters (see @ref leobject_filters)
191
-    # @param cls
192
-    # return bool
193
-    def update(cls, filters, datas):
194
-        return leapi.lefactory.LeFactory.leobject().update(letype = cls, filters = filters, datas = datas)
195
-        

+ 60
- 0
leapi/test/test_lecrud.py View File

@@ -179,5 +179,65 @@ class LeCrudTestCase(TestCase):
179 179
         for lecclass, ndats in ndatas:
180 180
             with self.assertRaises(leapi.lecrud.LeApiDataCheckError, msg="But trying to insert %s as %s"%(ndats, lecclass.__name__)):
181 181
                 lecclass.insert(ndats)
182
+                assert not dsmock.called
182 183
         pass
184
+    
185
+    ## @todo test invalid get
186
+    @patch('leapi.datasources.dummy.DummyDatasource.select')
187
+    def test_get(self, dsmock):
188
+        from dyncode import Publication, Numero, LeObject, Textes
189
+        
190
+        args = [
191
+            (
192
+                Numero,
193
+                ['lodel_id', 'superior.parent'],
194
+                ['titre != "foobar"'],
195
+
196
+                ['lodel_id', (leapi.leobject.REL_SUP, 'parent')],
197
+                [('titre','!=', '"foobar"')],
198
+                []
199
+            ),
200
+            (
201
+                Numero,
202
+                ['lodel_id', 'titre', 'superior.parent', 'subordinate.translation'],
203
+                ['superior.parent in  [1,2,3,4,5]'],
204
+
205
+                ['lodel_id', 'titre', (leapi.leobject.REL_SUP,'parent'), (leapi.leobject.REL_SUB, 'translation')],
206
+                [],
207
+                [( (leapi.leobject.REL_SUP, 'parent'), ' in ', '[1,2,3,4,5]')]
208
+            ),
209
+            (
210
+                Numero,
211
+                [],
212
+                [],
213
+
214
+                Numero._fields,
215
+                [],
216
+                []
217
+            ),
218
+            (
219
+                Textes,
220
+                ['lodel_id', 'titre', 'soustitre', 'superior.parent'],
221
+                ['titre != "foobar"'],
222
+
223
+                ['lodel_id', 'titre', 'soustitre', (leapi.leobject.REL_SUP, 'parent')],
224
+                [('titre','!=', '"foobar"')],
225
+                [],
226
+            ),
227
+            (
228
+                LeObject,
229
+                ['lodel_id'],
230
+                [],
231
+
232
+                ['lodel_id'],
233
+                [],
234
+                [],
235
+            ),
236
+        ]
237
+
238
+        for callcls, field_list, filters, fl_ds, filters_ds, rfilters_ds in args:
239
+            callcls.get(filters, field_list)
240
+            dsmock.assert_called_with(callcls, fl_ds, filters_ds, rfilters_ds)
241
+            dsmock.reset_mock()
242
+
183 243
 

+ 23
- 135
leapi/test/test_leobject.py View File

@@ -18,6 +18,7 @@ class LeObjectTestCase(TestCase):
18 18
     def setUpClass(cls):
19 19
         """ Write the generated code in a temporary directory and import it """
20 20
         cls.tmpdir = leapi.test.utils.tmp_load_factory_code()
21
+
21 22
     @classmethod
22 23
     def tearDownClass(cls):
23 24
         """ Remove the temporary directory created at class setup """
@@ -90,24 +91,6 @@ class LeObjectTestCase(TestCase):
90 91
         for (leclass, letype) in test_v:
91 92
             with self.assertRaises(ValueError):
92 93
                 LeObject._prepare_targets(letype, leclass)
93
-    @unittest.skip("Obsolete, the method should be deleted soon")
94
-    def test_check_fields(self):
95
-        """ Testing the _check_fields() method """
96
-        from dyncode import Publication, Numero, LeObject, Personnes
97
-        
98
-        #Valid fields given
99
-        LeObject._check_fields(None, Publication, Publication._fieldtypes.keys())
100
-        LeObject._check_fields(Numero, None, Numero._fields)
101
-
102
-        #Specials fields
103
-        LeObject._check_fields(Numero, Publication,  ['lodel_id'])
104
-        #Common fields
105
-        LeObject._check_fields(None, None, EditorialModel.classtypes.common_fields.keys())
106
-
107
-        #Invalid fields
108
-        with self.assertRaises(leapi.leobject.LeObjectQueryError):
109
-            LeObject._check_fields(None, None, Numero._fields)
110
-
111 94
 
112 95
 class LeObjectMockDatasourceTestCase(TestCase):
113 96
     """ Testing _LeObject using a mock on the datasource """
@@ -121,24 +104,24 @@ class LeObjectMockDatasourceTestCase(TestCase):
121 104
         """ Remove the temporary directory created at class setup """
122 105
         leapi.test.utils.cleanup(cls.tmpdir)
123 106
     
124
-    @unittest.skip("Wait reimplementation in lecrud")
125 107
     @patch('leapi.datasources.dummy.DummyDatasource.insert')
126 108
     def test_insert(self, dsmock):
127 109
         from dyncode import Publication, Numero, LeObject
128 110
         ndatas = [
129
-            [{'titre' : 'FooBar'}],
130
-            [{'titre':'hello'},{'titre':'world'}],
111
+            {'titre' : 'FooBar'},
112
+            {'titre':'hello'},
113
+            {'titre':'world'},
131 114
         ]
132 115
         for ndats in ndatas:
133
-            LeObject.insert(Numero,ndats)
134
-            dsmock.assert_called_once_with(Numero, Publication, ndats)
116
+            Publication.insert(ndats, classname='Numero')
117
+            dsmock.assert_called_once_with(Numero, **ndats)
135 118
             dsmock.reset_mock()
136 119
 
137
-            LeObject.insert('Numero',ndats)
138
-            dsmock.assert_called_once_with(Numero, Publication, ndats)
120
+            LeObject.insert(ndats, classname = 'Numero')
121
+            dsmock.assert_called_once_with(Numero, **ndats)
139 122
             dsmock.reset_mock()
140 123
 
141
-    @unittest.skip("Wait reimplementation in lecrud")
124
+    @unittest.skip("Wait FieldTypes modification (cf. #90) and classmethod capabilities for update")
142 125
     @patch('leapi.datasources.dummy.DummyDatasource.update')
143 126
     def test_update(self, dsmock):
144 127
         from dyncode import Publication, Numero, LeObject
@@ -165,129 +148,34 @@ class LeObjectMockDatasourceTestCase(TestCase):
165 148
             dsmock.assert_called_once_with(Numero, Publication, ds_filters, ds_relfilters, datas)
166 149
             dsmock.reset_mock()
167 150
     
168
-    @unittest.skip("Wait reimplementation in lecrud")
169 151
     @patch('leapi.datasources.dummy.DummyDatasource.delete')
170 152
     def test_delete(self, dsmock):
171
-        from dyncode import Publication, Numero, LeObject
153
+        from dyncode import Publication, Numero, LeObject, LeType
172 154
 
173 155
         args = [
174 156
             (
157
+                'Numero',
175 158
                 ['lodel_id=1'],
176
-                [('lodel_id', '=', '1')],
159
+                [('lodel_id', '=', '1'), ('type_id', '=', Numero._type_id)],
177 160
                 []
178 161
             ),
179 162
             (
163
+                'Publication',
180 164
                 ['subordinate.parent not in [1,2,3]', 'titre = "titre nul"'],
181
-                [('titre','=', '"titre nul"')],
165
+                [('titre','=', '"titre nul"'), ('class_id', '=', Publication._class_id)],
182 166
                 [( (leapi.leobject.REL_SUB, 'parent'), ' not in ', '[1,2,3]')]
183 167
             ),
184 168
         ]
185 169
 
186
-        for filters, ds_filters, ds_relfilters in args:
187
-            LeObject.delete(Numero, filters)
188
-            dsmock.assert_called_once_with(Numero, Publication, ds_filters, ds_relfilters)
189
-            dsmock.reset_mock()
170
+        for classname, filters, ds_filters, ds_relfilters in args:
171
+            ccls = LeObject.name2class(classname)
190 172
 
191
-            LeObject.delete('Numero', filters)
192
-            dsmock.assert_called_once_with(Numero, Publication, ds_filters, ds_relfilters)
173
+            LeObject.delete(filters, classname)
174
+            dsmock.assert_called_once_with(ccls, ds_filters, ds_relfilters)
193 175
             dsmock.reset_mock()
176
+            
177
+            if not (LeType in ccls.__bases__): #tests for calls with a LeClass child
178
+                ccls.delete(filters)
179
+                dsmock.assert_called_once_with(ccls, ds_filters, ds_relfilters)
180
+                dsmock.reset_mock()
194 181
         
195
-    @patch('leapi.datasources.dummy.DummyDatasource.get')
196
-    @unittest.skip('Dummy datasource doesn\'t fit anymore')
197
-    def test_get(self, dsmock):
198
-        from dyncode import Publication, Numero, LeObject
199
-        
200
-        args = [
201
-            (
202
-                ['lodel_id', 'superior.parent'],
203
-                ['titre != "foobar"'],
204
-
205
-                ['lodel_id', (leapi.leobject.REL_SUP, 'parent')],
206
-                [('titre','!=', '"foobar"')],
207
-                []
208
-            ),
209
-            (
210
-                ['lodel_id', 'titre', 'superior.parent', 'subordinate.translation'],
211
-                ['superior.parent in  [1,2,3,4,5]'],
212
-
213
-                ['lodel_id', 'titre', (leapi.leobject.REL_SUP,'parent'), (leapi.leobject.REL_SUB, 'translation')],
214
-                [],
215
-                [( (leapi.leobject.REL_SUP, 'parent'), ' in ', '[1,2,3,4,5]')]
216
-            ),
217
-            (
218
-                [],
219
-                [],
220
-
221
-                Numero._fields,
222
-                [],
223
-                []
224
-            ),
225
-        ]
226
-
227
-        for field_list, filters, fl_ds, filters_ds, rfilters_ds in args:
228
-            LeObject.get(filters, field_list, Numero)
229
-            dsmock.assert_called_with(Publication, Numero, fl_ds, filters_ds, rfilters_ds)
230
-            dsmock.reset_mock()
231
-
232
-    @patch('leapi.datasources.dummy.DummyDatasource.get')
233
-    @unittest.skip('Dummy datasource doesn\'t fit anymore')
234
-    def test_get_incomplete_target(self, dsmock):
235
-        """ Testing LeObject.get() method with partial target specifier """
236
-        from dyncode import Publication, Numero, LeObject
237
-
238
-        args = [
239
-            (
240
-                ['lodel_id'],
241
-                [],
242
-                None,
243
-                None,
244
-
245
-                ['lodel_id', 'type_id'],
246
-                [],
247
-                [],
248
-                None,
249
-                None
250
-            ),
251
-            (
252
-                [],
253
-                [],
254
-                None,
255
-                None,
256
-
257
-                list(EditorialModel.classtypes.common_fields.keys()),
258
-                [],
259
-                [],
260
-                None,
261
-                None
262
-            ),
263
-            (
264
-                ['lodel_id'],
265
-                [],
266
-                None,
267
-                Publication,
268
-
269
-                ['lodel_id', 'type_id'],
270
-                [],
271
-                [],
272
-                None,
273
-                Publication
274
-            ),
275
-            (
276
-                [],
277
-                [],
278
-                Numero,
279
-                None,
280
-
281
-                Numero._fields,
282
-                [],
283
-                [],
284
-                Numero,
285
-                Publication
286
-            ),
287
-        ]
288
-
289
-        for field_list, filters, letype, leclass, fl_ds, filters_ds, rfilters_ds, letype_ds, leclass_ds in args:
290
-            LeObject.get(filters, field_list, letype, leclass)
291
-            dsmock.assert_called_with(leclass_ds, letype_ds, fl_ds, filters_ds, rfilters_ds)
292
-            dsmock.reset_mock()
293
-

+ 12
- 30
leapi/test/test_letype.py View File

@@ -50,7 +50,7 @@ class LeTypeTestCase(TestCase):
50 50
         """ Testing the datas @property method """
51 51
         from dyncode import Publication, Numero, LeObject
52 52
         num = Numero(42, titre = 'foofoo')
53
-        self.assertEqual({'lodel_id' : 42, 'titre': 'foofoo'}, num.datas)
53
+        self.assertEqual({'lodel_id' : 42, 'titre': 'foofoo'}, num.datas())
54 54
         num.all_datas
55 55
         dsmock.assert_called_once()
56 56
 
@@ -66,49 +66,31 @@ class LeTypeMockDsTestCase(TestCase):
66 66
         """ Remove the temporary directory created at class setup """
67 67
         leapi.test.utils.cleanup(cls.tmpdir)
68 68
 
69
-    @unittest.skip('Dummy datasource doesn\'t fit anymore')
70
-    @patch('leapi.datasources.dummy.DummyDatasource.get')
69
+    @patch('leapi.datasources.dummy.DummyDatasource.select')
71 70
     def test_populate(self, dsmock):
72 71
         from dyncode import Publication, Numero, LeObject
73 72
 
74 73
         num = Numero(1, type_id = Numero._type_id)
75 74
         missing_fields = [f for f in Numero._fields if not (f in ['lodel_id', 'type_id'])]
76 75
         num.populate()
77
-        dsmock.assert_called_once_with(Publication, Numero, missing_fields, [('lodel_id','=','1')],[])
76
+        dsmock.assert_called_once_with(Numero, missing_fields, [('lodel_id','=',1)],[])
78 77
 
79
-    @unittest.skip("Waiting for reimplementation in LeCrud")
80 78
     @patch('leapi.datasources.dummy.DummyDatasource.update')
81 79
     def test_update(self, dsmock):
82 80
         from dyncode import Publication, Numero, LeObject
83 81
         
84
-        datas = { 'titre' : 'foobar', 'string': 'wow' }
85
-
86
-        Numero.update(['lodel_id = 1'], datas)
87
-        dsmock.assert_called_once_with(Numero, Publication, [('lodel_id','=','1')], [], datas)
82
+        datas = { 'titre' : 'foobar' }
83
+        
84
+        #Testing as instance method
85
+        num = Numero(lodel_id = 1)
86
+        num.update(datas)
87
+        dsmock.assert_called_once_with(Numero, [('lodel_id','=',1)], [], datas)
88 88
     
89
-    @unittest.skip("Waiting for reimplementation in LeCrud")
90 89
     @patch('leapi.datasources.dummy.DummyDatasource.delete')
91 90
     def test_delete(self, dsmock):
92 91
         from dyncode import Publication, Numero, LeObject
93 92
         
94
-        Numero.delete(['lodel_id = 1'])
95
-        dsmock.assert_called_once_with(Numero, Publication, [('lodel_id','=','1')], [])
96
-
97
-    @unittest.skip("Waiting for reimplementation in LeCrud")
98
-    @patch('leapi.datasources.dummy.DummyDatasource.update')
99
-    def test_db_update(self, dsmock):
100
-        from dyncode import Publication, Numero, LeObject
101
-        
102
-        num = Numero(1, type_id = Numero._type_id, class_id = Numero._class_id, titre = 'Hello world !')
103
-        num.db_update()
104
-        dsmock.assert_called_once_with(Numero, Publication, [('lodel_id','=','1')], [], num.datas)
105
-
106
-    @unittest.skip("Waiting for reimplementation in LeCrud")
107
-    @patch('leapi.datasources.dummy.DummyDatasource.delete')
108
-    def test_db_delete(self, dsmock):
109
-        from dyncode import Publication, Numero, LeObject
110
-
111
-        num = Numero(1, type_id = Numero._type_id, class_id = Numero._class_id, titre = 'Hello world !')
112
-        num.db_delete()
113
-        dsmock.assert_called_once_with(Numero, Publication, [('lodel_id','=','1')], [])
93
+        num = Numero(lodel_id = 1)
94
+        num.delete()
95
+        dsmock.assert_called_once_with(Numero, [('lodel_id','=',1)], [])
114 96
 

Loading…
Cancel
Save