Code Overview

Here is an overview of some of the concepts in the framework.

As an example, we’ll see the steps for exporting an invoice to Magento. The steps won’t show all the steps, but a simplified excerpt of a real use case exposing the main ideas.

Backends

All start with the declaration of the Backend:

import openerp.addons.connector.backend as backend

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

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

As you see, Magento is the parent of Magento 1.7. We can define a hierarchy of backends.

Bindings

The binding is the link between an OpenERP record and an external record. There is no forced implementation for the bindings. The most straightforward techniques are: storing the external ID in the same model (account.invoice), in a new link model or in a new link model which _inherits account.invoice. Here we choose the latter solution:

class magento_account_invoice(orm.Model):
    _name = 'magento.account.invoice'
    _inherits = {'account.invoice': 'openerp_id'}
    _description = 'Magento Invoice'
    _columns = {
        'backend_id': fields.many2one('magento.backend', 'Magento Backend', required=True, ondelete='restrict'),
        'openerp_id': fields.many2one('account.invoice', string='Invoice', required=True, ondelete='cascade'),
        'magento_id': fields.char('ID on Magento'), # fields.char because 0 is a valid Magento ID
        'sync_date': fields.datetime('Last synchronization date'),
        'magento_order_id': fields.many2one('magento.sale.order', string='Magento Sale Order', ondelete='set null'),
        # we can also store additional data related to the Magento Invoice
    }

Session

The framework uses ConnectorSession objects to store the cr, uid and context as well as the current pool. A session object also features shortcuts to the most used ORM methods like:

invoice = session.browse('magento.account.invoice', record_id)

Events

We can create Event on which we’ll be able to subscribe consumers. The connector already integrates the most generic ones: on_record_create(), on_record_write(), on_record_unlink()

When we create a magento.account.invoice record, we want to delay a job to export it to Magento, so we subscribe a new consumer on on_record_create():

@on_record_create(model_names='magento.account.invoice')
def delay_export_account_invoice(session, model_name, record_id):
    """
    Delay the job to export the magento invoice.
    """
    export_invoice.delay(session, model_name, record_id)

On the last line, you can notice an export_invoice.delay. We’ll discuss about that in Jobs

Jobs

A Job is a task to execute later. In that case: create the invoice on Magento.

Any function decorated with job() can be posted in the queue of jobs using a delay() function and will be run as soon as possible:

@job
def export_invoice(session, model_name, record_id):
    """ Export a validated or paid invoice. """
    invoice = session.browse(model_name, record_id)
    backend_id = invoice.backend_id.id
    env = get_environment(session, model_name, backend_id)
    invoice_exporter = env.get_connector_unit(MagentoInvoiceSynchronizer)
    return invoice_exporter.run(record_id)

There is a few things happening there:

  • We find the backend on which we’ll export the invoice.
  • We build an Environment with the current ConnectorSession, the model we work with and the target backend.
  • We get the ConnectorUnit responsible for the work using get_connector_unit() (according the backend version and the model) and we call run() on it.

ConnectorUnit

These are all classes which are responsible for a specific work. The main types of ConnectorUnit are (the implementation of theses classes belongs to the connectors):

Binder

The binders give the external ID or OpenERP ID from respectively an OpenERP ID or an external ID.

Mapper

The mappers transform a external record into an OpenERP record or conversely.

BackendAdapter

The adapters implements the discussion with the backend's APIs. They usually adapt their APIs to a common interface (CRUD).

Synchronizer

The synchronizers are the main piece of a synchronization. They define the flow of a synchronization and use the other ConnectorUnit (the ones above or specific ones).

For the export of the invoice, we just need an adapter and a synchronizer (the real implementation is more complete):

@magento
class AccountInvoiceAdapter(GenericAdapter):
    """ Backend Adapter for the Magento Invoice """
    _model_name = 'magento.account.invoice'
    _magento_model = 'sales_order_invoice'

    def create(self, order_increment_id, items, comment, email, include_comment):
        """ Create a record on the external system """
        return self._call('%s.create' % self._magento_model,
                          [order_increment_id, items, comment,
                          email, include_comment])
@magento
class MagentoInvoiceSynchronizer(ExportSynchronizer):
    """ Export invoices to Magento """
    _model_name = ['magento.account.invoice']

    def _export_invoice(self, magento_id, lines_info, mail_notification):
        # use the ``backend adapter`` to create the invoice
        return self.backend_adapter.create(magento_id, lines_info,
                                          _("Invoice Created"),
                                          mail_notification, False)

    def _get_lines_info(self, invoice):
        [...]

    def run(self, binding_id):
        """ Run the job to export the validated/paid invoice """
        sess = self.session
        invoice = sess.browse(self.model._name, binding_id)
        magento_order = invoice.magento_order_id
        magento_id = self._export_invoice(magento_order.magento_id, lines_info, True)
        # use the ``binder`` to write the external ID
        self.binder.bind(magento_id, binding_id)