Feature and power of the integration of Odoo with the external system

"Seamless Integration: Boosting Efficiency"


INTRODUCTION

Odoo is an all-in-one management software solution that provides an integrated suite of business applications, including modules for accounting, inventory management, sales, and more. One of the key strengths of Odoo is its ability to integrate with external systems, which greatly enhances its functionality and power.

The integration of Odoo with external systems allows businesses to streamline their operations and automate many of their processes, making them more efficient and cost-effective. This integration can be achieved through various methods, including API integrations, custom module development, and third-party integrations.

The feature and power of the integration of Odoo with the external system are vast. With integration, businesses can leverage the power of Odoo to access and manage data from external systems, such as e-commerce platforms, payment gateways, and shipping providers. This integration enables businesses to automate their sales processes, manage their inventory and supply chain, and gain valuable insights into their operations.

Moreover, the integration of Odoo with external systems enables businesses to customize their workflows and processes to meet their unique requirements. They can add new features and functionalities to their existing systems without having to replace them entirely, saving time and resources. Additionally, this integration allows businesses to access data in real time, enabling them to make informed decisions and take action quickly.

Odoo Connector

The Odoo Connector is a framework that allows seamless integration between Odoo and external systems. It was developed by the Odoo Community Association (OCA) to meet the growing demand for a flexible and reliable way to connect Odoo with external systems.

The idea for the Odoo Connector came from the need for a standardized way to integrate Odoo with various external systems, such as e-commerce platforms, shipping providers, and payment gateways. Prior to the development of the Odoo Connector, businesses had to rely on custom development or third-party integrations, which were often costly, time-consuming, and difficult to maintain.

To address this issue, the OCA developed the Odoo Connector as an open-source framework that provides a standardized set of tools and protocols for integrating Odoo with external systems. The framework includes a range of modules that can be used to connect Odoo with various systems, as well as a set of APIs and protocols for data exchange.

The Odoo Connector has since become a popular choice for businesses looking to integrate Odoo with external systems. It is highly flexible and can be customized to meet the specific needs of each business, making it a powerful tool for streamlining operations and improving efficiency.

Connector Concepts

  1. Events
  2. Jobs Queue
  3. Backend
  4. WorkContext
  5. Component
  6. Bindings
  7. Checkpoint

Events: 

Events are signals that are emitted by the connector framework to notify other components of certain actions or states. They are used to trigger actions or workflows in response to specific events.

Events are a notification system. On one side, one or many listeners await for an event to happen. On the other side, when such an event happens, a notification is sent to the listeners.

An example of an event is: ‘when a record has been created’.

The event system allows us to write the notification code in only one place, in one Odoo addon, and to write as many listeners as we want, in different places, and different addons.

We’ll see below how the on_record_create is implemented.

Notifier

The first thing is to find where/when the notification should be sent. For the creation of a record, it is in odoo.models.BaseModel.create(). We can inherit from the ‘base’ model to add this line:

class Base(models.AbstractModel):
    _inherit = 'base'

    @api.model
    def create(self, vals):
        record = super(Base, self).create(vals)
        self._event('on_record_create').notify(record, fields=vals.keys())
        return record

The models.base.Base._event() method has been added to the ‘base’ model, so an event can be notified from any model. The CollectedEvents.notify() method triggers the event and forward the arguments to the listeners.

This should be done only once. See models.base.Base for a list of events that are implemented in the ‘base’ model.

Listeners

Listeners are Components that respond to the event names. The components must have a _usage equals to 'event.listener', but it doesn’t have to be set manually if the component inherits from 'base.event.listener'

Here is how we would log something each time a record is created:

class MyEventListener(Component):
    _name = 'my.event.listener'
    _inherit = 'base.event.listener'

    def on_record_create(self, record, fields=None):
        _logger.info("%r has been created", record)

Many listeners such as this one could be added for the same event.

Collection and models

In the example above, the listeners are global. It will be executed for any model and collection. You can also restrict a listener to only a collection or model, using the _collection or _apply_on attributes.


