1 ##############################################################################
3 # Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
4 # Fabien Pinckaers <fp@tiny.Be>
6 # WARNING: This program as such is intended to be used by professional
7 # programmers who take the whole responsability of assessing all potential
8 # consequences resulting from its eventual inadequacies and bugs
9 # End users who are looking for a ready-to-use solution with commercial
10 # garantees and support are strongly adviced to contract a Free Software
13 # This program is Free Software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; either version 2
16 # of the License, or (at your option) any later version.
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 ##############################################################################
30 # OSV: Objects Services
40 from netsvc import Logger, LOG_ERROR
43 module_class_list = {}
46 class except_osv(Exception):
47 def __init__(self, name, value, exc_type='warning'):
49 self.exc_type = exc_type
51 self.args = (exc_type,name)
53 class osv_pool(netsvc.Service):
57 self.module_object_list = {}
60 netsvc.Service.__init__(self, 'object_proxy', audience='')
61 self.joinGroup('web-services')
62 self.exportMethod(self.exportedMethods)
63 self.exportMethod(self.obj_list)
64 self.exportMethod(self.exec_workflow)
65 self.exportMethod(self.execute)
66 self.exportMethod(self.execute_cr)
68 def execute_cr(self, cr, uid, obj, method, *args, **kw):
70 # TODO: check security level
73 object = pooler.get_pool(cr.dbname).get(obj)
75 self.abortResponse(1, 'Object Error', 'warning',
76 'Object %s doesn\'t exist' % str(obj))
77 if (not method in getattr(object,'_protected')) and len(args) \
78 and args[0] and len(object._inherits):
79 types = {obj: args[0]}
80 cr.execute('select inst_type,inst_id,obj_id \
83 and obj_id in ('+','.join(map(str,args[0]))+')', (obj,))
84 for ty,id,id2 in cr.fetchall():
88 types[obj].remove(id2)
89 for t,ids in types.items():
91 object_t = pooler.get_pool(cr.dbname).get(t)
92 res = getattr(object_t,method)(cr, uid, ids, *args[1:], **kw)
94 res = getattr(object,method)(cr, uid, *args, **kw)
96 except orm.except_orm, inst:
97 self.abortResponse(1, inst.name, 'warning', inst.value)
98 except except_osv, inst:
99 self.abortResponse(1, inst.name, inst.exc_type, inst.value)
100 except psycopg.IntegrityError, inst:
101 for key in self._sql_error.keys():
103 self.abortResponse(1, 'Constraint Error', 'warning',
104 self._sql_error[key])
105 self.abortResponse(1, 'Integrity Error', 'warning', inst[0])
108 tb_s = reduce(lambda x, y: x+y, traceback.format_exception(
109 sys.exc_type, sys.exc_value, sys.exc_traceback))
111 logger.notifyChannel("web-services", LOG_ERROR,
112 'Exception in call: ' + tb_s)
115 def execute(self, db, uid, obj, method, *args, **kw):
116 db, pool = pooler.get_db_and_pool(db)
120 res = pool.execute_cr(cr, uid, obj, method, *args, **kw)
129 def exec_workflow_cr(self, cr, uid, obj, method, *args):
130 wf_service = netsvc.LocalService("workflow")
131 wf_service.trg_validate(uid, obj, args[0], method, cr)
134 def exec_workflow(self, db, uid, obj, method, *args):
135 cr = pooler.get_db(db).cursor()
138 res = self.exec_workflow_cr(cr, uid, obj, method, *args)
148 return self.obj_pool.keys()
150 # adds a new object instance to the object pool.
151 # if it already existed, the instance is replaced
152 def add(self, name, obj_inst):
153 if self.obj_pool.has_key(name):
154 del self.obj_pool[name]
155 self.obj_pool[name] = obj_inst
157 module = str(obj_inst.__class__)[6:]
158 module = module[:len(module)-1]
159 module = module.split('.')[0][2:]
160 self.module_object_list.setdefault(module, []).append(obj_inst)
163 obj = self.obj_pool.get(name, None)
164 # We cannot uncomment this line because it breaks initialisation since objects do not initialize
165 # in the correct order and the ORM doesnt support correctly when some objets do not exist yet
166 # assert obj, "object %s does not exist !" % name
169 #TODO: pass a list of modules to load
170 def instanciate(self, module):
171 # print "module list:", module_list
172 # for module in module_list:
174 class_list = module_class_list.get(module, [])
175 # if module not in self.module_object_list:
176 # print "%s class_list:" % module, class_list
177 for klass in class_list:
178 res.append(klass.createInstance(self, module))
181 # print "skipping module", module
183 #pooler.get_pool(cr.dbname) = osv_pool()
186 # See if we can use the pool var instead of the class_pool one
189 #class inheritor(type):
190 # def __new__(cls, name, bases, d):
191 # parent_name = d.get('_inherit', None)
193 # parent_class = class_pool.get(parent_name)
194 # assert parent_class, "parent class %s does not exist !" % parent_name
195 # for s in ('_columns', '_defaults', '_inherits'):
196 # new_dict = copy.copy(getattr(parent_class, s))
197 # new_dict.update(d.get(s, {}))
199 # bases = (parent_class,)
200 # res = type.__new__(cls, name, bases, d)
202 # # update _inherits of others objects
209 #__metaclass__ = inheritor
212 module = str(cls)[6:]
213 module = module[:len(module)-1]
214 module = module.split('.')[0][2:]
215 if not hasattr(cls, '_module'):
217 module_class_list.setdefault(cls._module, []).append(cls)
218 class_pool[cls._name] = cls
219 if module not in module_list:
220 module_list.append(cls._module)
224 # Goal: try to apply inheritancy at the instanciation level and
225 # put objects in the pool var
227 def createInstance(cls, pool, module):
229 parent_name = hasattr(cls, '_inherit') and cls._inherit
231 parent_class = pool.get(parent_name).__class__
232 assert parent_class, "parent class %s does not exist !" % parent_name
234 for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'):
235 new = copy.copy(getattr(pool.get(parent_name), s))
236 if hasattr(new, 'update'):
237 new.update(cls.__dict__.get(s, {}))
239 new.extend(cls.__dict__.get(s, []))
241 #bases = (parent_class,)
242 #obj.__class__ += (parent_class,)
243 #res = type.__new__(cls, name, bases, d)
244 name = hasattr(cls,'_name') and cls._name or cls._inherit
246 cls = type(name, (cls, parent_class), nattr)
248 obj = object.__new__(cls)
251 # return object.__new__(cls, pool)
252 createInstance = classmethod(createInstance)
254 def __init__(self, pool):
255 # print "__init__", self._name, pool
256 pool.add(self._name, self)
258 orm.orm.__init__(self)
260 # pooler.get_pool(cr.dbname).add(self._name, self)
261 # print self._name, module
263 class Cacheable(object):
268 def __delete_key(self, key):
270 for key_item in key[:-1]:
271 odico = odico[key_item]
274 def __add_key(self, key, value):
276 for key_item in key[:-1]:
277 odico = odico.setdefault(key_item, {})
278 odico[key[-1]] = value
280 def add(self, key, value):
281 self.__add_key(key, value)
283 def invalidate(self, key):
284 self.__delete_key(key)
297 def filter_dict(d, fields):
299 for f in fields + ['id']:
304 class cacheable_osv(osv, Cacheable):
309 super(cacheable_osv, self).__init__()
311 def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
316 fields = fields or self._columns.keys()
317 ctx = [context.get(x, False) for x in self._relevant]
318 result, tofetch = [], []
320 res = self.get(self._name, id, ctx)
324 result.append(filter_dict(res, fields))
326 # gen the list of "local" (ie not inherited) fields which are classic or many2one
327 nfields = filter(lambda x: x[1]._classic_write, self._columns.items())
328 # gen the list of inherited fields
329 inherits = map(lambda x: (x[0], x[1][2]), self._inherit_fields.items())
330 # complete the field list with the inherited fields which are classic or many2one
331 nfields += filter(lambda x: x[1]._classic_write, inherits)
332 nfields = [x[0] for x in nfields]
334 res = super(cacheable_osv, self).read(cr, user, tofetch, nfields, context, load)
336 self.add((self._name, r['id'], ctx), r)
337 result.append(filter_dict(r, fields))
339 # Appel de fonction si necessaire
345 fvals = self._columns[f].get(cr, self, ids, f, user, context=context)
347 r[f] = fvals[r['id']]
349 # TODO: tri par self._order !!
352 def invalidate(self, key):
353 del self._cache[key[0]][key[1]]
355 def write(self, cr, user, ids, values, context=None):
359 self.invalidate((self._name, id))
360 return super(cacheable_osv, self).write(cr, user, ids, values, context)
362 def unlink(self, cr, user, ids):
364 return super(cacheable_osv, self).unlink(cr, user, ids)
370 #class FakePool(object):
371 # def __init__(self, module):
372 # self.preferred_module = module
374 # def get(self, name):
375 # localpool = module_objects_dict.get(self.preferred_module, {'dict': {}})['dict']
376 # if name in localpool:
377 # obj = localpool[name]
379 # obj = pooler.get_pool(cr.dbname).get(name)
383 # class fake_class(obj.__class__):
384 # def __init__(self):
385 # super(fake_class, self).__init__()
386 # self.pool = fake_pool
388 # return fake_class()