X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=bin%2Fosv%2Fosv.py;h=fc72069b22f3cabee9d7b4286655650813b98812;hb=41c9098ca994a3712bcdd0e6dc61805ed946b02d;hp=4c503a0009513ad2712da2ec334a361a1c24bc89;hpb=d9d06783284fa93bd284be1ea66d322291b1abf3;p=odoo%2Fodoo.git diff --git a/bin/osv/osv.py b/bin/osv/osv.py index 4c503a0..fc72069 100644 --- a/bin/osv/osv.py +++ b/bin/osv/osv.py @@ -1,28 +1,21 @@ +# -*- coding: utf-8 -*- ############################################################################## # -# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved. -# Fabien Pinckaers +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). # -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsability of assessing all potential -# consequences resulting from its eventual inadequacies and bugs -# End users who are looking for a ready-to-use solution with commercial -# garantees and support are strongly adviced to contract a Free Software -# Service Company +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. # -# This program is Free Software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . # ############################################################################## @@ -34,340 +27,337 @@ import orm import netsvc import pooler import copy - -import psycopg +import sys +import traceback +import logging +from psycopg2 import IntegrityError, errorcodes +from tools.func import wraps +from tools.translate import translate module_list = [] module_class_list = {} class_pool = {} class except_osv(Exception): - def __init__(self, name, value, exc_type='warning'): - self.name = name - self.exc_type = exc_type - self.value = value - self.args = (exc_type,name) + def __init__(self, name, value, exc_type='warning'): + self.name = name + self.exc_type = exc_type + self.value = value + self.args = (exc_type, name) -class osv_pool(netsvc.Service): - def __init__(self): - self.obj_pool = {} - self.module_object_list = {} - self.created = [] - self._sql_error = {} - netsvc.Service.__init__(self, 'object_proxy', audience='') - self.joinGroup('web-services') - self.exportMethod(self.exportedMethods) - self.exportMethod(self.obj_list) - self.exportMethod(self.exec_workflow) - self.exportMethod(self.execute) - self.exportMethod(self.execute_cr) - - def execute_cr(self, cr, uid, obj, method, *args, **kw): - # - # TODO: check security level - # - try: - if (not method in getattr(self.obj_pool[obj],'_protected')) and len(args) and args[0] and len(self.obj_pool[obj]._inherits): - types = {obj: args[0]} - cr.execute('select inst_type,inst_id,obj_id from inherit where obj_type=%s and obj_id in ('+','.join(map(str,args[0]))+')', (obj,)) - for ty,id,id2 in cr.fetchall(): - if not ty in types: - types[ty]=[] - types[ty].append(id) - types[obj].remove(id2) - for t,ids in types.items(): - if len(ids): - t = self.obj_pool[t] - res = getattr(t,method)(cr, uid, ids, *args[1:], **kw) - else: - obj = self.obj_pool[obj] - res = getattr(obj,method)(cr, uid, *args, **kw) - return res - except orm.except_orm, inst: - #self.abortResponse(1, inst.value[0], inst.name, inst.value[1]) - self.abortResponse(1, inst.name, 'warning', inst.value) - except except_osv, inst: - self.abortResponse(1, inst.name, inst.exc_type, inst.value) - except psycopg.IntegrityError, inst: - for key in self._sql_error.keys(): - if key in inst[0]: - self.abortResponse(1, 'Constraint Error', 'warning', self._sql_error[key]) - self.abortResponse(1, 'Integrity Error', 'warning', inst[0]) - - - def execute(self, db, uid, obj, method, *args, **kw): - db, pool = pooler.get_db_and_pool(db) - cr = db.cursor() - try: - try: - res = pool.execute_cr(cr, uid, obj, method, *args, **kw) - cr.commit() - except Exception: - cr.rollback() - raise - finally: - cr.close() - return res - - def exec_workflow_cr(self, cr, uid, obj, method, *args): - wf_service = netsvc.LocalService("workflow") - wf_service.trg_validate(uid, obj, args[0], method, cr) - return True - - def exec_workflow(self, db, uid, obj, method, *args): - cr = pooler.get_db(db).cursor() - try: - try: - res = self.exec_workflow_cr(cr, uid, obj, method, *args) - cr.commit() - except Exception: - cr.rollback() - raise - finally: - cr.close() - return res - - def obj_list(self): - return self.obj_pool.keys() - - # adds a new object instance to the object pool. - # if it already existed, the instance is replaced - def add(self, name, obj_inst): - if self.obj_pool.has_key(name): - del self.obj_pool[name] - self.obj_pool[name] = obj_inst - - module = str(obj_inst.__class__)[6:] - module = module[:len(module)-1] - module = module.split('.')[0][2:] - self.module_object_list.setdefault(module, []).append(obj_inst) - - def get(self, name): - obj = self.obj_pool.get(name, None) -# We cannot uncomment this line because it breaks initialisation since objects do not initialize -# in the correct order and the ORM doesnt support correctly when some objets do not exist yet -# assert obj, "object %s does not exist !" % name - return obj - - #TODO: pass a list of modules to load - def instanciate(self, module): -# print "module list:", module_list -# for module in module_list: - res = [] - class_list = module_class_list.get(module, []) -# if module not in self.module_object_list: -# print "%s class_list:" % module, class_list - for klass in class_list: - res.append(klass.createInstance(self, module)) - return res -# else: -# print "skipping module", module - -#pooler.get_pool(cr.dbname) = osv_pool() +class osv_pool(netsvc.Service): -# -# See if we can use the pool var instead of the class_pool one -# -# XXX no more used -#class inheritor(type): -# def __new__(cls, name, bases, d): -# parent_name = d.get('_inherit', None) -# if parent_name: -# parent_class = class_pool.get(parent_name) -# assert parent_class, "parent class %s does not exist !" % parent_name -# for s in ('_columns', '_defaults', '_inherits'): -# new_dict = copy.copy(getattr(parent_class, s)) -# new_dict.update(d.get(s, {})) -# d[s] = new_dict -# bases = (parent_class,) -# res = type.__new__(cls, name, bases, d) -# # -# # update _inherits of others objects -# # -# return res - - - -class osv(orm.orm): - #__metaclass__ = inheritor - - def __new__(cls): - module = str(cls)[6:] - module = module[:len(module)-1] - module = module.split('.')[0][2:] - if not hasattr(cls, '_module'): - cls._module = module - module_class_list.setdefault(cls._module, []).append(cls) - class_pool[cls._name] = cls - if module not in module_list: - module_list.append(cls._module) - return None - - # - # Goal: try to apply inheritancy at the instanciation level and - # put objects in the pool var - # - def createInstance(cls, pool, module): -# obj = cls() - parent_name = hasattr(cls, '_inherit') and cls._inherit - if parent_name: - parent_class = pool.get(parent_name).__class__ - assert parent_class, "parent class %s does not exist !" % parent_name - nattr = {} - for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'): - new = copy.copy(getattr(pool.get(parent_name), s)) - if hasattr(new, 'update'): - new.update(cls.__dict__.get(s, {})) - else: - new.extend(cls.__dict__.get(s, [])) - nattr[s] = new - #bases = (parent_class,) - #obj.__class__ += (parent_class,) - #res = type.__new__(cls, name, bases, d) - name = hasattr(cls,'_name') and cls._name or cls._inherit - #name = str(cls) - cls = type(name, (cls, parent_class), nattr) - - obj = object.__new__(cls) - obj.__init__(pool) - return obj -# return object.__new__(cls, pool) - createInstance = classmethod(createInstance) - - def __init__(self, pool): -# print "__init__", self._name, pool - pool.add(self._name, self) - self.pool = pool - orm.orm.__init__(self) - -# pooler.get_pool(cr.dbname).add(self._name, self) -# print self._name, module - -class Cacheable(object): - - _cache = {} - count = 0 - - def __delete_key(self, key): - odico = self._cache - for key_item in key[:-1]: - odico = odico[key_item] - del odico[key[-1]] - - def __add_key(self, key, value): - odico = self._cache - for key_item in key[:-1]: - odico = odico.setdefault(key_item, {}) - odico[key[-1]] = value - - def add(self, key, value): - self.__add_key(key, value) - - def invalidate(self, key): - self.__delete_key(key) - - def get(self, key): - try: - w = self._cache[key] - return w - except KeyError: - return None - - def clear(self): - self._cache.clear() - self._items = [] - -def filter_dict(d, fields): - res = {} - for f in fields + ['id']: - if f in d: - res[f] = d[f] - return res - -class cacheable_osv(osv, Cacheable): - - _relevant = ['lang'] - - def __init__(self): - super(cacheable_osv, self).__init__() - - def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'): - if not fields: - fields=[] - if not context: - context={} - fields = fields or self._columns.keys() - ctx = [context.get(x, False) for x in self._relevant] - result, tofetch = [], [] - for id in ids: - res = self.get(self._name, id, ctx) - if not res: - tofetch.append(id) - else: - result.append(filter_dict(res, fields)) - - # gen the list of "local" (ie not inherited) fields which are classic or many2one - nfields = filter(lambda x: x[1]._classic_write, self._columns.items()) - # gen the list of inherited fields - inherits = map(lambda x: (x[0], x[1][2]), self._inherit_fields.items()) - # complete the field list with the inherited fields which are classic or many2one - nfields += filter(lambda x: x[1]._classic_write, inherits) - nfields = [x[0] for x in nfields] - - res = super(cacheable_osv, self).read(cr, user, tofetch, nfields, context, load) - for r in res: - self.add((self._name, r['id'], ctx), r) - result.append(filter_dict(r, fields)) - - # Appel de fonction si necessaire - tofetch = [] - for f in fields: - if f not in nfields: - tofetch.append(f) - for f in tofetch: - fvals = self._columns[f].get(cr, self, ids, f, user, context=context) - for r in result: - r[f] = fvals[r['id']] - - # TODO: tri par self._order !! - return result - - def invalidate(self, key): - del self._cache[key[0]][key[1]] - - def write(self, cr, user, ids, values, context=None): - if not context: - context={} - for id in ids: - self.invalidate((self._name, id)) - return super(cacheable_osv, self).write(cr, user, ids, values, context) - - def unlink(self, cr, user, ids): - self.clear() - return super(cacheable_osv, self).unlink(cr, user, ids) - -#cacheable_osv = osv - -# vim:noexpandtab: - -#class FakePool(object): -# def __init__(self, module): -# self.preferred_module = module - -# def get(self, name): -# localpool = module_objects_dict.get(self.preferred_module, {'dict': {}})['dict'] -# if name in localpool: -# obj = localpool[name] -# else: -# obj = pooler.get_pool(cr.dbname).get(name) -# return obj - -# fake_pool = self -# class fake_class(obj.__class__): -# def __init__(self): -# super(fake_class, self).__init__() -# self.pool = fake_pool - -# return fake_class() + def check(f): + @wraps(f) + def wrapper(self, dbname, *args, **kwargs): + """ Wraps around OSV functions and normalises a few exceptions + """ + + def tr(src, ttype): + # We try to do the same as the _(), but without the frame + # inspection, since we aready are wrapping an osv function + # trans_obj = self.get('ir.translation') cannot work yet :( + ctx = {} + if not kwargs: + if args and isinstance(args[-1], dict): + ctx = args[-1] + else: + ctx = {} + elif isinstance(kwargs, dict): + ctx = kwargs.get('context', {}) + else: + ctx = {} + + uid = 1 + if args and isinstance(args[0], (long, int)): + uid = args[0] + + lang = ctx and ctx.get('lang', False) + if not (lang or hasattr(src, '__call__')): + return src + + # We open a *new* cursor here, one reason is that failed SQL + # queries (as in IntegrityError) will invalidate the current one. + cr = False + + if hasattr(src, '__call__'): + # callable. We need to find the right parameters to call + # the orm._sql_message(self, cr, uid, ids, context) function, + # or we skip.. + # our signature is f(osv_pool, dbname [,uid, obj, method, args]) + try: + if args and len(args) > 1: + obj = self.get(args[1]) + if len(args) > 3 and isinstance(args[3], (long, int, list)): + ids = args[3] + else: + ids = [] + cr = pooler.get_db_only(dbname).cursor() + return src(obj, cr, uid, ids, context=(ctx or {})) + except Exception, e: + pass + finally: + if cr: cr.close() + + return False # so that the original SQL error will + # be returned, it is the best we have. + + try: + cr = pooler.get_db_only(dbname).cursor() + #return trans_obj._get_source( name=?) + res = translate(cr, name=False, source_type=ttype, + lang=lang, source=src) + if res: + return res + else: + return src + finally: + if cr: cr.close() + + def _(src): + return tr(src, 'code') + + try: + if not pooler.get_pool(dbname)._ready: + raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.') + return f(self, dbname, *args, **kwargs) + except orm.except_orm, inst: + if inst.name == 'AccessError': + self.logger.debug("AccessError", exc_info=True) + self.abortResponse(1, inst.name, 'warning', inst.value) + except except_osv, inst: + self.abortResponse(1, inst.name, inst.exc_type, inst.value) + except IntegrityError, inst: + for key in self._sql_error.keys(): + if key in inst[0]: + self.abortResponse(1, _('Constraint Error'), 'warning', + tr(self._sql_error[key], 'sql_constraint') or inst[0]) + if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION): + 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') + self.logger.debug("IntegrityError", exc_info=True) + try: + errortxt = inst.pgerror.replace('«','"').replace('»','"') + if '"public".' in errortxt: + context = errortxt.split('"public".')[1] + model_name = table = context.split('"')[1] + else: + last_quote_end = errortxt.rfind('"') + last_quote_begin = errortxt.rfind('"', 0, last_quote_end) + model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip() + print "MODEL", last_quote_begin, last_quote_end, model_name + model = table.replace("_",".") + model_obj = self.get(model) + if model_obj: + model_name = model_obj._description or model_obj._name + msg += _('\n\n[object with reference: %s - %s]') % (model_name, model) + except Exception: + pass + self.abortResponse(1, _('Integrity Error'), 'warning', msg) + else: + self.abortResponse(1, _('Integrity Error'), 'warning', inst[0]) + except Exception, e: + self.logger.exception("Uncaught exception") + raise + + return wrapper + + + def __init__(self): + self._ready = False + self.obj_pool = {} + self.module_object_list = {} + self.created = [] + self._sql_error = {} + self._store_function = {} + self._init = True + self._init_parent = {} + self.logger = logging.getLogger("web-services") + netsvc.Service.__init__(self, 'object_proxy', audience='') + self.exportMethod(self.obj_list) + self.exportMethod(self.exec_workflow) + self.exportMethod(self.execute) + + def init_set(self, cr, mode): + different = mode != self._init + if different: + if mode: + self._init_parent = {} + if not mode: + for o in self._init_parent: + self.get(o)._parent_store_compute(cr) + self._init = mode + + self._ready = True + return different + + def execute_cr(self, cr, uid, obj, method, *args, **kw): + object = pooler.get_pool(cr.dbname).get(obj) + if not object: + raise except_osv('Object Error', 'Object %s doesn\'t exist' % str(obj)) + return getattr(object, method)(cr, uid, *args, **kw) + + @check + def execute(self, db, uid, obj, method, *args, **kw): + db, pool = pooler.get_db_and_pool(db) + cr = db.cursor() + try: + try: + if method.startswith('_'): + raise except_osv('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,)) + res = pool.execute_cr(cr, uid, obj, method, *args, **kw) + if res is None: + self.logger.warning('The method %s of the object %s can not return `None` !', method, obj) + cr.commit() + except Exception: + cr.rollback() + raise + finally: + cr.close() + return res + + def exec_workflow_cr(self, cr, uid, obj, method, *args): + wf_service = netsvc.LocalService("workflow") + return wf_service.trg_validate(uid, obj, args[0], method, cr) + + @check + def exec_workflow(self, db, uid, obj, method, *args): + cr = pooler.get_db(db).cursor() + try: + try: + res = self.exec_workflow_cr(cr, uid, obj, method, *args) + cr.commit() + except Exception: + cr.rollback() + raise + finally: + cr.close() + return res + + def obj_list(self): + return self.obj_pool.keys() + + # adds a new object instance to the object pool. + # if it already existed, the instance is replaced + def add(self, name, obj_inst): + if name in self.obj_pool: + del self.obj_pool[name] + self.obj_pool[name] = obj_inst + + module = str(obj_inst.__class__)[6:] + module = module[:len(module)-1] + module = module.split('.')[0][2:] + self.module_object_list.setdefault(module, []).append(obj_inst) + + # Return None if object does not exist + def get(self, name): + obj = self.obj_pool.get(name, None) + return obj + + #TODO: pass a list of modules to load + def instanciate(self, module, cr): + res = [] + class_list = module_class_list.get(module, []) + for klass in class_list: + res.append(klass.createInstance(self, module, cr)) + return res + +class osv_base(object): + def __init__(self, pool, cr): + pool.add(self._name, self) + self.pool = pool + super(osv_base, self).__init__(cr) + + def __new__(cls): + module = str(cls)[6:] + module = module[:len(module)-1] + module = module.split('.')[0][2:] + if not hasattr(cls, '_module'): + cls._module = module + module_class_list.setdefault(cls._module, []).append(cls) + class_pool[cls._name] = cls + if module not in module_list: + module_list.append(cls._module) + return None + +class osv_memory(osv_base, orm.orm_memory): + # + # Goal: try to apply inheritancy at the instanciation level and + # put objects in the pool var + # + def createInstance(cls, pool, module, cr): + parent_names = getattr(cls, '_inherit', None) + if parent_names: + if isinstance(parent_names, (str, unicode)): + name = cls._name or parent_names + parent_names = [parent_names] + else: + name = cls._name + if not name: + raise TypeError('_name is mandatory in case of multiple inheritance') + + for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]): + parent_class = pool.get(parent_name).__class__ + assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module) + nattr = {} + for s in ('_columns', '_defaults'): + new = copy.copy(getattr(pool.get(parent_name), s)) + if hasattr(new, 'update'): + new.update(cls.__dict__.get(s, {})) + else: + new.extend(cls.__dict__.get(s, [])) + nattr[s] = new + cls = type(name, (cls, parent_class), nattr) + + obj = object.__new__(cls) + obj.__init__(pool, cr) + return obj + createInstance = classmethod(createInstance) + +class osv(osv_base, orm.orm): + # + # Goal: try to apply inheritancy at the instanciation level and + # put objects in the pool var + # + def createInstance(cls, pool, module, cr): + parent_names = getattr(cls, '_inherit', None) + if parent_names: + if isinstance(parent_names, (str, unicode)): + name = cls._name or parent_names + parent_names = [parent_names] + else: + name = cls._name + if not name: + raise TypeError('_name is mandatory in case of multiple inheritance') + + for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]): + parent_class = pool.get(parent_name).__class__ + assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module) + nattr = {} + for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'): + new = copy.copy(getattr(pool.get(parent_name), s)) + if hasattr(new, 'update'): + new.update(cls.__dict__.get(s, {})) + else: + if s=='_constraints': + for c in cls.__dict__.get(s, []): + exist = False + for c2 in range(len(new)): + #For _constraints, we should check field and methods as well + if new[c2][2]==c[2] and new[c2][0]==c[0]: + new[c2] = c + exist = True + break + if not exist: + new.append(c) + else: + new.extend(cls.__dict__.get(s, [])) + nattr[s] = new + cls = type(name, (cls, parent_class), nattr) + obj = object.__new__(cls) + obj.__init__(pool, cr) + return obj + createInstance = classmethod(createInstance) + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: