[REVERT] orm/expression: reverted revision 2563 (it breaks fields.related) until...
[odoo/odoo.git] / bin / 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 #
23 # OSV: Objects Services
24 #
25
26 import orm
27 import netsvc
28 import pooler
29 import copy
30 import sys
31 import traceback
32 import logging
33 from psycopg2 import IntegrityError, errorcodes
34 from tools.func import wraps
35
36
37 module_list = []
38 module_class_list = {}
39 class_pool = {}
40
41 class except_osv(Exception):
42     def __init__(self, name, value, exc_type='warning'):
43         self.name = name
44         self.exc_type = exc_type
45         self.value = value
46         self.args = (exc_type, name)
47
48
49 class osv_pool(netsvc.Service):
50
51     def check(f):
52         @wraps(f)
53         def wrapper(self, dbname, *args, **kwargs):
54             try:
55                 if not pooler.get_pool(dbname)._ready:
56                     raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.')
57                 return f(self, dbname, *args, **kwargs)
58             except orm.except_orm, inst:
59                 if inst.name == 'AccessError':
60                     self.logger.debug("AccessError", exc_info=True)
61                 self.abortResponse(1, inst.name, 'warning', inst.value)
62             except except_osv, inst:
63                 self.abortResponse(1, inst.name, inst.exc_type, inst.value)
64             except IntegrityError, inst:
65                 for key in self._sql_error.keys():
66                     if key in inst[0]:
67                         self.abortResponse(1, 'Constraint Error', 'warning', self._sql_error[key])
68                 if inst.pgcode == errorcodes.NOT_NULL_VIOLATION:
69                     msg = 'Sorry, this record cannot be deleted at the moment because other records still reference it.'
70                     self.logger.debug("IntegrityError", exc_info=True)
71                     try:
72                         context = inst.pgerror.split('"public".')[1]
73                         model_name = table = context.split('"')[1]
74                         model = table.replace("_",".")
75                         model_obj = self.get(model)
76                         if model_obj:
77                             model_name = model_obj._description or model_obj._name
78                         msg += '\n\n[object with reference: %s - %s]' % (model_name, model)
79                     except Exception:
80                         pass
81                     self.abortResponse(1, 'Integrity Error', 'warning', msg)
82                 else:
83                     self.abortResponse(1, 'Integrity Error', 'warning', inst[0])
84             except Exception, e:
85                 self.logger.exception("Uncaught exception")
86                 raise
87
88         return wrapper
89
90
91     def __init__(self):
92         self._ready = False
93         self.obj_pool = {}
94         self.module_object_list = {}
95         self.created = []
96         self._sql_error = {}
97         self._store_function = {}
98         self._init = True
99         self._init_parent = {}
100         self.logger = logging.getLogger("web-services")
101         netsvc.Service.__init__(self, 'object_proxy', audience='')
102         self.exportMethod(self.obj_list)
103         self.exportMethod(self.exec_workflow)
104         self.exportMethod(self.execute)
105
106     def init_set(self, cr, mode):
107         different = mode != self._init
108         if different:
109             if mode:
110                 self._init_parent = {}
111             if not mode:
112                 for o in self._init_parent:
113                     self.get(o)._parent_store_compute(cr)
114             self._init = mode
115
116         self._ready = True
117         return different
118
119     def execute_cr(self, cr, uid, obj, method, *args, **kw):
120         object = pooler.get_pool(cr.dbname).get(obj)
121         if not object:
122             raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj))
123         return getattr(object, method)(cr, uid, *args, **kw)
124
125     @check
126     def execute(self, db, uid, obj, method, *args, **kw):
127         db, pool = pooler.get_db_and_pool(db)
128         cr = db.cursor()
129         try:
130             try:
131                 if method.startswith('_'):
132                     raise except_osv('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,))
133                 res = pool.execute_cr(cr, uid, obj, method, *args, **kw)
134                 if res is None:
135                     self.logger.warning('The method %s of the object %s can not return `None` !', method, obj)
136                 cr.commit()
137             except Exception:
138                 cr.rollback()
139                 raise
140         finally:
141             cr.close()
142         return res
143
144     def exec_workflow_cr(self, cr, uid, obj, method, *args):
145         wf_service = netsvc.LocalService("workflow")
146         return wf_service.trg_validate(uid, obj, args[0], method, cr)
147
148     @check
149     def exec_workflow(self, db, uid, obj, method, *args):
150         cr = pooler.get_db(db).cursor()
151         try:
152             try:
153                 res = self.exec_workflow_cr(cr, uid, obj, method, *args)
154                 cr.commit()
155             except Exception:
156                 cr.rollback()
157                 raise
158         finally:
159             cr.close()
160         return res
161
162     def obj_list(self):
163         return self.obj_pool.keys()
164
165     # adds a new object instance to the object pool.
166     # if it already existed, the instance is replaced
167     def add(self, name, obj_inst):
168         if name in self.obj_pool:
169             del self.obj_pool[name]
170         self.obj_pool[name] = obj_inst
171
172         module = str(obj_inst.__class__)[6:]
173         module = module[:len(module)-1]
174         module = module.split('.')[0][2:]
175         self.module_object_list.setdefault(module, []).append(obj_inst)
176
177     # Return None if object does not exist
178     def get(self, name):
179         obj = self.obj_pool.get(name, None)
180         return obj
181
182     #TODO: pass a list of modules to load
183     def instanciate(self, module, cr):
184         res = []
185         class_list = module_class_list.get(module, [])
186         for klass in class_list:
187             res.append(klass.createInstance(self, module, cr))
188         return res
189
190 class osv_base(object):
191     def __init__(self, pool, cr):
192         pool.add(self._name, self)
193         self.pool = pool
194         super(osv_base, self).__init__(cr)
195
196     def __new__(cls):
197         module = str(cls)[6:]
198         module = module[:len(module)-1]
199         module = module.split('.')[0][2:]
200         if not hasattr(cls, '_module'):
201             cls._module = module
202         module_class_list.setdefault(cls._module, []).append(cls)
203         class_pool[cls._name] = cls
204         if module not in module_list:
205             module_list.append(cls._module)
206         return None
207
208 class osv_memory(osv_base, orm.orm_memory):
209     #
210     # Goal: try to apply inheritancy at the instanciation level and
211     #       put objects in the pool var
212     #
213     def createInstance(cls, pool, module, cr):
214         parent_names = getattr(cls, '_inherit', None)
215         if parent_names:
216             if isinstance(parent_names, (str, unicode)):
217                 name = cls._name or parent_names
218                 parent_names = [parent_names]
219             else:
220                 name = cls._name
221             if not name:
222                 raise TypeError('_name is mandatory in case of multiple inheritance')
223
224             for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
225                 parent_class = pool.get(parent_name).__class__
226                 assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
227                 nattr = {}
228                 for s in ('_columns', '_defaults'):
229                     new = copy.copy(getattr(pool.get(parent_name), s))
230                     if hasattr(new, 'update'):
231                         new.update(cls.__dict__.get(s, {}))
232                     else:
233                         new.extend(cls.__dict__.get(s, []))
234                     nattr[s] = new
235                 cls = type(name, (cls, parent_class), nattr)
236
237         obj = object.__new__(cls)
238         obj.__init__(pool, cr)
239         return obj
240     createInstance = classmethod(createInstance)
241
242 class osv(osv_base, orm.orm):
243     #
244     # Goal: try to apply inheritancy at the instanciation level and
245     #       put objects in the pool var
246     #
247     def createInstance(cls, pool, module, cr):
248         parent_names = getattr(cls, '_inherit', None)
249         if parent_names:
250             if isinstance(parent_names, (str, unicode)):
251                 name = cls._name or parent_names
252                 parent_names = [parent_names]
253             else:
254                 name = cls._name
255             if not name:
256                 raise TypeError('_name is mandatory in case of multiple inheritance')
257
258             for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
259                 parent_class = pool.get(parent_name).__class__
260                 assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
261                 nattr = {}
262                 for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'):
263                     new = copy.copy(getattr(pool.get(parent_name), s))
264                     if hasattr(new, 'update'):
265                         new.update(cls.__dict__.get(s, {}))
266                     else:
267                         if s=='_constraints':
268                             for c in cls.__dict__.get(s, []):
269                                 exist = False
270                                 for c2 in range(len(new)):
271                                      #For _constraints, we should check field and methods as well
272                                      if new[c2][2]==c[2] and new[c2][0]==c[0]:
273                                         new[c2] = c
274                                         exist = True
275                                         break
276                                 if not exist:
277                                     new.append(c)
278                         else:
279                             new.extend(cls.__dict__.get(s, []))
280                     nattr[s] = new
281                 cls = type(name, (cls, parent_class), nattr)
282         obj = object.__new__(cls)
283         obj.__init__(pool, cr)
284         return obj
285     createInstance = classmethod(createInstance)
286
287 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
288