[FIX] _constraints : Multiple constraints on the same field should be allowed
[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
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                 self.abortResponse(1, 'Integrity Error', 'warning', inst[0])
69             except Exception, e:
70                 self.logger.exception("Uncaught exception")
71                 raise
72
73         return wrapper
74
75
76     def __init__(self):
77         self._ready = False
78         self.obj_pool = {}
79         self.module_object_list = {}
80         self.created = []
81         self._sql_error = {}
82         self._store_function = {}
83         self._init = True
84         self._init_parent = {}
85         self.logger = logging.getLogger("web-services")
86         netsvc.Service.__init__(self, 'object_proxy', audience='')
87         self.exportMethod(self.obj_list)
88         self.exportMethod(self.exec_workflow)
89         self.exportMethod(self.execute)
90
91     def init_set(self, cr, mode):
92         different = mode != self._init
93         if different:
94             if mode:
95                 self._init_parent = {}
96             if not mode:
97                 for o in self._init_parent:
98                     self.get(o)._parent_store_compute(cr)
99             self._init = mode
100
101         self._ready = True
102         return different
103
104     def execute_cr(self, cr, uid, obj, method, *args, **kw):
105         object = pooler.get_pool(cr.dbname).get(obj)
106         if not object:
107             raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj))
108         return getattr(object, method)(cr, uid, *args, **kw)
109
110     @check
111     def execute(self, db, uid, obj, method, *args, **kw):
112         db, pool = pooler.get_db_and_pool(db)
113         cr = db.cursor()
114         try:
115             try:
116                 if method.startswith('_'):
117                     raise except_osv('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,))
118                 res = pool.execute_cr(cr, uid, obj, method, *args, **kw)
119                 if res is None:
120                     self.logger.warning('RPC methods cannot return `None` at the moment!')
121                 cr.commit()
122             except Exception:
123                 cr.rollback()
124                 raise
125         finally:
126             cr.close()
127         return res
128
129     def exec_workflow_cr(self, cr, uid, obj, method, *args):
130         wf_service = netsvc.LocalService("workflow")
131         return wf_service.trg_validate(uid, obj, args[0], method, cr)
132
133     @check
134     def exec_workflow(self, db, uid, obj, method, *args):
135         cr = pooler.get_db(db).cursor()
136         try:
137             try:
138                 res = self.exec_workflow_cr(cr, uid, obj, method, *args)
139                 cr.commit()
140             except Exception:
141                 cr.rollback()
142                 raise
143         finally:
144             cr.close()
145         return res
146
147     def obj_list(self):
148         return self.obj_pool.keys()
149
150     # adds a new object instance to the object pool.
151     # if it already existed, the instance is replaced
152     def add(self, name, obj_inst):
153         if name in self.obj_pool:
154             del self.obj_pool[name]
155         self.obj_pool[name] = obj_inst
156
157         module = str(obj_inst.__class__)[6:]
158         module = module[:len(module)-1]
159         module = module.split('.')[0][2:]
160         self.module_object_list.setdefault(module, []).append(obj_inst)
161
162     # Return None if object does not exist
163     def get(self, name):
164         obj = self.obj_pool.get(name, None)
165         return obj
166
167     #TODO: pass a list of modules to load
168     def instanciate(self, module, cr):
169         res = []
170         class_list = module_class_list.get(module, [])
171         for klass in class_list:
172             res.append(klass.createInstance(self, module, cr))
173         return res
174
175 class osv_base(object):
176     def __init__(self, pool, cr):
177         pool.add(self._name, self)
178         self.pool = pool
179         super(osv_base, self).__init__(cr)
180
181     def __new__(cls):
182         module = str(cls)[6:]
183         module = module[:len(module)-1]
184         module = module.split('.')[0][2:]
185         if not hasattr(cls, '_module'):
186             cls._module = module
187         module_class_list.setdefault(cls._module, []).append(cls)
188         class_pool[cls._name] = cls
189         if module not in module_list:
190             module_list.append(cls._module)
191         return None
192
193 class osv_memory(osv_base, orm.orm_memory):
194     #
195     # Goal: try to apply inheritancy at the instanciation level and
196     #       put objects in the pool var
197     #
198     def createInstance(cls, pool, module, cr):
199         parent_names = getattr(cls, '_inherit', None)
200         if parent_names:
201             if isinstance(parent_names, (str, unicode)):
202                 name = cls._name or parent_names
203                 parent_names = [parent_names]
204             else:
205                 name = cls._name
206             if not name:
207                 raise TypeError('_name is mandatory in case of multiple inheritance')
208
209             for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
210                 parent_class = pool.get(parent_name).__class__
211                 assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
212                 nattr = {}
213                 for s in ('_columns', '_defaults'):
214                     new = copy.copy(getattr(pool.get(parent_name), s))
215                     if hasattr(new, 'update'):
216                         new.update(cls.__dict__.get(s, {}))
217                     else:
218                         new.extend(cls.__dict__.get(s, []))
219                     nattr[s] = new
220                 cls = type(name, (cls, parent_class), nattr)
221
222         obj = object.__new__(cls)
223         obj.__init__(pool, cr)
224         return obj
225     createInstance = classmethod(createInstance)
226
227 class osv(osv_base, orm.orm):
228     #
229     # Goal: try to apply inheritancy at the instanciation level and
230     #       put objects in the pool var
231     #
232     def createInstance(cls, pool, module, cr):
233         parent_names = getattr(cls, '_inherit', None)
234         if parent_names:
235             if isinstance(parent_names, (str, unicode)):
236                 name = cls._name or parent_names
237                 parent_names = [parent_names]
238             else:
239                 name = cls._name
240             if not name:
241                 raise TypeError('_name is mandatory in case of multiple inheritance')
242
243             for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
244                 parent_class = pool.get(parent_name).__class__
245                 assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
246                 nattr = {}
247                 for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'):
248                     new = copy.copy(getattr(pool.get(parent_name), s))
249                     if hasattr(new, 'update'):
250                         new.update(cls.__dict__.get(s, {}))
251                     else:
252                         if s=='_constraints':
253                             for c in cls.__dict__.get(s, []):
254                                 exist = False
255                                 for c2 in range(len(new)):
256                                      #For _constraints, we should check field and methods as well
257                                      if new[c2][2]==c[2] and new[c2][0]==c[0]:
258                                         new[c2] = c
259                                         exist = True
260                                         break
261                                 if not exist:
262                                     new.append(c)
263                         else:
264                             new.extend(cls.__dict__.get(s, []))
265                     nattr[s] = new
266                 cls = type(name, (cls, parent_class), nattr)
267         obj = object.__new__(cls)
268         obj.__init__(pool, cr)
269         return obj
270     createInstance = classmethod(createInstance)
271
272 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
273