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