code_overview.rst 6.72 KB
Newer Older
François C. committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
.. _code-overview:

#############
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 :py:class:`~connector.backend.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 Odoo 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 MagentoAccountInvoice(models.Model):
      _name = 'magento.account.invoice'
      _inherits = {'account.invoice': 'openerp_id'}
      _description = 'Magento Invoice'

      backend_id = fields.Many2one(comodel_name='magento.backend', string='Magento Backend', required=True, ondelete='restrict')
      openerp_id = fields.Many2one(comodel_name='account.invoice', string='Invoice', required=True, ondelete='cascade')
      magento_id = fields.Char(string='ID on Magento')  # fields.char because 0 is a valid Magento ID
      sync_date = fields.Datetime(string='Last synchronization date')
      magento_order_id = fields.Many2one(comodel_name='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 :py:class:`~connector.session.ConnectorSession`
objects to store the ``cr``, ``uid`` and ``context`` in a
:class:`openerp.api.Environment`.  So from a session, we can access to
the usual ``self.env`` (new API) or ``self.pool`` (old API).

******
Events
******

We can create :py:class:`~connector.event.Event` on which we'll be able
to subscribe consumers.  The connector already integrates the most
generic ones:
:py:meth:`~connector.event.on_record_create`,
:py:meth:`~connector.event.on_record_write`,
:py:meth:`~connector.event.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
:py:meth:`~connector.event.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 :py:class:`~connector.queue.job.Job` is a task to execute later.
In that case: create the invoice on Magento.

Any function decorated with :py:meth:`~connector.queue.job.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.env[model_name].browse(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 :py:class:`~connector.connector.Environment` with the
  current :py:class:`~connector.session.ConnectorSession`,
  the model we work with and the target backend.
* We get the :py:class:`~connector.connector.ConnectorUnit` responsible
  for the work using
  :py:meth:`~connector.connector.Environment.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 :py:class:`~connector.connector.ConnectorUnit` are
(the implementation of theses classes belongs to the connectors):

:py:class:`~connector.connector.Binder`

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

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

  The ``mappers`` transform a external record into an Odoo record or
  conversely.

:py:class:`~connector.unit.backend_adapter.BackendAdapter`

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

:py:class:`~connector.unit.synchronizer.Synchronizer`

    The ``synchronizers`` are the main piece of a synchronization.  They
    define the flow of a synchronization and use the other
    :py:class:`~connector.connector.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(Exporter):
      """ 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 """
          invoice = self.model.browse(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)