[MERGE] merged changes from 6.0
[odoo/odoo.git] / openerp / 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 openerp.netsvc as netsvc
28 import openerp.pooler as pooler
29 import copy
30 import logging
31 from psycopg2 import IntegrityError, errorcodes
32 from openerp.tools.func import wraps
33 from openerp.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         module = obj_inst.__class__.__module__.split('.')[0]
243         self.module_object_list.setdefault(module, []).append(obj_inst)
244
245     # Return None if object does not exist
246     def get(self, name):
247         obj = self.obj_pool.get(name, None)
248         return obj
249
250     #TODO: pass a list of modules to load
251     def instanciate(self, module, cr):
252         res = []
253         class_list = module_class_list.get(module, [])
254         for klass in class_list:
255             res.append(klass.createInstance(self, module, cr))
256         return res
257
258 class osv_base(object):
259     def __init__(self, pool, cr):
260         pool.add(self._name, self)
261         self.pool = pool
262         super(osv_base, self).__init__(cr)
263
264     def __new__(cls):
265         module = cls.__module__.split('.')[0]
266         if not hasattr(cls, '_module'):
267             cls._module = module
268         module_class_list.setdefault(cls._module, []).append(cls)
269         class_pool[cls._name] = cls
270         if module not in module_list:
271             module_list.append(cls._module)
272         return None
273
274 class osv_memory(osv_base, orm.orm_memory):
275     #
276     # Goal: try to apply inheritancy at the instanciation level and
277     #       put objects in the pool var
278     #
279     def createInstance(cls, pool, module, cr):
280         parent_names = getattr(cls, '_inherit', None)
281         if parent_names:
282             if isinstance(parent_names, (str, unicode)):
283                 name = cls._name or parent_names
284                 parent_names = [parent_names]
285             else:
286                 name = cls._name
287             if not name:
288                 raise TypeError('_name is mandatory in case of multiple inheritance')
289
290             for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
291                 parent_class = pool.get(parent_name).__class__
292                 assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
293                 nattr = {}
294                 for s in ('_columns', '_defaults'):
295                     new = copy.copy(getattr(pool.get(parent_name), s))
296                     if s == '_columns':
297                         # Don't _inherit custom fields.
298                         for c in new.keys():
299                             if new[c].manual:
300                                 del new[c]
301                     if hasattr(new, 'update'):
302                         new.update(cls.__dict__.get(s, {}))
303                     else:
304                         new.extend(cls.__dict__.get(s, []))
305                     nattr[s] = new
306                 cls = type(name, (cls, parent_class), nattr)
307
308         obj = object.__new__(cls)
309         obj.__init__(pool, cr)
310         return obj
311     createInstance = classmethod(createInstance)
312
313 class osv(osv_base, orm.orm):
314     #
315     # Goal: try to apply inheritancy at the instanciation level and
316     #       put objects in the pool var
317     #
318     def createInstance(cls, pool, module, cr):
319         parent_names = getattr(cls, '_inherit', None)
320         if parent_names:
321             if isinstance(parent_names, (str, unicode)):
322                 name = cls._name or parent_names
323                 parent_names = [parent_names]
324             else:
325                 name = cls._name
326             if not name:
327                 raise TypeError('_name is mandatory in case of multiple inheritance')
328
329             for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
330                 parent_class = pool.get(parent_name).__class__
331                 assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
332                 nattr = {}
333                 for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'):
334                     new = copy.copy(getattr(pool.get(parent_name), s))
335                     if s == '_columns':
336                         # Don't _inherit custom fields.
337                         for c in new.keys():
338                             if new[c].manual:
339                                 del new[c]
340                     if hasattr(new, 'update'):
341                         new.update(cls.__dict__.get(s, {}))
342                     else:
343                         if s=='_constraints':
344                             for c in cls.__dict__.get(s, []):
345                                 exist = False
346                                 for c2 in range(len(new)):
347                                      #For _constraints, we should check field and methods as well
348                                      if new[c2][2]==c[2] and (new[c2][0] == c[0] \
349                                             or getattr(new[c2][0],'__name__', True) == \
350                                                 getattr(c[0],'__name__', False)):
351                                         # If new class defines a constraint with
352                                         # same function name, we let it override
353                                         # the old one.
354                                         new[c2] = c
355                                         exist = True
356                                         break
357                                 if not exist:
358                                     new.append(c)
359                         else:
360                             new.extend(cls.__dict__.get(s, []))
361                     nattr[s] = new
362                 cls = type(name, (cls, parent_class), nattr)
363         obj = object.__new__(cls)
364         obj.__init__(pool, cr)
365         return obj
366     createInstance = classmethod(createInstance)
367
368 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
369