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