From 7ffa499718601cd76b756696b436046dfb081cca Mon Sep 17 00:00:00 2001 From: resteve Date: Wed, 11 Jan 2012 17:53:18 +0100 Subject: [PATCH 01/18] If type datetime, we get str, not datetime. Convert str to datetime --- ooop.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) mode change 100644 => 100755 ooop.py diff --git a/ooop.py b/ooop.py old mode 100644 new mode 100755 index 0d23fad..8964174 --- a/ooop.py +++ b/ooop.py @@ -520,7 +520,10 @@ def __init__(self, manager, ref=None, model=None, copy=False, data=None, fields= # convert DateTime instance to datetime.datetime object for i in default_values: if self.fields[i]['ttype'] == 'datetime': - t = default_values[i].timetuple() + if isinstance(default_values[i], str): + t = datetime.strptime(default_values[i], "%Y-%m-%d %H:%M:%S").timetuple() + else: + t = default_values[i].timetuple() default_values[i] = datetime(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec) # active by default ? if self._ooop.active: From ca619504de1db28b7443eefdcc39e97768e29d5f Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Tue, 13 Dec 2011 11:30:24 +0100 Subject: [PATCH 02/18] no need to try to instanciate ``False`` (empty) m2o fields. --- ooop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ooop.py b/ooop.py index 8964174..0c1209e 100755 --- a/ooop.py +++ b/ooop.py @@ -544,7 +544,7 @@ def init_values(self, *args, **kargs): else: self.__dict__[name] = List(Manager(relation, self._ooop), data=self, model=relation) elif ttype == 'many2one': - if name in keys and kargs[name]: + if name in keys and kargs[name] is not False: # manager, ref=None, model=None, copy=False instance = Data(Manager(relation, self._ooop), kargs[name], relation) self.INSTANCES['%s:%s' % (relation, kargs[name])] = instance From 748b080c9b3037a24e1be19ede2773128bc70576 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Wed, 1 Feb 2012 18:52:00 +0100 Subject: [PATCH 03/18] allow value ``None`` to be sent through XML-RPC, as openerp use it a lot as default value for some arguments. --- ooop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ooop.py b/ooop.py index 0c1209e..8bbe110 100755 --- a/ooop.py +++ b/ooop.py @@ -140,7 +140,7 @@ def __init__(self, user='admin', pwd='admin', dbname='openerp', def connect(self): """login and sockets to xmlrpc services: common, object and report""" self.uid = self.login(self.dbname, self.user, self.pwd) - self.objectsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/object' % (self.uri, self.port)) + self.objectsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/object' % (self.uri, self.port), allow_none=True) self.reportsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/report' % (self.uri, self.port)) def login(self, dbname, user, pwd): From 9f7a97f64afd6e2e1a1bc032f30ed556ae89e365 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Fri, 4 May 2012 09:41:57 +0200 Subject: [PATCH 04/18] cleaner way to be compatible with new datetime formats that includes milliseconds. --- ooop.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ooop.py b/ooop.py index 8bbe110..8a0117d 100755 --- a/ooop.py +++ b/ooop.py @@ -644,11 +644,10 @@ def __getattr__(self, field): self.__dict__[name] = List(Manager(relation, self._ooop), data=self, model=relation) elif ttype == "datetime" and data[name]: - p1, p2 = data[name].split(".", 1) - d1 = datetime.strptime(p1, "%Y-%m-%d %H:%M:%S") - ms = int(p2.ljust(6,'0')[:6]) - d1.replace(microsecond=ms) - self.__dict__[name] = d1 + if len(data[name]) > 19: + self.__dict__[name] = datetime.strptime(data[name], "%Y-%m-%d %H:%M:%S.%f") + else: + self.__dict__[name] = datetime.strptime(data[name], "%Y-%m-%d %H:%M:%S") elif ttype == "date" and data[name]: self.__dict__[name] = date.fromordinal(datetime.strptime(data[name], "%Y-%m-%d").toordinal()) else: From 87f784fca434fd4fd449d7605720e3d437493a37 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Thu, 9 Feb 2012 16:31:57 +0100 Subject: [PATCH 05/18] some calls to ``unlink`` or ``write`` didn't send list of ids but full objects or single id to underlying xmlrpc call. --- ooop.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ooop.py b/ooop.py index 8a0117d..c592617 100755 --- a/ooop.py +++ b/ooop.py @@ -381,7 +381,7 @@ def delete(self): if self.parent: objects = self.parent.objects self.parent.objects = objects[:self.low] + objects[self.high:] - return self.manager._ooop.unlink(self.model, self.objects) + return self.manager._ooop.unlink(self.model, [o._ref for o in self.objects]) def append(self, value): if self.data: @@ -694,7 +694,7 @@ def save(self): # create or write the object if self._ref > 0 and not self._copy: # same object - self._ooop.write(self._model, self._ref, data) + self._ooop.write(self._model, [self._ref], data) else: self._ref = self._ooop.create(self._model, data) @@ -705,7 +705,7 @@ def save(self): def delete(self): if self._ref > 0: - self._ooop.unlink(self._model, self._ref) + self._ooop.unlink(self._model, [self._ref]) #else: # pass # TODO remove(self) From b419417d01c1e342b04f854315350bd3d06b10a7 Mon Sep 17 00:00:00 2001 From: Zikzakmedia Date: Mon, 11 Jul 2011 11:06:51 +0200 Subject: [PATCH 06/18] ``offset``, ``limit``, and ``order`` optional argument added to ``search`` method. --- AUTHORS | 1 + ooop.py | 30 ++++++++++++++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/AUTHORS b/AUTHORS index c8b2a90..c0c1d66 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,2 +1,3 @@ Pedro A. Gracia Fajardo Gamaliel Toro +Raimon Esteve Cusiné diff --git a/ooop.py b/ooop.py index c592617..f1ccb9d 100755 --- a/ooop.py +++ b/ooop.py @@ -156,6 +156,8 @@ def create(self, model, data): """ create a new register """ if self.debug: print "DEBUG [create]:", model, data + if 'id' in data: + del data['id'] return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'create', data) def unlink(self, model, ids): @@ -182,11 +184,11 @@ def read_all(self, model, fields=[]): print "DEBUG [read_all]:", model, fields return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'read', self.all(model), fields) - def search(self, model, query): + def search(self, model, query, offset=0, limit=999, order=''): """ return ids that match with 'query' """ if self.debug: - print "DEBUG [search]:", model, query - return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'search', query) + print "DEBUG [search]:", model, query, offset, limit, order + return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'search', query, offset, limit, order) # TODO: verify if remove this def custom_execute(self, model, ids, remote_method, data): @@ -451,15 +453,27 @@ def all(self, fields=[], offset=0, limit=999999, as_list=False): def filter(self, fields=[], as_list=False, **kargs): q = [] # query dict + offset = 0 + limit = 999 + order = '' for key, value in kargs.items(): - if not '__' in key: - op = '=' - else: + if key == 'offset': + if int(value): + offset = value + elif key == 'limit': + if int(value): + limit = value + elif key == 'order': + order = value + elif '__' in key: i = key.find('__') op = OPERATORS[key[i+2:]] key = key[:i] - q.append(('%s' % key, op, value)) - ids = self._ooop.search(self._model, q) + q.append(('%s' % key, op, value)) + else: + op = '=' + q.append(('%s' % key, op, value)) + ids = self._ooop.search(self._model, q, offset, limit, order) if as_list: return self.read(ids, fields) return List(self, ids) From a416d87bb8cfd14380c9723d8c458d61b7b71422 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Fri, 4 May 2012 11:38:07 +0200 Subject: [PATCH 07/18] avoid using ``.name`` when not existing... And what is the use of this __%s attribute ? --- ooop.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ooop.py b/ooop.py index f1ccb9d..58b2eff 100755 --- a/ooop.py +++ b/ooop.py @@ -699,7 +699,10 @@ def save(self): if self.__dict__[name]: data[name] = self.__dict__[name]._ref # update __name and INSTANCES (cache) - self.__dict__['__%s' % name] = [self.__dict__[name]._ref, self.__dict__[name].name] + ## TODO: remove the need of having a ".name" in object ! + ## XXXvlab: what's the use of the '__%s' attributes ? + if 'name' in dir(self.__dict__[name]): + self.__dict__['__%s' % name] = [self.__dict__[name]._ref, self.__dict__[name].name] self.INSTANCES['%s:%s' % (relation, self.__dict__[name]._ref)] = self.__dict__[name] if self._ooop.debug: From 561decb9cc6d529a71f929c5006f29ba93f353dc Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Fri, 4 May 2012 12:12:01 +0200 Subject: [PATCH 08/18] added ``context`` parameter support to allow different ``lang`` settings. --- ooop.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/ooop.py b/ooop.py index 58b2eff..2bcf8b6 100755 --- a/ooop.py +++ b/ooop.py @@ -112,7 +112,7 @@ class OOOP: """ Main class to manage xml-rpc communication with openerp-server """ def __init__(self, user='admin', pwd='admin', dbname='openerp', uri='http://localhost', port=8069, debug=False, - exe=False, active=True, **kwargs): + exe=False, active=True, context=None, lang=None, **kwargs): self.user = user # default: 'admin' self.pwd = pwd # default: 'admin' self.dbname = dbname # default: 'openerp' @@ -128,6 +128,11 @@ def __init__(self, user='admin', pwd='admin', dbname='openerp', self.models = {} self.fields = {} + self.context = context if context else {} + + if lang: + self.context['lang'] = lang + #has to be uid, cr, parent (the openerp model to get the pool) if len(kwargs) == 3: self.uid = kwargs['uid'] @@ -158,43 +163,43 @@ def create(self, model, data): print "DEBUG [create]:", model, data if 'id' in data: del data['id'] - return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'create', data) + return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'create', data, self.context) def unlink(self, model, ids): """ remove register """ if self.debug: print "DEBUG [unlink]:", model, ids - return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'unlink', ids) + return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'unlink', ids, self.context) def write(self, model, ids, value): """ update register """ if self.debug: print "DEBUG [write]:", model, ids, value - return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'write', ids, value) + return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'write', ids, value, self.context) def read(self, model, ids, fields=[]): """ update register """ if self.debug: print "DEBUG [read]:", model, ids, fields - return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'read', ids, fields) + return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'read', ids, fields, self.context) def read_all(self, model, fields=[]): """ update register """ if self.debug: print "DEBUG [read_all]:", model, fields - return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'read', self.all(model), fields) + return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'read', self.all(model), fields, self.context) def search(self, model, query, offset=0, limit=999, order=''): """ return ids that match with 'query' """ if self.debug: print "DEBUG [search]:", model, query, offset, limit, order - return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'search', query, offset, limit, order) - + return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'search', query, offset, limit, order, self.context) + # TODO: verify if remove this def custom_execute(self, model, ids, remote_method, data): if self.debug: print "DEBUG [custom_execute]:", self.dbname, self.uid, self.pwd, model, args - return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, ids, remote_method, data) + return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, ids, remote_method, data, self.context) def all(self, model, query=[]): """ return all ids """ From f2e74654ab96f917b206e10052974276ac0092a6 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Thu, 20 Dec 2012 17:25:04 +0100 Subject: [PATCH 09/18] fix: allow to query bogus model as ``worklow_instances`` that do not have ``active`` field. --- ooop.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ooop.py b/ooop.py index 2bcf8b6..17b50c8 100755 --- a/ooop.py +++ b/ooop.py @@ -538,6 +538,8 @@ def __init__(self, manager, ref=None, model=None, copy=False, data=None, fields= default_values = self._manager.default_get(field_names) # convert DateTime instance to datetime.datetime object for i in default_values: + if i not in self.fields: + continue if self.fields[i]['ttype'] == 'datetime': if isinstance(default_values[i], str): t = datetime.strptime(default_values[i], "%Y-%m-%d %H:%M:%S").timetuple() @@ -545,7 +547,7 @@ def __init__(self, manager, ref=None, model=None, copy=False, data=None, fields= t = default_values[i].timetuple() default_values[i] = datetime(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec) # active by default ? - if self._ooop.active: + if 'active' in self.fields and self._ooop.active: default_values['active'] = True default_values.update(**kargs) # initial values from caller self.init_values(**default_values) From 005ebb409b66755f1b17f099b7384e4c5f039f3d Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Thu, 20 Dec 2012 17:26:13 +0100 Subject: [PATCH 10/18] fix: remove default limit to search (which is applied by default to ``all()``) --- ooop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ooop.py b/ooop.py index 17b50c8..0295054 100755 --- a/ooop.py +++ b/ooop.py @@ -189,7 +189,7 @@ def read_all(self, model, fields=[]): print "DEBUG [read_all]:", model, fields return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'read', self.all(model), fields, self.context) - def search(self, model, query, offset=0, limit=999, order=''): + def search(self, model, query, offset=0, limit=None, order=None): """ return ids that match with 'query' """ if self.debug: print "DEBUG [search]:", model, query, offset, limit, order From 33fb3a59b2f4976f3473e22a0f8a8849c30e0e05 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Fri, 3 May 2013 19:59:33 +0200 Subject: [PATCH 11/18] fix: should cast an AttributeError so that ``getattr()`` can be used with a default value. --- ooop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ooop.py b/ooop.py index 0295054..f5ffad1 100755 --- a/ooop.py +++ b/ooop.py @@ -628,7 +628,7 @@ def __getattr__(self, field): try: name = self.fields[field]['name'] except: - raise NameError('field \'%s\' is not defined' % field) + raise AttributeError('field \'%s\' is not defined' % field) ttype = self.fields[field]['ttype'] relation = self.fields[field]['relation'] if ttype == 'many2one': From ededdfed375b07ccb4ad8d7c7cb5e56da483429d Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Mon, 30 Jun 2014 10:52:25 +0200 Subject: [PATCH 12/18] fix: remove arbitrary limit when using filter. It should be added this limit could not be removed easily due to the fact that providing 'None' or '0' was ignored due to the ``if int(limit)`` barrier. --- ooop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ooop.py b/ooop.py index f5ffad1..713f1b7 100755 --- a/ooop.py +++ b/ooop.py @@ -459,7 +459,7 @@ def all(self, fields=[], offset=0, limit=999999, as_list=False): def filter(self, fields=[], as_list=False, **kargs): q = [] # query dict offset = 0 - limit = 999 + limit = None order = '' for key, value in kargs.items(): if key == 'offset': From 6bbc6de8aa89a44cf8b1bfae6879e44bff13dc37 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Thu, 12 Mar 2015 14:49:52 +0700 Subject: [PATCH 13/18] new: detect when login returns ``False`` and cast an exception. --- ooop.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ooop.py b/ooop.py index 713f1b7..45dea03 100755 --- a/ooop.py +++ b/ooop.py @@ -108,6 +108,10 @@ def execute(self, *args, **kargs): return getattr(_model, action)(self.cr, uid) # is callable +class LoginFailed(Exception): + pass + + class OOOP: """ Main class to manage xml-rpc communication with openerp-server """ def __init__(self, user='admin', pwd='admin', dbname='openerp', @@ -145,6 +149,8 @@ def __init__(self, user='admin', pwd='admin', dbname='openerp', def connect(self): """login and sockets to xmlrpc services: common, object and report""" self.uid = self.login(self.dbname, self.user, self.pwd) + if self.uid is False: + raise LoginFailed() self.objectsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/object' % (self.uri, self.port), allow_none=True) self.reportsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/report' % (self.uri, self.port)) From cf12466c32bbed5e759e0ffb6438ab5c751f70c8 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Mon, 13 Apr 2015 15:04:27 +0800 Subject: [PATCH 14/18] new: add ``verify`` to enable/disable SSL cert validation. Note that in python version < 2.7.9, this will fail if ``verify`` is ``True`` as by default, python library would NOT verify SSL certs. backporting the validation was not included in this version so it'll just fail with a hopefully clear error message. On version > 2.7.9, you can disable (or leave enabled) the verification. --- ooop.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/ooop.py b/ooop.py index 45dea03..797b3b3 100755 --- a/ooop.py +++ b/ooop.py @@ -20,6 +20,7 @@ # ######################################################################## +import sys import xmlrpclib import time import base64 @@ -116,7 +117,9 @@ class OOOP: """ Main class to manage xml-rpc communication with openerp-server """ def __init__(self, user='admin', pwd='admin', dbname='openerp', uri='http://localhost', port=8069, debug=False, - exe=False, active=True, context=None, lang=None, **kwargs): + exe=False, active=True, context=None, lang=None, + verify=True, + **kwargs): self.user = user # default: 'admin' self.pwd = pwd # default: 'admin' self.dbname = dbname # default: 'openerp' @@ -131,12 +134,25 @@ def __init__(self, user='admin', pwd='admin', dbname='openerp', self.uid = None self.models = {} self.fields = {} + self.verify = verify self.context = context if context else {} if lang: self.context['lang'] = lang + self.http_kws = {} + if self.uri.startswith('https://'): + if sys.version_info < (2, 7, 9): + if self.verify: + raise ValueError( + "SSL connections are not verified for " + "valid certificate in python version < 2.7.9") + else: + if not self.verify: + import ssl + self.http_kws["context"] = ssl._create_unverified_context() + #has to be uid, cr, parent (the openerp model to get the pool) if len(kwargs) == 3: self.uid = kwargs['uid'] @@ -151,11 +167,11 @@ def connect(self): self.uid = self.login(self.dbname, self.user, self.pwd) if self.uid is False: raise LoginFailed() - self.objectsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/object' % (self.uri, self.port), allow_none=True) - self.reportsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/report' % (self.uri, self.port)) - + self.objectsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/object' % (self.uri, self.port), allow_none=True, **self.http_kws) + self.reportsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/report' % (self.uri, self.port), **self.http_kws) + def login(self, dbname, user, pwd): - self.commonsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/common' % (self.uri, self.port)) + self.commonsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/common' % (self.uri, self.port), **self.http_kws) return self.commonsock.login(dbname, user, pwd) def execute(self, model, *args): From ad661ebdf805711f4d175a050981311fbb9a0142 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Mon, 13 Apr 2015 15:14:19 +0800 Subject: [PATCH 15/18] chg: dev: avoid importing pydot globally. This is an loading time optimization as ``pydot`` is only used in one function and that the import time of ``pydot`` is non neglectable. --- ooop.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ooop.py b/ooop.py index 797b3b3..7285133 100755 --- a/ooop.py +++ b/ooop.py @@ -27,11 +27,6 @@ import types from datetime import datetime, date -# check if pydot is installed -try: - import pydot -except: - pydot = False __author__ = "Pedro Gracia " __license__ = "GPLv3+" @@ -266,7 +261,15 @@ def set_model(self, model, r={}, deep=None): def export(self, filename, filetype, showfields=True, model=None, deep=-1): """Export the model to dot file""" #o2m 0..* m2m *..* m2o *..0 - + + ## import pydot only there because its the sole function using it, + ## and it's quite long to import. + # check if pydot is installed + try: + import pydot + except: + pydot = False + if not pydot: raise ImportError('no pydot package found') From 0a3fbae04efb9c1cb7ccea6730d0a9076b25c213 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Mon, 13 Apr 2015 15:21:02 +0800 Subject: [PATCH 16/18] new: add ``load_models`` if you don't intend to use CRUD api. --- ooop.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ooop.py b/ooop.py index 7285133..3f00142 100755 --- a/ooop.py +++ b/ooop.py @@ -113,7 +113,7 @@ class OOOP: def __init__(self, user='admin', pwd='admin', dbname='openerp', uri='http://localhost', port=8069, debug=False, exe=False, active=True, context=None, lang=None, - verify=True, + load_models=True, verify=True, **kwargs): self.user = user # default: 'admin' self.pwd = pwd # default: 'admin' @@ -154,8 +154,10 @@ def __init__(self, user='admin', pwd='admin', dbname='openerp', self.objectsock = objectsock_mock(kwargs['parent'], kwargs['cr']) else: self.connect() - - self.load_models() + + if load_models: + self.load_models() + def connect(self): """login and sockets to xmlrpc services: common, object and report""" From 24ac54a8565ab11a48d8cca78e959c785f8aab61 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Mon, 26 Jan 2015 15:09:30 +0700 Subject: [PATCH 17/18] new: doc: added warnings. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8d4248..9ede61e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - +This is an experimental fork of OOOP. Use at your own risk. **Warning: this is a very initial release.** From 296c202622698fd312cdff0f67e5c04111dd53a1 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Wed, 19 Sep 2018 10:55:13 +0200 Subject: [PATCH 18/18] fix: compatibility with 11.0 --- ooop.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ooop.py b/ooop.py index 3f00142..37247b6 100755 --- a/ooop.py +++ b/ooop.py @@ -30,7 +30,7 @@ __author__ = "Pedro Gracia " __license__ = "GPLv3+" -__version__ = "0.2.3" +__version__ = "0.3.0" OOOPMODELS = 'ir.model' @@ -208,11 +208,11 @@ def read_all(self, model, fields=[]): print "DEBUG [read_all]:", model, fields return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'read', self.all(model), fields, self.context) - def search(self, model, query, offset=0, limit=None, order=None): + def search(self, model, query, offset=0, limit=None, order=None, count=False): """ return ids that match with 'query' """ if self.debug: print "DEBUG [search]:", model, query, offset, limit, order - return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'search', query, offset, limit, order, self.context) + return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'search', query, offset, limit, order, count, self.context) # TODO: verify if remove this def custom_execute(self, model, ids, remote_method, data): @@ -658,16 +658,19 @@ def __getattr__(self, field): raise AttributeError('field \'%s\' is not defined' % field) ttype = self.fields[field]['ttype'] relation = self.fields[field]['relation'] + assert(isinstance(data, list) and len(data) == 1) + data = data[0] if ttype == 'many2one': if data[name]: # TODO: review this self.__dict__['__%s' % name] = data[name] - key = '%s:%i' % (relation, data[name][0]) + assert(isinstance(data[name], int)) + key = '%s:%i' % (relation, data[name]) if key in self.INSTANCES.keys(): self.__dict__[name] = self.INSTANCES[key] else: # TODO: use a Manager instance, not Data instance = Data(Manager(relation, self._ooop), - data[name][0], relation, data=self) + data[name], relation, data=self) self.__dict__[name] = instance self.INSTANCES[key] = instance else: