mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-21 02:32:03 +02:00
Initial commit: OCA Technical packages (595 packages)
This commit is contained in:
commit
2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions
|
|
@ -0,0 +1,152 @@
|
|||
.. _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`_
|
||||
|
||||
Be aware that the connector API has changed in Odoo 10.0, so the examples
|
||||
might be outdated.
|
||||
|
||||
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/__manifest__.py`` manifest.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3,4
|
||||
|
||||
{'name': 'Coffee Connector',
|
||||
'version': '1.0.0',
|
||||
'category': 'Connector',
|
||||
'depends': ['connector',
|
||||
],
|
||||
'author': 'Myself',
|
||||
'license': 'LGPL-3',
|
||||
'description': """
|
||||
Coffee Connector
|
||||
================
|
||||
|
||||
Connect Odoo to my coffee machine.
|
||||
|
||||
Features:
|
||||
|
||||
* Pour a coffee when Odoo is busy for too long
|
||||
""",
|
||||
'data': [],
|
||||
'installable': True,
|
||||
}
|
||||
|
||||
Nothing special but 2 things to note:
|
||||
|
||||
* It depends from ``connector``. ``connector`` itself depends from
|
||||
``queue_job``, ``component`` and ``component_event``. ``queue_job`` is in the
|
||||
`OCA/queue`_ repository.
|
||||
* 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.
|
||||
|
||||
.. _OCA/queue: https://github.com/OCA/queue
|
||||
|
||||
|
||||
*************
|
||||
Backend Model
|
||||
*************
|
||||
|
||||
Reference: :ref:`api-backend-model`
|
||||
|
||||
We need to create a Backend representing the external service. Every record we
|
||||
synchronize will be linked with a record of ``coffee.backend``. This backend
|
||||
is our *collection* of Components.
|
||||
|
||||
The ``coffee.backend`` model is an ``_inherit`` of ``connector.backend``. In
|
||||
``connector_coffee/models/coffee_binding.py``::
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class CoffeeBackend(models.Model):
|
||||
_name = 'coffee.backend'
|
||||
_description = 'Coffee Backend'
|
||||
_inherit = 'connector.backend'
|
||||
|
||||
location = fields.Char(string='Location')
|
||||
username = fields.Char(string='Username')
|
||||
password = fields.Char(string='Password')
|
||||
|
||||
Notes:
|
||||
|
||||
* We can other fields for the configuration of the connection or the
|
||||
synchronizations.
|
||||
|
||||
****************
|
||||
Abstract Binding
|
||||
****************
|
||||
|
||||
Reference: :ref:`api-binding-model`
|
||||
|
||||
In order to share common features between all the bindings (see
|
||||
:ref:`binding`), create an abstract binding model.
|
||||
|
||||
It can be as follows (in ``connector_coffee/models/coffee_binding.py``)::
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class CoffeeBinding(models.AbstractModel):
|
||||
_name = 'coffee.binding'
|
||||
_inherit = 'external.binding'
|
||||
_description = 'Coffee Binding (abstract)'
|
||||
|
||||
# odoo_id = odoo-side id must be declared in concrete model
|
||||
backend_id = fields.Many2one(
|
||||
comodel_name='coffee.backend',
|
||||
string='Coffee Backend',
|
||||
required=True,
|
||||
ondelete='restrict',
|
||||
)
|
||||
coffee_id = fields.Integer(string='ID in the Coffee Machine',
|
||||
index=True)
|
||||
|
||||
Notes:
|
||||
|
||||
* This model inherit from ``external.binding``
|
||||
* Any number of fields or methods can be added
|
||||
|
||||
|
||||
**********
|
||||
Components
|
||||
**********
|
||||
|
||||
Reference: :ref:`api-component`
|
||||
|
||||
We'll probably need to create synchronizers, mappers, backend adapters,
|
||||
binders and maybe our own kind of components.
|
||||
|
||||
Their implementation can vary from a project to another. 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
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
.. _code-overview:
|
||||
|
||||
#############
|
||||
Code Overview
|
||||
#############
|
||||
|
||||
Some simple code examples.
|
||||
|
||||
***************************
|
||||
Trigger and listen an event
|
||||
***************************
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
_inherit = 'account.invoice'
|
||||
|
||||
@api.multi
|
||||
def action_invoice_paid(self):
|
||||
res = super(AccountInvoice, self).action_invoice_paid()
|
||||
for record in self:
|
||||
self._event('on_invoice_paid').notify(record)
|
||||
return res
|
||||
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
|
||||
class MyEventListener(Component):
|
||||
_name = 'my.event.listener'
|
||||
_inherit = 'base.event.listener'
|
||||
|
||||
def on_invoice_paid(self, record):
|
||||
_logger.info('invoice %s has been paid!', record.name)
|
||||
|
||||
Ref: :ref:`api-event`
|
||||
|
||||
|
||||
*************************
|
||||
Delay an Asynchronous Job
|
||||
*************************
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.queue_job.job import job
|
||||
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
_inherit = 'account.invoice'
|
||||
|
||||
@job
|
||||
@api.multi
|
||||
def export_payment(self):
|
||||
self.ensure_one()
|
||||
_logger.info("I'm exporting the payment for %s", self.name)
|
||||
|
||||
@api.multi
|
||||
def action_invoice_paid(self):
|
||||
res = super(AccountInvoice, self).action_invoice_paid()
|
||||
for record in self:
|
||||
record.with_delay(priority=5).export_payment()
|
||||
return res
|
||||
|
||||
Ref: :ref:`api-queue`
|
||||
|
||||
********************
|
||||
Work with components
|
||||
********************
|
||||
|
||||
This is a highly simplified version of a micro-connector, without using
|
||||
events or jobs, for the sake of the example.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import AbstractComponent
|
||||
|
||||
|
||||
class MagentoBackend(models.Model):
|
||||
_name = 'magento.backend'
|
||||
_description = 'Magento Backend'
|
||||
_inherit = 'connector.backend'
|
||||
|
||||
location = fields.Char(string='Location', required=True)
|
||||
username = fields.Char(string='Username')
|
||||
password = fields.Char(string='Password')
|
||||
|
||||
def import_partner(self, external_id):
|
||||
with self.work_on(model_name='magento.res.partner') as work:
|
||||
importer = work.component(usage='record.importer')
|
||||
# returns an instance of PartnerImporter, which has been
|
||||
# found with:the collection name (magento.backend, the model,
|
||||
# and the usage).
|
||||
importer.run(partner_id)
|
||||
|
||||
# the next 2 components are abstract and are used by inheritance
|
||||
# by the others
|
||||
class BaseMagentoConnectorComponent(AbstractComponent):
|
||||
# same inheritance than Odoo models
|
||||
_name = 'base.magento.connector'
|
||||
_inherit = 'base.connector'
|
||||
# subscribe to:
|
||||
_collection = 'magento.backend'
|
||||
# the collection will be inherited to the components below,
|
||||
# because they inherit from this component
|
||||
|
||||
|
||||
class GenericAdapter(AbstractComponent):
|
||||
# same inheritance than Odoo models
|
||||
_name = 'magento.adapter'
|
||||
_inherit = ['base.backend.adapter', 'base.magento.connector']
|
||||
# usage is used for lookups of components
|
||||
_usage = 'backend.adapter'
|
||||
|
||||
_magento_model = None
|
||||
|
||||
def _call(self, *args, **kwargs):
|
||||
location = self.backend_record.location
|
||||
# use client API
|
||||
|
||||
def read(self, fields=None):
|
||||
""" Search records according to some criterias
|
||||
and returns a list of ids
|
||||
|
||||
:rtype: list
|
||||
"""
|
||||
return self._call('%s.info' % self._magento_model, fields)
|
||||
|
||||
|
||||
# these are the components we need for our synchronization
|
||||
class PartnerAdapter(Component):
|
||||
_name = 'magento.partner.adapter'
|
||||
_inherit = 'magento.adapter'
|
||||
_apply_on = ['magento.res.partner']
|
||||
_magento_model = 'customer'
|
||||
|
||||
|
||||
class PartnerMapper(Component):
|
||||
_name = 'magento.partner.import.mapper'
|
||||
_inherit = 'magento.import.mapper' # parent component omitted for brevity
|
||||
_apply_on = ['magento.res.partner']
|
||||
_usage = 'import.mapper'
|
||||
|
||||
|
||||
class PartnerBinder(Component):
|
||||
_name = 'magento.partner.binder'
|
||||
_inherit = 'magento.binder' # parent component omitted for brevity
|
||||
_apply_on = ['magento.res.partner']
|
||||
_usage = 'binder'
|
||||
|
||||
|
||||
class PartnerImporter(Component):
|
||||
_name = 'magento.partner.importer'
|
||||
_inherit = 'magento.importer' # parent component omitted for brevity
|
||||
_apply_on = ['magento.res.partner']
|
||||
_usage = 'record.importer'
|
||||
|
||||
def run(self, external_id):
|
||||
# get the components we need for the sync
|
||||
|
||||
# this one knows how to speak to magento
|
||||
backend_adapter = self.component(usage='backend.adapter')
|
||||
# this one knows how to convert magento data to odoo data
|
||||
mapper = self.component(usage='import.mapper')
|
||||
# this one knows how to link magento/odoo records
|
||||
binder = self.component(usage='binder')
|
||||
|
||||
# read external data from magento
|
||||
external_data = backend_adapter.read(external_id)
|
||||
# convert to odoo data
|
||||
internal_data = mapper.map_record(external_data).values()
|
||||
# find if the magento id already exists in odoo
|
||||
binding = binder.to_internal(external_id)
|
||||
if binding:
|
||||
# if yes, we update it
|
||||
binding.write(internal_data)
|
||||
else:
|
||||
# or we create it
|
||||
binding = self.model.create(internal_data)
|
||||
# finally, we bind both, so the next time we import
|
||||
# the record, we'll update the same record instead of
|
||||
# creating a new one
|
||||
binder.bind(external_id, binding)
|
||||
|
||||
|
||||
Ref: :ref:`api-component`
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
.. _concepts:
|
||||
|
||||
##################
|
||||
Connector Concepts
|
||||
##################
|
||||
|
||||
The framework to develop connectors is decoupled in small pieces of
|
||||
codes interacting together. Each of them can be used or not in an
|
||||
implementation.
|
||||
|
||||
An example of implementation is the `Odoo Magento Connector`_.
|
||||
|
||||
This document describes them from a high-level point of view and gives
|
||||
pointers to more concrete 'how-to' or small tutorials.
|
||||
|
||||
.. _`Odoo Magento Connector`: http://www.odoo-magento-connector.com
|
||||
|
||||
******
|
||||
Events
|
||||
******
|
||||
|
||||
Reference: :ref:`api-event`
|
||||
|
||||
Events are hooks in Odoo on which we can plug some actions. They are
|
||||
based on an Observer pattern.
|
||||
|
||||
The same event can be shared across several connectors, easing their
|
||||
implementation.
|
||||
For instance, the module connector_ecommerce_ which extends the
|
||||
framework with common e-commerce capabilities, adds its own events
|
||||
common to e-commerce.
|
||||
|
||||
A connectors developer is mostly interested by:
|
||||
|
||||
* adding and listening to events (see :ref:`api-event`)
|
||||
|
||||
.. _jobs-queue:
|
||||
|
||||
**********
|
||||
Jobs Queue
|
||||
**********
|
||||
|
||||
Reference: :ref:`api-queue`
|
||||
|
||||
This feature is part of a standalone addon, but is a prerequisite for
|
||||
the connector framework.
|
||||
|
||||
The module is ``queue_job`` in https://github.com/OCA/queue.
|
||||
|
||||
A connectors developer is mostly interested by:
|
||||
|
||||
* Delay a job (see the decorator :py:func:`~odoo.addons.queue_job.job.job`)
|
||||
|
||||
|
||||
*******
|
||||
Backend
|
||||
*******
|
||||
|
||||
Reference: :ref:`api-backend-model`
|
||||
|
||||
The Backend Model is what represents the external service / system we
|
||||
synchronize with. The name on the backend indicates what is the collection the
|
||||
Components will be registered into. Put another way: every backend has its own
|
||||
collection of Components.
|
||||
|
||||
It must use an ``_inherit`` on ``connector.backend``.
|
||||
|
||||
``connector.backend`` inherits
|
||||
:class:`odoo.addons.component.models.collection.Collection` which has a
|
||||
:meth:`odoo.addons.component.models.collection.Collection.work_on` that will be
|
||||
used as entrypoint for the component system. This method returns a
|
||||
:class:`~odoo.addons.component.core.WorkContext`
|
||||
|
||||
***********
|
||||
WorkContext
|
||||
***********
|
||||
|
||||
Reference: :class:`~odoo.addons.component.core.WorkContext`
|
||||
|
||||
A :class:`~odoo.addons.component.core.WorkContext` is the work environment or
|
||||
context that will be passed transversally through all the components. This is
|
||||
also the entrypoint to the component system.
|
||||
|
||||
A connectors developer is mostly interested by:
|
||||
|
||||
* Get a Component from a WorkContext (:py:meth:`~odoo.addons.component.core.WorkContext.component`)
|
||||
|
||||
*********
|
||||
Component
|
||||
*********
|
||||
|
||||
Reference: :ref:`api-component`
|
||||
|
||||
:py:class:`~odoo.addons.component.core.Component` are pluggable classes used
|
||||
for the synchronizations with the external systems (or anything!)
|
||||
|
||||
The Components system has been extracted in a standalone addon (``component``),
|
||||
which means it can really be used in a totally different way.
|
||||
|
||||
The connector defines some base components, which you can find below. Note
|
||||
that you can and are encouraged to define your own Components as well.
|
||||
|
||||
Mappings
|
||||
========
|
||||
|
||||
The base class is :py:class:`connector.components.mapper.Mapper`.
|
||||
|
||||
In your components, you probably want to inherit from:
|
||||
|
||||
* ``_inherit = 'base.import.mapper'``
|
||||
* ``_inherit = 'base.export.mapper'``
|
||||
|
||||
And the usages for the lookups are:
|
||||
|
||||
* ``import.mapper``
|
||||
* ``export.mapper``
|
||||
|
||||
A mapping translates an external record to an Odoo record and
|
||||
conversely.
|
||||
|
||||
It supports:
|
||||
|
||||
direct mappings
|
||||
Fields *a* is written in field *b*.
|
||||
|
||||
method mappings
|
||||
A method is used to convert one or many fields to one or many
|
||||
fields, with transformation.
|
||||
It can be filtered, for example only applied when the record is
|
||||
created or when the source fields are modified.
|
||||
|
||||
submapping
|
||||
a sub-record (lines of a sale order) is converted using another
|
||||
Mapper
|
||||
|
||||
See the documentation of the class for more details.
|
||||
|
||||
Synchronizers
|
||||
=============
|
||||
|
||||
The base class is :py:class:`connector.components.synchronizer.Synchronizer`.
|
||||
|
||||
In your components, you probably want to inherit from:
|
||||
|
||||
* ``_inherit = 'base.importer'``
|
||||
* ``_inherit = 'base.exporter'``
|
||||
|
||||
And the usages for the lookups are:
|
||||
|
||||
* ``importer``
|
||||
* ``exporter``
|
||||
|
||||
However, in your implementation, it is advised to use more refined usages such
|
||||
as:
|
||||
|
||||
* ``record.importer``
|
||||
* ``record.exporter``
|
||||
* ``batch.importer``
|
||||
* ``batch.exporter``
|
||||
* ..
|
||||
|
||||
A synchronizer orchestrates a synchronization with a backend. It can be a
|
||||
record's import or export, a deletion of something, or anything else. For
|
||||
instance, it will use the mappings to convert the data between both systems,
|
||||
the backend adapters to read or write data on the backend and the binders to
|
||||
create the link between them.
|
||||
|
||||
Backend Adapters
|
||||
================
|
||||
|
||||
The base class is
|
||||
:py:class:`connector.components.backend_adapter.BackendAdapter`.
|
||||
|
||||
In your components, you probably want to inherit from:
|
||||
|
||||
* ``_inherit = 'base.backend.adapter'``
|
||||
* ``_inherit = 'base.backend.adapter.crud'``
|
||||
|
||||
And the usages for the lookups are:
|
||||
|
||||
* ``backend.adapter``
|
||||
|
||||
An external adapter has a common interface to speak with the backend.
|
||||
It translates the basic orders (search, read, write) to the protocol
|
||||
used by the backend.
|
||||
|
||||
Binders
|
||||
=======
|
||||
|
||||
The base class is
|
||||
:py:class:`connector.components.binder.Binder`.
|
||||
|
||||
In your components, you probably want to inherit from:
|
||||
|
||||
* ``_inherit = 'base.binder'``
|
||||
|
||||
And the usages for the lookups are:
|
||||
|
||||
* ``binder``
|
||||
|
||||
Binders are components that know how to find the external ID for an
|
||||
Odoo ID, how to find the Odoo ID for an external ID and how to
|
||||
create the binding between them. A default implementation is
|
||||
available and can be inherited if needed.
|
||||
|
||||
Listeners
|
||||
=========
|
||||
|
||||
The base class is
|
||||
:py:class:`connector.components.listener.ConnectorListener`.
|
||||
|
||||
In your components, you probably want to inherit from:
|
||||
|
||||
* ``_inherit = 'base.connector.listener'``
|
||||
|
||||
This is where you will register your event listeners.
|
||||
See :mod:`addons.component_event.components.event`.
|
||||
|
||||
|
||||
.. _binding:
|
||||
|
||||
********
|
||||
Bindings
|
||||
********
|
||||
|
||||
Reference: :ref:`api-binding-model`
|
||||
|
||||
A binding represents the link of a record between Odoo and a backend.
|
||||
|
||||
The proposed implementation for the connectors widely use the
|
||||
`_inherits` capabilities.
|
||||
|
||||
Say we import a customer from *Magento*.
|
||||
|
||||
We create a `magento.res.partner` model, which `_inherits`
|
||||
`res.partner`.
|
||||
|
||||
This model, called a *binding* model, knows the ID of the partner in
|
||||
Odoo, the ID in Magento and the relation to the backend model.
|
||||
|
||||
It also stores all the necessary metadata related to this customer
|
||||
coming from Magento.
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
.. _jobrunner:
|
||||
|
||||
|
||||
#######################################
|
||||
Configuring channels and the job runner
|
||||
#######################################
|
||||
|
||||
.. automodule:: odoo.addons.queue_job.jobrunner.runner
|
||||
|
||||
|
||||
What is a channel?
|
||||
------------------
|
||||
|
||||
.. autoclass:: odoo.addons.queue_job.jobrunner.channels.Channel
|
||||
:noindex:
|
||||
|
||||
How to configure Channels?
|
||||
--------------------------
|
||||
|
||||
The ``ODOO_QUEUE_JOB_CHANNELS`` environment variable must be
|
||||
set before starting Odoo in order to enable the job runner
|
||||
and configure the capacity of the channels.
|
||||
|
||||
The general syntax is ``channel(.subchannel)*(:capacity(:key(=value)?)*)?,...``.
|
||||
|
||||
Intermediate subchannels which are not configured explicitly are autocreated
|
||||
with an unlimited capacity (except the root channel which if not configured gets
|
||||
a default capacity of 1).
|
||||
|
||||
A delay in seconds between jobs can be set at the channel level with
|
||||
the ``throttle`` key.
|
||||
|
||||
Example ``ODOO_QUEUE_JOB_CHANNELS``:
|
||||
|
||||
* ``root:4``: allow up to 4 concurrent jobs in the root channel.
|
||||
* ``root:4,root.sub:2``: allow up to 4 concurrent jobs in the root channel and
|
||||
up to 2 concurrent jobs in the channel named ``root.sub``.
|
||||
* ``sub:2``: the same.
|
||||
* ``root:4:throttle=2``: wait at least 2 seconds before starting the next job
|
||||
|
|
@ -0,0 +1,900 @@
|
|||
.. _migration-guide:
|
||||
|
||||
########################################
|
||||
Migration Guide to the new Connector API
|
||||
########################################
|
||||
|
||||
During the year 2017, the connector evolved greatly.
|
||||
We can recognize three different aspect of the framework, they all have been
|
||||
rewritten:
|
||||
|
||||
* The Job Queue API (:ref:`api-queue`)
|
||||
* The Event API (:ref:`api-event`)
|
||||
* The ``ConnectorUnit`` API, which is the core of the composability
|
||||
of the Connector. It has been replaced by a standalone addon
|
||||
called ``component``. (:ref:`api-component`)
|
||||
|
||||
The Connector has been splitted in different addons:
|
||||
|
||||
* ``queue_job`` in https://github.com/OCA/queue
|
||||
* ``component`` in the same repository
|
||||
* ``component_event`` in the same repository
|
||||
* ``connector`` uses the 3 addons and the parts specifics to the connectors
|
||||
|
||||
This guide will show how to migrate from the old API to the new one.
|
||||
|
||||
The previous API will stay until the migration to Odoo 11.0.
|
||||
|
||||
.. contents:: Sections:
|
||||
:local:
|
||||
:backlinks: top
|
||||
:depth: 2
|
||||
|
||||
**************
|
||||
Migrating Jobs
|
||||
**************
|
||||
|
||||
Jobs are now more integrated within the Odoo API. They are no longer
|
||||
standalone functions but are applied on methods of Models. Another change is
|
||||
that they have been extracted into their own addon, so obviously the Python
|
||||
paths change.
|
||||
|
||||
Declaration of a job
|
||||
====================
|
||||
|
||||
Before
|
||||
------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.connector.queue.job import job, related_action
|
||||
from ..related_action import unwrap_binding, link
|
||||
|
||||
# function at module-level
|
||||
@job(default_channel='root.magento')
|
||||
@related_action(action=link)
|
||||
def import_record(session, model_name, backend_id, magento_id, force=False):
|
||||
""" Import a record from Magento """
|
||||
# ...
|
||||
|
||||
@job(default_channel='root.magento')
|
||||
@related_action(action=unwrap_binding)
|
||||
def export_record(session, model_name, binding_id, fields=None):
|
||||
""" Import a record from Magento """
|
||||
# ...
|
||||
|
||||
|
||||
After
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.queue_job.job import job, related_action
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class MagentoBinding(models.AbstractModel):
|
||||
_name = 'magento.binding'
|
||||
_inherit = 'external.binding'
|
||||
_description = 'Magento Binding (abstract)'
|
||||
|
||||
@job(default_channel='root.magento')
|
||||
@related_action(action='related_action_magento_link')
|
||||
@api.model
|
||||
def import_record(self, backend, external_id, force=False):
|
||||
""" Import a Magento record """
|
||||
backend.ensure_one()
|
||||
# ...
|
||||
|
||||
@job(default_channel='root.magento')
|
||||
@related_action(action='related_action_unwrap_binding')
|
||||
@api.multi
|
||||
def export_record(self, fields=None):
|
||||
""" Export a record on Magento """
|
||||
self.ensure_one()
|
||||
# ...
|
||||
|
||||
|
||||
Observations
|
||||
------------
|
||||
|
||||
* The job is declared on the generic abstract binding model from which all
|
||||
bindings inherit. This is not a requirement, but for this kind of job it is
|
||||
the perfect fit.
|
||||
* ``session``, ``model_name`` and ``binding_id`` are no longer required as they
|
||||
are already known in ``self``. Jobs can be used as well on ``@api.multi`` and
|
||||
``@api.model``.
|
||||
* Passing arguments as records is supported, in the new version of
|
||||
``import_record``, no need to browse on the backend if a record was passed
|
||||
* The action of a related action is now the name of a method on the
|
||||
``queue.job`` model.
|
||||
* If you need to share a job between several models, put them in an
|
||||
AbstractModel and add an ``_inherit`` on the models.
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* :meth:`odoo.addons.queue_job.job.job`
|
||||
* :meth:`odoo.addons.queue_job.job.related_action`
|
||||
|
||||
|
||||
Invocation of a job
|
||||
===================
|
||||
|
||||
Before
|
||||
------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.connector.session import ConnectorSession
|
||||
from .unit.export_synchronizer import export_record
|
||||
|
||||
|
||||
class MyBinding(models.Model):
|
||||
_name = 'my.binding'
|
||||
_inherit = 'magento.binding'
|
||||
|
||||
@api.multi
|
||||
def button_trigger_export_sync(self):
|
||||
session = ConnectorSession.from_env(self.env)
|
||||
export_record(session, binding._name, self.id, fields=['name'])
|
||||
|
||||
@api.multi
|
||||
def button_trigger_export_async(self):
|
||||
session = ConnectorSession.from_env(self.env)
|
||||
export_record.delay(session, self._name, self.id,
|
||||
fields=['name'], priority=12)
|
||||
|
||||
|
||||
After
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyBinding(models.Model):
|
||||
_name = 'my.binding'
|
||||
|
||||
@api.multi
|
||||
def button_trigger_export_sync(self):
|
||||
self.export_record(fields=['name'])
|
||||
|
||||
@api.multi
|
||||
def button_trigger_export_async(self):
|
||||
self.with_delay(priority=12).export_record(fields=['name'])
|
||||
|
||||
Observations
|
||||
------------
|
||||
|
||||
* No more imports are needed for the invocation
|
||||
* ``ConnectorSession`` is now dead
|
||||
* Arguments for the job (such as ``priority``) are no longer mixed with the
|
||||
arguments passed to the method
|
||||
* When the job is called on a "browse" record, the job will be executed
|
||||
on an instance of this record:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> binding = self.env['my.binding'].browse(1)
|
||||
>>> binding.button_trigger_export_async()
|
||||
|
||||
In the execution of the job:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@job
|
||||
def export_record(self, fields=None):
|
||||
print self
|
||||
print fields
|
||||
# =>
|
||||
# my.binding,1
|
||||
# ['name']
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* :meth:`odoo.addons.queue_job.job.job`
|
||||
* :meth:`odoo.addons.queue_job.models.base.Base.with_delay`
|
||||
|
||||
****************
|
||||
Migrating Events
|
||||
****************
|
||||
|
||||
Events are now handled by the ``component_event`` addon.
|
||||
|
||||
Triggering an event
|
||||
===================
|
||||
|
||||
Before
|
||||
------
|
||||
|
||||
First you had to create an :class:`~odoo.addons.connector.event.Event` instance:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
on_record_create = Event()
|
||||
|
||||
And then import and trigger it, passing a lot of arguments to it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.connector.event import on_record_create
|
||||
|
||||
class Base(models.AbstractModel):
|
||||
""" The base model, which is implicitly inherited by all models. """
|
||||
_inherit = 'base'
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
record = super(Base, self).create(vals)
|
||||
on_record_create.fire(self.env, self._name, record.id, vals)
|
||||
return record
|
||||
|
||||
|
||||
After
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
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
|
||||
|
||||
Observations
|
||||
------------
|
||||
|
||||
* No more imports are needed for the invocation
|
||||
Only the arguments you want to pass should be passed to
|
||||
:meth:`odoo.addons.component_event.components.event.CollectedEvents.notify`.
|
||||
* The name of the event must start with ``'on_'``
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* :mod:`odoo.addons.component_event.components.event`
|
||||
|
||||
|
||||
Listening to an event
|
||||
=====================
|
||||
|
||||
Before
|
||||
------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.connector.event import on_record_create
|
||||
|
||||
@on_record_create
|
||||
def delay_export(env, model_name, record_id, vals):
|
||||
if session.context.get('connector_no_export'):
|
||||
return
|
||||
fields = vals.keys()
|
||||
export_record.delay(session, model_name, record_id, fields=fields)
|
||||
|
||||
@on_something
|
||||
def do_anything(env, model_name, record_id):
|
||||
# ...
|
||||
|
||||
After
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.component_event import skip_if
|
||||
|
||||
class MagentoListener(Component):
|
||||
_name = 'magento.event.listener'
|
||||
_inherit = 'base.connector.listener'
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_create(self, record, fields=None):
|
||||
""" Called when a record is created """
|
||||
record.with_delay().export_record(fields=fields)
|
||||
|
||||
def on_something(self, record):
|
||||
# ...
|
||||
|
||||
Observations
|
||||
------------
|
||||
|
||||
* The listeners are now components
|
||||
* The name of the method is the same than the one notified in the previous
|
||||
section
|
||||
* A listener Component might container several listener methods
|
||||
* It must inherit from ``'base.event.listener'``, or one of its descendants.
|
||||
* The check of the key ``connector_no_export`` in the context can
|
||||
be replaced by the decorator :func:`odoo.addons.component_event.skip_if`
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* :mod:`odoo.addons.component_event.components.event`
|
||||
|
||||
|
||||
Listening to an event only for some Models
|
||||
==========================================
|
||||
|
||||
Before
|
||||
------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.connector.event import on_record_create
|
||||
|
||||
@on_record_create(model_names=['magento.address', 'magento.res.partner'])
|
||||
def delay_export(env, model_name, record_id, vals):
|
||||
if session.context.get('connector_no_export'):
|
||||
return
|
||||
fields = vals.keys()
|
||||
export_record.delay(session, model_name, record_id, fields=fields)
|
||||
|
||||
After
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.component_event import skip_if
|
||||
|
||||
class MagentoListener(Component):
|
||||
_name = 'magento.event.listener'
|
||||
_inherit = 'base.event.listener'
|
||||
_apply_on = ['magento.address', 'magento.res.partner']
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_create(self, record, fields=None):
|
||||
""" Called when a record is created """
|
||||
record.with_delay().export_record(fields=fields)
|
||||
|
||||
Observations
|
||||
------------
|
||||
|
||||
* Same than previous example but we added ``_apply_on`` on the Component.
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* :mod:`odoo.addons.component_event.components.event`
|
||||
|
||||
|
||||
********************
|
||||
Migrating Components
|
||||
********************
|
||||
|
||||
Backends
|
||||
========
|
||||
|
||||
Before
|
||||
------
|
||||
|
||||
You could have several versions for a backend:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
magento = backend.Backend('magento')
|
||||
""" Generic Magento Backend """
|
||||
|
||||
magento1700 = backend.Backend(parent=magento, version='1.7')
|
||||
""" Magento Backend for version 1.7 """
|
||||
|
||||
magento1900 = backend.Backend(parent=magento, version='1.9')
|
||||
""" Magento Backend for version 1.9 """
|
||||
|
||||
|
||||
|
||||
It was linked with a Backend model such as:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MagentoBackend(models.Model):
|
||||
_name = 'magento.backend'
|
||||
_description = 'Magento Backend'
|
||||
_inherit = 'connector.backend'
|
||||
|
||||
_backend_type = 'magento'
|
||||
|
||||
@api.model
|
||||
def select_versions(self):
|
||||
""" Available versions in the backend.
|
||||
Can be inherited to add custom versions. Using this method
|
||||
to add a version from an ``_inherit`` does not constrain
|
||||
to redefine the ``version`` field in the ``_inherit`` model.
|
||||
"""
|
||||
return [('1.7', '1.7+')]
|
||||
|
||||
version = fields.Selection(selection='select_versions', required=True)
|
||||
|
||||
|
||||
|
||||
After
|
||||
-----
|
||||
|
||||
All the :class:`backend.Backend` instances must be deleted.
|
||||
|
||||
And the ``_backend_type`` must be removed from the Backend model.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MagentoBackend(models.Model):
|
||||
_name = 'magento.backend'
|
||||
_description = 'Magento Backend'
|
||||
_inherit = 'connector.backend'
|
||||
|
||||
@api.model
|
||||
def select_versions(self):
|
||||
""" Available versions in the backend.
|
||||
Can be inherited to add custom versions. Using this method
|
||||
to add a version from an ``_inherit`` does not constrain
|
||||
to redefine the ``version`` field in the ``_inherit`` model.
|
||||
"""
|
||||
return [('1.7', '1.7+')]
|
||||
|
||||
version = fields.Selection(selection='select_versions', required=True)
|
||||
|
||||
|
||||
Observations
|
||||
------------
|
||||
|
||||
* The version is now optional in the Backend Models.
|
||||
* Backend Models are based on Component's Collections:
|
||||
:class:`odoo.addons.component.models.collection.Collection`
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* :ref:`api-component`
|
||||
* :class:`odoo.addons.component.models.collection.Collection`
|
||||
|
||||
|
||||
Inheritance
|
||||
===========
|
||||
|
||||
Before
|
||||
------
|
||||
|
||||
You could inherit a ``ConnectorUnit`` by creating a custom Backend
|
||||
version and decorating your class with it
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
magento_custom = backend.Backend(parent=magento1700, version='custom')
|
||||
""" Custom Magento Backend """
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# base one
|
||||
@magento
|
||||
class MagentoPartnerAdapter(GenericAdapter):
|
||||
# ...
|
||||
|
||||
# other file...
|
||||
|
||||
from .backend import magento_custom
|
||||
|
||||
# custom one
|
||||
@magento_custom
|
||||
class MyPartnerAdapter(MagentoPartnerAdapter):
|
||||
# ...
|
||||
|
||||
def do_something(self):
|
||||
# do it this way
|
||||
|
||||
You could also replace an existing class, this is mentionned in `Replace or
|
||||
unregister a component`_.
|
||||
|
||||
|
||||
After
|
||||
-----
|
||||
|
||||
For an existing component:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
class MagentoPartnerAdapter(Component):
|
||||
_name = 'magento.partner.adapter'
|
||||
_inherit = 'magento.adapter'
|
||||
|
||||
def do_something(self):
|
||||
# do it this way
|
||||
|
||||
You can extend it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
class MyPartnerAdapter(Component):
|
||||
_inherit = 'magento.partner.adapter'
|
||||
|
||||
def do_something(self):
|
||||
# do it this way
|
||||
|
||||
Or create a new different component with the existing one as base:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
class MyPartnerAdapter(Component):
|
||||
_name = 'my.magento.partner.adapter'
|
||||
_inherit = 'magento.partner.adapter'
|
||||
|
||||
def do_something(self):
|
||||
# do it this way
|
||||
|
||||
|
||||
Observations
|
||||
------------
|
||||
|
||||
* The inheritance is similar to the Odoo's one (without ``_inherits``.
|
||||
* All components have a Python inheritance on
|
||||
:class:`~odoo.addons.component.core.AbstractComponent` or
|
||||
:class:`~odoo.addons.component.core.Component`
|
||||
* The names are global (as in Odoo), so you should prefix them with a namespace
|
||||
* The name of the classes has no effect
|
||||
* As in Odoo Models, a Component can ``_inherit`` from a list of Components
|
||||
* All components implicitly inherits from a ``'base'`` component
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* :ref:`api-component`
|
||||
* :class:`odoo.addons.component.core.AbstractComponent`
|
||||
|
||||
|
||||
|
||||
Entrypoint for working with components
|
||||
======================================
|
||||
|
||||
Before
|
||||
------
|
||||
|
||||
Previously, when you had to work with ``ConnectorUnit`` from a Model or from a job,
|
||||
depending of the Odoo version you to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.connector.connector import ConnectorEnvironment
|
||||
|
||||
# ...
|
||||
|
||||
backend_record = session.env['magento.backend'].browse(backend_id)
|
||||
env = ConnectorEnvironment(backend_record, 'magento.res.partner')
|
||||
importer = env.get_connector_unit(MagentoImporter)
|
||||
importer.run(magento_id, force=force)
|
||||
|
||||
Or:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.connector.connector import ConnectorEnvironment
|
||||
from odoo.addons.connector.session import ConnectorSession
|
||||
|
||||
#...
|
||||
|
||||
backend_record = session.env['magento.backend'].browse(backend_id)
|
||||
session = ConnectorSession.from_env(self.env)
|
||||
env = ConnectorEnvironment(backend_record, session, 'magento.res.partner')
|
||||
importer = env.get_connector_unit(MagentoImporter)
|
||||
importer.run(external_id, force=force)
|
||||
|
||||
Which was commonly abstracted in a helper function such as:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def get_environment(session, model_name, backend_id):
|
||||
""" Create an environment to work with. """
|
||||
backend_record = session.env['magento.backend'].browse(backend_id)
|
||||
env = ConnectorEnvironment(backend_record, session, 'magento.res.partner')
|
||||
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
|
||||
|
||||
After
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# ...
|
||||
backend_record = self.env['magento.backend'].browse(backend_id)
|
||||
with backend_record.work_on('magento.res.partner') as work:
|
||||
importer = work.component(usage='record.importer')
|
||||
importer.run(external_id, force=force)
|
||||
|
||||
Observations
|
||||
------------
|
||||
|
||||
* And when you are already in a Component, refer to `Find a component`_
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* :class:`~odoo.addons.component.core.WorkContext`
|
||||
|
||||
|
||||
Find a component
|
||||
================
|
||||
|
||||
Before
|
||||
------
|
||||
|
||||
To find a ``ConnectorUnit``, you had to ask for given class or subclass:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# our ConnectorUnit to find
|
||||
@magento
|
||||
class MagentoPartnerAdapter(GenericAdapter):
|
||||
_model_name = ['magent.res.partner']
|
||||
|
||||
# other file...
|
||||
|
||||
def run(self, record):
|
||||
backend_adapter = self.unit_for(GenericAdapter)
|
||||
|
||||
It was searched for the current model and the current backend.
|
||||
|
||||
After
|
||||
-----
|
||||
|
||||
For an existing component:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
class MagentoPartnerAdapter(Component):
|
||||
_name = 'magento.partner.adapter'
|
||||
_inherit = 'magento.adapter'
|
||||
|
||||
_usage = 'backend.adapter'
|
||||
_collection = 'magento.backend'
|
||||
_apply_on = ['res.partner']
|
||||
|
||||
# other file...
|
||||
|
||||
def run(self, record):
|
||||
backend_adapter = self.component(usage='backend.adapter')
|
||||
|
||||
|
||||
|
||||
Observations
|
||||
------------
|
||||
|
||||
* The model is compared with the ``_apply_on`` attribute
|
||||
* The Backend is compared with the ``_collection`` attribute, it must
|
||||
have the same name than the Backend Model.
|
||||
* The ``_usage`` indicates what the purpose of the component is, and
|
||||
allow to find the correct one for our task. It allow more dynamic
|
||||
usages than the previous usage of a class.
|
||||
* Usually, the ``_usage`` and the ``_collection`` will be ``_inherit`` 'ed from
|
||||
a component (here from ``'magento.adapter``), so they won't need to be
|
||||
repeated in all Components.
|
||||
* A good idea is to have a base abstract Component for the Collection, then
|
||||
an abstract Component for every usage::
|
||||
|
||||
class BaseMagentoConnectorComponent(AbstractComponent):
|
||||
|
||||
_name = 'base.magento.connector'
|
||||
_inherit = 'base.connector'
|
||||
_collection = 'magento.backend'
|
||||
|
||||
class MagentoBaseExporter(AbstractComponent):
|
||||
""" Base exporter for Magento """
|
||||
|
||||
_name = 'magento.base.exporter'
|
||||
_inherit = ['base.exporter', 'base.magento.connector']
|
||||
_usage = 'record.exporter'
|
||||
|
||||
class MagentoImportMapper(AbstractComponent):
|
||||
_name = 'magento.import.mapper'
|
||||
_inherit = ['base.magento.connector', 'base.import.mapper']
|
||||
_usage = 'import.mapper'
|
||||
|
||||
# ...
|
||||
|
||||
* The main usages are:
|
||||
* import.mapper
|
||||
* export.mapper
|
||||
* backend.adapter
|
||||
* importer
|
||||
* exporter
|
||||
* binder
|
||||
* event.listener
|
||||
* But for the importer and exporter, I recommend to use more precise ones in
|
||||
the connectors: record.importer, record.exporter, batch.importer,
|
||||
batch.exporter
|
||||
* You are allowed to be creative with the ``_usage``, it's the key that will
|
||||
allow you to find the right one component you need. (e.g. on
|
||||
``stock.picking`` you need to 1. export the record, 2. export the tracking.
|
||||
Then use ``record.exporter`` and ``tracking.exporter``).
|
||||
* AbstractComponent will never be returned by a lookup
|
||||
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* :ref:`api-component`
|
||||
* :class:`odoo.addons.component.core.AbstractComponent`
|
||||
|
||||
|
||||
Backend Versions
|
||||
================
|
||||
|
||||
Before
|
||||
------
|
||||
|
||||
You could have several versions for a backend:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
magento = backend.Backend('magento')
|
||||
""" Generic Magento Backend """
|
||||
|
||||
magento1700 = backend.Backend(parent=magento, version='1.7')
|
||||
""" Magento Backend for version 1.7 """
|
||||
|
||||
magento1900 = backend.Backend(parent=magento, version='1.9')
|
||||
""" Magento Backend for version 1.9 """
|
||||
|
||||
|
||||
And use them for a class-level dynamic dispatch
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.magentoerpconnect.backend import magento1700, magento1900
|
||||
|
||||
@magento1700
|
||||
class PartnerAdapter1700(GenericAdapter):
|
||||
# ...
|
||||
|
||||
def do_something(self):
|
||||
# do it this way
|
||||
|
||||
@magento1900
|
||||
class PartnerAdapter1900(GenericAdapter):
|
||||
# ...
|
||||
|
||||
def do_something(self):
|
||||
# do it that way
|
||||
|
||||
|
||||
After
|
||||
-----
|
||||
|
||||
This feature has been removed, it introduced a lot of complexity (notably
|
||||
regarding inheritance) for few gain. The version is now optional on the
|
||||
backends and the version dispatch, if needed, should be handled manually.
|
||||
|
||||
In methods:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
class PartnerAdapter(Component):
|
||||
# ...
|
||||
|
||||
def do_something(self):
|
||||
if self.backend_record.version == '1.7':
|
||||
# do it this way
|
||||
else:
|
||||
# do it that way
|
||||
|
||||
Or with a factory:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
class PartnerAdapterFactory(Component):
|
||||
# ...
|
||||
|
||||
def get_component(self, version):
|
||||
if self.backend_record.version == '1.7':
|
||||
return self.component(usage='backend.adapter.1.7')
|
||||
else:
|
||||
return self.component(usage='backend.adapter.1.9')
|
||||
|
||||
Observations
|
||||
------------
|
||||
|
||||
* None
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* :ref:`api-component`
|
||||
|
||||
|
||||
Replace or unregister a component
|
||||
=================================
|
||||
|
||||
Before
|
||||
------
|
||||
|
||||
You could replace a ``ConnectorUnit`` with the ``replace`` argument passed to
|
||||
the backend decorator:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@magento(replacing=product.ProductImportMapper)
|
||||
class ProductImportMapper(product.ProductImportMapper):
|
||||
|
||||
|
||||
After
|
||||
-----
|
||||
|
||||
First point: this should hardly be needed now, as you can inherit a component
|
||||
like Odoo Models. Still, if you need to totally replace a component by
|
||||
another, let's say there is this component:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
class ProductImportMapper(Component):
|
||||
_name = 'magento.product.import.mapper'
|
||||
_inherit = 'magento.import.mapper'
|
||||
|
||||
_apply_on = ['magento.product.product']
|
||||
# normally the following attrs are inherited from the _inherit
|
||||
_usage = 'import.mapper'
|
||||
_collection = 'magento.backend'
|
||||
|
||||
|
||||
Then you can remove the usage of the component: it will never be used:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
class ProductImportMapper(Component):
|
||||
_inherit = 'magento.product.import.mapper'
|
||||
_usage = None
|
||||
|
||||
And create your own, that will be picked up instead of the base one:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
class MyProductImportMapper(Component):
|
||||
_name = 'my.magento.product.import.mapper'
|
||||
_inherit = 'magento.import.mapper'
|
||||
|
||||
_apply_on = ['magento.product.product']
|
||||
# normally the following attrs are inherited from the _inherit
|
||||
_usage = 'import.mapper'
|
||||
_collection = 'magento.backend'
|
||||
|
||||
|
||||
Observations
|
||||
------------
|
||||
|
||||
* None
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* :ref:`api-component`
|
||||
|
||||
|
||||
Various hints
|
||||
=============
|
||||
|
||||
* The components and the jobs know how to work with Model instances,
|
||||
so prefer them over ids in parameters.
|
||||
Loading…
Add table
Add a link
Reference in a new issue