class MyEventListener(Component):
    _name = 'my.event.listener'
    _inherit = 'base.event.listener'
    _collection = 'magento.backend'

    def on_record_create(self, record, fields=None):
        _logger.info("%r has been created", record)


class MyModelEventListener(Component):
    _name = 'my.event.listener'
    _inherit = 'base.event.listener'
    _apply_on = ['res.users']

    def on_record_create(self, record, fields=None):
        _logger.info("%r has been created", record)

If you want an event to be restricted to a collection, the notification must also be precise about the collection, otherwise, all listeners will be executed:

collection = self.env['magento.backend']
self._event('on_foo_created', collection=collection).notify(record, vals)


An event can be skipped based on a condition evaluated from the notified arguments. See skip_if()

odoo.addons.component_event.components.event.skip_if(cond)[source]

The decorator is allowed to skip an event based on a condition

The condition is a Python lambda expression, which takes the same arguments as the event.

Example:

@skip_if(lambda self, *args, **kwargs:
         self.env.context.get('connector_no_export'))
def on_record_write(self, record, fields=None):
    _logger('I'll delay a job, but only if we didn't disabled '
            ' the export with a context key')
    record.with_delay().export_record()

@skip_if(lambda self, record, kind: kind == 'complete')
def on_record_write(self, record, kind):
    _logger("I'll delay a job, but only if the kind is 'complete'")
    record.with_delay().export_record()


Jobs Queue:

The jobs queue is a system for managing long-running tasks and background jobs in the connector framework. It allows tasks to be executed asynchronously, without blocking the main Odoo process.

The module is queue_job in https://github.com/OCA/queue.

A connectors developer is mostly interested by:

  • Delay a job


Example : 

class ProductProduct(models.Model):
    _inherit = 'product.product'

    @job
    def export_one_thing(self, one_thing):
        # work
        # export one_thing

# [...]

env['a.model'].export_one_thing(the_thing_to_export)
# => normal and synchronous function call

env['a.model'].with_delay().export_one_thing(the_thing_to_export)
# => the job will be executed as soon as possible

delayable = env['a.model'].with_delay(priority=30, eta=60*60*5)
delayable.export_one_thing(the_thing_to_export)
# => the job will be executed with a low priority and not before a
# delay of 5 hours from now

@job(default_channel='root.subchannel')
def export_one_thing(one_thing):
    # work
    # export one_thing

@job(retry_pattern={1: 10 * 60,
                    5: 20 * 60,
                    10: 30 * 60,
                    15: 12 * 60 * 60})
def retryable_example():
    # 5 first retries postponed 10 minutes later
    # retries 5 to 10 postponed 20 minutes later
    # retries 10 to 15 postponed 30 minutes later
    # all subsequent retries postponed 12 hours later
    raise RetryableJobError('Must be retried later')

env['a.model'].with_delay().retryable_example()


Backend: 

The backend is a Python class that provides configuration and authentication information for a specific external system. It defines methods for connecting to the system and retrieving data.

​Example:

	​from odoo.addons.connector.components.backend import BackendComponent

	​class ExternalSystemBackend(BackendComponent):
    
    		​_name = 'external.system.backend'
    		​_inherit = 'connector.backend'
    
    		def connect(self):
        		# Code for connecting to the external system
    
    		def get_orders(self, since_date):
        		# Code for retrieving orders from the external system


WorkContext:

The work context is a Python class that provides context information for a specific task or operation. It includes information about the current session, user, and other relevant data.

The entry point to work with the components.

collection = self.env['my.collection'].browse(1)
work = WorkContext(model_name='res.partner', collection=collection)
component = work.component(usage='record.importer')

Usually, you will use the context manager on the collection.base Model:

collection = self.env['my.collection'].browse(1)
with collection.work_on('res.partner') as work:
    component = work.component(usage='record.importer')

It supports any arbitrary keyword arguments that will become attributes of the instance, and be propagated throughout all the components.

collection = self.env['my.collection'].browse(1)
with collection.work_on('res.partner', hello='world') as work:
    assert work.hello == 'world'

