mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-18 05:12:07 +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
167
odoo-bringout-oca-connector-component/component/README.rst
Normal file
167
odoo-bringout-oca-connector-component/component/README.rst
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
==========
|
||||
Components
|
||||
==========
|
||||
|
||||
..
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:460c927ffe0fe97c01fdc7decac3df3b2b01700e16dc071eef0c9d2765ba0b1e
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
|
||||
:target: https://odoo-community.org/page/development-status
|
||||
:alt: Production/Stable
|
||||
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
|
||||
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
|
||||
:alt: License: LGPL-3
|
||||
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fconnector-lightgray.png?logo=github
|
||||
:target: https://github.com/OCA/connector/tree/16.0/component
|
||||
:alt: OCA/connector
|
||||
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
|
||||
:target: https://translation.odoo-community.org/projects/connector-16-0/connector-16-0-component
|
||||
:alt: Translate me on Weblate
|
||||
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
|
||||
:target: https://runboat.odoo-community.org/builds?repo=OCA/connector&target_branch=16.0
|
||||
:alt: Try me on Runboat
|
||||
|
||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||
|
||||
This module implements a component system and is a base block for the Connector
|
||||
Framework. It can be used without using the full Connector though.
|
||||
|
||||
Documentation: http://odoo-connector.com/
|
||||
|
||||
You may also want to check the `Introduction to Odoo Components`_ by @guewen.
|
||||
|
||||
.. _Introduction to Odoo Components: https://dev.to/guewen/introduction-to-odoo-components-bn0
|
||||
|
||||
**Table of contents**
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
As a developer, you have access to a component system. You can find the
|
||||
documentation in the code or on http://odoo-connector.com
|
||||
|
||||
In a nutshell, you can create components::
|
||||
|
||||
|
||||
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']
|
||||
|
||||
And later, find the component you need at runtime (dynamic dispatch at
|
||||
component level)::
|
||||
|
||||
def run(self, external_id):
|
||||
backend_adapter = self.component(usage='backend.adapter')
|
||||
external_data = backend_adapter.read(external_id)
|
||||
|
||||
|
||||
In order for tests using components to work, you will need to use the base
|
||||
class provided by `odoo.addons.component.tests.common`:
|
||||
|
||||
* `TransactionComponentCase`
|
||||
|
||||
There are also some specific base classes for testing the component registry,
|
||||
using the ComponentRegistryCase as a base class. See the docstrings in
|
||||
`tests/common.py`.
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
.. [ The change log. The goal of this file is to help readers
|
||||
understand changes between version. The primary audience is
|
||||
end users and integrators. Purely technical changes such as
|
||||
code refactoring must not be mentioned here.
|
||||
|
||||
This file may contain ONE level of section titles, underlined
|
||||
with the ~ (tilde) character. Other section markers are
|
||||
forbidden and will likely break the structure of the README.rst
|
||||
or other documents where this fragment is included. ]
|
||||
|
||||
16.0.1.0.0 (2022-10-04)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [MIGRATION] from 15.0
|
||||
|
||||
15.0.1.0.0 (2021-11-25)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [MIGRATION] from 14.0
|
||||
|
||||
14.0.1.0.0 (2020-10-22)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [MIGRATION] from 13.0
|
||||
|
||||
13.0.1.0.0 (2019-10-23)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [MIGRATION] from 12.0
|
||||
|
||||
12.0.1.0.0 (2018-10-02)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [MIGRATION] from 11.0 branched at rev. 324e006
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/connector/issues>`_.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||
`feedback <https://github.com/OCA/connector/issues/new?body=module:%20component%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||
|
||||
Do not contact contributors directly about support or help with technical issues.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Authors
|
||||
~~~~~~~
|
||||
|
||||
* Camptocamp
|
||||
|
||||
Contributors
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
||||
* Laurent Mignon <laurent.mignon@acsone.eu>
|
||||
* Simone Orsi <simone.orsi@camptocamp.com>
|
||||
|
||||
Maintainers
|
||||
~~~~~~~~~~~
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.
|
||||
|
||||
.. |maintainer-guewen| image:: https://github.com/guewen.png?size=40px
|
||||
:target: https://github.com/guewen
|
||||
:alt: guewen
|
||||
|
||||
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|
||||
|
||||
|maintainer-guewen|
|
||||
|
||||
This module is part of the `OCA/connector <https://github.com/OCA/connector/tree/16.0/component>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
from . import core
|
||||
|
||||
from . import components
|
||||
from . import builder
|
||||
from . import models
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
{
|
||||
"name": "Components",
|
||||
"summary": "Add capabilities to register and use decoupled components,"
|
||||
" as an alternative to model classes",
|
||||
"version": "16.0.1.1.1",
|
||||
"author": "Camptocamp," "Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/connector",
|
||||
"license": "LGPL-3",
|
||||
"category": "Generic Modules",
|
||||
"depends": ["base"],
|
||||
"external_dependencies": {
|
||||
"python": [
|
||||
"cachetools",
|
||||
]
|
||||
},
|
||||
"installable": True,
|
||||
"development_status": "Production/Stable",
|
||||
"maintainers": ["guewen"],
|
||||
}
|
||||
96
odoo-bringout-oca-connector-component/component/builder.py
Normal file
96
odoo-bringout-oca-connector-component/component/builder.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# Copyright 2019 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
"""
|
||||
|
||||
Components Builder
|
||||
==================
|
||||
|
||||
Build the components at the build of a registry.
|
||||
|
||||
"""
|
||||
import odoo
|
||||
from odoo import models
|
||||
|
||||
from .core import DEFAULT_CACHE_SIZE, ComponentRegistry, _component_databases
|
||||
|
||||
|
||||
class ComponentBuilder(models.AbstractModel):
|
||||
"""Build the component classes
|
||||
|
||||
And register them in a global registry.
|
||||
|
||||
Every time an Odoo registry is built, the know components are cleared and
|
||||
rebuilt as well. The Component classes are built using the same mechanism
|
||||
than Odoo's Models: a final class is created, taking every Components with
|
||||
a ``_name`` and applying Components with an ``_inherits`` upon them.
|
||||
|
||||
The final Component classes are registered in global registry.
|
||||
|
||||
This class is an Odoo model, allowing us to hook the build of the
|
||||
components at the end of the Odoo's registry loading, using
|
||||
``_register_hook``. This method is called after all modules are loaded, so
|
||||
we are sure that we have all the components Classes and in the correct
|
||||
order.
|
||||
|
||||
"""
|
||||
|
||||
_name = "component.builder"
|
||||
_description = "Component Builder"
|
||||
|
||||
_components_registry_cache_size = DEFAULT_CACHE_SIZE
|
||||
|
||||
def _register_hook(self):
|
||||
# This method is called by Odoo when the registry is built,
|
||||
# so in case the registry is rebuilt (cache invalidation, ...),
|
||||
# we have to to rebuild the components. We use a new
|
||||
# registry so we have an empty cache and we'll add components in it.
|
||||
components_registry = self._init_global_registry()
|
||||
self.build_registry(components_registry)
|
||||
components_registry.ready = True
|
||||
|
||||
def _init_global_registry(self):
|
||||
components_registry = ComponentRegistry(
|
||||
cachesize=self._components_registry_cache_size
|
||||
)
|
||||
_component_databases[self.env.cr.dbname] = components_registry
|
||||
return components_registry
|
||||
|
||||
def build_registry(self, components_registry, states=None, exclude_addons=None):
|
||||
if not states:
|
||||
states = ("installed", "to upgrade")
|
||||
# lookup all the installed (or about to be) addons and generate
|
||||
# the graph, so we can load the components following the order
|
||||
# of the addons' dependencies
|
||||
graph = odoo.modules.graph.Graph()
|
||||
graph.add_module(self.env.cr, "base")
|
||||
|
||||
query = "SELECT name " "FROM ir_module_module " "WHERE state IN %s "
|
||||
params = [tuple(states)]
|
||||
if exclude_addons:
|
||||
query += " AND name NOT IN %s "
|
||||
params.append(tuple(exclude_addons))
|
||||
self.env.cr.execute(query, params)
|
||||
|
||||
module_list = [name for (name,) in self.env.cr.fetchall() if name not in graph]
|
||||
graph.add_modules(self.env.cr, module_list)
|
||||
|
||||
for module in graph:
|
||||
self.load_components(module.name, components_registry=components_registry)
|
||||
|
||||
def load_components(self, module, components_registry=None):
|
||||
"""Build every component known by MetaComponent for an odoo module
|
||||
|
||||
The final component (composed by all the Component classes in this
|
||||
module) will be pushed into the registry.
|
||||
|
||||
:param module: the name of the addon for which we want to load
|
||||
the components
|
||||
:type module: str | unicode
|
||||
:param registry: the registry in which we want to put the Component
|
||||
:type registry: :py:class:`~.core.ComponentRegistry`
|
||||
"""
|
||||
components_registry = (
|
||||
components_registry or _component_databases[self.env.cr.dbname]
|
||||
)
|
||||
components_registry.load_components(module)
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import base
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from ..core import AbstractComponent
|
||||
|
||||
|
||||
class BaseComponent(AbstractComponent):
|
||||
"""This is the base component for every component
|
||||
|
||||
It is implicitely inherited by all components.
|
||||
|
||||
All your base are belong to us
|
||||
"""
|
||||
|
||||
_name = "base"
|
||||
938
odoo-bringout-oca-connector-component/component/core.py
Normal file
938
odoo-bringout-oca-connector-component/component/core.py
Normal file
|
|
@ -0,0 +1,938 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# Copyright 2017 Odoo
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
"""
|
||||
|
||||
Core
|
||||
====
|
||||
|
||||
Core classes for the components.
|
||||
The most common classes used publicly are:
|
||||
|
||||
* :class:`Component`
|
||||
* :class:`AbstractComponent`
|
||||
* :class:`WorkContext`
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import operator
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
from odoo import models
|
||||
from odoo.tools import LastOrderedSet, OrderedSet
|
||||
|
||||
from .exception import NoComponentError, RegistryNotReadyError, SeveralComponentError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from cachetools import LRUCache, cachedmethod
|
||||
except ImportError:
|
||||
_logger.debug("Cannot import 'cachetools'.")
|
||||
|
||||
|
||||
# The Cache size represents the number of items, so the number
|
||||
# of components (include abstract components) we will keep in the LRU
|
||||
# cache. We would need stats to know what is the average but this is a bit
|
||||
# early.
|
||||
DEFAULT_CACHE_SIZE = 512
|
||||
|
||||
|
||||
# this is duplicated from odoo.models.MetaModel._get_addon_name() which we
|
||||
# unfortunately can't use because it's an instance method and should have been
|
||||
# a @staticmethod
|
||||
def _get_addon_name(full_name):
|
||||
# The (Odoo) module name can be in the ``odoo.addons`` namespace
|
||||
# or not. For instance, module ``sale`` can be imported as
|
||||
# ``odoo.addons.sale`` (the right way) or ``sale`` (for backward
|
||||
# compatibility).
|
||||
module_parts = full_name.split(".")
|
||||
if len(module_parts) > 2 and module_parts[:2] == ["odoo", "addons"]:
|
||||
addon_name = full_name.split(".")[2]
|
||||
else:
|
||||
addon_name = full_name.split(".")[0]
|
||||
return addon_name
|
||||
|
||||
|
||||
class ComponentDatabases(dict):
|
||||
"""Holds a registry of components for each database"""
|
||||
|
||||
|
||||
class ComponentRegistry:
|
||||
"""Store all the components and allow to find them using criteria
|
||||
|
||||
The key is the ``_name`` of the components.
|
||||
|
||||
This is an OrderedDict, because we want to keep the registration order of
|
||||
the components, addons loaded first have their components found first.
|
||||
|
||||
The :attr:`ready` attribute must be set to ``True`` when all the components
|
||||
are loaded.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, cachesize=DEFAULT_CACHE_SIZE):
|
||||
self._cache = LRUCache(maxsize=cachesize)
|
||||
self._components = OrderedDict()
|
||||
self._loaded_modules = set()
|
||||
self.ready = False
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._components[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._components[key] = value
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._components
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self._components.get(key, default)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components)
|
||||
|
||||
def load_components(self, module):
|
||||
if module in self._loaded_modules:
|
||||
return
|
||||
for component_class in MetaComponent._modules_components[module]:
|
||||
component_class._build_component(self)
|
||||
self._loaded_modules.add(module)
|
||||
|
||||
@cachedmethod(operator.attrgetter("_cache"))
|
||||
def lookup(self, collection_name=None, usage=None, model_name=None):
|
||||
"""Find and return a list of components for a usage
|
||||
|
||||
If a component is not registered in a particular collection (no
|
||||
``_collection``), it will be returned in any case (as far as
|
||||
the ``usage`` and ``model_name`` match). This is useful to share
|
||||
generic components across different collections.
|
||||
|
||||
If no collection name is given, components from any collection
|
||||
will be returned.
|
||||
|
||||
Then, the components of a collection are filtered by usage and/or
|
||||
model. The ``_usage`` is mandatory on the components. When the
|
||||
``_model_name`` is empty, it means it can be used for every models,
|
||||
and it will ignore the ``model_name`` argument.
|
||||
|
||||
The abstract components are never returned.
|
||||
|
||||
This is a rather low-level function, usually you will use the
|
||||
high-level :meth:`AbstractComponent.component`,
|
||||
:meth:`AbstractComponent.many_components` or even
|
||||
:meth:`AbstractComponent.component_by_name`.
|
||||
|
||||
:param collection_name: the name of the collection the component is
|
||||
registered into.
|
||||
:param usage: the usage of component we are looking for
|
||||
:param model_name: filter on components that apply on this model
|
||||
|
||||
"""
|
||||
|
||||
# keep the order so addons loaded first have components used first
|
||||
candidates = (
|
||||
component
|
||||
for component in self._components.values()
|
||||
if not component._abstract
|
||||
)
|
||||
|
||||
if collection_name is not None:
|
||||
candidates = (
|
||||
component
|
||||
for component in candidates
|
||||
if (
|
||||
component._collection == collection_name
|
||||
or component._collection is None
|
||||
)
|
||||
)
|
||||
|
||||
if usage is not None:
|
||||
candidates = (
|
||||
component for component in candidates if component._usage == usage
|
||||
)
|
||||
|
||||
if model_name is not None:
|
||||
candidates = (
|
||||
c
|
||||
for c in candidates
|
||||
if c.apply_on_models is None or model_name in c.apply_on_models
|
||||
)
|
||||
|
||||
return list(candidates)
|
||||
|
||||
|
||||
# We will store a ComponentRegistry per database here,
|
||||
# it will be cleared and updated when the odoo's registry is rebuilt
|
||||
_component_databases = ComponentDatabases()
|
||||
|
||||
|
||||
class WorkContext:
|
||||
"""Transport the context required to work with components
|
||||
|
||||
It is propagated through all the components, so any
|
||||
data or instance (like a random RPC client) that need
|
||||
to be propagated transversally to the components
|
||||
should be kept here.
|
||||
|
||||
Including:
|
||||
|
||||
.. attribute:: model_name
|
||||
|
||||
Name of the model we are working with. It means that any lookup for a
|
||||
component will be done for this model. It also provides a shortcut
|
||||
as a `model` attribute to use directly with the Odoo model from
|
||||
the components
|
||||
|
||||
.. attribute:: collection
|
||||
|
||||
The collection we are working with. The collection is an Odoo
|
||||
Model that inherit from 'collection.base'. The collection attribute
|
||||
can be a record or an "empty" model.
|
||||
|
||||
.. attribute:: model
|
||||
|
||||
Odoo Model for ``model_name`` with the same Odoo
|
||||
:class:`~odoo.api.Environment` than the ``collection`` attribute.
|
||||
|
||||
This is also the entrypoint 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'
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, model_name=None, collection=None, components_registry=None, **kwargs
|
||||
):
|
||||
self.collection = collection
|
||||
self.model_name = model_name
|
||||
self.model = self.env[model_name]
|
||||
# lookup components in an alternative registry, used by the tests
|
||||
if components_registry is not None:
|
||||
self.components_registry = components_registry
|
||||
else:
|
||||
dbname = self.env.cr.dbname
|
||||
try:
|
||||
self.components_registry = _component_databases[dbname]
|
||||
except KeyError as exc:
|
||||
msg = (
|
||||
"No component registry for database %s. "
|
||||
"Probably because the Odoo registry has not been built "
|
||||
"yet."
|
||||
)
|
||||
_logger.error(
|
||||
msg,
|
||||
dbname,
|
||||
)
|
||||
raise RegistryNotReadyError(msg) from exc
|
||||
self._propagate_kwargs = ["collection", "model_name", "components_registry"]
|
||||
for attr_name, value in kwargs.items():
|
||||
setattr(self, attr_name, value)
|
||||
self._propagate_kwargs.append(attr_name)
|
||||
|
||||
@property
|
||||
def env(self):
|
||||
"""Return the current Odoo env
|
||||
|
||||
This is the environment of the current collection.
|
||||
"""
|
||||
return self.collection.env
|
||||
|
||||
def work_on(self, model_name=None, collection=None):
|
||||
"""Create a new work context for another model keeping attributes
|
||||
|
||||
Used when one need to lookup components for another model.
|
||||
"""
|
||||
kwargs = {
|
||||
attr_name: getattr(self, attr_name) for attr_name in self._propagate_kwargs
|
||||
}
|
||||
if collection is not None:
|
||||
kwargs["collection"] = collection
|
||||
if model_name is not None:
|
||||
kwargs["model_name"] = model_name
|
||||
return self.__class__(**kwargs)
|
||||
|
||||
def _component_class_by_name(self, name):
|
||||
components_registry = self.components_registry
|
||||
component_class = components_registry.get(name)
|
||||
if not component_class:
|
||||
raise NoComponentError("No component with name '%s' found." % name)
|
||||
return component_class
|
||||
|
||||
def component_by_name(self, name, model_name=None):
|
||||
"""Return a component by its name
|
||||
|
||||
If the component exists, an instance of it will be returned,
|
||||
initialized with the current :class:`WorkContext`.
|
||||
|
||||
A :exc:`odoo.addons.component.exception.NoComponentError` is raised
|
||||
if:
|
||||
|
||||
* no component with this name exists
|
||||
* the ``_apply_on`` of the found component does not match
|
||||
with the current working model
|
||||
|
||||
In the latter case, it can be an indication that you need to switch to
|
||||
a different model, you can do so by providing the ``model_name``
|
||||
argument.
|
||||
|
||||
"""
|
||||
if isinstance(model_name, models.BaseModel):
|
||||
model_name = model_name._name
|
||||
component_class = self._component_class_by_name(name)
|
||||
work_model = model_name or self.model_name
|
||||
if (
|
||||
component_class._collection
|
||||
and self.collection._name != component_class._collection
|
||||
):
|
||||
raise NoComponentError(
|
||||
"Component with name '%s' can't be used for collection '%s'."
|
||||
% (name, self.collection._name)
|
||||
)
|
||||
|
||||
if (
|
||||
component_class.apply_on_models
|
||||
and work_model not in component_class.apply_on_models
|
||||
):
|
||||
if len(component_class.apply_on_models) == 1:
|
||||
hint_models = "'{}'".format(component_class.apply_on_models[0])
|
||||
else:
|
||||
hint_models = "<one of {!r}>".format(component_class.apply_on_models)
|
||||
raise NoComponentError(
|
||||
"Component with name '%s' can't be used for model '%s'.\n"
|
||||
"Hint: you might want to use: "
|
||||
"component_by_name('%s', model_name=%s)"
|
||||
% (name, work_model, name, hint_models)
|
||||
)
|
||||
|
||||
if work_model == self.model_name:
|
||||
work_context = self
|
||||
else:
|
||||
work_context = self.work_on(model_name)
|
||||
return component_class(work_context)
|
||||
|
||||
def _lookup_components(self, usage=None, model_name=None, **kw):
|
||||
component_classes = self.components_registry.lookup(
|
||||
self.collection._name, usage=usage, model_name=model_name
|
||||
)
|
||||
matching_components = []
|
||||
for cls in component_classes:
|
||||
try:
|
||||
matching = cls._component_match(
|
||||
self, usage=usage, model_name=model_name, **kw
|
||||
)
|
||||
except TypeError as err:
|
||||
# Backward compat
|
||||
_logger.info(str(err))
|
||||
_logger.info(
|
||||
"The signature of %s._component_match has changed. "
|
||||
"Please, adapt your code as "
|
||||
"(self, usage=usage, model_name=model_name, **kw)",
|
||||
cls.__name__,
|
||||
)
|
||||
matching = cls._component_match(self)
|
||||
if matching:
|
||||
matching_components.append(cls)
|
||||
return matching_components
|
||||
|
||||
def _filter_components_by_collection(self, component_classes):
|
||||
return [c for c in component_classes if c._collection == self.collection._name]
|
||||
|
||||
def _filter_components_by_model(self, component_classes, model_name):
|
||||
return [
|
||||
c
|
||||
for c in component_classes
|
||||
if c.apply_on_models and model_name in c.apply_on_models
|
||||
]
|
||||
|
||||
def _ensure_model_name(self, model_name):
|
||||
"""Make sure model name is a string or fallback to current ctx value."""
|
||||
if isinstance(model_name, models.BaseModel):
|
||||
model_name = model_name._name
|
||||
return model_name or self.model_name
|
||||
|
||||
def _matching_components(self, usage=None, model_name=None, **kw):
|
||||
"""Retrieve matching components and their work context."""
|
||||
component_classes = self._lookup_components(
|
||||
usage=usage, model_name=model_name, **kw
|
||||
)
|
||||
if model_name == self.model_name:
|
||||
work_context = self
|
||||
else:
|
||||
work_context = self.work_on(model_name)
|
||||
return component_classes, work_context
|
||||
|
||||
def component(self, usage=None, model_name=None, **kw):
|
||||
"""Find a component by usage and model for the current collection
|
||||
|
||||
It searches a component using the rules of
|
||||
:meth:`ComponentRegistry.lookup`. When a component is found,
|
||||
it initialize it with the current :class:`WorkContext` and returned.
|
||||
|
||||
A component with a ``_apply_on`` matching the asked ``model_name``
|
||||
takes precedence over a generic component without ``_apply_on``.
|
||||
A component with a ``_collection`` matching the current collection
|
||||
takes precedence over a generic component without ``_collection``.
|
||||
This behavior allows to define generic components across collections
|
||||
and/or models and override them only for a particular collection and/or
|
||||
model.
|
||||
|
||||
A :exc:`odoo.addons.component.exception.SeveralComponentError` is
|
||||
raised if more than one component match for the provided
|
||||
``usage``/``model_name``.
|
||||
|
||||
A :exc:`odoo.addons.component.exception.NoComponentError` is raised
|
||||
if no component is found for the provided ``usage``/``model_name``.
|
||||
|
||||
"""
|
||||
model_name = self._ensure_model_name(model_name)
|
||||
component_classes, work_context = self._matching_components(
|
||||
usage=usage, model_name=model_name, **kw
|
||||
)
|
||||
if not component_classes:
|
||||
raise NoComponentError(
|
||||
"No component found for collection '%s', "
|
||||
"usage '%s', model_name '%s'."
|
||||
% (self.collection._name, usage, model_name)
|
||||
)
|
||||
elif len(component_classes) > 1:
|
||||
# If we have more than one component, try to find the one
|
||||
# specifically linked to the collection...
|
||||
component_classes = self._filter_components_by_collection(component_classes)
|
||||
if len(component_classes) > 1:
|
||||
# ... or try to find the one specifically linked to the model
|
||||
component_classes = self._filter_components_by_model(
|
||||
component_classes, model_name
|
||||
)
|
||||
if len(component_classes) != 1:
|
||||
raise SeveralComponentError(
|
||||
"Several components found for collection '%s', "
|
||||
"usage '%s', model_name '%s'. Found: %r"
|
||||
% (
|
||||
self.collection._name,
|
||||
usage or "",
|
||||
model_name or "",
|
||||
component_classes,
|
||||
)
|
||||
)
|
||||
return component_classes[0](work_context)
|
||||
|
||||
def many_components(self, usage=None, model_name=None, **kw):
|
||||
"""Find many components by usage and model for the current collection
|
||||
|
||||
It searches a component using the rules of
|
||||
:meth:`ComponentRegistry.lookup`. When components are found, they
|
||||
initialized with the current :class:`WorkContext` and returned as a
|
||||
list.
|
||||
|
||||
If no component is found, an empty list is returned.
|
||||
|
||||
"""
|
||||
model_name = self._ensure_model_name(model_name)
|
||||
component_classes, work_context = self._matching_components(
|
||||
usage=usage, model_name=model_name, **kw
|
||||
)
|
||||
return [comp(work_context) for comp in component_classes]
|
||||
|
||||
def __str__(self):
|
||||
return "WorkContext({}, {})".format(self.model_name, repr(self.collection))
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class MetaComponent(type):
|
||||
"""Metaclass for Components
|
||||
|
||||
Every new :class:`Component` will be added to ``_modules_components``,
|
||||
that will be used by the component builder.
|
||||
|
||||
"""
|
||||
|
||||
_modules_components = defaultdict(list)
|
||||
|
||||
def __init__(cls, name, bases, attrs):
|
||||
if not cls._register:
|
||||
cls._register = True
|
||||
super().__init__(name, bases, attrs)
|
||||
return
|
||||
|
||||
# If components are declared in tests, exclude them from the
|
||||
# "components of the addon" list. If not, when we use the
|
||||
# "load_components" method, all the test components would be loaded.
|
||||
# This should never be an issue when running the app normally, as the
|
||||
# Python tests should never be executed. But this is an issue when a
|
||||
# test creates a test components for the purpose of the test, then a
|
||||
# second tests uses the "load_components" to load all the addons of the
|
||||
# module: it will load the component of the previous test.
|
||||
if "tests" in cls.__module__.split("."):
|
||||
return
|
||||
|
||||
if not hasattr(cls, "_module"):
|
||||
cls._module = _get_addon_name(cls.__module__)
|
||||
|
||||
cls._modules_components[cls._module].append(cls)
|
||||
|
||||
@property
|
||||
def apply_on_models(cls):
|
||||
# None means all models
|
||||
if cls._apply_on is None:
|
||||
return None
|
||||
# always return a list, used for the lookup
|
||||
elif isinstance(cls._apply_on, str):
|
||||
return [cls._apply_on]
|
||||
return cls._apply_on
|
||||
|
||||
|
||||
class AbstractComponent(metaclass=MetaComponent):
|
||||
"""Main Component Model
|
||||
|
||||
All components have a Python inheritance either on
|
||||
:class:`AbstractComponent` or either on :class:`Component`.
|
||||
|
||||
Abstract Components will not be returned by lookups on components, however
|
||||
they can be used as a base for other Components through inheritance (using
|
||||
``_inherit``).
|
||||
|
||||
Inheritance mechanism
|
||||
The inheritance mechanism is like the Odoo's one for Models. Each
|
||||
component has a ``_name``. This is the absolute minimum in a Component
|
||||
class.
|
||||
|
||||
::
|
||||
|
||||
class MyComponent(Component):
|
||||
_name = 'my.component'
|
||||
|
||||
def speak(self, message):
|
||||
print message
|
||||
|
||||
Every component implicitly inherit from the `'base'` component.
|
||||
|
||||
There are two close but distinct inheritance types, which look
|
||||
familiar if you already know Odoo. The first uses ``_inherit`` with
|
||||
an existing name, the name of the component we want to extend. With
|
||||
the following example, ``my.component`` is now able to speak and to
|
||||
yell.
|
||||
|
||||
::
|
||||
|
||||
class MyComponent(Component): # name of the class does not matter
|
||||
_inherit = 'my.component'
|
||||
|
||||
def yell(self, message):
|
||||
print message.upper()
|
||||
|
||||
The second has a different ``_name``, it creates a new component,
|
||||
including the behavior of the inherited component, but without
|
||||
modifying it. In the following example, ``my.component`` is still able
|
||||
to speak and to yell (brough by the previous inherit), but not to
|
||||
sing. ``another.component`` is able to speak, to yell and to sing.
|
||||
|
||||
::
|
||||
|
||||
class AnotherComponent(Component):
|
||||
_name = 'another.component'
|
||||
_inherit = 'my.component'
|
||||
|
||||
def sing(self, message):
|
||||
print message.upper()
|
||||
|
||||
Registration and lookups
|
||||
It is handled by 3 attributes on the class:
|
||||
|
||||
_collection
|
||||
The name of the collection where we want to register the
|
||||
component. This is not strictly mandatory as a component can be
|
||||
shared across several collections. But usually, you want to set a
|
||||
collection to segregate the components for a domain. A collection
|
||||
can be for instance ``magento.backend``. It is also the name of a
|
||||
model that inherits from ``collection.base``. See also
|
||||
:class:`~WorkContext` and
|
||||
:class:`~odoo.addons.component.models.collection.Collection`.
|
||||
|
||||
_apply_on
|
||||
List of names or name of the Odoo model(s) for which the component
|
||||
can be used. When not set, the component can be used on any model.
|
||||
|
||||
_usage
|
||||
The collection and the model (``_apply_on``) will help to filter
|
||||
the candidate components according to our working context (e.g. I'm
|
||||
working on ``magento.backend`` with the model
|
||||
``magento.res.partner``). The usage will define **what** kind of
|
||||
task the component we are looking for serves to. For instance, it
|
||||
might be ``record.importer``, ``export.mapper```... but you can be
|
||||
as creative as you want.
|
||||
|
||||
Now, to get a component, you'll likely use
|
||||
:meth:`WorkContext.component` when you start to work with components
|
||||
in your flow, but then from within your components, you are more
|
||||
likely to use one of:
|
||||
|
||||
* :meth:`component`
|
||||
* :meth:`many_components`
|
||||
* :meth:`component_by_name` (more rarely though)
|
||||
|
||||
Declaration of some Components can look like::
|
||||
|
||||
class FooBar(models.Model):
|
||||
_name = 'foo.bar.collection'
|
||||
_inherit = 'collection.base' # this inherit is required
|
||||
|
||||
|
||||
class FooBarBase(AbstractComponent):
|
||||
_name = 'foo.bar.base'
|
||||
_collection = 'foo.bar.collection' # name of the model above
|
||||
|
||||
|
||||
class Foo(Component):
|
||||
_name = 'foo'
|
||||
_inherit = 'foo.bar.base' # we will inherit the _collection
|
||||
_apply_on = 'res.users'
|
||||
_usage = 'speak'
|
||||
|
||||
def utter(self, message):
|
||||
print message
|
||||
|
||||
|
||||
class Bar(Component):
|
||||
_name = 'bar'
|
||||
_inherit = 'foo.bar.base' # we will inherit the _collection
|
||||
_apply_on = 'res.users'
|
||||
_usage = 'yell'
|
||||
|
||||
def utter(self, message):
|
||||
print message.upper() + '!!!'
|
||||
|
||||
|
||||
class Vocalizer(Component):
|
||||
_name = 'vocalizer'
|
||||
_inherit = 'foo.bar.base'
|
||||
_usage = 'vocalizer'
|
||||
# can be used for any model
|
||||
|
||||
def vocalize(action, message):
|
||||
self.component(usage=action).utter(message)
|
||||
|
||||
|
||||
And their usage::
|
||||
|
||||
>>> coll = self.env['foo.bar.collection'].browse(1)
|
||||
>>> with coll.work_on('res.users') as work:
|
||||
... vocalizer = work.component(usage='vocalizer')
|
||||
... vocalizer.vocalize('speak', 'hello world')
|
||||
...
|
||||
hello world
|
||||
... vocalizer.vocalize('yell', 'hello world')
|
||||
HELLO WORLD!!!
|
||||
|
||||
Hints:
|
||||
|
||||
* If you want to create components without ``_apply_on``, choose a
|
||||
``_usage`` that will not conflict other existing components.
|
||||
* Unless this is what you want and in that case you use
|
||||
:meth:`many_components` which will return all components for a usage
|
||||
with a matching or a not set ``_apply_on``.
|
||||
* It is advised to namespace the names of the components (e.g.
|
||||
``magento.xxx``) to prevent conflicts between addons.
|
||||
|
||||
"""
|
||||
|
||||
_register = False
|
||||
_abstract = True
|
||||
|
||||
# used for inheritance
|
||||
_name = None #: Name of the component
|
||||
|
||||
#: Name or list of names of the component(s) to inherit from
|
||||
_inherit = None
|
||||
|
||||
#: name of the collection to subscribe in
|
||||
_collection = None
|
||||
|
||||
#: List of models on which the component can be applied.
|
||||
#: None means any Model, can be a list ['res.users', ...]
|
||||
_apply_on = None
|
||||
|
||||
#: Component purpose ('import.mapper', ...).
|
||||
_usage = None
|
||||
|
||||
def __init__(self, work_context):
|
||||
super().__init__()
|
||||
self.work = work_context
|
||||
|
||||
@classmethod
|
||||
def _component_match(cls, work, usage=None, model_name=None, **kw):
|
||||
"""Evaluated on candidate components
|
||||
|
||||
When a component lookup is done and candidate(s) have
|
||||
been found for a usage, a final call is done on this method.
|
||||
If the method return False, the candidate component is ignored.
|
||||
|
||||
It can be used for instance to dynamically choose a component
|
||||
according to a value in the :class:`WorkContext`.
|
||||
|
||||
Beware, if the lookups from usage, model and collection are
|
||||
cached, the calls to :meth:`_component_match` are executed
|
||||
each time we get components. Heavy computation should be
|
||||
avoided.
|
||||
|
||||
:param work: the :class:`WorkContext` we are working with
|
||||
|
||||
"""
|
||||
return True
|
||||
|
||||
@property
|
||||
def collection(self):
|
||||
"""Collection we are working with"""
|
||||
return self.work.collection
|
||||
|
||||
@property
|
||||
def env(self):
|
||||
"""Current Odoo environment, the one of the collection record"""
|
||||
return self.work.env
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
"""The model instance we are working with"""
|
||||
return self.work.model
|
||||
|
||||
def component_by_name(self, name, model_name=None):
|
||||
"""Return a component by its name
|
||||
|
||||
Shortcut to meth:`~WorkContext.component_by_name`
|
||||
"""
|
||||
return self.work.component_by_name(name, model_name=model_name)
|
||||
|
||||
def component(self, usage=None, model_name=None, **kw):
|
||||
"""Return a component
|
||||
|
||||
Shortcut to meth:`~WorkContext.component`
|
||||
"""
|
||||
return self.work.component(usage=usage, model_name=model_name, **kw)
|
||||
|
||||
def many_components(self, usage=None, model_name=None, **kw):
|
||||
"""Return several components
|
||||
|
||||
Shortcut to meth:`~WorkContext.many_components`
|
||||
"""
|
||||
return self.work.many_components(usage=usage, model_name=model_name, **kw)
|
||||
|
||||
def __str__(self):
|
||||
return "Component(%s)" % self._name
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
@classmethod
|
||||
def _build_component(cls, registry):
|
||||
"""Instantiate a given Component in the components registry.
|
||||
|
||||
This method is called at the end of the Odoo's registry build. The
|
||||
caller is :meth:`component.builder.ComponentBuilder.load_components`.
|
||||
|
||||
It generates new classes, which will be the Component classes we will
|
||||
be using. The new classes are generated following the inheritance
|
||||
of ``_inherit``. It ensures that the ``__bases__`` of the generated
|
||||
Component classes follow the ``_inherit`` chain.
|
||||
|
||||
Once a Component class is created, it adds it in the Component Registry
|
||||
(:class:`ComponentRegistry`), so it will be available for
|
||||
lookups.
|
||||
|
||||
At the end of new class creation, a hook method
|
||||
:meth:`_complete_component_build` is called, so you can customize
|
||||
further the created components. An example can be found in
|
||||
:meth:`odoo.addons.connector.components.mapper.Mapper._complete_component_build`
|
||||
|
||||
The following code is roughly the same than the Odoo's one for
|
||||
building Models.
|
||||
|
||||
"""
|
||||
|
||||
# In the simplest case, the component's registry class inherits from
|
||||
# cls and the other classes that define the component in a flat
|
||||
# hierarchy. The registry contains the instance ``component`` (on the
|
||||
# left). Its class, ``ComponentClass``, carries inferred metadata that
|
||||
# is shared between all the component's instances for this registry
|
||||
# only.
|
||||
#
|
||||
# class A1(Component): Component
|
||||
# _name = 'a' / | \
|
||||
# A3 A2 A1
|
||||
# class A2(Component): \ | /
|
||||
# _inherit = 'a' ComponentClass
|
||||
#
|
||||
# class A3(Component):
|
||||
# _inherit = 'a'
|
||||
#
|
||||
# When a component is extended by '_inherit', its base classes are
|
||||
# modified to include the current class and the other inherited
|
||||
# component classes.
|
||||
# Note that we actually inherit from other ``ComponentClass``, so that
|
||||
# extensions to an inherited component are immediately visible in the
|
||||
# current component class, like in the following example:
|
||||
#
|
||||
# class A1(Component):
|
||||
# _name = 'a' Component
|
||||
# / / \ \
|
||||
# class B1(Component): / A2 A1 \
|
||||
# _name = 'b' / \ / \
|
||||
# B2 ComponentA B1
|
||||
# class B2(Component): \ | /
|
||||
# _name = 'b' \ | /
|
||||
# _inherit = ['b', 'a'] \ | /
|
||||
# ComponentB
|
||||
# class A2(Component):
|
||||
# _inherit = 'a'
|
||||
|
||||
# determine inherited components
|
||||
parents = cls._inherit
|
||||
if isinstance(parents, str):
|
||||
parents = [parents]
|
||||
elif parents is None:
|
||||
parents = []
|
||||
|
||||
if cls._name in registry and not parents:
|
||||
raise TypeError(
|
||||
"Component %r (in class %r) already exists. "
|
||||
"Consider using _inherit instead of _name "
|
||||
"or using a different _name." % (cls._name, cls)
|
||||
)
|
||||
|
||||
# determine the component's name
|
||||
name = cls._name or (len(parents) == 1 and parents[0])
|
||||
|
||||
if not name:
|
||||
raise TypeError("Component %r must have a _name" % cls)
|
||||
|
||||
# all components except 'base' implicitly inherit from 'base'
|
||||
if name != "base":
|
||||
parents = list(parents) + ["base"]
|
||||
|
||||
# create or retrieve the component's class
|
||||
if name in parents:
|
||||
if name not in registry:
|
||||
raise TypeError("Component %r does not exist in registry." % name)
|
||||
ComponentClass = registry[name]
|
||||
ComponentClass._build_component_check_base(cls)
|
||||
check_parent = ComponentClass._build_component_check_parent
|
||||
else:
|
||||
ComponentClass = type(
|
||||
name,
|
||||
(AbstractComponent,),
|
||||
{
|
||||
"_name": name,
|
||||
"_register": False,
|
||||
# names of children component
|
||||
"_inherit_children": OrderedSet(),
|
||||
},
|
||||
)
|
||||
check_parent = cls._build_component_check_parent
|
||||
|
||||
# determine all the classes the component should inherit from
|
||||
bases = LastOrderedSet([cls])
|
||||
for parent in parents:
|
||||
if parent not in registry:
|
||||
raise TypeError(
|
||||
"Component %r inherits from non-existing component %r."
|
||||
% (name, parent)
|
||||
)
|
||||
parent_class = registry[parent]
|
||||
if parent == name:
|
||||
for base in parent_class.__bases__:
|
||||
bases.add(base)
|
||||
else:
|
||||
check_parent(cls, parent_class)
|
||||
bases.add(parent_class)
|
||||
parent_class._inherit_children.add(name)
|
||||
ComponentClass.__bases__ = tuple(bases)
|
||||
|
||||
ComponentClass._complete_component_build()
|
||||
|
||||
registry[name] = ComponentClass
|
||||
|
||||
return ComponentClass
|
||||
|
||||
@classmethod
|
||||
def _build_component_check_base(cls, extend_cls):
|
||||
"""Check whether ``cls`` can be extended with ``extend_cls``."""
|
||||
if cls._abstract and not extend_cls._abstract:
|
||||
msg = (
|
||||
"%s transforms the abstract component %r into a "
|
||||
"non-abstract component. "
|
||||
"That class should either inherit from AbstractComponent, "
|
||||
"or set a different '_name'."
|
||||
)
|
||||
raise TypeError(msg % (extend_cls, cls._name))
|
||||
|
||||
@classmethod
|
||||
def _build_component_check_parent(component_class, cls, parent_class): # noqa: B902
|
||||
"""Check whether ``model_class`` can inherit from ``parent_class``."""
|
||||
if component_class._abstract and not parent_class._abstract:
|
||||
msg = (
|
||||
"In %s, the abstract Component %r cannot inherit "
|
||||
"from the non-abstract Component %r."
|
||||
)
|
||||
raise TypeError(msg % (cls, component_class._name, parent_class._name))
|
||||
|
||||
@classmethod
|
||||
def _complete_component_build(cls):
|
||||
"""Complete build of the new component class
|
||||
|
||||
After the component has been built from its bases, this method is
|
||||
called, and can be used to customize the class before it can be used.
|
||||
|
||||
Nothing is done in the base Component, but a Component can inherit
|
||||
the method to add its own behavior.
|
||||
"""
|
||||
|
||||
|
||||
class Component(AbstractComponent):
|
||||
"""Concrete Component class
|
||||
|
||||
This is the class you inherit from when you want your component to
|
||||
be registered in the component collections.
|
||||
|
||||
Look in :class:`AbstractComponent` for more details.
|
||||
|
||||
"""
|
||||
|
||||
_register = False
|
||||
_abstract = False
|
||||
18
odoo-bringout-oca-connector-component/component/exception.py
Normal file
18
odoo-bringout-oca-connector-component/component/exception.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
|
||||
class ComponentException(Exception):
|
||||
"""Base Exception for the components"""
|
||||
|
||||
|
||||
class NoComponentError(ComponentException):
|
||||
"""No component has been found"""
|
||||
|
||||
|
||||
class SeveralComponentError(ComponentException):
|
||||
"""More than one component have been found"""
|
||||
|
||||
|
||||
class RegistryNotReadyError(ComponentException):
|
||||
"""Component registry not ready yet for given DB."""
|
||||
32
odoo-bringout-oca-connector-component/component/i18n/am.po
Normal file
32
odoo-bringout-oca-connector-component/component/i18n/am.po
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2018-01-05 16:56+0000\n"
|
||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2018\n"
|
||||
"Language-Team: Amharic (https://www.transifex.com/oca/teams/23907/am/)\n"
|
||||
"Language: am\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr ""
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
24
odoo-bringout-oca-connector-component/component/i18n/bs.po
Normal file
24
odoo-bringout-oca-connector-component/component/i18n/bs.po
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr "Osnovna apstraktna kolekcija"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr "Kreator komponenti"
|
||||
32
odoo-bringout-oca-connector-component/component/i18n/ca.po
Normal file
32
odoo-bringout-oca-connector-component/component/i18n/ca.po
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2018-01-05 16:56+0000\n"
|
||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2018\n"
|
||||
"Language-Team: Catalan (https://www.transifex.com/oca/teams/23907/ca/)\n"
|
||||
"Language: ca\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr ""
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr ""
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr ""
|
||||
39
odoo-bringout-oca-connector-component/component/i18n/de.po
Normal file
39
odoo-bringout-oca-connector-component/component/i18n/de.po
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2025-03-17 14:06+0000\n"
|
||||
"Last-Translator: davidbeckercbl <becker@cbl-computer.de>\n"
|
||||
"Language-Team: German (https://www.transifex.com/oca/teams/23907/de/)\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.10.2\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr "Basis Abstrakte Sammlung"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr "Komponentenerzeuger"
|
||||
|
||||
#~ msgid "Display Name"
|
||||
#~ msgstr "Anzeigebezeichnung"
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
||||
#~ msgid "Last Modified on"
|
||||
#~ msgstr "Zuletzt aktualisiert am"
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2018-01-05 16:56+0000\n"
|
||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2018\n"
|
||||
"Language-Team: Greek (Greece) (https://www.transifex.com/oca/teams/23907/"
|
||||
"el_GR/)\n"
|
||||
"Language: el_GR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr ""
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "Κωδικός"
|
||||
39
odoo-bringout-oca-connector-component/component/i18n/es.po
Normal file
39
odoo-bringout-oca-connector-component/component/i18n/es.po
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2023-08-02 13:09+0000\n"
|
||||
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
|
||||
"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.17\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr "Colección abstracta de base"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr "Constructor de componentes"
|
||||
|
||||
#~ msgid "Display Name"
|
||||
#~ msgstr "Nombre mostrado"
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
||||
#~ msgid "Last Modified on"
|
||||
#~ msgstr "Última modificación el"
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2018-01-05 16:56+0000\n"
|
||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2018\n"
|
||||
"Language-Team: Spanish (Spain) (https://www.transifex.com/oca/teams/23907/"
|
||||
"es_ES/)\n"
|
||||
"Language: es_ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr ""
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
38
odoo-bringout-oca-connector-component/component/i18n/fi.po
Normal file
38
odoo-bringout-oca-connector-component/component/i18n/fi.po
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2018-01-05 16:56+0000\n"
|
||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2018\n"
|
||||
"Language-Team: Finnish (https://www.transifex.com/oca/teams/23907/fi/)\n"
|
||||
"Language: fi\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr ""
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Display Name"
|
||||
#~ msgstr "Nimi"
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
||||
#~ msgid "Last Modified on"
|
||||
#~ msgstr "Viimeksi muokattu"
|
||||
40
odoo-bringout-oca-connector-component/component/i18n/fr.po
Normal file
40
odoo-bringout-oca-connector-component/component/i18n/fr.po
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
# Nicolas JEUDY <njeudy@panda-chi.io>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-02-01 01:48+0000\n"
|
||||
"PO-Revision-Date: 2018-06-28 07:13+0000\n"
|
||||
"Last-Translator: Guewen Baconnier <guewen.baconnier@camptocamp.com>\n"
|
||||
"Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 3.0.1\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr "Abstract Model inital pour une collection"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr "Constructeur de composants"
|
||||
|
||||
#~ msgid "Display Name"
|
||||
#~ msgstr "Nom affiché"
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
||||
#~ msgid "Last Modified on"
|
||||
#~ msgstr "Dernière modification le"
|
||||
32
odoo-bringout-oca-connector-component/component/i18n/gl.po
Normal file
32
odoo-bringout-oca-connector-component/component/i18n/gl.po
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2018-01-05 16:56+0000\n"
|
||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2018\n"
|
||||
"Language-Team: Galician (https://www.transifex.com/oca/teams/23907/gl/)\n"
|
||||
"Language: gl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr ""
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
39
odoo-bringout-oca-connector-component/component/i18n/it.po
Normal file
39
odoo-bringout-oca-connector-component/component/i18n/it.po
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2024-02-26 09:38+0000\n"
|
||||
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
|
||||
"Language-Team: Italian (https://www.transifex.com/oca/teams/23907/it/)\n"
|
||||
"Language: it\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.17\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr "Raccolta astratta base"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr "Costruttore componente"
|
||||
|
||||
#~ msgid "Display Name"
|
||||
#~ msgstr "Nome da visualizzare"
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
||||
#~ msgid "Last Modified on"
|
||||
#~ msgstr "Ultima modifica il"
|
||||
32
odoo-bringout-oca-connector-component/component/i18n/pt.po
Normal file
32
odoo-bringout-oca-connector-component/component/i18n/pt.po
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2018-01-05 16:56+0000\n"
|
||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2018\n"
|
||||
"Language-Team: Portuguese (https://www.transifex.com/oca/teams/23907/pt/)\n"
|
||||
"Language: pt\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr ""
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2020-08-12 20:00+0000\n"
|
||||
"Last-Translator: Rodrigo Macedo <rmsolucoeseminformatic4@gmail.com>\n"
|
||||
"Language-Team: Portuguese (Brazil) (https://www.transifex.com/oca/"
|
||||
"teams/23907/pt_BR/)\n"
|
||||
"Language: pt_BR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 3.10\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr "Coleção Base Abstrata"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr "Construtor de Componentes"
|
||||
|
||||
#~ msgid "Display Name"
|
||||
#~ msgstr "Exibir Nome"
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
||||
#~ msgid "Last Modified on"
|
||||
#~ msgstr "Última modificação em"
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2018-01-05 16:56+0000\n"
|
||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2018\n"
|
||||
"Language-Team: Portuguese (Portugal) (https://www.transifex.com/oca/"
|
||||
"teams/23907/pt_PT/)\n"
|
||||
"Language: pt_PT\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr ""
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
39
odoo-bringout-oca-connector-component/component/i18n/sl.po
Normal file
39
odoo-bringout-oca-connector-component/component/i18n/sl.po
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2018-01-05 16:56+0000\n"
|
||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2018\n"
|
||||
"Language-Team: Slovenian (https://www.transifex.com/oca/teams/23907/sl/)\n"
|
||||
"Language: sl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n"
|
||||
"%100==4 ? 2 : 3);\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr ""
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Display Name"
|
||||
#~ msgstr "Prikazni naziv"
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
||||
#~ msgid "Last Modified on"
|
||||
#~ msgstr "Zadnjič spremenjeno"
|
||||
32
odoo-bringout-oca-connector-component/component/i18n/tr.po
Normal file
32
odoo-bringout-oca-connector-component/component/i18n/tr.po
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
# Translators:
|
||||
# OCA Transbot <transbot@odoo-community.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-05 16:56+0000\n"
|
||||
"PO-Revision-Date: 2018-01-05 16:56+0000\n"
|
||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2018\n"
|
||||
"Language-Team: Turkish (https://www.transifex.com/oca/teams/23907/tr/)\n"
|
||||
"Language: tr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr ""
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * component
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 13.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2019-09-01 06:14+0000\n"
|
||||
"Last-Translator: 黎伟杰 <674416404@qq.com>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 3.8\n"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_collection_base
|
||||
msgid "Base Abstract Collection"
|
||||
msgstr "基础抽象集合"
|
||||
|
||||
#. module: component
|
||||
#: model:ir.model,name:component.model_component_builder
|
||||
msgid "Component Builder"
|
||||
msgstr "组件构建器"
|
||||
|
||||
#~ msgid "Display Name"
|
||||
#~ msgstr "显示名称"
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
||||
#~ msgid "Last Modified on"
|
||||
#~ msgstr "最后修改时间"
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import collection
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
"""
|
||||
|
||||
Collection Model
|
||||
================
|
||||
|
||||
This is the base Model shared by all the Collections.
|
||||
In the context of the Connector, a collection is the Backend.
|
||||
The `_name` given to the Collection Model will be the name
|
||||
to use in the `_collection` of the Components usable for the Backend.
|
||||
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from odoo import models
|
||||
|
||||
from ..core import WorkContext
|
||||
|
||||
|
||||
class Collection(models.AbstractModel):
|
||||
"""The model on which components are subscribed
|
||||
|
||||
It would be for instance the ``backend`` for the connectors.
|
||||
|
||||
Example::
|
||||
|
||||
class MagentoBackend(models.Model):
|
||||
_name = 'magento.backend' # name of the collection
|
||||
_inherit = 'collection.base'
|
||||
|
||||
|
||||
class MagentoSaleImporter(Component):
|
||||
_name = 'magento.sale.importer'
|
||||
_apply_on = 'magento.sale.order'
|
||||
_collection = 'magento.backend' # name of the collection
|
||||
|
||||
def run(self, magento_id):
|
||||
mapper = self.component(usage='import.mapper')
|
||||
extra_mappers = self.many_components(
|
||||
usage='import.mapper.extra',
|
||||
)
|
||||
# ...
|
||||
|
||||
Use it::
|
||||
|
||||
>>> backend = self.env['magento.backend'].browse(1)
|
||||
>>> with backend.work_on('magento.sale.order') as work:
|
||||
... importer = work.component(usage='magento.sale.importer')
|
||||
... importer.run(1)
|
||||
|
||||
See also: :class:`odoo.addons.component.core.WorkContext`
|
||||
|
||||
|
||||
"""
|
||||
|
||||
_name = "collection.base"
|
||||
_description = "Base Abstract Collection"
|
||||
|
||||
@contextmanager
|
||||
def work_on(self, model_name, **kwargs):
|
||||
"""Entry-point for the components, context manager
|
||||
|
||||
Start a work using the components on the model.
|
||||
Any keyword argument will be assigned to the work context.
|
||||
See documentation of :class:`odoo.addons.component.core.WorkContext`.
|
||||
|
||||
It is a context manager, so you can attach objects and clean them
|
||||
at the end of the work session, such as::
|
||||
|
||||
@contextmanager
|
||||
def work_on(self, model_name, **kwargs):
|
||||
self.ensure_one()
|
||||
magento_location = MagentoLocation(
|
||||
self.location,
|
||||
self.username,
|
||||
self.password,
|
||||
)
|
||||
# We create a Magento Client API here, so we can create the
|
||||
# client once (lazily on the first use) and propagate it
|
||||
# through all the sync session, instead of recreating a client
|
||||
# in each backend adapter usage.
|
||||
with MagentoAPI(magento_location) as magento_api:
|
||||
_super = super(MagentoBackend, self)
|
||||
# from the components we'll be able to do:
|
||||
# self.work.magento_api
|
||||
with _super.work_on(
|
||||
model_name, magento_api=magento_api, **kwargs
|
||||
) as work:
|
||||
yield work
|
||||
|
||||
"""
|
||||
self.ensure_one()
|
||||
# Allow propagation of custom component registry via context
|
||||
# TODO: maybe to be moved to `WorkContext.__init__`
|
||||
components_registry = self.env.context.get("components_registry")
|
||||
if components_registry:
|
||||
kwargs["components_registry"] = components_registry
|
||||
yield WorkContext(model_name=model_name, collection=self, **kwargs)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
||||
* Laurent Mignon <laurent.mignon@acsone.eu>
|
||||
* Simone Orsi <simone.orsi@camptocamp.com>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
This module implements a component system and is a base block for the Connector
|
||||
Framework. It can be used without using the full Connector though.
|
||||
|
||||
Documentation: http://odoo-connector.com/
|
||||
|
||||
You may also want to check the `Introduction to Odoo Components`_ by @guewen.
|
||||
|
||||
.. _Introduction to Odoo Components: https://dev.to/guewen/introduction-to-odoo-components-bn0
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
.. [ The change log. The goal of this file is to help readers
|
||||
understand changes between version. The primary audience is
|
||||
end users and integrators. Purely technical changes such as
|
||||
code refactoring must not be mentioned here.
|
||||
|
||||
This file may contain ONE level of section titles, underlined
|
||||
with the ~ (tilde) character. Other section markers are
|
||||
forbidden and will likely break the structure of the README.rst
|
||||
or other documents where this fragment is included. ]
|
||||
|
||||
16.0.1.0.0 (2022-10-04)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [MIGRATION] from 15.0
|
||||
|
||||
15.0.1.0.0 (2021-11-25)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [MIGRATION] from 14.0
|
||||
|
||||
14.0.1.0.0 (2020-10-22)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [MIGRATION] from 13.0
|
||||
|
||||
13.0.1.0.0 (2019-10-23)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [MIGRATION] from 12.0
|
||||
|
||||
12.0.1.0.0 (2018-10-02)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [MIGRATION] from 11.0 branched at rev. 324e006
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
As a developer, you have access to a component system. You can find the
|
||||
documentation in the code or on http://odoo-connector.com
|
||||
|
||||
In a nutshell, you can create components::
|
||||
|
||||
|
||||
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']
|
||||
|
||||
And later, find the component you need at runtime (dynamic dispatch at
|
||||
component level)::
|
||||
|
||||
def run(self, external_id):
|
||||
backend_adapter = self.component(usage='backend.adapter')
|
||||
external_data = backend_adapter.read(external_id)
|
||||
|
||||
|
||||
In order for tests using components to work, you will need to use the base
|
||||
class provided by `odoo.addons.component.tests.common`:
|
||||
|
||||
* `TransactionComponentCase`
|
||||
|
||||
There are also some specific base classes for testing the component registry,
|
||||
using the ComponentRegistryCase as a base class. See the docstrings in
|
||||
`tests/common.py`.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
|
|
@ -0,0 +1,511 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
|
||||
<title>Components</title>
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
|
||||
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
|
||||
customize this style sheet.
|
||||
*/
|
||||
|
||||
/* used to remove borders from tables and images */
|
||||
.borderless, table.borderless td, table.borderless th {
|
||||
border: 0 }
|
||||
|
||||
table.borderless td, table.borderless th {
|
||||
/* Override padding for "table.docutils td" with "! important".
|
||||
The right padding separates the table cells. */
|
||||
padding: 0 0.5em 0 0 ! important }
|
||||
|
||||
.first {
|
||||
/* Override more specific margin styles with "! important". */
|
||||
margin-top: 0 ! important }
|
||||
|
||||
.last, .with-subtitle {
|
||||
margin-bottom: 0 ! important }
|
||||
|
||||
.hidden {
|
||||
display: none }
|
||||
|
||||
.subscript {
|
||||
vertical-align: sub;
|
||||
font-size: smaller }
|
||||
|
||||
.superscript {
|
||||
vertical-align: super;
|
||||
font-size: smaller }
|
||||
|
||||
a.toc-backref {
|
||||
text-decoration: none ;
|
||||
color: black }
|
||||
|
||||
blockquote.epigraph {
|
||||
margin: 2em 5em ; }
|
||||
|
||||
dl.docutils dd {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||
dl.docutils dt {
|
||||
font-weight: bold }
|
||||
*/
|
||||
|
||||
div.abstract {
|
||||
margin: 2em 5em }
|
||||
|
||||
div.abstract p.topic-title {
|
||||
font-weight: bold ;
|
||||
text-align: center }
|
||||
|
||||
div.admonition, div.attention, div.caution, div.danger, div.error,
|
||||
div.hint, div.important, div.note, div.tip, div.warning {
|
||||
margin: 2em ;
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.admonition p.admonition-title, div.hint p.admonition-title,
|
||||
div.important p.admonition-title, div.note p.admonition-title,
|
||||
div.tip p.admonition-title {
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
div.attention p.admonition-title, div.caution p.admonition-title,
|
||||
div.danger p.admonition-title, div.error p.admonition-title,
|
||||
div.warning p.admonition-title, .code .error {
|
||||
color: red ;
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||
compound paragraphs.
|
||||
div.compound .compound-first, div.compound .compound-middle {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
div.compound .compound-last, div.compound .compound-middle {
|
||||
margin-top: 0.5em }
|
||||
*/
|
||||
|
||||
div.dedication {
|
||||
margin: 2em 5em ;
|
||||
text-align: center ;
|
||||
font-style: italic }
|
||||
|
||||
div.dedication p.topic-title {
|
||||
font-weight: bold ;
|
||||
font-style: normal }
|
||||
|
||||
div.figure {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
div.footer, div.header {
|
||||
clear: both;
|
||||
font-size: smaller }
|
||||
|
||||
div.line-block {
|
||||
display: block ;
|
||||
margin-top: 1em ;
|
||||
margin-bottom: 1em }
|
||||
|
||||
div.line-block div.line-block {
|
||||
margin-top: 0 ;
|
||||
margin-bottom: 0 ;
|
||||
margin-left: 1.5em }
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em ;
|
||||
border: medium outset ;
|
||||
padding: 1em ;
|
||||
background-color: #ffffee ;
|
||||
width: 40% ;
|
||||
float: right ;
|
||||
clear: right }
|
||||
|
||||
div.sidebar p.rubric {
|
||||
font-family: sans-serif ;
|
||||
font-size: medium }
|
||||
|
||||
div.system-messages {
|
||||
margin: 5em }
|
||||
|
||||
div.system-messages h1 {
|
||||
color: red }
|
||||
|
||||
div.system-message {
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.system-message p.system-message-title {
|
||||
color: red ;
|
||||
font-weight: bold }
|
||||
|
||||
div.topic {
|
||||
margin: 2em }
|
||||
|
||||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
|
||||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
|
||||
margin-top: 0.4em }
|
||||
|
||||
h1.title {
|
||||
text-align: center }
|
||||
|
||||
h2.subtitle {
|
||||
text-align: center }
|
||||
|
||||
hr.docutils {
|
||||
width: 75% }
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left, table.align-left {
|
||||
clear: left ;
|
||||
float: left ;
|
||||
margin-right: 1em }
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right, table.align-right {
|
||||
clear: right ;
|
||||
float: right ;
|
||||
margin-left: 1em }
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table.align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left }
|
||||
|
||||
.align-center {
|
||||
clear: both ;
|
||||
text-align: center }
|
||||
|
||||
.align-right {
|
||||
text-align: right }
|
||||
|
||||
/* reset inner alignment in figures */
|
||||
div.align-right {
|
||||
text-align: inherit }
|
||||
|
||||
/* div.align-center * { */
|
||||
/* text-align: left } */
|
||||
|
||||
.align-top {
|
||||
vertical-align: top }
|
||||
|
||||
.align-middle {
|
||||
vertical-align: middle }
|
||||
|
||||
.align-bottom {
|
||||
vertical-align: bottom }
|
||||
|
||||
ol.simple, ul.simple {
|
||||
margin-bottom: 1em }
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal }
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha }
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha }
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman }
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman }
|
||||
|
||||
p.attribution {
|
||||
text-align: right ;
|
||||
margin-left: 50% }
|
||||
|
||||
p.caption {
|
||||
font-style: italic }
|
||||
|
||||
p.credits {
|
||||
font-style: italic ;
|
||||
font-size: smaller }
|
||||
|
||||
p.label {
|
||||
white-space: nowrap }
|
||||
|
||||
p.rubric {
|
||||
font-weight: bold ;
|
||||
font-size: larger ;
|
||||
color: maroon ;
|
||||
text-align: center }
|
||||
|
||||
p.sidebar-title {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold ;
|
||||
font-size: larger }
|
||||
|
||||
p.sidebar-subtitle {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
p.topic-title {
|
||||
font-weight: bold }
|
||||
|
||||
pre.address {
|
||||
margin-bottom: 0 ;
|
||||
margin-top: 0 ;
|
||||
font: inherit }
|
||||
|
||||
pre.literal-block, pre.doctest-block, pre.math, pre.code {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
pre.code .ln { color: grey; } /* line numbers */
|
||||
pre.code, code { background-color: #eeeeee }
|
||||
pre.code .comment, code .comment { color: #5C6576 }
|
||||
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
|
||||
pre.code .literal.string, code .literal.string { color: #0C5404 }
|
||||
pre.code .name.builtin, code .name.builtin { color: #352B84 }
|
||||
pre.code .deleted, code .deleted { background-color: #DEB0A1}
|
||||
pre.code .inserted, code .inserted { background-color: #A3D289}
|
||||
|
||||
span.classifier {
|
||||
font-family: sans-serif ;
|
||||
font-style: oblique }
|
||||
|
||||
span.classifier-delimiter {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
span.interpreted {
|
||||
font-family: sans-serif }
|
||||
|
||||
span.option {
|
||||
white-space: nowrap }
|
||||
|
||||
span.pre {
|
||||
white-space: pre }
|
||||
|
||||
span.problematic {
|
||||
color: red }
|
||||
|
||||
span.section-subtitle {
|
||||
/* font-size relative to parent (h1..h6 element) */
|
||||
font-size: 80% }
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docinfo {
|
||||
margin: 2em 4em }
|
||||
|
||||
table.docutils {
|
||||
margin-top: 0.5em ;
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
table.footnote {
|
||||
border-left: solid 1px black;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docutils td, table.docutils th,
|
||||
table.docinfo td, table.docinfo th {
|
||||
padding-left: 0.5em ;
|
||||
padding-right: 0.5em ;
|
||||
vertical-align: top }
|
||||
|
||||
table.docutils th.field-name, table.docinfo th.docinfo-name {
|
||||
font-weight: bold ;
|
||||
text-align: left ;
|
||||
white-space: nowrap ;
|
||||
padding-left: 0 }
|
||||
|
||||
/* "booktabs" style (no vertical lines) */
|
||||
table.docutils.booktabs {
|
||||
border: 0px;
|
||||
border-top: 2px solid;
|
||||
border-bottom: 2px solid;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.docutils.booktabs * {
|
||||
border: 0px;
|
||||
}
|
||||
table.docutils.booktabs th {
|
||||
border-bottom: thin solid;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
|
||||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
|
||||
font-size: 100% }
|
||||
|
||||
ul.auto-toc {
|
||||
list-style-type: none }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="document" id="components">
|
||||
<h1 class="title">Components</h1>
|
||||
|
||||
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:460c927ffe0fe97c01fdc7decac3df3b2b01700e16dc071eef0c9d2765ba0b1e
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/connector/tree/16.0/component"><img alt="OCA/connector" src="https://img.shields.io/badge/github-OCA%2Fconnector-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/connector-16-0/connector-16-0-component"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/connector&target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
||||
<p>This module implements a component system and is a base block for the Connector
|
||||
Framework. It can be used without using the full Connector though.</p>
|
||||
<p>Documentation: <a class="reference external" href="http://odoo-connector.com/">http://odoo-connector.com/</a></p>
|
||||
<p>You may also want to check the <a class="reference external" href="https://dev.to/guewen/introduction-to-odoo-components-bn0">Introduction to Odoo Components</a> by @guewen.</p>
|
||||
<p><strong>Table of contents</strong></p>
|
||||
<div class="contents local topic" id="contents">
|
||||
<ul class="simple">
|
||||
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
|
||||
<li><a class="reference internal" href="#changelog" id="toc-entry-2">Changelog</a><ul>
|
||||
<li><a class="reference internal" href="#section-1" id="toc-entry-3">16.0.1.0.0 (2022-10-04)</a></li>
|
||||
<li><a class="reference internal" href="#section-2" id="toc-entry-4">15.0.1.0.0 (2021-11-25)</a></li>
|
||||
<li><a class="reference internal" href="#section-3" id="toc-entry-5">14.0.1.0.0 (2020-10-22)</a></li>
|
||||
<li><a class="reference internal" href="#section-4" id="toc-entry-6">13.0.1.0.0 (2019-10-23)</a></li>
|
||||
<li><a class="reference internal" href="#section-5" id="toc-entry-7">12.0.1.0.0 (2018-10-02)</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-8">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="toc-entry-9">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="toc-entry-10">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="toc-entry-11">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="toc-entry-12">Maintainers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="usage">
|
||||
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
|
||||
<p>As a developer, you have access to a component system. You can find the
|
||||
documentation in the code or on <a class="reference external" href="http://odoo-connector.com">http://odoo-connector.com</a></p>
|
||||
<p>In a nutshell, you can create components:</p>
|
||||
<pre class="literal-block">
|
||||
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']
|
||||
</pre>
|
||||
<p>And later, find the component you need at runtime (dynamic dispatch at
|
||||
component level):</p>
|
||||
<pre class="literal-block">
|
||||
def run(self, external_id):
|
||||
backend_adapter = self.component(usage='backend.adapter')
|
||||
external_data = backend_adapter.read(external_id)
|
||||
</pre>
|
||||
<p>In order for tests using components to work, you will need to use the base
|
||||
class provided by <cite>odoo.addons.component.tests.common</cite>:</p>
|
||||
<ul class="simple">
|
||||
<li><cite>TransactionComponentCase</cite></li>
|
||||
</ul>
|
||||
<p>There are also some specific base classes for testing the component registry,
|
||||
using the ComponentRegistryCase as a base class. See the docstrings in
|
||||
<cite>tests/common.py</cite>.</p>
|
||||
</div>
|
||||
<div class="section" id="changelog">
|
||||
<h1><a class="toc-backref" href="#toc-entry-2">Changelog</a></h1>
|
||||
<!-- [ The change log. The goal of this file is to help readers
|
||||
understand changes between version. The primary audience is
|
||||
end users and integrators. Purely technical changes such as
|
||||
code refactoring must not be mentioned here.
|
||||
|
||||
This file may contain ONE level of section titles, underlined
|
||||
with the ~ (tilde) character. Other section markers are
|
||||
forbidden and will likely break the structure of the README.rst
|
||||
or other documents where this fragment is included. ] -->
|
||||
<div class="section" id="section-1">
|
||||
<h2><a class="toc-backref" href="#toc-entry-3">16.0.1.0.0 (2022-10-04)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>[MIGRATION] from 15.0</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="section-2">
|
||||
<h2><a class="toc-backref" href="#toc-entry-4">15.0.1.0.0 (2021-11-25)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>[MIGRATION] from 14.0</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="section-3">
|
||||
<h2><a class="toc-backref" href="#toc-entry-5">14.0.1.0.0 (2020-10-22)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>[MIGRATION] from 13.0</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="section-4">
|
||||
<h2><a class="toc-backref" href="#toc-entry-6">13.0.1.0.0 (2019-10-23)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>[MIGRATION] from 12.0</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="section-5">
|
||||
<h2><a class="toc-backref" href="#toc-entry-7">12.0.1.0.0 (2018-10-02)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>[MIGRATION] from 11.0 branched at rev. 324e006</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h1><a class="toc-backref" href="#toc-entry-8">Bug Tracker</a></h1>
|
||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/connector/issues">GitHub Issues</a>.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||
<a class="reference external" href="https://github.com/OCA/connector/issues/new?body=module:%20component%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
||||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||
</div>
|
||||
<div class="section" id="credits">
|
||||
<h1><a class="toc-backref" href="#toc-entry-9">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#toc-entry-10">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Camptocamp</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
<h2><a class="toc-backref" href="#toc-entry-11">Contributors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Guewen Baconnier <<a class="reference external" href="mailto:guewen.baconnier@camptocamp.com">guewen.baconnier@camptocamp.com</a>></li>
|
||||
<li>Laurent Mignon <<a class="reference external" href="mailto:laurent.mignon@acsone.eu">laurent.mignon@acsone.eu</a>></li>
|
||||
<li>Simone Orsi <<a class="reference external" href="mailto:simone.orsi@camptocamp.com">simone.orsi@camptocamp.com</a>></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2><a class="toc-backref" href="#toc-entry-12">Maintainers</a></h2>
|
||||
<p>This module is maintained by the OCA.</p>
|
||||
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
|
||||
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.</p>
|
||||
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
|
||||
<p><a class="reference external image-reference" href="https://github.com/guewen"><img alt="guewen" src="https://github.com/guewen.png?size=40px" /></a></p>
|
||||
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/connector/tree/16.0/component">OCA/connector</a> project on GitHub.</p>
|
||||
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
from . import test_build_component
|
||||
from . import test_component
|
||||
from . import test_lookup
|
||||
from . import test_work_on
|
||||
from . import test_utils
|
||||
212
odoo-bringout-oca-connector-component/component/tests/common.py
Normal file
212
odoo-bringout-oca-connector-component/component/tests/common.py
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
import copy
|
||||
from contextlib import contextmanager
|
||||
|
||||
import odoo
|
||||
from odoo import api
|
||||
from odoo.tests import common
|
||||
|
||||
from odoo.addons.component.core import ComponentRegistry, MetaComponent, _get_addon_name
|
||||
|
||||
|
||||
@contextmanager
|
||||
def new_rollbacked_env():
|
||||
registry = odoo.registry(common.get_db_name())
|
||||
uid = odoo.SUPERUSER_ID
|
||||
cr = registry.cursor()
|
||||
try:
|
||||
yield api.Environment(cr, uid, {})
|
||||
finally:
|
||||
cr.rollback() # we shouldn't have to commit anything
|
||||
cr.close()
|
||||
|
||||
|
||||
class ComponentMixin:
|
||||
@classmethod
|
||||
def setUpComponent(cls):
|
||||
with new_rollbacked_env() as env:
|
||||
builder = env["component.builder"]
|
||||
# build the components of every installed addons
|
||||
comp_registry = builder._init_global_registry()
|
||||
cls._components_registry = comp_registry
|
||||
# ensure that we load only the components of the 'installed'
|
||||
# modules, not 'to install', which means we load only the
|
||||
# dependencies of the tested addons, not the siblings or
|
||||
# children addons
|
||||
builder.build_registry(comp_registry, states=("installed",))
|
||||
# build the components of the current tested addon
|
||||
current_addon = _get_addon_name(cls.__module__)
|
||||
env["component.builder"].load_components(current_addon)
|
||||
if hasattr(cls, "env"):
|
||||
cls.env.context = dict(
|
||||
cls.env.context, components_registry=cls._components_registry
|
||||
)
|
||||
|
||||
# pylint: disable=W8106
|
||||
def setUp(self):
|
||||
# should be ready only during tests, never during installation
|
||||
# of addons
|
||||
self._components_registry.ready = True
|
||||
|
||||
@self.addCleanup
|
||||
def notready():
|
||||
self._components_registry.ready = False
|
||||
|
||||
|
||||
class TransactionComponentCase(common.TransactionCase, ComponentMixin):
|
||||
"""A TransactionCase that loads all the components
|
||||
|
||||
It it used like an usual Odoo's TransactionCase, but it ensures
|
||||
that all the components of the current addon and its dependencies
|
||||
are loaded.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.setUpComponent()
|
||||
|
||||
# pylint: disable=W8106
|
||||
def setUp(self):
|
||||
# resolve an inheritance issue (common.TransactionCase does not call
|
||||
# super)
|
||||
common.TransactionCase.setUp(self)
|
||||
ComponentMixin.setUp(self)
|
||||
# There's no env on setUpClass of TransactionCase, must do it here.
|
||||
self.env.context = dict(
|
||||
self.env.context, components_registry=self._components_registry
|
||||
)
|
||||
|
||||
|
||||
class ComponentRegistryCase:
|
||||
"""This test case can be used as a base for writings tests on components
|
||||
|
||||
This test case is meant to test components in a special component registry,
|
||||
where you want to have maximum control on which components are loaded
|
||||
or not, or when you want to create additional components in your tests.
|
||||
|
||||
If you only want to *use* the components of the tested addon in your tests,
|
||||
then consider using:
|
||||
|
||||
* :class:`TransactionComponentCase`
|
||||
|
||||
This test case creates a special
|
||||
:class:`odoo.addons.component.core.ComponentRegistry` for the purpose of
|
||||
the tests. By default, it loads all the components of the dependencies, but
|
||||
not the components of the current addon (which you have to handle
|
||||
manually). In your tests, you can add more components in 2 manners.
|
||||
|
||||
All the components of an Odoo module::
|
||||
|
||||
self._load_module_components('connector')
|
||||
|
||||
Only specific components::
|
||||
|
||||
self._build_components(MyComponent1, MyComponent2)
|
||||
|
||||
Note: for the lookups of the components, the default component
|
||||
registry is a global registry for the database. Here, you will
|
||||
need to explicitly pass ``self.comp_registry`` in the
|
||||
:class:`~odoo.addons.component.core.WorkContext`::
|
||||
|
||||
work = WorkContext(model_name='res.users',
|
||||
collection='my.collection',
|
||||
components_registry=self.comp_registry)
|
||||
|
||||
Or::
|
||||
|
||||
collection_record = self.env['my.collection'].browse(1)
|
||||
with collection_record.work_on(
|
||||
'res.partner',
|
||||
components_registry=self.comp_registry) as work:
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _setup_registry(class_or_instance):
|
||||
# keep the original classes registered by the metaclass
|
||||
# so we'll restore them at the end of the tests, it avoid
|
||||
# to pollute it with Stub / Test components
|
||||
class_or_instance._original_components = copy.deepcopy(
|
||||
MetaComponent._modules_components
|
||||
)
|
||||
|
||||
# it will be our temporary component registry for our test session
|
||||
class_or_instance.comp_registry = ComponentRegistry()
|
||||
|
||||
# it builds the 'final component' for every component of the
|
||||
# 'component' addon and push them in the component registry
|
||||
class_or_instance.comp_registry.load_components("component")
|
||||
# build the components of every installed addons already installed
|
||||
# but the current addon (when running with pytest/nosetest, we
|
||||
# simulate the --test-enable behavior by excluding the current addon
|
||||
# which is in 'to install' / 'to upgrade' with --test-enable).
|
||||
current_addon = _get_addon_name(class_or_instance.__module__)
|
||||
with new_rollbacked_env() as env:
|
||||
env["component.builder"].build_registry(
|
||||
class_or_instance.comp_registry,
|
||||
states=("installed",),
|
||||
exclude_addons=[current_addon],
|
||||
)
|
||||
|
||||
# Fake that we are ready to work with the registry
|
||||
# normally, it is set to True and the end of the build
|
||||
# of the components. Here, we'll add components later in
|
||||
# the components registry, but we don't mind for the tests.
|
||||
class_or_instance.comp_registry.ready = True
|
||||
if hasattr(class_or_instance, "env"):
|
||||
# let it propagate via ctx
|
||||
class_or_instance.env.context = dict(
|
||||
class_or_instance.env.context,
|
||||
components_registry=class_or_instance.comp_registry,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _teardown_registry(class_or_instance):
|
||||
# restore the original metaclass' classes
|
||||
MetaComponent._modules_components = class_or_instance._original_components
|
||||
|
||||
def _load_module_components(self, module):
|
||||
self.comp_registry.load_components(module)
|
||||
|
||||
def _build_components(self, *classes):
|
||||
for cls in classes:
|
||||
cls._build_component(self.comp_registry)
|
||||
|
||||
|
||||
class TransactionComponentRegistryCase(common.TransactionCase, ComponentRegistryCase):
|
||||
"""Adds Odoo Transaction in the base Component TestCase.
|
||||
|
||||
This class doesn't set up the registry for you.
|
||||
You're supposed to explicitly call `_setup_registry` and `_teardown_registry`
|
||||
when you need it, either on setUpClass and tearDownClass or setUp and tearDown.
|
||||
|
||||
class MyTestCase(TransactionComponentRegistryCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._setup_registry(self)
|
||||
|
||||
def tearDown(self):
|
||||
self._teardown_registry(self)
|
||||
super().tearDown()
|
||||
|
||||
class MyTestCase(TransactionComponentRegistryCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._setup_registry(cls)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls._teardown_registry(cls)
|
||||
super().tearDownClass()
|
||||
"""
|
||||
|
||||
# pylint: disable=W8106
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.collection = cls.env["collection.base"]
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
# Tell pylint to not bother us for all our fake component classes
|
||||
# pylint: disable=consider-merging-classes-inherited
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from odoo.addons.component.core import AbstractComponent, Component
|
||||
|
||||
from .common import TransactionComponentRegistryCase
|
||||
|
||||
|
||||
class TestBuildComponent(TransactionComponentRegistryCase):
|
||||
"""Test build of components
|
||||
|
||||
All the tests in this suite are based on the same principle with
|
||||
variations:
|
||||
|
||||
* Create new Components (classes inheriting from
|
||||
:class:`component.core.Component` or
|
||||
:class:`component.core.AbstractComponent`
|
||||
* Call :meth:`component.core.Component._build_component` on them
|
||||
in order to build the 'final class' composed from all the ``_inherit``
|
||||
and push it in the components registry (``self.comp_registry`` here)
|
||||
* Assert that classes are built, registered, have correct ``__bases__``...
|
||||
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._setup_registry(self)
|
||||
|
||||
def tearDown(self):
|
||||
self._teardown_registry(self)
|
||||
super().tearDown()
|
||||
|
||||
def test_no_name(self):
|
||||
"""Ensure that a component has a _name"""
|
||||
|
||||
class Component1(Component):
|
||||
pass
|
||||
|
||||
msg = ".*must have a _name.*"
|
||||
with self.assertRaisesRegex(TypeError, msg):
|
||||
Component1._build_component(self.comp_registry)
|
||||
|
||||
def test_register(self):
|
||||
"""Able to register components in components registry"""
|
||||
|
||||
class Component1(Component):
|
||||
_name = "component1"
|
||||
|
||||
class Component2(Component):
|
||||
_name = "component2"
|
||||
|
||||
# build the 'final classes' for the components and check that we find
|
||||
# them in the components registry
|
||||
Component1._build_component(self.comp_registry)
|
||||
Component2._build_component(self.comp_registry)
|
||||
self.assertEqual(["base", "component1", "component2"], list(self.comp_registry))
|
||||
|
||||
def test_inherit_bases(self):
|
||||
"""Check __bases__ of Component with _inherit"""
|
||||
|
||||
class Component1(Component):
|
||||
_name = "component1"
|
||||
|
||||
class Component2(Component):
|
||||
_inherit = "component1"
|
||||
|
||||
class Component3(Component):
|
||||
_inherit = "component1"
|
||||
|
||||
Component1._build_component(self.comp_registry)
|
||||
Component2._build_component(self.comp_registry)
|
||||
Component3._build_component(self.comp_registry)
|
||||
self.assertEqual(
|
||||
(Component3, Component2, Component1, self.comp_registry["base"]),
|
||||
self.comp_registry["component1"].__bases__,
|
||||
)
|
||||
|
||||
def test_prototype_inherit_bases(self):
|
||||
"""Check __bases__ of Component with _inherit and different _name"""
|
||||
|
||||
class Component1(Component):
|
||||
_name = "component1"
|
||||
|
||||
class Component2(Component):
|
||||
_name = "component2"
|
||||
_inherit = "component1"
|
||||
|
||||
class Component3(Component):
|
||||
_name = "component3"
|
||||
_inherit = "component1"
|
||||
|
||||
class Component4(Component):
|
||||
_name = "component4"
|
||||
_inherit = ["component2", "component3"]
|
||||
|
||||
Component1._build_component(self.comp_registry)
|
||||
Component2._build_component(self.comp_registry)
|
||||
Component3._build_component(self.comp_registry)
|
||||
Component4._build_component(self.comp_registry)
|
||||
self.assertEqual(
|
||||
(Component1, self.comp_registry["base"]),
|
||||
self.comp_registry["component1"].__bases__,
|
||||
)
|
||||
self.assertEqual(
|
||||
(Component2, self.comp_registry["component1"], self.comp_registry["base"]),
|
||||
self.comp_registry["component2"].__bases__,
|
||||
)
|
||||
self.assertEqual(
|
||||
(Component3, self.comp_registry["component1"], self.comp_registry["base"]),
|
||||
self.comp_registry["component3"].__bases__,
|
||||
)
|
||||
self.assertEqual(
|
||||
(
|
||||
Component4,
|
||||
self.comp_registry["component2"],
|
||||
self.comp_registry["component3"],
|
||||
self.comp_registry["base"],
|
||||
),
|
||||
self.comp_registry["component4"].__bases__,
|
||||
)
|
||||
|
||||
# pylint: disable=W8110
|
||||
def test_custom_build(self):
|
||||
"""Check that we can hook at the end of a Component build"""
|
||||
|
||||
class Component1(Component):
|
||||
_name = "component1"
|
||||
|
||||
@classmethod
|
||||
def _complete_component_build(cls):
|
||||
# This method should be called after the Component
|
||||
# is built, and before it is pushed in the registry
|
||||
cls._build_done = True
|
||||
|
||||
Component1._build_component(self.comp_registry)
|
||||
# we inspect that our custom build has been executed
|
||||
self.assertTrue(self.comp_registry["component1"]._build_done)
|
||||
|
||||
def test_inherit_attrs(self):
|
||||
"""Check attributes inheritance of Components with _inherit"""
|
||||
|
||||
class Component1(Component):
|
||||
_name = "component1"
|
||||
|
||||
msg = "ping"
|
||||
|
||||
def say(self):
|
||||
return "foo"
|
||||
|
||||
class Component2(Component):
|
||||
_name = "component2"
|
||||
_inherit = "component1"
|
||||
|
||||
msg = "pong"
|
||||
|
||||
def say(self):
|
||||
return super().say() + " bar"
|
||||
|
||||
Component1._build_component(self.comp_registry)
|
||||
Component2._build_component(self.comp_registry)
|
||||
# we initialize the components, normally we should pass
|
||||
# an instance of WorkContext, but we don't need a real one
|
||||
# for this test
|
||||
component1 = self.comp_registry["component1"](mock.Mock())
|
||||
component2 = self.comp_registry["component2"](mock.Mock())
|
||||
self.assertEqual("ping", component1.msg)
|
||||
self.assertEqual("pong", component2.msg)
|
||||
self.assertEqual("foo", component1.say())
|
||||
self.assertEqual("foo bar", component2.say())
|
||||
|
||||
def test_duplicate_component(self):
|
||||
"""Check that we can't have 2 components with the same name"""
|
||||
|
||||
class Component1(Component):
|
||||
_name = "component1"
|
||||
|
||||
class Component2(Component):
|
||||
_name = "component1"
|
||||
|
||||
Component1._build_component(self.comp_registry)
|
||||
msg = "Component.*already exists.*"
|
||||
with self.assertRaisesRegex(TypeError, msg):
|
||||
Component2._build_component(self.comp_registry)
|
||||
|
||||
def test_no_parent(self):
|
||||
"""Ensure we can't _inherit a non-existent component"""
|
||||
|
||||
class Component1(Component):
|
||||
_name = "component1"
|
||||
_inherit = "component1"
|
||||
|
||||
msg = "Component.*does not exist in registry.*"
|
||||
with self.assertRaisesRegex(TypeError, msg):
|
||||
Component1._build_component(self.comp_registry)
|
||||
|
||||
def test_no_parent2(self):
|
||||
"""Ensure we can't _inherit by prototype a non-existent component"""
|
||||
|
||||
class Component1(Component):
|
||||
_name = "component1"
|
||||
|
||||
class Component2(Component):
|
||||
_name = "component2"
|
||||
_inherit = ["component1", "component3"]
|
||||
|
||||
Component1._build_component(self.comp_registry)
|
||||
msg = "Component.*inherits from non-existing component.*"
|
||||
with self.assertRaisesRegex(TypeError, msg):
|
||||
Component2._build_component(self.comp_registry)
|
||||
|
||||
def test_add_inheritance(self):
|
||||
"""Ensure we can add a new inheritance"""
|
||||
|
||||
class Component1(Component):
|
||||
_name = "component1"
|
||||
|
||||
class Component2(Component):
|
||||
_name = "component2"
|
||||
|
||||
class Component2bis(Component):
|
||||
_name = "component2"
|
||||
_inherit = ["component2", "component1"]
|
||||
|
||||
Component1._build_component(self.comp_registry)
|
||||
Component2._build_component(self.comp_registry)
|
||||
Component2bis._build_component(self.comp_registry)
|
||||
|
||||
self.assertEqual(
|
||||
(
|
||||
Component2bis,
|
||||
Component2,
|
||||
self.comp_registry["component1"],
|
||||
self.comp_registry["base"],
|
||||
),
|
||||
self.comp_registry["component2"].__bases__,
|
||||
)
|
||||
|
||||
def test_check_parent_component_over_abstract(self):
|
||||
"""Component can inherit from AbstractComponent"""
|
||||
|
||||
class Component1(AbstractComponent):
|
||||
_name = "component1"
|
||||
|
||||
class Component2(Component):
|
||||
_name = "component2"
|
||||
_inherit = "component1"
|
||||
|
||||
Component1._build_component(self.comp_registry)
|
||||
Component2._build_component(self.comp_registry)
|
||||
self.assertTrue(self.comp_registry["component1"]._abstract)
|
||||
self.assertFalse(self.comp_registry["component2"]._abstract)
|
||||
|
||||
def test_check_parent_abstract_over_component(self):
|
||||
"""Prevent AbstractComponent to inherit from Component"""
|
||||
|
||||
class Component1(Component):
|
||||
_name = "component1"
|
||||
|
||||
class Component2(AbstractComponent):
|
||||
_name = "component2"
|
||||
_inherit = "component1"
|
||||
|
||||
Component1._build_component(self.comp_registry)
|
||||
msg = ".*cannot inherit from the non-abstract.*"
|
||||
with self.assertRaisesRegex(TypeError, msg):
|
||||
Component2._build_component(self.comp_registry)
|
||||
|
||||
def test_check_transform_abstract_to_component(self):
|
||||
"""Prevent AbstractComponent to be transformed to Component"""
|
||||
|
||||
class Component1(AbstractComponent):
|
||||
_name = "component1"
|
||||
|
||||
class Component1bis(Component):
|
||||
_inherit = "component1"
|
||||
|
||||
Component1._build_component(self.comp_registry)
|
||||
msg = ".*transforms the abstract component.*into a non-abstract.*"
|
||||
with self.assertRaisesRegex(TypeError, msg):
|
||||
Component1bis._build_component(self.comp_registry)
|
||||
|
|
@ -0,0 +1,388 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.component.exception import NoComponentError, SeveralComponentError
|
||||
|
||||
from .common import TransactionComponentRegistryCase
|
||||
|
||||
|
||||
class TestComponent(TransactionComponentRegistryCase):
|
||||
"""Test usage of components
|
||||
|
||||
These tests are a bit more broad that mere unit tests.
|
||||
We test the chain odoo Model -> generate a WorkContext instance -> Work
|
||||
with Component.
|
||||
|
||||
Tests are inside Odoo transactions, so we can work
|
||||
with Odoo's env / models.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._setup_registry(self)
|
||||
self._setUpComponents()
|
||||
|
||||
def tearDown(self):
|
||||
self._teardown_registry(self)
|
||||
super().tearDown()
|
||||
|
||||
def _setUpComponents(self):
|
||||
# create some Component to play with
|
||||
class Component1(Component):
|
||||
_name = "component1"
|
||||
_collection = "collection.base"
|
||||
_usage = "for.test"
|
||||
_apply_on = ["res.partner"]
|
||||
|
||||
class Component2(Component):
|
||||
_name = "component2"
|
||||
_collection = "collection.base"
|
||||
_usage = "for.test"
|
||||
_apply_on = ["res.users"]
|
||||
|
||||
# build the components and register them in our
|
||||
# test component registry
|
||||
Component1._build_component(self.comp_registry)
|
||||
Component2._build_component(self.comp_registry)
|
||||
|
||||
# our collection, in a less abstract use case, it
|
||||
# could be a record of 'magento.backend' for instance
|
||||
self.collection_record = self.collection.new()
|
||||
|
||||
@contextmanager
|
||||
def get_base():
|
||||
# Our WorkContext, it will be passed along in every
|
||||
# components so we can share data transversally.
|
||||
# We are working with res.partner in the following tests,
|
||||
# unless we change it in the test.
|
||||
with self.collection_record.work_on(
|
||||
"res.partner",
|
||||
# we use a custom registry only
|
||||
# for the sake of the tests
|
||||
components_registry=self.comp_registry,
|
||||
) as work:
|
||||
# We get the 'base' component, handy to test the base
|
||||
# methods component, many_components, ...
|
||||
yield work.component_by_name("base")
|
||||
|
||||
self.get_base = get_base
|
||||
|
||||
def test_component_attrs(self):
|
||||
"""Basic access to a Component's attribute"""
|
||||
with self.get_base() as base:
|
||||
# as we are working on res.partner, we should get 'component1'
|
||||
comp = base.work.component(usage="for.test")
|
||||
# but this is not what we test here, we test the attributes:
|
||||
self.assertEqual(self.collection_record, comp.collection)
|
||||
self.assertEqual(base.work, comp.work)
|
||||
self.assertEqual(self.env, comp.env)
|
||||
self.assertEqual(self.env["res.partner"], comp.model)
|
||||
|
||||
def test_component_get_by_name_same_model(self):
|
||||
"""Use component_by_name with current working model"""
|
||||
with self.get_base() as base:
|
||||
# we ask a component directly by it's name, considering
|
||||
# we work with res.partner, we should get 'component1'
|
||||
# this is ok because it's _apply_on contains res.partner
|
||||
comp = base.component_by_name("component1")
|
||||
self.assertEqual("component1", comp._name)
|
||||
self.assertEqual(self.env["res.partner"], comp.model)
|
||||
|
||||
def test_component_get_by_name_other_model(self):
|
||||
"""Use component_by_name with another model"""
|
||||
with self.get_base() as base:
|
||||
# we ask a component directly by it's name, but we
|
||||
# want to work with 'res.users', this is ok since
|
||||
# component2's _apply_on contains res.users
|
||||
comp = base.component_by_name("component2", model_name="res.users")
|
||||
self.assertEqual("component2", comp._name)
|
||||
self.assertEqual(self.env["res.users"], comp.model)
|
||||
# what happens under the hood, is that a new WorkContext
|
||||
# has been created for this model, with all the other values
|
||||
# identical to the previous WorkContext (the one for res.partner)
|
||||
# We can check that with:
|
||||
self.assertNotEqual(base.work, comp.work)
|
||||
self.assertEqual("res.partner", base.work.model_name)
|
||||
self.assertEqual("res.users", comp.work.model_name)
|
||||
|
||||
def test_component_get_by_name_wrong_model(self):
|
||||
"""Use component_by_name with a model not in _apply_on"""
|
||||
msg = (
|
||||
"Component with name 'component2' can't be used "
|
||||
"for model 'res.partner'.*"
|
||||
)
|
||||
with self.get_base() as base:
|
||||
with self.assertRaisesRegex(NoComponentError, msg):
|
||||
# we ask for the model 'component2' but we are working
|
||||
# with res.partner, and it only accepts res.users
|
||||
base.component_by_name("component2")
|
||||
|
||||
def test_component_get_by_name_not_exist(self):
|
||||
"""Use component_by_name on a component that do not exist"""
|
||||
msg = "No component with name 'foo' found."
|
||||
with self.get_base() as base:
|
||||
with self.assertRaisesRegex(NoComponentError, msg):
|
||||
base.component_by_name("foo")
|
||||
|
||||
def test_component_by_usage_same_model(self):
|
||||
"""Use component(usage=...) on the same model"""
|
||||
# we ask for a component having _usage == 'for.test', and
|
||||
# model being res.partner (the model in the current WorkContext)
|
||||
with self.get_base() as base:
|
||||
comp = base.component(usage="for.test")
|
||||
self.assertEqual("component1", comp._name)
|
||||
self.assertEqual(self.env["res.partner"], comp.model)
|
||||
|
||||
def test_component_by_usage_other_model(self):
|
||||
"""Use component(usage=...) on a different model (name)"""
|
||||
# we ask for a component having _usage == 'for.test', and
|
||||
# a different model (res.users)
|
||||
with self.get_base() as base:
|
||||
comp = base.component(usage="for.test", model_name="res.users")
|
||||
self.assertEqual("component2", comp._name)
|
||||
self.assertEqual(self.env["res.users"], comp.model)
|
||||
# what happens under the hood, is that a new WorkContext
|
||||
# has been created for this model, with all the other values
|
||||
# identical to the previous WorkContext (the one for res.partner)
|
||||
# We can check that with:
|
||||
self.assertNotEqual(base.work, comp.work)
|
||||
self.assertEqual("res.partner", base.work.model_name)
|
||||
self.assertEqual("res.users", comp.work.model_name)
|
||||
|
||||
def test_component_by_usage_other_model_env(self):
|
||||
"""Use component(usage=...) on a different model (instance)"""
|
||||
with self.get_base() as base:
|
||||
comp = base.component(usage="for.test", model_name=self.env["res.users"])
|
||||
self.assertEqual("component2", comp._name)
|
||||
self.assertEqual(self.env["res.users"], comp.model)
|
||||
|
||||
def test_component_error_several(self):
|
||||
"""Use component(usage=...) when more than one generic component match"""
|
||||
# we create 1 new Component with _usage 'for.test', in the same
|
||||
# collection and no _apply_on, and we remove the _apply_on of component
|
||||
# 1 so they are generic components for a collection
|
||||
class Component3(Component):
|
||||
_name = "component3"
|
||||
_collection = "collection.base"
|
||||
_usage = "for.test"
|
||||
|
||||
class Component1(Component):
|
||||
_inherit = "component1"
|
||||
_collection = "collection.base"
|
||||
_usage = "for.test"
|
||||
_apply_on = None
|
||||
|
||||
Component3._build_component(self.comp_registry)
|
||||
Component1._build_component(self.comp_registry)
|
||||
|
||||
with self.get_base() as base:
|
||||
with self.assertRaises(SeveralComponentError):
|
||||
# When a component has no _apply_on, it means it can be applied
|
||||
# on *any* model. Here, the candidates components would be:
|
||||
# component3 (because it has no _apply_on so apply in any case)
|
||||
# component4 (for the same reason)
|
||||
base.component(usage="for.test")
|
||||
|
||||
def test_component_error_several_same_model(self):
|
||||
"""Use component(usage=...) when more than one component match a model"""
|
||||
# we create a new Component with _usage 'for.test', in the same
|
||||
# collection and no _apply_on
|
||||
class Component3(Component):
|
||||
_name = "component3"
|
||||
_collection = "collection.base"
|
||||
_usage = "for.test"
|
||||
_apply_on = ["res.partner"]
|
||||
|
||||
Component3._build_component(self.comp_registry)
|
||||
|
||||
with self.get_base() as base:
|
||||
with self.assertRaises(SeveralComponentError):
|
||||
# Here, the candidates components would be:
|
||||
# component1 (because we are working with res.partner),
|
||||
# component3 (for the same reason)
|
||||
base.component(usage="for.test")
|
||||
|
||||
def test_component_specific_model(self):
|
||||
"""Use component(usage=...) when more than one component match but
|
||||
only one for the specific model"""
|
||||
# we create a new Component with _usage 'for.test', in the same
|
||||
# collection and no _apply_on. This is a generic component for the
|
||||
# collection
|
||||
class Component3(Component):
|
||||
_name = "component3"
|
||||
_collection = "collection.base"
|
||||
_usage = "for.test"
|
||||
|
||||
Component3._build_component(self.comp_registry)
|
||||
|
||||
with self.get_base() as base:
|
||||
# When a component has no _apply_on, it means it can be applied on
|
||||
# *any* model. Here, the candidates components would be:
|
||||
# component1 # (because we are working with res.partner),
|
||||
# component3 (because it # has no _apply_on so apply in any case).
|
||||
# When a component is specifically linked to a model with
|
||||
# _apply_on, it takes precedence over a generic component. It
|
||||
# allows to create a generic implementation (component3 here) and
|
||||
# override it only for a given model. So in this case, the final
|
||||
# component is component1.
|
||||
comp = base.component(usage="for.test")
|
||||
self.assertEqual("component1", comp._name)
|
||||
|
||||
def test_component_specific_collection(self):
|
||||
"""Use component(usage=...) when more than one component match but
|
||||
only one for the specific collection"""
|
||||
# we create a new Component with _usage 'for.test', without collection
|
||||
# and no _apply_on
|
||||
class Component3(Component):
|
||||
_name = "component3"
|
||||
_usage = "for.test"
|
||||
|
||||
Component3._build_component(self.comp_registry)
|
||||
|
||||
with self.get_base() as base:
|
||||
# When a component has no _apply_on, it means it can be applied
|
||||
# on *any* model. Here, the candidates components would be:
|
||||
# component1 (because we are working with res.partner),
|
||||
# component3 (because it has no _apply_on so apply in any case).
|
||||
# When a component has no _collection, it means it can be applied
|
||||
# on all model if no component is found for the current collection:
|
||||
# component3 must be ignored since a component (component1) exists
|
||||
# and is specificaly linked to the expected collection.
|
||||
comp = base.component(usage="for.test")
|
||||
self.assertEqual("component1", comp._name)
|
||||
|
||||
def test_component_specific_collection_specific_model(self):
|
||||
"""Use component(usage=...) when more than one component match but
|
||||
only one for the specific model and collection"""
|
||||
# we create a new Component with _usage 'for.test', without collection
|
||||
# and no _apply_on. This is a component generic for all collections and
|
||||
# models
|
||||
class Component3(Component):
|
||||
_name = "component3"
|
||||
_usage = "for.test"
|
||||
|
||||
Component3._build_component(self.comp_registry)
|
||||
|
||||
with self.get_base() as base:
|
||||
# When a component has no _apply_on, it means it can be applied on
|
||||
# *any* model, no _collection, it can be applied on *any*
|
||||
# collection.
|
||||
# Here, the candidates components would be:
|
||||
# component1 (because we are working with res.partner),
|
||||
# component3 (because it has no _apply_on and no _collection so
|
||||
# apply in any case).
|
||||
# When a component is specifically linked to a model with
|
||||
# _apply_on, it takes precedence over a generic component, the same
|
||||
# happens for collection. It allows to create a generic
|
||||
# implementation (component3 here) and override it only for a given
|
||||
# collection and model. So in this case, the final component is
|
||||
# component1.
|
||||
comp = base.component(usage="for.test")
|
||||
self.assertEqual("component1", comp._name)
|
||||
|
||||
def test_many_components(self):
|
||||
"""Use many_components(usage=...) on the same model"""
|
||||
|
||||
class Component3(Component):
|
||||
_name = "component3"
|
||||
_collection = "collection.base"
|
||||
_usage = "for.test"
|
||||
|
||||
Component3._build_component(self.comp_registry)
|
||||
|
||||
with self.get_base() as base:
|
||||
comps = base.many_components(usage="for.test")
|
||||
|
||||
# When a component has no _apply_on, it means it can be applied
|
||||
# on *any* model. So here, both component1 and component3 match
|
||||
self.assertEqual(["component1", "component3"], [c._name for c in comps])
|
||||
|
||||
def test_many_components_other_model(self):
|
||||
"""Use many_components(usage=...) on a different model (name)"""
|
||||
|
||||
class Component3(Component):
|
||||
_name = "component3"
|
||||
_collection = "collection.base"
|
||||
_apply_on = "res.users"
|
||||
_usage = "for.test"
|
||||
|
||||
Component3._build_component(self.comp_registry)
|
||||
|
||||
with self.get_base() as base:
|
||||
comps = base.many_components(usage="for.test", model_name="res.users")
|
||||
|
||||
self.assertEqual(["component2", "component3"], [c._name for c in comps])
|
||||
|
||||
def test_many_components_other_model_env(self):
|
||||
"""Use many_components(usage=...) on a different model (instance)"""
|
||||
|
||||
class Component3(Component):
|
||||
_name = "component3"
|
||||
_collection = "collection.base"
|
||||
_apply_on = "res.users"
|
||||
_usage = "for.test"
|
||||
|
||||
Component3._build_component(self.comp_registry)
|
||||
|
||||
with self.get_base() as base:
|
||||
comps = base.many_components(
|
||||
usage="for.test", model_name=self.env["res.users"]
|
||||
)
|
||||
|
||||
self.assertEqual(["component2", "component3"], [c._name for c in comps])
|
||||
|
||||
def test_no_component(self):
|
||||
"""No component found for asked usage"""
|
||||
with self.get_base() as base:
|
||||
with self.assertRaises(NoComponentError):
|
||||
base.component(usage="foo")
|
||||
|
||||
def test_no_many_component(self):
|
||||
"""No component found for asked usage for many_components()"""
|
||||
with self.get_base() as base:
|
||||
self.assertEqual([], base.many_components(usage="foo"))
|
||||
|
||||
def test_work_on_component(self):
|
||||
"""Check WorkContext.component() (shortcut to Component.component)"""
|
||||
with self.get_base() as base:
|
||||
comp = base.work.component(usage="for.test")
|
||||
self.assertEqual("component1", comp._name)
|
||||
|
||||
def test_work_on_many_components(self):
|
||||
"""Check WorkContext.many_components()
|
||||
|
||||
(shortcut to Component.many_components)
|
||||
"""
|
||||
with self.get_base() as base:
|
||||
comps = base.work.many_components(usage="for.test")
|
||||
self.assertEqual("component1", comps[0]._name)
|
||||
|
||||
def test_component_match(self):
|
||||
"""Lookup with match method"""
|
||||
|
||||
class Foo(Component):
|
||||
_name = "foo"
|
||||
_collection = "collection.base"
|
||||
_usage = "speaker"
|
||||
_apply_on = ["res.partner"]
|
||||
|
||||
@classmethod
|
||||
def _component_match(cls, work, **kw):
|
||||
return False
|
||||
|
||||
class Bar(Component):
|
||||
_name = "bar"
|
||||
_collection = "collection.base"
|
||||
_usage = "speaker"
|
||||
_apply_on = ["res.partner"]
|
||||
|
||||
self._build_components(Foo, Bar)
|
||||
|
||||
with self.get_base() as base:
|
||||
# both components would we returned without the
|
||||
# _component_match method
|
||||
comp = base.component(usage="speaker", model_name=self.env["res.partner"])
|
||||
self.assertEqual("bar", comp._name)
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from odoo.addons.component.core import AbstractComponent, Component
|
||||
|
||||
from .common import TransactionComponentRegistryCase
|
||||
|
||||
|
||||
class TestLookup(TransactionComponentRegistryCase):
|
||||
"""Test the ComponentRegistry
|
||||
|
||||
Tests in this testsuite mainly do:
|
||||
|
||||
* Create new Components (classes inheriting from
|
||||
:class:`component.core.Component` or
|
||||
:class:`component.core.AbstractComponent`
|
||||
* Call :meth:`component.core.Component._build_component` on them
|
||||
in order to build the 'final class' composed from all the ``_inherit``
|
||||
and push it in the components registry (``self.comp_registry`` here)
|
||||
* Use the lookup method of the components registry and check
|
||||
that we get the correct result
|
||||
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._setup_registry(self)
|
||||
|
||||
def tearDown(self):
|
||||
self._teardown_registry(self)
|
||||
super().tearDown()
|
||||
|
||||
def test_lookup_collection(self):
|
||||
"""Lookup components of a collection"""
|
||||
# we register 2 components in foobar and one in other
|
||||
class Foo(Component):
|
||||
_name = "foo"
|
||||
_collection = "foobar"
|
||||
|
||||
class Bar(Component):
|
||||
_name = "bar"
|
||||
_collection = "foobar"
|
||||
|
||||
class Homer(Component):
|
||||
_name = "homer"
|
||||
_collection = "other"
|
||||
|
||||
self._build_components(Foo, Bar, Homer)
|
||||
|
||||
# we should no see the component in 'other'
|
||||
components = self.comp_registry.lookup("foobar")
|
||||
self.assertEqual(["foo", "bar"], [c._name for c in components])
|
||||
|
||||
def test_lookup_usage(self):
|
||||
"""Lookup components by usage"""
|
||||
|
||||
class Foo(Component):
|
||||
_name = "foo"
|
||||
_collection = "foobar"
|
||||
_usage = "speaker"
|
||||
|
||||
class Bar(Component):
|
||||
_name = "bar"
|
||||
_collection = "foobar"
|
||||
_usage = "speaker"
|
||||
|
||||
class Baz(Component):
|
||||
_name = "baz"
|
||||
_collection = "foobar"
|
||||
_usage = "listener"
|
||||
|
||||
self._build_components(Foo, Bar, Baz)
|
||||
|
||||
components = self.comp_registry.lookup("foobar", usage="listener")
|
||||
self.assertEqual("baz", components[0]._name)
|
||||
|
||||
components = self.comp_registry.lookup("foobar", usage="speaker")
|
||||
self.assertEqual(["foo", "bar"], [c._name for c in components])
|
||||
|
||||
def test_lookup_no_component(self):
|
||||
"""No component"""
|
||||
# we just expect an empty list when no component match, the error
|
||||
# handling is handled at an higher level
|
||||
self.assertEqual([], self.comp_registry.lookup("something", usage="something"))
|
||||
|
||||
def test_get_by_name(self):
|
||||
"""Get component by name"""
|
||||
|
||||
class Foo(AbstractComponent):
|
||||
_name = "foo"
|
||||
_collection = "foobar"
|
||||
|
||||
self._build_components(Foo)
|
||||
# this is just a dict access
|
||||
self.assertEqual("foo", self.comp_registry["foo"]._name)
|
||||
|
||||
def test_lookup_abstract(self):
|
||||
"""Do not include abstract components in lookup"""
|
||||
|
||||
class Foo(AbstractComponent):
|
||||
_name = "foo"
|
||||
_collection = "foobar"
|
||||
_usage = "speaker"
|
||||
|
||||
class Bar(Component):
|
||||
_name = "bar"
|
||||
_inherit = "foo"
|
||||
|
||||
self._build_components(Foo, Bar)
|
||||
|
||||
comp_registry = self.comp_registry
|
||||
|
||||
# we should never have 'foo' in the returned components
|
||||
# as it is abstract
|
||||
components = comp_registry.lookup("foobar", usage="speaker")
|
||||
self.assertEqual("bar", components[0]._name)
|
||||
|
||||
components = comp_registry.lookup("foobar", usage="speaker")
|
||||
self.assertEqual(["bar"], [c._name for c in components])
|
||||
|
||||
def test_lookup_model_name(self):
|
||||
"""Lookup with model names"""
|
||||
|
||||
class Foo(Component):
|
||||
_name = "foo"
|
||||
_collection = "foobar"
|
||||
_usage = "speaker"
|
||||
# support list
|
||||
_apply_on = ["res.partner"]
|
||||
|
||||
class Bar(Component):
|
||||
_name = "bar"
|
||||
_collection = "foobar"
|
||||
_usage = "speaker"
|
||||
# support string
|
||||
_apply_on = "res.users"
|
||||
|
||||
class Any(Component):
|
||||
# can be used with any model as far as we look it up
|
||||
# with its usage
|
||||
_name = "any"
|
||||
_collection = "foobar"
|
||||
_usage = "listener"
|
||||
|
||||
self._build_components(Foo, Bar, Any)
|
||||
|
||||
components = self.comp_registry.lookup(
|
||||
"foobar", usage="speaker", model_name="res.partner"
|
||||
)
|
||||
self.assertEqual("foo", components[0]._name)
|
||||
|
||||
components = self.comp_registry.lookup(
|
||||
"foobar", usage="speaker", model_name="res.users"
|
||||
)
|
||||
self.assertEqual("bar", components[0]._name)
|
||||
|
||||
components = self.comp_registry.lookup(
|
||||
"foobar", usage="listener", model_name="res.users"
|
||||
)
|
||||
self.assertEqual("any", components[0]._name)
|
||||
|
||||
def test_lookup_cache(self):
|
||||
"""Lookup uses a cache"""
|
||||
|
||||
class Foo(Component):
|
||||
_name = "foo"
|
||||
_collection = "foobar"
|
||||
|
||||
self._build_components(Foo)
|
||||
|
||||
components = self.comp_registry.lookup("foobar")
|
||||
self.assertEqual(["foo"], [c._name for c in components])
|
||||
|
||||
# we add a new component
|
||||
class Bar(Component):
|
||||
_name = "bar"
|
||||
_collection = "foobar"
|
||||
|
||||
self._build_components(Bar)
|
||||
|
||||
# As the lookups are cached, we should still see only foo,
|
||||
# even if we added a new component.
|
||||
# We do this for testing, but in a real use case, we can't
|
||||
# add new Component classes on the fly, and when we install
|
||||
# new addons, the registry is rebuilt and cache cleared.
|
||||
components = self.comp_registry.lookup("foobar")
|
||||
self.assertEqual(["foo"], [c._name for c in components])
|
||||
|
||||
self.comp_registry._cache.clear()
|
||||
# now we should find them both as the cache has been cleared
|
||||
components = self.comp_registry.lookup("foobar")
|
||||
self.assertEqual(["foo", "bar"], [c._name for c in components])
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Copyright 2023 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from odoo.addons.component.utils import is_component_registry_ready
|
||||
|
||||
from .common import TransactionComponentRegistryCase
|
||||
|
||||
|
||||
class TestUtils(TransactionComponentRegistryCase):
|
||||
def test_registry_ready(self):
|
||||
path = "odoo.addons.component.utils.get_component_registry"
|
||||
with mock.patch(path) as mocked:
|
||||
mocked.return_value = None
|
||||
self.assertFalse(is_component_registry_ready(self.env.cr.dbname))
|
||||
self._setup_registry(self)
|
||||
mocked.return_value = self.comp_registry
|
||||
self.assertTrue(is_component_registry_ready(self.env.cr.dbname))
|
||||
self._teardown_registry(self)
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from odoo.addons.component.core import ComponentRegistry, WorkContext
|
||||
|
||||
from .common import TransactionComponentRegistryCase
|
||||
|
||||
|
||||
class TestWorkOn(TransactionComponentRegistryCase):
|
||||
"""Test on WorkContext
|
||||
|
||||
This model is mostly a container, so we check the access
|
||||
to the attributes and properties.
|
||||
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._setup_registry(self)
|
||||
|
||||
def tearDown(self):
|
||||
self._teardown_registry(self)
|
||||
super().tearDown()
|
||||
|
||||
def test_collection_work_on(self):
|
||||
"""Create a new instance and test attributes access"""
|
||||
collection_record = self.collection.new()
|
||||
with collection_record.work_on("res.partner") as work:
|
||||
self.assertEqual(collection_record, work.collection)
|
||||
self.assertEqual("collection.base", work.collection._name)
|
||||
self.assertEqual("res.partner", work.model_name)
|
||||
self.assertEqual(self.env["res.partner"], work.model)
|
||||
self.assertEqual(self.env, work.env)
|
||||
|
||||
def test_collection_work_on_registry_via_context(self):
|
||||
"""Test propagation of registry via context"""
|
||||
registry = ComponentRegistry()
|
||||
collection_record = self.collection.with_context(
|
||||
components_registry=registry
|
||||
).new()
|
||||
with collection_record.work_on("res.partner") as work:
|
||||
self.assertEqual(collection_record, work.collection)
|
||||
self.assertEqual("collection.base", work.collection._name)
|
||||
self.assertEqual("res.partner", work.model_name)
|
||||
self.assertEqual(self.env["res.partner"], work.model)
|
||||
self.assertEqual(work.env, collection_record.env)
|
||||
self.assertEqual(work.components_registry, registry)
|
||||
|
||||
def test_propagate_work_on(self):
|
||||
"""Check custom attributes and their propagation"""
|
||||
registry = ComponentRegistry()
|
||||
work = WorkContext(
|
||||
model_name="res.partner",
|
||||
collection=self.collection,
|
||||
# we can customize the lookup registry, but used mostly for tests
|
||||
components_registry=registry,
|
||||
# we can pass our own keyword args that will set as attributes
|
||||
test_keyword="value",
|
||||
)
|
||||
self.assertIs(registry, work.components_registry)
|
||||
# check that our custom keyword is set as attribute
|
||||
self.assertEqual("value", work.test_keyword)
|
||||
|
||||
# when we want to work on another model, work_on() create
|
||||
# another instance and propagate the attributes to it
|
||||
work2 = work.work_on("res.users")
|
||||
self.assertNotEqual(work, work2)
|
||||
self.assertEqual(self.env, work2.env)
|
||||
self.assertEqual(self.collection, work2.collection)
|
||||
self.assertEqual("res.users", work2.model_name)
|
||||
self.assertIs(registry, work2.components_registry)
|
||||
# test_keyword has been propagated to the new WorkContext instance
|
||||
self.assertEqual("value", work2.test_keyword)
|
||||
14
odoo-bringout-oca-connector-component/component/utils.py
Normal file
14
odoo-bringout-oca-connector-component/component/utils.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Copyright 2023 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from .core import _component_databases
|
||||
|
||||
|
||||
def get_component_registry(dbname):
|
||||
return _component_databases.get(dbname)
|
||||
|
||||
|
||||
def is_component_registry_ready(dbname):
|
||||
"""Return True if the registry is ready to be used."""
|
||||
comp_registry = get_component_registry(dbname)
|
||||
return comp_registry.ready if comp_registry else False
|
||||
Loading…
Add table
Add a link
Reference in a new issue