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