[MERGE] OPW 578099: ir.filters should be translated according to the user language
[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                         # Duplicate float fields because they have a .digits
307                         # cache (which must be per-pool, not server-wide).
308                         for c in new.keys():
309                             if new[c]._type == 'float':
310                                 new[c] = copy.copy(new[c])
311                     if hasattr(new, 'update'):
312                         new.update(cls.__dict__.get(s, {}))
313                     else:
314                         new.extend(cls.__dict__.get(s, []))
315                     nattr[s] = new
316                 cls = type(name, (cls, parent_class), nattr)
317         else:
318             # Duplicate float fields because they have a .digits
319             # cache (which must be per-pool, not server-wide).
320             for field_name, field in cls._columns.items():
321                 if field._type == 'float':
322                     cls._columns[field_name] = copy.copy(field)
323
324         obj = object.__new__(cls)
325         obj.__init__(pool, cr)
326         return obj
327     createInstance = classmethod(createInstance)
328
329 class osv(osv_base, orm.orm):
330     #
331     # Goal: try to apply inheritancy at the instanciation level and
332     #       put objects in the pool var
333     #
334     def createInstance(cls, pool, module, cr):
335         parent_names = getattr(cls, '_inherit', None)
336         if parent_names:
337             if isinstance(parent_names, (str, unicode)):
338                 name = cls._name or parent_names
339                 parent_names = [parent_names]
340             else:
341                 name = cls._name
342             if not name:
343                 raise TypeError('_name is mandatory in case of multiple inheritance')
344
345             for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
346                 parent_class = pool.get(parent_name).__class__
347                 assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
348                 nattr = {}
349                 for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'):
350                     new = copy.copy(getattr(pool.get(parent_name), s))
351                     if s == '_columns':
352                         # Don't _inherit custom fields.
353                         for c in new.keys():
354                             if new[c].manual:
355                                 del new[c]
356                         # Duplicate float fields because they have a .digits
357                         # cache (which must be per-pool, not server-wide).
358                         for c in new.keys():
359                             if new[c]._type == 'float':
360                                 new[c] = copy.copy(new[c])
361
362                     if hasattr(new, 'update'):
363                         new.update(cls.__dict__.get(s, {}))
364                     else:
365                         if s=='_constraints':
366                             for c in cls.__dict__.get(s, []):
367                                 exist = False
368                                 for c2 in range(len(new)):
369                                      #For _constraints, we should check field and methods as well
370                                      if new[c2][2]==c[2] and (new[c2][0] == c[0] \
371                                             or getattr(new[c2][0],'__name__', True) == \
372                                                 getattr(c[0],'__name__', False)):
373                                         # If new class defines a constraint with
374                                         # same function name, we let it override
375                                         # the old one.
376                                         new[c2] = c
377                                         exist = True
378                                         break
379                                 if not exist:
380                                     new.append(c)
381                         else:
382                             new.extend(cls.__dict__.get(s, []))
383                     nattr[s] = new
384                 cls = type(name, (cls, parent_class), nattr)
385         else:
386             # Duplicate float fields because they have a .digits
387             # cache (which must be per-pool, not server-wide).
388             for field_name, field in cls._columns.items():
389                 if field._type == 'float':
390                     cls._columns[field_name] = copy.copy(field)
391         obj = object.__new__(cls)
392         obj.__init__(pool, cr)
393         return obj
394     createInstance = classmethod(createInstance)
395
396 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
397