Backend

A backend for a version (for instance Magento 1.7), is represented by an instance of the Backend class.

Each connector will also create a connector.backend which allows the users to register their backends. For instance, the Magento connector has magento.backend (_inherit connector.backend_model.connector_backend). This model contains a version field which should have the same list of versions (with the exact same name) than the instances of Backend.

Example with the Magento Connector:

# in magentoerpconnect/backend.py

magento = backend.Backend('magento')
""" Generic Magento Backend """

magento1700 = backend.Backend(parent=magento, version='1.7')
""" Magento Backend for version 1.7 """

# in magentoerpconnect/magento_model.py

class MagentoBackend(models.Model):
    _name = 'magento.backend'
    _description = 'Magento Backend'
    _inherit = 'connector.backend'

    _backend_type = 'magento'

    @api.model
    def _select_versions(self):
        """ Available versions

        Can be inherited to add custom versions.
        """
        return [('1.7', 'Magento 1.7')]

    # <snip>

    version = fields.Selection(
        selection='_select_versions',
        string='Version',
        required=True,
    )
    location = fields.Char(string='Location', required=True)
    username = fields.Char(string='Username')
    password = fields.Char(string='Password')

    # <snip>

In the code above, ‘1.7’ is the matching key between the Backend instance (magento1700) and the magento_backend record.

class connector.backend.Backend(service=None, version=None, parent=None, registry=None)[source]

Bases: object

A backend represents a system to interact with, like Magento, Prestashop, Redmine, ...

It owns 3 properties:

service

Name of the service, for instance ‘magento’

version

The version of the service. For instance: ‘1.7’

parent

A parent backend. When no ConnectorUnit is found for a backend, it will search it in the parent.

The Backends structure is a key part of the framework, but is rather simple.

  • A Backend instance holds a registry of ConnectorUnit classes
  • It can return the appropriate ConnectorUnit to use for a task
  • If no ConnectorUnit is registered for a task, it will ask it to its direct parent (and so on)

The Backends support 2 different extension mechanisms. One is more vertical - across the versions - and the other would be more horizontal as it allows to modify the behavior for 1 version of backend.

For the sake of the example, let’s say we have theses backend versions:

         <Magento>
             |
      -----------------
      |               |
<Magento 1.7>   <Magento 2.0>
      |
<Magento with specific>

And here is the way they are declared in Python:

magento = Backend('magento')
magento1700 = Backend(parent=magento, version='1.7')
magento2000 = Backend(parent=magento, version='2.0')

magento_specific = Backend(parent=magento1700, version='1.7-specific')

In the graph above, <Magento> will hold all the classes shared between all the versions. Each Magento version (<Magento 1.7>, <Magento 2.0>) will use the classes defined on <Magento>, excepted if they registered their own ones instead. That’s the same for <Magento with specific> but this one contains customizations which are specific to an instance (typically you want specific mappings for one instance).

Here is how you would register classes on <Magento> and another on <Magento 1.7>:

@magento
class Synchronizer(ConnectorUnit):
    _model_name = 'res.partner'

@magento
class Mapper(ConnectorUnit):
    _model_name = 'res.partner'

@magento1700
class Synchronizer1700(Synchronizer):
    _model_name = 'res.partner'

Here, the get_class() called on magento1700 would return:

magento1700.get_class(Synchronizer, session, 'res.partner')
# => Synchronizer1700
magento1700.get_class(Mapper, session, 'res.partner')
# => Mapper

This is the vertical extension mechanism, it says that each child version is able to extend or replace the behavior of its parent.

Note

when using the framework, you won’t need to call

get_class(), usually, you will call connector.connector.ConnectorEnvironment.get_connector_unit().

The vertical extension is the one you will probably use the most, because most of the things you will change concern your custom adaptations or different behaviors between the versions of the backend.

However, some time, we need to change the behavior of a connector, by installing an addon. For example, say that we already have an ImportMapper for the products in the Magento Connector. We create a - generic - addon to handle the catalog in a more advanced manner. We redefine an AdvancedImportMapper, which should be used when the addon is installed. This is the horizontal extension mechanism.

Replace a ConnectorUnit by another one in a backend:

@backend(replacing=ImportMapper)
class AdvancedImportMapper(ImportMapper):
    _model_name = 'product.product'

Warning

The horizontal extension should be used sparingly and cautiously since as soon as 2 addons want to replace the same class, you’ll have a conflict (which would need to create a third addon to glue them, replacing can take a tuple of classes to replace and this is exponential). This mechanism should be used only in some well placed circumstances for generic addons.

get_class(base_class, session, model_name)[source]

Find a matching subclass of base_class in the registered classes.

Parameters:
match(service, version)[source]

Used to find the backend for a service and a version

register_class(cls, replacing=None)[source]

Register a class in the backend.

Parameters:
service

Backend Models

class connector.backend_model.ConnectorBackend(pool, cr)[source]

Bases: openerp.models.AbstractModel

An instance of an external backend to synchronize with.

The backends have to _inherit this model in the connectors modules.

get_backend(*args, **kwargs)[source]

For a record of backend, returns the appropriate instance of Backend.

name

Basic string field, can be length-limited, usually displayed as a single-line string in clients

Parameters:
  • size (int) – the maximum size of values stored for that field
  • translate (bool) – whether the values of this field can be translated
version
Parameters:
  • selection – specifies the possible values for this field. It is given as either a list of pairs (value, string), or a model method, or a method name.
  • selection_add – provides an extension of the selection in the case of an overridden field. It is a list of pairs (value, string).

The attribute selection is mandatory except in the case of related fields or field extensions.

class connector.backend_model.ExternalBinding(pool, cr)[source]

Bases: openerp.models.AbstractModel

An abstract model for bindings to external records.

An external binding is a binding between a backend and OpenERP. For example, for a partner, it could be magento.res.partner or for a product, magento.product.

The final model, will be an _inherits of the OpenERP model and will _inherit this model.

It will have a relation to the record (via _inherits) and to the concrete backend model (magento.backend for instance).

It will also contains all the data relative to the backend for the record.

It needs to implements at least these fields:

openerp_id

The many2one to the record it links (used by _inherits).

backend_id

The many2one to the backend (for instance magento.backend).

magento_id or prestashop_id or ...

The ID on the backend.

sync_date

Last date of synchronization

The definition of the relations in _columns is to be done in the concrete classes because the relations themselves do not exist in this addon.

For example, for a res.partner.category from Magento, I would have (this is a consolidation of all the columns from the abstract models, in magentoerpconnect you would not find that):

class MagentoResPartnerCategory(models.Model):
    _name = 'magento.res.partner.category'

    _inherits = {'res.partner.category': 'openerp_id'}

    openerp_id = fields.Many2one(comodel_name='res.partner.category',
                                  string='Partner Category',
                                  required=True,
                                  ondelete='cascade')
    backend_id = fields.Many2one(
        comodel_name='magento.backend',
        string='Magento Backend',
        required=True,
        ondelete='restrict')
    magento_id = fields.Char(string='ID on Magento')
    tax_class_id = fields.Integer(string='Tax Class ID')

    _sql_constraints = [
        ('magento_uniq', 'unique(backend_id, magento_id)',
         'Partner Tag with same ID on Magento already exists.'),
    ]
sync_date