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