Using distant OpenERP objects pythonically

written by nicoe, on May 19, 2010 10:03:00 PM.

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.

Comments

  • 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

  • Comment by B. — May 31, 2010 4:05:54 PM | # - re

Leave a Reply