Using distant OpenERP objects pythonically
At work we have a project to realize a pygtk POS interface for OpenERP. It is getting closer and closer everyday and after much hagglings to create the right UI, I had to connect it to the underlying server. Using the so called netrpc protocol out of the box is not really the most pythonic you can get.
So I came up with those classes that made me fells just like home when I needed to work with the OpenERP objects.
class MetaOEObject(type):
def __init__(cls, name, bases, dict):
super(MetaOEObject, cls).__init__(name, bases, dict)
cls.proxy = Config.client.create_proxy(Config.database,
dict['dotted_name'])
cls.fields = cls.proxy.fields_get()
OEObject.proxies[dict['dotted_name']] = cls
class OEObject(object):
proxies = {}
def __init__(self, id=None):
self.id = id
if id is not None:
self.value = self.proxy.read(id)
@classmethod
def select(cls, condition):
ids = cls.proxy.search(condition)
return [cls(id) for id in ids]
def __getattr__(self, name):
if name not in self.fields:
raise AttributeError
elif self.fields[name]['type'] == 'many2one':
oeobj = OEObject.proxies[self.fields[name]['relation']]
if self.value[name]:
return oeobj(self.value[name][0])
else:
return None
elif self.fields[name]['type'] == 'one2many':
oeobj = OEObject.proxies[self.fields[name]['relation']]
return [oeobj(id) for ids in self.value[name]]
elif self.fields[name]['type'] == 'binary':
if not self.value[name]:
return None
filename = os.tempnam()
fd = open(filename, 'w')
fd.write(base64.b64decode(self.value[name]))
fd.flush()
return filename
return self.value[name]
def __str__(self):
print self.dotted_name, self.id
return '<%s (%d)>' % (self.dotted_name, self.id)
class Category(OEObject):
__metaclass__ = MetaOEObject
dotted_name = 'product.category'
class Product(OEObject):
__metaclass__ = MetaOEObject
dotted_name = 'product.product'
Those kind of classes makes use of metaclasses so that proxy and fields are class-attributes which seems nicer to me. It allows me write pieces of code like this
for category in Category.select([('parent_id', '=', None)]):
page = self.create_category_page(category, notebook)
for product in Product.select([('categ_id', '=', category.id)]):
page.add_product(product)
Which populates a GtkIconView into a notebook used to display products sorted by categories.
I'm not familiar with OpenERP but in case the fields for a given dotted_name are fixed, you can define one property per field in the metaclass instead of defining __getattr__. This should be a bit faster but more importantly it will be introspectable, i.e. dir(Category) would list the available field (property) names.
It's also possible to reduce the boilerplate even more by an appropriate MetaOEObject factory so that it can be used with something like:
class Category(object): __metaclass__ = MetaOEObjectFactory('product.category')
class Product(object): __metaclass__ = MetaOEObjectFactory('product.product')
Comment by George — May 21, 2010 9:21:46 AM | # - re
Indeed I could create a property by field this could be a nice addition. But my solution is already introspectable since cls.fields.keys() will give the list of the openerp specific attributes.
You're idea on how to reduce boilerplate is nice too.
Comment by nicoe — May 21, 2010 12:59:25 PM | # - re
You should store the objects instantiate for many2one and one2many. This will prevent to re-read them each time they are accessed.
Comment by Cédric Krier — May 23, 2010 12:58:43 AM | # - re
It seems that you have inspired Cedric.
groups.google.com/group/tryton-dev/browse_thread/thread/f1a7ceb4762a7411
Comment by B. — May 31, 2010 4:05:54 PM | # - re