[MERGE] trunk-service-thu (remove the class openerp.netsvc.ExportService)
[odoo/odoo.git] / openerp / service / model.py
1 # -*- coding: utf-8 -*-
2
3 from functools import wraps
4 import logging
5 from psycopg2 import IntegrityError, errorcodes
6 import threading
7
8 import openerp
9 from openerp.tools.translate import translate
10 from openerp.osv.orm import except_orm
11
12 import security
13
14 _logger = logging.getLogger(__name__)
15
16 def dispatch(method, params):
17     (db, uid, passwd ) = params[0:3]
18     threading.current_thread().uid = uid
19     params = params[3:]
20     if method == 'obj_list':
21         raise NameError("obj_list has been discontinued via RPC as of 6.0, please query ir.model directly!")
22     if method not in ['execute', 'execute_kw', 'exec_workflow']:
23         raise NameError("Method not available %s" % method)
24     security.check(db,uid,passwd)
25     openerp.modules.registry.RegistryManager.check_registry_signaling(db)
26     fn = globals()[method]
27     res = fn(db, uid, *params)
28     openerp.modules.registry.RegistryManager.signal_caches_change(db)
29     return res
30
31 def check(f):
32     @wraps(f)
33     def wrapper(dbname, *args, **kwargs):
34         """ Wraps around OSV functions and normalises a few exceptions
35         """
36
37         def tr(src, ttype):
38             # We try to do the same as the _(), but without the frame
39             # inspection, since we aready are wrapping an osv function
40             # trans_obj = self.get('ir.translation') cannot work yet :(
41             ctx = {}
42             if not kwargs:
43                 if args and isinstance(args[-1], dict):
44                     ctx = args[-1]
45             elif isinstance(kwargs, dict):
46                 ctx = kwargs.get('context', {})
47
48             uid = 1
49             if args and isinstance(args[0], (long, int)):
50                 uid = args[0]
51
52             lang = ctx and ctx.get('lang')
53             if not (lang or hasattr(src, '__call__')):
54                 return src
55
56             # We open a *new* cursor here, one reason is that failed SQL
57             # queries (as in IntegrityError) will invalidate the current one.
58             cr = False
59
60             if hasattr(src, '__call__'):
61                 # callable. We need to find the right parameters to call
62                 # the  orm._sql_message(self, cr, uid, ids, context) function,
63                 # or we skip..
64                 # our signature is f(osv_pool, dbname [,uid, obj, method, args])
65                 try:
66                     if args and len(args) > 1:
67                         # TODO self doesn't exist, but was already wrong before (it was not a registry but just the object_service.
68                         obj = self.get(args[1])
69                         if len(args) > 3 and isinstance(args[3], (long, int, list)):
70                             ids = args[3]
71                         else:
72                             ids = []
73                     cr = openerp.sql_db.db_connect(dbname).cursor()
74                     return src(obj, cr, uid, ids, context=(ctx or {}))
75                 except Exception:
76                     pass
77                 finally:
78                     if cr: cr.close()
79
80                 return False # so that the original SQL error will
81                              # be returned, it is the best we have.
82
83             try:
84                 cr = openerp.sql_db.db_connect(dbname).cursor()
85                 res = translate(cr, name=False, source_type=ttype,
86                                 lang=lang, source=src)
87                 if res:
88                     return res
89                 else:
90                     return src
91             finally:
92                 if cr: cr.close()
93
94         def _(src):
95             return tr(src, 'code')
96
97         try:
98             if openerp.pooler.get_pool(dbname)._init:
99                 raise openerp.exceptions.Warning('Currently, this database is not fully loaded and can not be used.')
100             return f(dbname, *args, **kwargs)
101         except except_orm:
102             raise
103         except IntegrityError, inst:
104             osv_pool = openerp.pooler.get_pool(dbname)
105             for key in osv_pool._sql_error.keys():
106                 if key in inst[0]:
107                     raise openerp.osv.orm.except_orm(_('Constraint Error'), tr(osv_pool._sql_error[key], 'sql_constraint') or inst[0])
108             if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
109                 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')
110                 _logger.debug("IntegrityError", exc_info=True)
111                 try:
112                     errortxt = inst.pgerror.replace('«','"').replace('»','"')
113                     if '"public".' in errortxt:
114                         context = errortxt.split('"public".')[1]
115                         model_name = table = context.split('"')[1]
116                     else:
117                         last_quote_end = errortxt.rfind('"')
118                         last_quote_begin = errortxt.rfind('"', 0, last_quote_end)
119                         model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
120                     model = table.replace("_",".")
121                     model_obj = osv_pool.get(model)
122                     if model_obj:
123                         model_name = model_obj._description or model_obj._name
124                     msg += _('\n\n[object with reference: %s - %s]') % (model_name, model)
125                 except Exception:
126                     pass
127                 raise openerp.osv.orm.except_orm(_('Integrity Error'), msg)
128             else:
129                 raise openerp.osv.orm.except_orm(_('Integrity Error'), inst[0])
130         except Exception:
131             _logger.exception("Uncaught exception")
132             raise
133
134     return wrapper
135
136 def execute_cr(cr, uid, obj, method, *args, **kw):
137     object = openerp.pooler.get_pool(cr.dbname).get(obj)
138     if not object:
139         raise except_orm('Object Error', 'Object %s doesn\'t exist' % str(obj))
140     return getattr(object, method)(cr, uid, *args, **kw)
141
142 def execute_kw(db, uid, obj, method, args, kw=None):
143     return execute(db, uid, obj, method, *args, **kw or {})
144
145 @check
146 def execute(db, uid, obj, method, *args, **kw):
147     threading.currentThread().dbname = db
148     cr = openerp.pooler.get_db(db).cursor()
149     try:
150         try:
151             if method.startswith('_'):
152                 raise except_orm('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,))
153             res = execute_cr(cr, uid, obj, method, *args, **kw)
154             if res is None:
155                 _logger.warning('The method %s of the object %s can not return `None` !', method, obj)
156             cr.commit()
157         except Exception:
158             cr.rollback()
159             raise
160     finally:
161         cr.close()
162     return res
163
164 def exec_workflow_cr(cr, uid, obj, signal, *args):
165     object = openerp.pooler.get_pool(cr.dbname).get(obj)
166     if not object:
167         raise except_orm('Object Error', 'Object %s doesn\'t exist' % str(obj))
168     res_id = args[0]
169     return object._workflow_signal(cr, uid, [res_id], signal)[res_id]
170
171 @check
172 def exec_workflow(db, uid, obj, signal, *args):
173     cr = openerp.pooler.get_db(db).cursor()
174     try:
175         try:
176             res = exec_workflow_cr(cr, uid, obj, signal, *args)
177             cr.commit()
178         except Exception:
179             cr.rollback()
180             raise
181     finally:
182         cr.close()
183     return res
184
185 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: