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