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