When you need to work on a different model, a new work instance will be created for you when you are using the high-level API. This is what happens under the hood:

collection = self.env['my.collection'].browse(1)
with collection.work_on('res.partner', hello='world') as work:
    assert work.model_name == 'res.partner'
    assert work.hello == 'world'
    work2 = work.work_on('res.users')
    # => spawn a new WorkContext with a copy of the attributes
    assert work2.model_name == 'res.users'
    assert work2.hello == 'world'
	

Component:

A component is a reusable Python class that provides a specific piece of functionality in the connector framework. It can be used to define adapters, mappers, and other components.

  1. Mapper Components:

    • ImportMapper: Maps external data to Odoo records during import operations.
    • ExportMapper: Maps Odoo records to external data during export operations.
    • RecordMapper: Maps individual fields or values during the import or export process.

    Example:

    from odoo.addons.connector.components.mapper import ImportMapper, ExportMapper, mapping
    
    class MyImportMapper(ImportMapper):
        _model_name = 'my.model'
    
        @mapping
        def my_field(self, record):
            return {'my_field': record['external_field']}
    
    class MyExportMapper(ExportMapper):
        _model_name = 'my.model'
    
        @mapping
        def external_field(self, record):
            return {'external_field': record.my_field}
    
  2. Adapter Components:

    • CRUDAdapter: Performs Create, Read, Update, and Delete operations on external systems.
    • SearchAdapter: Performs search operations on external systems.
    • ExportAdapter: Handles export operations to external systems.

    Example:

    from odoo.addons.connector.components.adapter import CRUDAdapter
    
    class MyCRUDAdapter(CRUDAdapter):
        _model_name = 'my.model'
    
        def create(self, data):
            # Create logic for the external system
            pass
    
        def read(self, id):
            # Read logic for the external system
            pass
    
        def update(self, id, data):
            # Update logic for the external system
            pass
    
        def delete(self, id):
            # Delete logic for the external system
            pass
    
  3. Binder Components:

    • Binder: Manages the binding between external records and Odoo records.
    • BaseBinder: Base class for creating custom binders.

    Example:

    from odoo.addons.connector.components.binder import Binder
    
    class MyBinder(Binder):
        _model_name = 'my.model'
    
        def to_internal(self, external_id):
            # Retrieve Odoo record ID based on external ID
            pass
    
        def to_external(self, internal_id):
            # Retrieve external ID based on Odoo record ID
            pass
    
        def bind(self, external_id, internal_id):
            # Bind an external ID to an Odoo record ID
            pass
    
  4. Synchronizer Component:

    • Synchronizer: Manages the synchronization of records between Odoo and an external system.

    Example:

    from odoo.addons.connector.components.synchronizer import Synchronizer
    
    class MySynchronizer(Synchronizer):
        _model_name = 'my.model'
    
        def run(self, binding_ids):
            # Synchronization logic for the model
            pass
    

These are the main components provided by the Odoo Connector module. Each component plays a specific role in the integration process, allowing you to customize and extend the synchronization between Odoo and external systems.


Bindings:

A binding represents the link of a record between Odoo and a backend.

The proposed implementation for the connectors widely uses the _inherits capabilities.

They include information about the external ID of the record and the status of the synchronization between Odoo and the external system.

​Example:

	from odoo.addons.connector.components.mapper import mapping

	​class ExternalSystemBinding(models.Model):
    
    		​_name = 'external.system.binding'
    		​_inherit = 'external.binding'
    
    		​external_id = fields.Char(string='External ID')
    		​synced = fields.Boolean(string='Synced')
    		​last_sync_date = fields.Datetime(string='Last Sync Date')


Checkpoint:

A checkpoint is a marker that is used to indicate progress during a long-running operation. It allows the operation to be resumed from the point at which it was interrupted.

