Speed Improvement (removed inherit checks)
[odoo/odoo.git] / bin / osv / osv.py
1 ##############################################################################
2 #
3 # Copyright (c) 2004-2008 Tiny SPRL (http://tiny.be) All Rights Reserved.
4 #
5 # $Id$
6 #
7 # WARNING: This program as such is intended to be used by professional
8 # programmers who take the whole responsability of assessing all potential
9 # consequences resulting from its eventual inadequacies and bugs
10 # End users who are looking for a ready-to-use solution with commercial
11 # garantees and support are strongly adviced to contract a Free Software
12 # Service Company
13 #
14 # This program is Free Software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; either version 2
17 # of the License, or (at your option) any later version.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
27 ###############################################################################
28
29 #
30 # OSV: Objects Services
31 #
32
33 import orm
34 import netsvc
35 import pooler
36 import copy
37 import sys
38
39 import psycopg
40 from netsvc import Logger, LOG_ERROR
41 from tools.misc import UpdateableDict
42
43 module_list = []
44 module_class_list = {}
45 class_pool = {}
46
47 class except_osv(Exception):
48         def __init__(self, name, value, exc_type='warning'):
49                 self.name = name
50                 self.exc_type = exc_type
51                 self.value = value
52                 self.args = (exc_type,name)
53
54 class osv_pool(netsvc.Service):
55
56         def __init__(self):
57                 self.obj_pool = {}
58                 self.module_object_list = {}
59                 self.created = []
60                 self._sql_error = {}
61                 self._store_function = {}
62                 netsvc.Service.__init__(self, 'object_proxy', audience='')
63                 self.joinGroup('web-services')
64                 self.exportMethod(self.exportedMethods)
65                 self.exportMethod(self.obj_list)
66                 self.exportMethod(self.exec_workflow)
67                 self.exportMethod(self.execute)
68                 self.exportMethod(self.execute_cr)
69
70         def execute_cr(self, cr, uid, obj, method, *args, **kw):
71                 try:
72                         object = pooler.get_pool(cr.dbname).get(obj)
73                         if not object:
74                                 self.abortResponse(1, 'Object Error', 'warning',
75                                 'Object %s doesn\'t exist' % str(obj))
76                         return getattr(object,method)(cr, uid, *args, **kw)
77                 except orm.except_orm, inst:
78                         self.abortResponse(1, inst.name, 'warning', inst.value)
79                 except except_osv, inst:
80                         self.abortResponse(1, inst.name, inst.exc_type, inst.value)
81                 except psycopg.IntegrityError, inst:
82                         for key in self._sql_error.keys():
83                                 if key in inst[0]:
84                                         self.abortResponse(1, 'Constraint Error', 'warning',
85                                                         self._sql_error[key])
86                         self.abortResponse(1, 'Integrity Error', 'warning', inst[0])
87                 except Exception, e:
88                         import traceback
89                         tb_s = reduce(lambda x, y: x+y, traceback.format_exception(
90                                 sys.exc_type, sys.exc_value, sys.exc_traceback))
91                         logger = Logger()
92                         logger.notifyChannel("web-services", LOG_ERROR,
93                                         'Exception in call: ' + tb_s)
94                         raise
95
96         def execute(self, db, uid, obj, method, *args, **kw):
97                 db, pool = pooler.get_db_and_pool(db)
98                 cr = db.cursor()
99                 try:
100                         try:
101                                 res = pool.execute_cr(cr, uid, obj, method, *args, **kw)
102                                 cr.commit()
103                         except Exception:
104                                 cr.rollback()
105                                 raise
106                 finally:
107                         cr.close()
108                 return res
109
110         def exec_workflow_cr(self, cr, uid, obj, method, *args):
111                 wf_service = netsvc.LocalService("workflow")
112                 return wf_service.trg_validate(uid, obj, args[0], method, cr)
113
114         def exec_workflow(self, db, uid, obj, method, *args):
115                 cr = pooler.get_db(db).cursor()
116                 try:
117                         try:
118                                 res = self.exec_workflow_cr(cr, uid, obj, method, *args)
119                                 cr.commit()
120                         except orm.except_orm, inst:
121                                 cr.rollback()
122                                 self.abortResponse(1, inst.name, 'warning', inst.value)
123                         except except_osv, inst:
124                                 cr.rollback()
125                                 self.abortResponse(1, inst.name, inst[0], inst.value)
126                 finally:
127                         cr.close()
128                 return res
129
130         def obj_list(self):
131                 return self.obj_pool.keys()
132
133         # adds a new object instance to the object pool.
134         # if it already existed, the instance is replaced
135         def add(self, name, obj_inst):
136                 if self.obj_pool.has_key(name):
137                         del self.obj_pool[name]
138                 self.obj_pool[name] = obj_inst
139
140                 module = str(obj_inst.__class__)[6:]
141                 module = module[:len(module)-1]
142                 module = module.split('.')[0][2:]
143                 self.module_object_list.setdefault(module, []).append(obj_inst)
144
145         def get(self, name):
146                 obj = self.obj_pool.get(name, None)
147 # We cannot uncomment this line because it breaks initialisation since objects do not initialize
148 # in the correct order and the ORM doesnt support correctly when some objets do not exist yet
149 #               assert obj, "object %s does not exist !" % name
150                 return obj
151
152         #TODO: pass a list of modules to load
153         def instanciate(self, module, cr):
154 #               print "module list:", module_list
155 #               for module in module_list:
156                 res = []
157                 class_list = module_class_list.get(module, [])
158 #                       if module not in self.module_object_list:
159 #               print "%s class_list:" % module, class_list
160                 for klass in class_list:
161                         res.append(klass.createInstance(self, module, cr))
162                 return res
163 #                       else:
164 #                               print "skipping module", module
165
166 #pooler.get_pool(cr.dbname) = osv_pool()
167
168 class osv_memory(orm.orm_memory):
169         #__metaclass__ = inheritor
170         def __new__(cls):
171                 module = str(cls)[6:]
172                 module = module[:len(module)-1]
173                 module = module.split('.')[0][2:]
174                 if not hasattr(cls, '_module'):
175                         cls._module = module
176                 module_class_list.setdefault(cls._module, []).append(cls)
177                 class_pool[cls._name] = cls
178                 if module not in module_list:
179                         module_list.append(cls._module)
180                 return None
181
182         #
183         # Goal: try to apply inheritancy at the instanciation level and
184         #       put objects in the pool var
185         #
186         def createInstance(cls, pool, module, cr):
187                 name = hasattr(cls,'_name') and cls._name or cls._inherit
188                 parent_name = hasattr(cls, '_inherit') and cls._inherit
189                 if parent_name:
190                         print 'Inherit not supported in osv_memory object !'
191                 obj = object.__new__(cls)
192                 obj.__init__(pool, cr)
193                 return obj
194         createInstance = classmethod(createInstance)
195
196         def __init__(self, pool, cr):
197                 pool.add(self._name, self)
198                 self.pool = pool
199                 orm.orm_memory.__init__(self, cr)
200
201
202
203 class osv(orm.orm):
204         #__metaclass__ = inheritor
205         def __new__(cls):
206                 module = str(cls)[6:]
207                 module = module[:len(module)-1]
208                 module = module.split('.')[0][2:]
209                 if not hasattr(cls, '_module'):
210                         cls._module = module
211                 module_class_list.setdefault(cls._module, []).append(cls)
212                 class_pool[cls._name] = cls
213                 if module not in module_list:
214                         module_list.append(cls._module)
215                 return None
216
217         #
218         # Goal: try to apply inheritancy at the instanciation level and
219         #       put objects in the pool var
220         #
221         def createInstance(cls, pool, module, cr):
222                 parent_name = hasattr(cls, '_inherit') and cls._inherit
223                 if parent_name:
224                         parent_class = pool.get(parent_name).__class__
225                         assert parent_class, "parent class %s does not exist !" % parent_name
226                         nattr = {}
227                         for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'):
228                                 new = copy.copy(getattr(pool.get(parent_name), s))
229                                 if hasattr(new, 'update'):
230                                         new.update(cls.__dict__.get(s, {}))
231                                 else:
232                                         new.extend(cls.__dict__.get(s, []))
233                                 nattr[s] = new
234                         name = hasattr(cls,'_name') and cls._name or cls._inherit
235                         cls = type(name, (cls, parent_class), nattr)
236                 obj = object.__new__(cls)
237                 obj.__init__(pool, cr)
238                 return obj
239         createInstance = classmethod(createInstance)
240
241         def __init__(self, pool, cr):
242                 pool.add(self._name, self)
243                 self.pool = pool
244                 orm.orm.__init__(self, cr)
245
246 class Cacheable(object):
247
248         _cache = UpdateableDict()
249
250         def add(self, key, value):
251                 self._cache[key] = value
252
253         def invalidate(self, key):
254                 del self._cache[key]
255
256         def get(self, key):
257                 try:
258                         w = self._cache[key]
259                         return w
260                 except KeyError:
261                         return None
262
263         def clear(self):
264                 self._cache.clear()
265                 self._items = []
266
267 def filter_dict(d, fields):
268         res = {}
269         for f in fields + ['id']:
270                 if f in d:
271                         res[f] = d[f]
272         return res
273
274 class cacheable_osv(osv, Cacheable):
275
276         _relevant = ['lang']
277
278         def __init__(self):
279                 super(cacheable_osv, self).__init__()
280
281         def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
282                 if not fields:
283                         fields=[]
284                 if not context:
285                         context={}
286                 fields = fields or self._columns.keys()
287                 ctx = [context.get(x, False) for x in self._relevant]
288                 result, tofetch = [], []
289                 for id in ids:
290                         res = self.get(self._name, id, ctx)
291                         if not res:
292                                 tofetch.append(id)
293                         else:
294                                 result.append(filter_dict(res, fields))
295
296                 # gen the list of "local" (ie not inherited) fields which are classic or many2one
297                 nfields = filter(lambda x: x[1]._classic_write, self._columns.items())
298                 # gen the list of inherited fields
299                 inherits = map(lambda x: (x[0], x[1][2]), self._inherit_fields.items())
300                 # complete the field list with the inherited fields which are classic or many2one
301                 nfields += filter(lambda x: x[1]._classic_write, inherits)
302                 nfields = [x[0] for x in nfields]
303
304                 res = super(cacheable_osv, self).read(cr, user, tofetch, nfields, context, load)
305                 for r in res:
306                         self.add((self._name, r['id'], ctx), r)
307                         result.append(filter_dict(r, fields))
308
309                 # Appel de fonction si necessaire
310                 tofetch = []
311                 for f in fields:
312                         if f not in nfields:
313                                 tofetch.append(f)
314                 for f in tofetch:
315                         fvals = self._columns[f].get(cr, self, ids, f, user, context=context)
316                         for r in result:
317                                 r[f] = fvals[r['id']]
318
319                 # TODO: tri par self._order !!
320                 return result
321
322         def invalidate(self, key):
323                 del self._cache[key[0]][key[1]]
324
325         def write(self, cr, user, ids, values, context=None):
326                 if not context:
327                         context={}
328                 for id in ids:
329                         self.invalidate((self._name, id))
330                 return super(cacheable_osv, self).write(cr, user, ids, values, context)
331
332         def unlink(self, cr, user, ids):
333                 self.clear()
334                 return super(cacheable_osv, self).unlink(cr, user, ids)
335
336 #cacheable_osv = osv
337
338 # vim:noexpandtab:
339
340 #class FakePool(object):
341 #       def __init__(self, module):
342 #               self.preferred_module = module
343
344 #       def get(self, name):
345 #               localpool = module_objects_dict.get(self.preferred_module, {'dict': {}})['dict']
346 #               if name in localpool:
347 #                       obj = localpool[name]
348 #               else:
349 #                       obj = pooler.get_pool(cr.dbname).get(name)
350 #               return obj
351
352 #               fake_pool = self
353 #               class fake_class(obj.__class__):
354 #                       def __init__(self):
355 #                               super(fake_class, self).__init__()
356 #                               self.pool = fake_pool
357
358 #               return fake_class()
359