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