​Example:

	from odoo.addons.connector.components.checkpoint import Checkpoint

	​class ExternalSystemCheckpoint(Checkpoint):
    
    		​_name = 'external.system.checkpoint'
    		​_inherit = 'connector.checkpoint'
    
    		def get_last_order_date(self):
        		# Code for retrieving the date of the last imported order


To learn deep Odoo connector please refer to the Odoo connector documentation (https://odoo-connector.com/guides/concepts.html).


Structure of Odoo Integration with Trello

Here below mention code is an example to just give you an idea, not the full complete code.

from odoo import models, fields, api
from odoo.addons.connector.unit.mapper import ImportMapper
from odoo.addons.component.core import AbstractComponent
from odoo.addons.connector.components.mapper import (
mapping,
ImportMapper,
ExportMapper,
)

# Create new model Trello List
class TrelloList(models.Model):
_name = 'trello.list'

name = fields.Char()
trello_id = fields.Char()


# You have to create backend for trello where you configure the trello api crendentials.
class BaseTrelloConnectorComponent(AbstractComponent):
_name = "base.trello.connector"
_inherit = "base.connector"
_collection = "trello.backend"

# Adapter
class CRUDAdapter(AbstractComponent):
# pylint: disable=method-required-super

_name = "trello.crud.adapter"
_inherit = ["base.backend.adapter", "base.trello.connector"]
_usage = "backend.adapter"

def read(self, id):
trello_client = self.unit_for(self._model_name)
trello_list = trello_client.get_list(id)
return trello_list

def search(self, filters=None):
trello_client = self.unit_for(self._model_name)
return trello_client.get_all_lists()

def write(self, id, data):
trello_client = self.unit_for(self._model_name)
trello_list = trello_client.update_list(id, data)
return trello_list

def create(self, data):
trello_client = self.unit_for(self._model_name)
trello_list = trello_client.create_list(data)
return trello_list

class GenericAdapter(AbstractComponent):
# pylint: disable=method-required-super

_name = "trello.adapter"
_inherit = "trello.crud.adapter"


# Importer (same you can create for exporter)
class TrelloListImporter(models.Model):
_name = 'trello.list.importer'
_inherit = 'connector.importer'
_apply_on = 'trello.list'

def _import_record(self, external_id, vals):
return self.model.create(vals)

def run(self, from_date):
trello_lists = self.backend_adapter.get_lists(from_date)
for trello_list in trello_lists:
import_batch = self._import_batch(trello_list['id'], trello_list)
self._record_created_batch(import_batch)


# Mapper (Importer)
class MyTrelloListMapper(ImportMapper):
_name = "trello.list.import.mapper"
_inherit = "base.import.mapper"
_apply_on = ["trello.list"]


direct = [
('name', 'name'),
('idBoard', 'board_id'),
('closed', 'active'),
]

@mapping
def trello_id(self, record):
return {'trello_id': record['id']}

# Mapper (Exporter)
class MyTrelloListExportMapper(ExportMapper):
_name = "trello.list.export.mapper"
_inherit = "base.export.mapper"
_apply_on = ["trello.list"]


direct = [
('name', 'name'),
('board_id', 'idBoard'),
('active', 'closed'),
]

@mapping
def trello_id(self, record):
return {'id': record.trello_id}

# Binder
class TrelloListBinder(models.Model):
_name = 'trello.list.binder'
_inherit = 'connector.binder'
_apply_on = 'trello.list'

def to_internal(self, external_id):
trello_list = self.env['trello.list'].search([('trello_list_id', '=', external_id)])
return trello_list.id if trello_list else None

def to_external(self, internal_id):
trello_list = self.env['trello.list'].browse(internal_id)
return trello_list.trello_list_id if trello_list else None


Unlock Efficiency With Odoo Seamless Integrations


CONCLUSION

The integration of Odoo with external systems is a powerful tool that provides businesses with enhanced functionality, streamlined processes, and improved efficiency. It enables businesses to leverage the full potential of Odoo, customize their workflows, and gain valuable insights into their operations.

Feature and power of the integration of Odoo with the external system
BizzAppDev Expert August 21, 2023
Share this post
Sign in to leave a comment