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

Default binder #76

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
72 changes: 65 additions & 7 deletions connector/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import logging
from contextlib import contextmanager
from openerp import models
from openerp import fields

from .deprecate import log_deprecate, DeprecatedClass

Expand Down Expand Up @@ -338,24 +339,37 @@ 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
"""

_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):
""" 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
else return the id of the binding record
: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
"""
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() if browse else None
bindings.ensure_one()
bindings = getattr(bindings, self._openerp_field) if unwrap else bindings
return bindings if browse else bindings.id

def to_backend(self, binding_id, wrap=False):
""" Give the external ID for an OpenERP binding ID
Expand All @@ -368,7 +382,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 @@ -377,7 +409,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 @@ -388,12 +432,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, browse=True)
self.assertEqual(openerp_id.id, partner.id)
openerp_id = self.partner_binder.to_openerp(0, unwrap=True, browse=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, browse=True), 1)
20 changes: 0 additions & 20 deletions connector/unit/binder.py

This file was deleted.