[FIX] translations: fix tests to adapt to new duplication behaviour and remove contex...
[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 #.apidoc title: Objects Services (OSV)
23
24 from functools import wraps
25 import logging
26 import threading
27
28 from psycopg2 import IntegrityError, OperationalError, errorcodes
29
30 import orm
31 import openerp
32 import openerp.netsvc as netsvc
33 import openerp.pooler as pooler
34 import openerp.sql_db as sql_db
35 from openerp.tools.translate import translate
36 from openerp.osv.orm import MetaModel, Model, TransientModel, AbstractModel
37 import openerp.exceptions
38
39 import time
40 import random
41
42 _logger = logging.getLogger(__name__)
43
44 PG_CONCURRENCY_ERRORS_TO_RETRY = (errorcodes.LOCK_NOT_AVAILABLE, errorcodes.SERIALIZATION_FAILURE, errorcodes.DEADLOCK_DETECTED)
45 MAX_TRIES_ON_CONCURRENCY_FAILURE = 5
46
47 # Deprecated.
48 class except_osv(Exception):
49     def __init__(self, name, value):
50         self.name = name
51         self.value = value
52         self.args = (name, value)
53
54 service = None
55
56 class object_proxy(object):
57     def __init__(self):
58         global service
59         service = self
60
61     def check(f):
62         @wraps(f)
63         def wrapper(self, dbname, *args, **kwargs):
64             """ Wraps around OSV functions and normalises a few exceptions
65             """
66
67             def tr(src, ttype):
68                 # We try to do the same as the _(), but without the frame
69                 # inspection, since we aready are wrapping an osv function
70                 # trans_obj = self.get('ir.translation') cannot work yet :(
71                 ctx = {}
72                 if not kwargs:
73                     if args and isinstance(args[-1], dict):
74                         ctx = args[-1]
75                 elif isinstance(kwargs, dict):
76                     ctx = kwargs.get('context', {})
77
78                 uid = 1
79                 if args and isinstance(args[0], (long, int)):
80                     uid = args[0]
81
82                 lang = ctx and ctx.get('lang')
83                 if not (lang or hasattr(src, '__call__')):
84                     return src
85
86                 # We open a *new* cursor here, one reason is that failed SQL
87                 # queries (as in IntegrityError) will invalidate the current one.
88                 cr = False
89
90                 if hasattr(src, '__call__'):
91                     # callable. We need to find the right parameters to call
92                     # the  orm._sql_message(self, cr, uid, ids, context) function,
93                     # or we skip..
94                     # our signature is f(osv_pool, dbname [,uid, obj, method, args])
95                     try:
96                         if args and len(args) > 1:
97                             obj = self.get(args[1])
98                             if len(args) > 3 and isinstance(args[3], (long, int, list)):
99                                 ids = args[3]
100                             else:
101                                 ids = []
102                         cr = sql_db.db_connect(dbname).cursor()
103                         return src(obj, cr, uid, ids, context=(ctx or {}))
104                     except Exception:
105                         pass
106                     finally:
107                         if cr: cr.close()
108
109                     return False # so that the original SQL error will
110                                  # be returned, it is the best we have.
111
112                 try:
113                     cr = sql_db.db_connect(dbname).cursor()
114                     res = translate(cr, name=False, source_type=ttype,
115                                     lang=lang, source=src)
116                     if res:
117                         return res
118                     else:
119                         return src
120                 finally:
121                     if cr: cr.close()
122
123             def _(src):
124                 return tr(src, 'code')
125
126             tries = 0
127             while True:
128                 try:
129                     if pooler.get_pool(dbname)._init:
130                         raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.')
131                     return f(self, dbname, *args, **kwargs)
132                 except OperationalError, e:
133                     # Automatically retry the typical transaction serialization errors
134                     if e.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY:
135                         raise
136                     if tries >= MAX_TRIES_ON_CONCURRENCY_FAILURE:
137                         _logger.warning("%s, maximum number of tries reached" % errorcodes.lookup(e.pgcode))
138                         raise
139                     wait_time = random.uniform(0.0, 2 ** tries)
140                     tries += 1
141                     _logger.info("%s, retry %d/%d in %.04f sec..." % (errorcodes.lookup(e.pgcode), tries, MAX_TRIES_ON_CONCURRENCY_FAILURE, wait_time))
142                     time.sleep(wait_time)
143                 except orm.except_orm, inst:
144                     raise except_osv(inst.name, inst.value)
145                 except except_osv:
146                     raise
147                 except IntegrityError, inst:
148                     osv_pool = pooler.get_pool(dbname)
149                     for key in osv_pool._sql_error.keys():
150                         if key in inst[0]:
151                             netsvc.abort_response(1, _('Constraint Error'), 'warning',
152                                             tr(osv_pool._sql_error[key], 'sql_constraint') or inst[0])
153                     if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
154                         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')
155                         _logger.debug("IntegrityError", exc_info=True)
156                         try:
157                             errortxt = inst.pgerror.replace('«','"').replace('»','"')
158                             if '"public".' in errortxt:
159                                 context = errortxt.split('"public".')[1]
160                                 model_name = table = context.split('"')[1]
161                             else:
162                                 last_quote_end = errortxt.rfind('"')
163                                 last_quote_begin = errortxt.rfind('"', 0, last_quote_end)
164                                 model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
165                             model = table.replace("_",".")
166                             model_obj = osv_pool.get(model)
167                             if model_obj:
168                                 model_name = model_obj._description or model_obj._name
169                             msg += _('\n\n[object with reference: %s - %s]') % (model_name, model)
170                         except Exception:
171                             pass
172                         netsvc.abort_response(1, _('Integrity Error'), 'warning', msg)
173                     else:
174                         netsvc.abort_response(1, _('Integrity Error'), 'warning', inst[0])
175                 except Exception:
176                     _logger.exception("Uncaught exception")
177                     raise
178
179         return wrapper
180
181     def execute_cr(self, cr, uid, obj, method, *args, **kw):
182         object = pooler.get_pool(cr.dbname).get(obj)
183         if not object:
184             raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj))
185         return getattr(object, method)(cr, uid, *args, **kw)
186
187     def execute_kw(self, db, uid, obj, method, args, kw=None):
188         return self.execute(db, uid, obj, method, *args, **kw or {})
189
190     @check
191     def execute(self, db, uid, obj, method, *args, **kw):
192         cr = pooler.get_db(db).cursor()
193         try:
194             try:
195                 if method.startswith('_'):
196                     raise except_osv('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,))
197                 res = self.execute_cr(cr, uid, obj, method, *args, **kw)
198                 if res is None:
199                     _logger.warning('The method %s of the object %s can not return `None` !', method, obj)
200                 cr.commit()
201             except Exception:
202                 cr.rollback()
203                 raise
204         finally:
205             cr.close()
206         return res
207
208     def exec_workflow_cr(self, cr, uid, obj, signal, *args):
209         object = pooler.get_pool(cr.dbname).get(obj)
210         if not object:
211             raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj))
212         res_id = args[0]
213         return object._workflow_signal(cr, uid, [res_id], signal)[res_id]
214
215     @check
216     def exec_workflow(self, db, uid, obj, signal, *args):
217         cr = pooler.get_db(db).cursor()
218         try:
219             try:
220                 res = self.exec_workflow_cr(cr, uid, obj, signal, *args)
221                 cr.commit()
222             except Exception:
223                 cr.rollback()
224                 raise
225         finally:
226             cr.close()
227         return res
228
229 # deprecated - for backward compatibility.
230 osv = Model
231 osv_memory = TransientModel
232 osv_abstract = AbstractModel # ;-)
233
234
235 def start_object_proxy():
236     object_proxy()
237
238 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
239