[FIX] osv: small imp for if condition
[odoo/odoo.git] / bin / osv / osv.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 #
23 # OSV: Objects Services
24 #
25
26 import orm
27 import netsvc
28 import pooler
29 import copy
30 import logging
31 from psycopg2 import IntegrityError, errorcodes
32 from tools.func import wraps
33 from tools.translate import translate
34
35 module_list = []
36 module_class_list = {}
37 class_pool = {}
38
39 class except_osv(Exception):
40     def __init__(self, name, value, exc_type='warning'):
41         self.name = name
42         self.exc_type = exc_type
43         self.value = value
44         self.args = (exc_type, name)
45
46
47 class object_proxy(netsvc.Service):
48     def __init__(self):
49         self.logger = logging.getLogger('web-services')
50         netsvc.Service.__init__(self, 'object_proxy', audience='')
51         self.exportMethod(self.exec_workflow)
52         self.exportMethod(self.execute)
53
54     def check(f):
55         @wraps(f)
56         def wrapper(self, dbname, *args, **kwargs):
57             """ Wraps around OSV functions and normalises a few exceptions
58             """
59
60             def tr(src, ttype):
61                 # We try to do the same as the _(), but without the frame
62                 # inspection, since we aready are wrapping an osv function
63                 # trans_obj = self.get('ir.translation') cannot work yet :(
64                 ctx = {}
65                 if not kwargs:
66                     if args and isinstance(args[-1], dict):
67                         ctx = args[-1]
68                 elif isinstance(kwargs, dict):
69                     ctx = kwargs.get('context', {})
70
71                 uid = 1
72                 if args and isinstance(args[0], (long, int)):
73                     uid = args[0]
74
75                 lang = ctx and ctx.get('lang')
76                 if not (lang or hasattr(src, '__call__')):
77                     return src
78
79                 # We open a *new* cursor here, one reason is that failed SQL
80                 # queries (as in IntegrityError) will invalidate the current one.
81                 cr = False
82                 
83                 if hasattr(src, '__call__'):
84                     # callable. We need to find the right parameters to call
85                     # the  orm._sql_message(self, cr, uid, ids, context) function,
86                     # or we skip..
87                     # our signature is f(osv_pool, dbname [,uid, obj, method, args])
88                     try:
89                         if args and len(args) > 1:
90                             obj = self.get(args[1])
91                             if len(args) > 3 and isinstance(args[3], (long, int, list)):
92                                 ids = args[3]
93                             else:
94                                 ids = []
95                         cr = pooler.get_db_only(dbname).cursor()
96                         return src(obj, cr, uid, ids, context=(ctx or {}))
97                     except Exception:
98                         pass
99                     finally:
100                         if cr: cr.close()
101                    
102                     return False # so that the original SQL error will
103                                  # be returned, it is the best we have.
104
105                 try:
106                     cr = pooler.get_db_only(dbname).cursor()
107                     res = translate(cr, name=False, source_type=ttype,
108                                     lang=lang, source=src)
109                     if res:
110                         return res
111                     else:
112                         return src
113                 finally:
114                     if cr: cr.close()
115
116             def _(src):
117                 return tr(src, 'code')
118
119             try:
120                 if not pooler.get_pool(dbname)._ready:
121                     raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.')
122                 return f(self, dbname, *args, **kwargs)
123             except orm.except_orm, inst:
124                 if inst.name == 'AccessError':
125                     self.logger.debug("AccessError", exc_info=True)
126                 self.abortResponse(1, inst.name, 'warning', inst.value)
127             except except_osv, inst:
128                 self.abortResponse(1, inst.name, inst.exc_type, inst.value)
129             except IntegrityError, inst:
130                 osv_pool = pooler.get_pool(dbname)
131                 for key in osv_pool._sql_error.keys():
132                     if key in inst[0]:
133                         self.abortResponse(1, _('Constraint Error'), 'warning',
134                                         tr(osv_pool._sql_error[key], 'sql_constraint') or inst[0])
135                 if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
136                     msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set')
137                     self.logger.debug("IntegrityError", exc_info=True)
138                     try:
139                         errortxt = inst.pgerror.replace('«','"').replace('»','"')
140                         if '"public".' in errortxt:
141                             context = errortxt.split('"public".')[1]
142                             model_name = table = context.split('"')[1]
143                         else:
144                             last_quote_end = errortxt.rfind('"')
145                             last_quote_begin = errortxt.rfind('"', 0, last_quote_end)
146                             model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
147                         model = table.replace("_",".")
148                         model_obj = osv_pool.get(model)
149                         if model_obj:
150                             model_name = model_obj._description or model_obj._name
151                         msg += _('\n\n[object with reference: %s - %s]') % (model_name, model)
152                     except Exception:
153                         pass
154                     self.abortResponse(1, _('Integrity Error'), 'warning', msg)
155                 else:
156                     self.abortResponse(1, _('Integrity Error'), 'warning', inst[0])
157             except Exception:
158                 self.logger.exception("Uncaught exception")
159                 raise
160
161         return wrapper
162
163     def execute_cr(self, cr, uid, obj, method, *args, **kw):
164         object = pooler.get_pool(cr.dbname).get(obj)
165         if not object:
166             raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj))
167         return getattr(object, method)(cr, uid, *args, **kw)
168
169     @check
170     def execute(self, db, uid, obj, method, *args, **kw):
171         cr = pooler.get_db(db).cursor()
172         try:
173             try:
174                 if method.startswith('_'):
175                     raise except_osv('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,))
176                 res = self.execute_cr(cr, uid, obj, method, *args, **kw)
177                 if res is None:
178                     self.logger.warning('The method %s of the object %s can not return `None` !', method, obj)
179                 cr.commit()
180             except Exception:
181                 cr.rollback()
182                 raise
183         finally:
184             cr.close()
185         return res
186
187     def exec_workflow_cr(self, cr, uid, obj, method, *args):
188         wf_service = netsvc.LocalService("workflow")
189         return wf_service.trg_validate(uid, obj, args[0], method, cr)
190
191     @check
192     def exec_workflow(self, db, uid, obj, method, *args):
193         cr = pooler.get_db(db).cursor()
194         try:
195             try:
196                 res = self.exec_workflow_cr(cr, uid, obj, method, *args)
197                 cr.commit()
198             except Exception:
199                 cr.rollback()
200                 raise
201         finally:
202             cr.close()
203         return res
204
205 object_proxy()
206
207 class osv_pool(object):
208     def __init__(self):
209         self._ready = False
210         self.obj_pool = {}
211         self.module_object_list = {}
212         self.created = []
213         self._sql_error = {}
214         self._store_function = {}
215         self._init = True
216         self._init_parent = {}
217         self.logger = logging.getLogger("pool")
218
219     def init_set(self, cr, mode):
220         different = mode != self._init
221         if different:
222             if mode:
223                 self._init_parent = {}
224             if not mode:
225                 for o in self._init_parent:
226                     self.get(o)._parent_store_compute(cr)
227             self._init = mode
228
229         self._ready = True
230         return different
231
232
233     def obj_list(self):
234         return self.obj_pool.keys()
235
236     # adds a new object instance to the object pool.
237     # if it already existed, the instance is replaced
238     def add(self, name, obj_inst):
239         if name in self.obj_pool:
240             del self.obj_pool[name]
241         self.obj_pool[name] = obj_inst
242
243         module = str(obj_inst.__class__)[6:]
244         module = module[:len(module)-1]
245         module = module.split('.')[0][2:]
246         self.module_object_list.setdefault(module, []).append(obj_inst)
247
248     # Return None if object does not exist
249     def get(self, name):
250         obj = self.obj_pool.get(name, None)
251         return obj
252
253     #TODO: pass a list of modules to load
254     def instanciate(self, module, cr):
255         res = []
256         class_list = module_class_list.get(module, [])
257         for klass in class_list:
258             res.append(klass.createInstance(self, module, cr))
259         return res
260
261 class osv_base(object):
262     def __init__(self, pool, cr):
263         pool.add(self._name, self)
264         self.pool = pool
265         super(osv_base, self).__init__(cr)
266
267     def __new__(cls):
268         module = str(cls)[6:]
269         module = module[:len(module)-1]
270         module = module.split('.')[0][2:]
271         if not hasattr(cls, '_module'):
272             cls._module = module
273         module_class_list.setdefault(cls._module, []).append(cls)
274         class_pool[cls._name] = cls
275         if module not in module_list:
276             module_list.append(cls._module)
277         return None
278
279 class osv_memory(osv_base, orm.orm_memory):
280     #
281     # Goal: try to apply inheritancy at the instanciation level and
282     #       put objects in the pool var
283     #
284     def createInstance(cls, pool, module, cr):
285         parent_names = getattr(cls, '_inherit', None)
286         if parent_names:
287             if isinstance(parent_names, (str, unicode)):
288                 name = cls._name or parent_names
289                 parent_names = [parent_names]
290             else:
291                 name = cls._name
292             if not name:
293                 raise TypeError('_name is mandatory in case of multiple inheritance')
294
295             for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
296                 parent_class = pool.get(parent_name).__class__
297                 assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
298                 nattr = {}
299                 for s in ('_columns', '_defaults'):
300                     new = copy.copy(getattr(pool.get(parent_name), s))
301                     if s == '_columns':
302                         # Don't _inherit custom fields.
303                         for c in new.keys():
304                             if new[c].manual:
305                                 del new[c]
306                     if hasattr(new, 'update'):
307                         new.update(cls.__dict__.get(s, {}))
308                     else:
309                         new.extend(cls.__dict__.get(s, []))
310                     nattr[s] = new
311                 cls = type(name, (cls, parent_class), nattr)
312
313         obj = object.__new__(cls)
314         obj.__init__(pool, cr)
315         return obj
316     createInstance = classmethod(createInstance)
317
318 class osv(osv_base, orm.orm):
319     #
320     # Goal: try to apply inheritancy at the instanciation level and
321     #       put objects in the pool var
322     #
323     def createInstance(cls, pool, module, cr):
324         parent_names = getattr(cls, '_inherit', None)
325         if parent_names:
326             if isinstance(parent_names, (str, unicode)):
327                 name = cls._name or parent_names
328                 parent_names = [parent_names]
329             else:
330                 name = cls._name
331             if not name:
332                 raise TypeError('_name is mandatory in case of multiple inheritance')
333
334             for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
335                 parent_class = pool.get(parent_name).__class__
336                 assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
337                 nattr = {}
338                 for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'):
339                     new = copy.copy(getattr(pool.get(parent_name), s))
340                     if s == '_columns':
341                         # Don't _inherit custom fields.
342                         for c in new.keys():
343                             if new[c].manual:
344                                 del new[c]
345                     if hasattr(new, 'update'):
346                         new.update(cls.__dict__.get(s, {}))
347                     else:
348                         if s=='_constraints':
349                             for c in cls.__dict__.get(s, []):
350                                 exist = False
351                                 for c2 in range(len(new)):
352                                      #For _constraints, we should check field and methods as well
353                                      if new[c2][2]==c[2] and (new[c2][0] == c[0] \
354                                             or getattr(new[c2][0],'__name__', True) == \
355                                                 getattr(c[0],'__name__', False)):
356                                         # If new class defines a constraint with
357                                         # same function name, we let it override
358                                         # the old one.
359                                         new[c2] = c
360                                         exist = True
361                                         break
362                                 if not exist:
363                                     new.append(c)
364                         else:
365                             new.extend(cls.__dict__.get(s, []))
366                     nattr[s] = new
367                 cls = type(name, (cls, parent_class), nattr)
368         obj = object.__new__(cls)
369         obj.__init__(pool, cr)
370         return obj
371     createInstance = classmethod(createInstance)
372
373 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
374