Improve exception: don't log exception_{osv, orm, wizard}
[odoo/odoo.git] / bin / osv / osv.py
1 ##############################################################################
2 #
3 # Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
4 #                    Fabien Pinckaers <fp@tiny.Be>
5 #
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
11 # Service Company
12 #
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.
17 #
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.
22 #
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.
26 #
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
42 module_list = []
43 module_class_list = {}
44 class_pool = {}
45
46 class except_osv(Exception):
47         def __init__(self, name, value, exc_type='warning'):
48                 self.name = name
49                 self.exc_type = exc_type
50                 self.value = value
51                 self.args = (exc_type,name)
52
53 class osv_pool(netsvc.Service):
54
55         def __init__(self):
56                 self.obj_pool = {}
57                 self.module_object_list = {}
58                 self.created = []
59                 self._sql_error = {}
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)
67
68         def execute_cr(self, cr, uid, obj, method, *args, **kw):
69                 #
70                 # TODO: check security level
71                 #
72                 try:
73                         object = pooler.get_pool(cr.dbname).get(obj)
74                         if not object:
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 \
81                                                 from inherit \
82                                                 where obj_type=%s \
83                                                         and  obj_id in ('+','.join(map(str,args[0]))+')', (obj,))
84                                 for ty,id,id2 in cr.fetchall():
85                                         if not ty in types:
86                                                 types[ty]=[]
87                                         types[ty].append(id)
88                                         types[obj].remove(id2)
89                                 for t,ids in types.items():
90                                         if len(ids):
91                                                 object_t = pooler.get_pool(cr.dbname).get(t)
92                                                 res = getattr(object_t,method)(cr, uid, ids, *args[1:], **kw)
93                         else:
94                                 res = getattr(object,method)(cr, uid, *args, **kw)
95                         return res
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():
102                                 if key in inst[0]:
103                                         self.abortResponse(1, 'Constraint Error', 'warning',
104                                                         self._sql_error[key])
105                         self.abortResponse(1, 'Integrity Error', 'warning', inst[0])
106                 except Exception, e:
107                         import traceback
108                         tb_s = reduce(lambda x, y: x+y, traceback.format_exception(
109                                 sys.exc_type, sys.exc_value, sys.exc_traceback))
110                         logger = Logger()
111                         logger.notifyChannel("web-services", LOG_ERROR,
112                                         'Exception in call: ' + tb_s)
113                         raise
114
115         def execute(self, db, uid, obj, method, *args, **kw):
116                 db, pool = pooler.get_db_and_pool(db)
117                 cr = db.cursor()
118                 try:
119                         try:
120                                 res = pool.execute_cr(cr, uid, obj, method, *args, **kw)
121                                 cr.commit()
122                         except Exception:
123                                 cr.rollback()
124                                 raise
125                 finally:
126                         cr.close()
127                 return res
128
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)
132                 return True
133
134         def exec_workflow(self, db, uid, obj, method, *args):
135                 cr = pooler.get_db(db).cursor()
136                 try:
137                         try:
138                                 res = self.exec_workflow_cr(cr, uid, obj, method, *args)
139                                 cr.commit()
140                         except Exception:
141                                 cr.rollback()
142                                 raise
143                 finally:
144                         cr.close()
145                 return res
146
147         def obj_list(self):
148                 return self.obj_pool.keys()
149
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
156
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)
161
162         def get(self, name):
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
167                 return obj
168
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:
173                 res = []
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))
179                 return res
180 #                       else:
181 #                               print "skipping module", module
182
183 #pooler.get_pool(cr.dbname) = osv_pool()
184
185 #
186 # See if we can use the pool var instead of the class_pool one
187 #
188 # XXX no more used
189 #class inheritor(type):
190 #       def __new__(cls, name, bases, d):
191 #               parent_name = d.get('_inherit', None)
192 #               if parent_name:
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, {}))
198 #                               d[s] = new_dict
199 #                       bases = (parent_class,)
200 #               res = type.__new__(cls, name, bases, d)
201 #               #
202 #               # update _inherits of others objects
203 #               #
204 #               return res
205
206
207
208 class osv(orm.orm):
209         #__metaclass__ = inheritor
210
211         def __new__(cls):
212                 module = str(cls)[6:]
213                 module = module[:len(module)-1]
214                 module = module.split('.')[0][2:]
215                 if not hasattr(cls, '_module'):
216                         cls._module = 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)
221                 return None
222                 
223         #
224         # Goal: try to apply inheritancy at the instanciation level and
225         #       put objects in the pool var
226         #
227         def createInstance(cls, pool, module):
228 #               obj = cls()
229                 parent_name = hasattr(cls, '_inherit') and cls._inherit
230                 if parent_name:
231                         parent_class = pool.get(parent_name).__class__
232                         assert parent_class, "parent class %s does not exist !" % parent_name
233                         nattr = {}
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, {}))
238                                 else:
239                                         new.extend(cls.__dict__.get(s, []))
240                                 nattr[s] = new
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
245                         #name = str(cls)
246                         cls = type(name, (cls, parent_class), nattr)
247
248                 obj = object.__new__(cls)
249                 obj.__init__(pool)
250                 return obj
251 #               return object.__new__(cls, pool)
252         createInstance = classmethod(createInstance)
253
254         def __init__(self, pool):
255 #               print "__init__", self._name, pool
256                 pool.add(self._name, self)
257                 self.pool = pool
258                 orm.orm.__init__(self)
259                 
260 #               pooler.get_pool(cr.dbname).add(self._name, self)
261 #               print self._name, module
262
263 class Cacheable(object):
264
265         _cache = {}
266         count = 0
267
268         def __delete_key(self, key):
269                 odico = self._cache
270                 for key_item in key[:-1]:
271                         odico = odico[key_item]
272                 del odico[key[-1]]
273         
274         def __add_key(self, key, value):
275                 odico = self._cache
276                 for key_item in key[:-1]:
277                         odico = odico.setdefault(key_item, {})
278                 odico[key[-1]] = value
279
280         def add(self, key, value):
281                 self.__add_key(key, value)
282         
283         def invalidate(self, key):
284                 self.__delete_key(key)
285         
286         def get(self, key):
287                 try:
288                         w = self._cache[key]
289                         return w
290                 except KeyError:
291                         return None
292         
293         def clear(self):
294                 self._cache.clear()
295                 self._items = []
296
297 def filter_dict(d, fields):
298         res = {}
299         for f in fields + ['id']:
300                 if f in d:
301                         res[f] = d[f]
302         return res
303
304 class cacheable_osv(osv, Cacheable):
305
306         _relevant = ['lang']
307
308         def __init__(self):
309                 super(cacheable_osv, self).__init__()
310         
311         def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
312                 if not fields:
313                         fields=[]
314                 if not context:
315                         context={}
316                 fields = fields or self._columns.keys()
317                 ctx = [context.get(x, False) for x in self._relevant]
318                 result, tofetch = [], []
319                 for id in ids:
320                         res = self.get(self._name, id, ctx)
321                         if not res:
322                                 tofetch.append(id)
323                         else:
324                                 result.append(filter_dict(res, fields))
325
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]
333
334                 res = super(cacheable_osv, self).read(cr, user, tofetch, nfields, context, load)
335                 for r in res:
336                         self.add((self._name, r['id'], ctx), r)
337                         result.append(filter_dict(r, fields))
338
339                 # Appel de fonction si necessaire
340                 tofetch = []
341                 for f in fields:
342                         if f not in nfields:
343                                 tofetch.append(f)
344                 for f in tofetch:
345                         fvals = self._columns[f].get(cr, self, ids, f, user, context=context)
346                         for r in result:
347                                 r[f] = fvals[r['id']]
348
349                 # TODO: tri par self._order !!
350                 return result
351
352         def invalidate(self, key):
353                 del self._cache[key[0]][key[1]]
354         
355         def write(self, cr, user, ids, values, context=None):
356                 if not context:
357                         context={}
358                 for id in ids:
359                         self.invalidate((self._name, id))
360                 return super(cacheable_osv, self).write(cr, user, ids, values, context)
361         
362         def unlink(self, cr, user, ids):
363                 self.clear()
364                 return super(cacheable_osv, self).unlink(cr, user, ids)
365
366 #cacheable_osv = osv
367
368 # vim:noexpandtab:
369
370 #class FakePool(object):
371 #       def __init__(self, module):
372 #               self.preferred_module = module
373         
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]
378 #               else:
379 #                       obj = pooler.get_pool(cr.dbname).get(name)
380 #               return obj
381
382 #               fake_pool = self
383 #               class fake_class(obj.__class__):
384 #                       def __init__(self):
385 #                               super(fake_class, self).__init__()
386 #                               self.pool = fake_pool
387                                 
388 #               return fake_class()
389