.. _bootstrap-connector:


########################
Boostrapping a connector
########################

We'll see the steps to bootstrap a new connector.

Besides that, you may want to use the existing connectors to have some
real implementation examples:

* `Odoo Magento Connector`_
* `Odoo Prestashop Connector`_

Some boilerplate is necessary, so this document will guide you through
some steps. Please also take a look on the :ref:`naming-convention`.

For the sake of the example, we'll imagine we have to synchronize
Odoo with a coffee machine.

*************
Odoo Manifest
*************

As we want to synchronize Odoo with a coffee machine, we'll name
our connector connector_coffee.

First, we need to create the Odoo addons itself, editing the
``connector_coffee/__openerp__.py`` manifest.


.. code-block:: python
   :emphasize-lines: 4,5

    # -*- coding: utf-8 -*-
    {'name': 'Coffee Connector',
     'version': '1.0.0',
     'category': 'Connector',
     'depends': ['connector',
                 ],
     'author': 'Myself',
     'license': 'AGPL-3',
     'description': """
    Coffee Connector
    ================

    Connect Odoo to my coffee machine.

    Features:

    * Poor a coffee when Odoo is busy for too long
    """,
     'data': [],
     'installable': True,
     'application': False,
    }

Nothing special but 2 things to note:

* It depends from ``connector``.
* The module category should be ``Connector``.

Of course, we also need to create the ``__init__.py`` file where we will
put the imports of our python modules.


********************
Declare the backends
********************

Our module is compatible with the coffee machines:

 * Coffee 1900
 * Coffee 2900

So we'll declare a backend `coffee`, the generic entity,
and a backend per version.

Put this in ``connector_coffee/backend.py``::

    import openerp.addons.connector.backend as backend


    coffee = backend.Backend('coffee')
    coffee1900 = backend.Backend(parent=coffee, version='1900')
    coffee2900 = backend.Backend(parent=coffee, version='2900')


*************
Backend Model
*************

We declared the backends, but we need a model to configure them.

We create a model ``coffee.backend`` which is an ``_inherit`` of
``connector.backend``. In ``connector_coffee/coffee_model.py``::

    from openerp import fields, models, api


    class CoffeeBackend(models.Model):
        _name = 'coffee.backend'
        _description = 'Coffee Backend'
        _inherit = 'connector.backend'

        _backend_type = 'coffee'

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

            Can be inherited to add custom versions.
            """
            return [('1900', 'Version 1900'),
                    ('2900', 'Version 2900')]

        version = fields.Selection(
            selection='_select_versions',
            string='Version',
            required=True,
        )
        location = fields.Char(string='Location')
        username = fields.Char(string='Username')
        password = fields.Char(string='Password')
        default_lang_id = fields.Many2one(
            comodel_name='res.lang',
            string='Default Language',
        )

Notes:

* The ``_backend_type`` must be the same than the name in the backend in
  `Declare the backends`_.
* the versions should be the same than the ones declared in `Declare the backends`_.
* We may want to add as many fields as we want to configure our
  connection or configuration regarding the backend in that model.


****************
Abstract Binding
****************

If we have many :ref:`binding`,
we may want to create an abstract model for them.

It can be as follows (in ``connector_coffee/connector.py``)::

    from openerp import models, fields


    class CoffeeBinding(models.AbstractModel):
        _name = 'coffee.binding'
        _inherit = 'external.binding'
        _description = 'Coffee Binding (abstract)'

        # 'openerp_id': openerp-side id must be declared in concrete model
        backend_id = fields.Many2one(
            comodel_name='coffee.backend',
            string='Coffee Backend',
            required=True,
            ondelete='restrict',
        )
        # fields.char because 0 is a valid coffee ID
        coffee_id = fields.Char(string='ID in the Coffee Machine',
                                select=True)


***********
Environment
***********

We'll often need to create a new environment to work with.
I propose to create a helper method which build it for us (in
``connector_coffee/connector.py``::

    from openerp.addons.connector.connector import Environment


    def get_environment(session, model_name, backend_id):
        """ Create an environment to work with. """
        backend_record = session.env['coffee.backend'].browse(backend_id)
        env = Environment(backend_record, session, model_name)
        lang = backend_record.default_lang_id
        lang_code = lang.code if lang else 'en_US'
        if lang_code == session.context.get('lang'):
            return env
        else:
            with env.session.change_context(lang=lang_code):
                return env

Note that the part regarding the language definition is totally
optional but I left it as an example.


***********
Checkpoints
***********

Record checkpoint
-----------------

When new records are imported and need a review, :ref:`checkpoint` are
created. You can add it like this::

    backend_record.add_checkpoint(
        model='res.partner', record_id=1, message='VAT number can be missing')


Message only checkpoint
-----------------------

When you need to show a warning message to a user
you can create a :ref:`checkpoint`. You can add it like this::

    backend_record.add_checkpoint(message='VAT number can be missing')

A typical use case for this is:

* you have a batch import of CSV file;
* you don't want to break a whole batch job just because some line failed;
* you want to notify the user with a nice warning message.


*********************
ConnectorUnit classes
*********************

We'll probably need to create synchronizers, mappers, backend adapters,
binders and maybe our own types of ConnectorUnit classes.

Their implementation can vary a lot. Have a look on the
`Odoo Magento Connector`_ and `Odoo Prestashop Connector`_ projects.


.. _`Odoo Magento Connector`: https://github.com/OCA/connector-magento
.. _`Odoo Prestashop Connector`: https://github.com/OCA/connector-prestashop