Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Here are several fixes and some features #22

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7ffa499
If type datetime, we get str, not datetime. Convert str to datetime
Jan 11, 2012
ca61950
no need to try to instanciate ``False`` (empty) m2o fields.
vaab Dec 13, 2011
748b080
allow value ``None`` to be sent through XML-RPC, as openerp use it a …
vaab Feb 1, 2012
9f7a97f
cleaner way to be compatible with new datetime formats that includes …
vaab May 4, 2012
87f784f
some calls to ``unlink`` or ``write`` didn't send list of ids but ful…
vaab Feb 9, 2012
b419417
``offset``, ``limit``, and ``order`` optional argument added to ``sea…
Jul 11, 2011
a416d87
avoid using ``.name`` when not existing... And what is the use of thi…
vaab May 4, 2012
561decb
added ``context`` parameter support to allow different ``lang`` setti…
vaab May 4, 2012
f2e7465
fix: allow to query bogus model as ``worklow_instances`` that do not …
vaab Dec 20, 2012
005ebb4
fix: remove default limit to search (which is applied by default to `…
vaab Dec 20, 2012
33fb3a5
fix: should cast an AttributeError so that ``getattr()`` can be used …
vaab May 3, 2013
ededdfe
fix: remove arbitrary limit when using filter.
vaab Jun 30, 2014
6bbc6de
new: detect when login returns ``False`` and cast an exception.
vaab Mar 12, 2015
cf12466
new: add ``verify`` to enable/disable SSL cert validation.
vaab Apr 13, 2015
ad661eb
chg: dev: avoid importing pydot globally.
vaab Apr 13, 2015
0a3fbae
new: add ``load_models`` if you don't intend to use CRUD api.
vaab Apr 13, 2015
24ac54a
new: doc: added warnings.
vaab Jan 26, 2015
296c202
fix: compatibility with 11.0
vaab Sep 19, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Pedro A. Gracia Fajardo <[email protected]>
Gamaliel Toro <[email protected]>
Raimon Esteve Cusiné <[email protected]>
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<img src="http://github.com/lasarux/ooop/raw/master/artwork/ooop.png" width="150px" height="150px" />
This is an experimental fork of OOOP. Use at your own risk.

**Warning: this is a very initial release.**

144 changes: 100 additions & 44 deletions ooop.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -20,21 +20,17 @@
#
########################################################################

import sys
import xmlrpclib
import time
import base64
import types
from datetime import datetime, date

# check if pydot is installed
try:
import pydot
except:
pydot = False

__author__ = "Pedro Gracia <[email protected]>"
__license__ = "GPLv3+"
__version__ = "0.2.3"
__version__ = "0.3.0"


OOOPMODELS = 'ir.model'
@@ -108,11 +104,17 @@ 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',
uri='http://localhost', port=8069, debug=False,
exe=False, active=True, **kwargs):
exe=False, active=True, context=None, lang=None,
load_models=True, verify=True,
**kwargs):
self.user = user # default: 'admin'
self.pwd = pwd # default: 'admin'
self.dbname = dbname # default: 'openerp'
@@ -127,24 +129,46 @@ 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']
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"""
self.uid = self.login(self.dbname, self.user, self.pwd)
self.objectsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/object' % (self.uri, self.port))
self.reportsock = xmlrpclib.ServerProxy('%s:%i/xmlrpc/report' % (self.uri, self.port))

if self.uid is False:
raise LoginFailed()
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):
@@ -156,43 +180,45 @@ def create(self, model, data):
""" create a new register """
if self.debug:
print "DEBUG [create]:", model, data
return self.objectsock.execute(self.dbname, self.uid, self.pwd, model, 'create', data)
if 'id' in data:
del data['id']
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):
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
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, count, 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 """
@@ -237,7 +263,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')

@@ -381,7 +415,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:
@@ -451,15 +485,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 = None
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)
@@ -519,11 +565,16 @@ 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':
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:
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)
@@ -541,7 +592,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
@@ -604,19 +655,22 @@ 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']
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:
@@ -641,11 +695,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:
@@ -683,7 +736,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:
@@ -692,7 +748,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)

@@ -703,7 +759,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)