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
- Events
- Jobs Queue
- Backend
- WorkContext
- Component
- Bindings
- 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.
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}
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
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
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