Skip to content

Commit

Permalink
Merge pull request #91 from guewen/anybox-default_binder
Browse files Browse the repository at this point in the history
Default binder
  • Loading branch information
sebastienbeau committed Sep 10, 2015
2 parents a553be3 + 320e779 commit 1f24696
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 36 deletions.
1 change: 1 addition & 0 deletions connector/CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Future
* method 'install_in_connector' is now deprecated
* Add a retry pattern for jobs (https://github.com/OCA/connector/pull/75)
* Use custom connector environments and instantiate them with needed attributes (https://github.com/OCA/connector/pull/108)
* A new default implementation for the binder (https://github.com/OCA/connector/pull/76)

3.1.0 (2015-05-15)
~~~~~~~~~~~~~~~~~~
Expand Down
89 changes: 75 additions & 14 deletions connector/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

import logging
from contextlib import contextmanager
from openerp import models, fields

from .deprecate import log_deprecate, DeprecatedClass

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -360,28 +362,43 @@ class Binder(ConnectorUnit):
""" For one record of a model, capable to find an external or
internal id, or create the binding (link) between them
The Binder should be implemented in the connectors.
This is a default implementation that can be inherited or reimplemented
in the connectors.
This implementation assumes that binding models are ``_inherits`` of
the models they are binding.
"""

_model_name = None # define in sub-classes
_external_field = 'external_id' # override in sub-classes
_backend_field = 'backend_id' # override in sub-classes
_openerp_field = 'openerp_id' # override in sub-classes
_sync_date_field = 'sync_date' # override in sub-classes

def to_openerp(self, external_id, unwrap=False, browse=False):
def to_openerp(self, external_id, unwrap=False):
""" Give the OpenERP ID for an external ID
:param external_id: external ID for which we want
the OpenERP ID
:param unwrap: if True, returns the openerp_id
else return the id of the binding
:param browse: if True, returns a recordset
:return: a record ID, depending on the value of unwrap,
or None if the external_id is not mapped
:rtype: int
:param unwrap: if True, returns the normal record
else return the binding record
:return: a recordset, depending on the value of unwrap,
or an empty recordset if the external_id is not mapped
:rtype: recordset
"""
raise NotImplementedError
bindings = self.model.with_context(active_test=False).search(
[(self._external_field, '=', str(external_id)),
(self._backend_field, '=', self.backend_record.id)]
)
if not bindings:
return self.model.browse()
bindings.ensure_one()
if unwrap:
bindings = getattr(bindings, self._openerp_field)
return bindings

def to_backend(self, binding_id, wrap=False):
""" Give the external ID for an OpenERP binding ID
(ID in a model magento.*)
:param binding_id: OpenERP binding ID for which we want the backend id
:param wrap: if False, binding_id is the ID of the binding,
Expand All @@ -390,7 +407,25 @@ def to_backend(self, binding_id, wrap=False):
the backend id of the binding
:return: external ID of the record
"""
raise NotImplementedError
record = self.model.browse()
if isinstance(binding_id, models.BaseModel):
binding_id.ensure_one()
record = binding_id
binding_id = binding_id.id
if wrap:
binding = self.model.with_context(active_test=False).search(
[(self._openerp_field, '=', binding_id),
(self._backend_field, '=', self.backend_record.id),
]
)
if not binding:
return None
binding.ensure_one()
return getattr(binding, self._external_field)
if not record:
record = self.model.browse(binding_id)
assert record
return getattr(record, self._external_field)

def bind(self, external_id, binding_id):
""" Create the link between an external ID and an OpenERP ID
Expand All @@ -399,7 +434,19 @@ def bind(self, external_id, binding_id):
:param binding_id: OpenERP ID to bind
:type binding_id: int
"""
raise NotImplementedError
# Prevent False, None, or "", but not 0
assert (external_id or external_id == 0) and binding_id, (
"external_id or binding_id missing, "
"got: %s, %s" % (external_id, binding_id)
)
# avoid to trigger the export when we modify the `external_id`
now_fmt = fields.Datetime.now()
if not isinstance(binding_id, models.BaseModel):
binding_id = self.model.browse(binding_id)
binding_id.with_context(connector_no_export=True).write(
{self._external_field: str(external_id),
self._sync_date_field: now_fmt,
})

def unwrap_binding(self, binding_id, browse=False):
""" For a binding record, gives the normal record.
Expand All @@ -410,12 +457,26 @@ def unwrap_binding(self, binding_id, browse=False):
:param browse: when True, returns a browse_record instance
rather than an ID
"""
raise NotImplementedError
if isinstance(binding_id, models.BaseModel):
binding = binding_id
else:
binding = self.model.browse(binding_id)

openerp_record = getattr(binding, self._openerp_field)
if browse:
return openerp_record
return openerp_record.id

def unwrap_model(self):
""" For a binding model, gives the normal model.
Example: when called on a binder for ``magento.product.product``,
it will return ``product.product``.
"""
raise NotImplementedError
try:
column = self.model._fields[self._openerp_field]
except KeyError:
raise ValueError(
'Cannot unwrap model %s, because it has no %s fields'
% (self.model._name, self._openerp_field))
return column.comodel_name
2 changes: 1 addition & 1 deletion connector/doc/guides/code_overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ The main types of :py:class:`~connector.connector.ConnectorUnit` are
:py:class:`~connector.connector.Binder`

The ``binders`` give the external ID or Odoo ID from respectively an
Odoo ID or an external ID.
Odoo ID or an external ID. A default implementation is available.

:py:class:`~connector.unit.mapper.Mapper`

Expand Down
3 changes: 2 additions & 1 deletion connector/doc/guides/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ The base class is

Binders are classes which know how to find the external ID for an
Odoo ID, how to find the Odoo ID for an external ID and how to
create the binding between them.
create the binding between them. A default implementation is
available and can be inherited if needed.


.. _binding:
Expand Down
55 changes: 55 additions & 0 deletions connector/tests/test_default_binder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-

import mock
from openerp.tests.common import TransactionCase
from openerp.addons.connector.connector import ConnectorEnvironment
from openerp.addons.connector.session import ConnectorSession
from openerp.addons.connector.connector import Binder
from openerp.addons.connector.backend import Backend


class TestDefaultBinder(TransactionCase):
""" Test the default binder implementation"""

def setUp(self):
super(TestDefaultBinder, self).setUp()

class PartnerBinder(Binder):
"we use already existing fields for the binding"
_model_name = 'res.partner'
_external_field = 'ref'
_sync_date_field = 'date'
_backend_field = 'color'
_openerp_field = 'id'

self.session = ConnectorSession(self.cr, self.uid)
self.backend = Backend('dummy', version='1.0')
backend_record = mock.Mock()
backend_record.id = 1
backend_record.get_backend.return_value = self.backend
self.connector_env = ConnectorEnvironment(
backend_record, self.session, 'res.partner')
self.partner_binder = PartnerBinder(self.connector_env)

def test_default_binder(self):
""" Small scenario with the default binder """
partner = self.env.ref('base.main_partner')
partner.write({'color': 1})
# bind the main partner to external id = 0
self.partner_binder.bind(0, partner.id)
# find the openerp partner bound to external partner 0
openerp_id = self.partner_binder.to_openerp(0)
self.assertEqual(openerp_id, partner.id)
openerp_id = self.partner_binder.to_openerp(0)
self.assertEqual(openerp_id.id, partner.id)
openerp_id = self.partner_binder.to_openerp(0, unwrap=True)
self.assertEqual(openerp_id, partner.id)
# find the external partner bound to openerp partner 1
external_id = self.partner_binder.to_backend(partner.id)
self.assertEqual(external_id, '0')
external_id = self.partner_binder.to_backend(partner.id, wrap=True)
self.assertEqual(external_id, '0')
# unwrap model should be None since we set 'id' as the _openerp_field
self.assertEqual(self.partner_binder.unwrap_model(), None)
# unwrapping the binding should give the same binding
self.assertEqual(self.partner_binder.unwrap_binding(1), 1)
20 changes: 0 additions & 20 deletions connector/unit/binder.py

This file was deleted.

0 comments on commit 1f24696

Please sign in to comment.