Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1 @@
from . import test_build_datamodel

View file

@ -0,0 +1,185 @@
# Copyright 2017 Camptocamp SA
# Copyright 2019 ACSONE SA/NV
# 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 ..core import (
DatamodelRegistry,
MetaDatamodel,
_datamodel_databases,
_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 DatamodelMixin(object):
@classmethod
def setUpDatamodel(cls):
with new_rollbacked_env() as env:
builder = env["datamodel.builder"]
# build the datamodels of every installed addons
datamodel_registry = builder._init_global_registry()
cls._datamodels_registry = datamodel_registry
# ensure that we load only the datamodels of the 'installed'
# modules, not 'to install', which means we load only the
# dependencies of the tested addons, not the siblings or
# chilren addons
builder.build_registry(datamodel_registry, states=("installed",))
# build the datamodels of the current tested addon
current_addon = _get_addon_name(cls.__module__)
env["datamodel.builder"].load_datamodels(current_addon)
# pylint: disable=W8106
def setUp(self):
# should be ready only during tests, never during installation
# of addons
self._datamodels_registry.ready = True
@self.addCleanup
def notready():
self._datamodels_registry.ready = False
class TransactionDatamodelCase(common.TransactionCase, DatamodelMixin):
"""A TransactionCase that loads all the datamodels
It is used like an usual Odoo's TransactionCase, but it ensures
that all the datamodels of the current addon and its dependencies
are loaded.
"""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.setUpDatamodel()
# pylint: disable=W8106
def setUp(self):
# resolve an inheritance issue (common.TransactionCase does not call
# super)
common.TransactionCase.setUp(self)
DatamodelMixin.setUp(self)
class DatamodelRegistryCase(
common.BaseCase, common.MetaCase("DummyCase", (object,), {})
):
"""This test case can be used as a base for writings tests on datamodels
This test case is meant to test datamodels in a special datamodel registry,
where you want to have maximum control on which datamodels are loaded
or not, or when you want to create additional datamodels in your tests.
If you only want to *use* the datamodels of the tested addon in your tests,
then consider using:
* :class:`TransactionDatamodelCase`
This test case creates a special
:class:`odoo.addons.datamodel.core.DatamodelRegistry` for the purpose of
the tests. By default, it loads all the datamodels of the dependencies, but
not the datamodels of the current addon (which you have to handle
manually). In your tests, you can add more datamodels in 2 manners.
All the datamodels of an Odoo module::
self._load_module_datamodels('connector')
Only specific datamodels::
self._build_datamodels(MyDatamodel1, MyDatamodel2)
Note: for the lookups of the datamodels, the default datamodel
registry is a global registry for the database. Here, you will
need to explicitly pass ``self.datamodel_registry`` in the
"""
def setUp(self):
super().setUp()
# 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 datamodels
self._original_datamodels = copy.deepcopy(MetaDatamodel._modules_datamodels)
# it will be our temporary datamodel registry for our test session
self.datamodel_registry = DatamodelRegistry()
# it builds the 'final datamodel' for every datamodel of the
# 'datamodel' addon and push them in the datamodel registry
self.datamodel_registry.load_datamodels("datamodel")
# build the datamodels 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(self.__module__)
registry = odoo.registry(common.get_db_name())
uid = odoo.SUPERUSER_ID
cr = registry.cursor()
env = api.Environment(cr, uid, {})
env["datamodel.builder"].build_registry(
self.datamodel_registry,
states=("installed",),
exclude_addons=[current_addon],
)
self.env = env
_datamodel_databases[self.env.cr.dbname] = self.datamodel_registry
@self.addCleanup
def _close_and_roolback():
cr.rollback() # we shouldn't have to commit anything
cr.close()
# Fake that we are ready to work with the registry
# normally, it is set to True and the end of the build
# of the datamodels. Here, we'll add datamodels later in
# the datamodels registry, but we don't mind for the tests.
self.datamodel_registry.ready = True
def tearDown(self):
super().tearDown()
# restore the original metaclass' classes
MetaDatamodel._modules_datamodels = self._original_datamodels
def _load_module_datamodels(self, module):
self.datamodel_registry.load_datamodels(module)
def _build_datamodels(self, *classes):
for cls in classes:
cls._build_datamodel(self.datamodel_registry)
class TransactionDatamodelRegistryCase(common.TransactionCase, DatamodelRegistryCase):
"""Adds Odoo Transaction in the base Datamodel TestCase"""
# pylint: disable=W8106
@classmethod
def setUpClass(cls):
# resolve an inheritance issue (common.TransactionCase does not use
# super)
common.TransactionCase.setUpClass(cls)
DatamodelRegistryCase.setUp(cls)
cls.collection = cls.env["collection.base"]
@classmethod
def tearDownClass(cls):
common.TransactionCase.tearDownClass(cls)
DatamodelRegistryCase.tearDown(cls)

View file

@ -0,0 +1,486 @@
# Copyright 2017 Camptocamp SA
# Copyright 2019 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
from unittest import mock
from marshmallow_objects.models import Model as MarshmallowModel
from odoo import SUPERUSER_ID, api
from .. import fields
from ..core import Datamodel
from .common import DatamodelRegistryCase, TransactionDatamodelCase
class TestBuildDatamodel(DatamodelRegistryCase):
"""Test build of datamodels
All the tests in this suite are based on the same principle with
variations:
* Create new Datamodels (classes inheriting from
:class:`datamodel.core.Datamodel`
* Call :meth:`datamodel.core.Datamodel._build_datamodel` on them
in order to build the 'final class' composed from all the ``_inherit``
and push it in the datamodels registry (``self.datamodel_registry`` here)
* Assert that classes are built, registered, have correct ``__bases__``...
"""
def test_type(self):
"""Ensure that a datamodels are instances of
marshomallow_objects.Model"""
class Datamodel1(Datamodel):
_name = "datamodel1"
class Datamodel2(Datamodel):
_name = "datamodel2"
_inherit = "datamodel1"
self.assertIsInstance(Datamodel1(), MarshmallowModel)
self.assertIsInstance(Datamodel2(), MarshmallowModel)
def test_no_name(self):
"""Ensure that a datamodel has a _name"""
class Datamodel1(Datamodel):
pass
msg = ".*must have a _name.*"
with self.assertRaisesRegex(TypeError, msg):
Datamodel1._build_datamodel(self.datamodel_registry)
def test_register(self):
"""Able to register datamodels in datamodels registry"""
class Datamodel1(Datamodel):
_name = "datamodel1"
class Datamodel2(Datamodel):
_name = "datamodel2"
# build the 'final classes' for the datamodels and check that we find
# them in the datamodels registry
Datamodel1._build_datamodel(self.datamodel_registry)
Datamodel2._build_datamodel(self.datamodel_registry)
self.assertEqual(
["base", "datamodel1", "datamodel2"], list(self.datamodel_registry)
)
# pylint: disable=R7980
def test_inherit_bases(self):
"""Check __bases__ of Datamodel with _inherit"""
class Datamodel1(Datamodel):
_name = "datamodel1"
field_str1 = fields.String(load_default="field_str1")
class Datamodel2(Datamodel):
_inherit = "datamodel1" # pylint:disable=R8180
field_str2 = fields.String(load_default="field_str2")
class Datamodel3(Datamodel):
_inherit = "datamodel1" # pylint:disable=R8180
field_str3 = fields.String(load_default="field_str3")
Datamodel1._build_datamodel(self.datamodel_registry)
Datamodel2._build_datamodel(self.datamodel_registry)
Datamodel3._build_datamodel(self.datamodel_registry)
self.assertEqual(
(Datamodel3, Datamodel2, Datamodel1, self.env.datamodels["base"]),
self.env.datamodels["datamodel1"].__bases__,
)
def test_prototype_inherit_bases(self):
"""Check __bases__ of Datamodel with _inherit and different _name"""
class Datamodel1(Datamodel):
_name = "datamodel1"
field_int = fields.Int(load_default=1)
class Datamodel2(Datamodel):
_name = "datamodel2"
_inherit = "datamodel1"
field_boolean = fields.Boolean(load_default=True)
field_int = fields.Int(load_default=2)
class Datamodel3(Datamodel):
_name = "datamodel3"
_inherit = "datamodel1"
field_float = fields.Float(load_default=0.3)
class Datamodel4(Datamodel):
_name = "datamodel4"
_inherit = ["datamodel2", "datamodel3"]
Datamodel1._build_datamodel(self.datamodel_registry)
Datamodel2._build_datamodel(self.datamodel_registry)
Datamodel3._build_datamodel(self.datamodel_registry)
Datamodel4._build_datamodel(self.datamodel_registry)
self.assertEqual(
(Datamodel1, self.env.datamodels["base"]),
self.env.datamodels["datamodel1"].__bases__,
)
self.assertEqual(
(
Datamodel2,
self.env.datamodels["datamodel1"],
self.env.datamodels["base"],
),
self.env.datamodels["datamodel2"].__bases__,
)
self.assertEqual(
(
Datamodel3,
self.env.datamodels["datamodel1"],
self.env.datamodels["base"],
),
self.env.datamodels["datamodel3"].__bases__,
)
self.assertEqual(
(
Datamodel4,
self.env.datamodels["datamodel2"],
self.env.datamodels["datamodel3"],
self.env.datamodels["base"],
),
self.env.datamodels["datamodel4"].__bases__,
)
def test_final_class_schema(self):
"""Check the MArshmallow schema of the final class"""
class Datamodel1(Datamodel):
_name = "datamodel1"
field_int = fields.Int(load_default=1)
class Datamodel2(Datamodel):
_name = "datamodel2"
_inherit = "datamodel1"
field_boolean = fields.Boolean(load_default=True)
field_int = fields.Int(load_default=2)
class Datamodel3(Datamodel):
_name = "datamodel3"
_inherit = "datamodel1"
field_float = fields.Float(load_default=0.3)
class Datamodel4(Datamodel):
_name = "datamodel4"
_inherit = ["datamodel2", "datamodel3"]
Datamodel1._build_datamodel(self.datamodel_registry)
Datamodel2._build_datamodel(self.datamodel_registry)
Datamodel3._build_datamodel(self.datamodel_registry)
Datamodel4._build_datamodel(self.datamodel_registry)
Datamodel1 = self.env.datamodels["datamodel1"]
Datamodel2 = self.env.datamodels["datamodel2"]
Datamodel3 = self.env.datamodels["datamodel3"]
Datamodel4 = self.env.datamodels["datamodel4"]
self.assertEqual(Datamodel1().dump(), {"field_int": 1})
self.assertDictEqual(
Datamodel2().dump(), {"field_boolean": True, "field_int": 2}
)
self.assertDictEqual(Datamodel3().dump(), {"field_float": 0.3, "field_int": 1})
self.assertDictEqual(
Datamodel4().dump(),
{"field_boolean": True, "field_int": 2, "field_float": 0.3},
)
def test_custom_build(self):
"""Check that we can hook at the end of a Datamodel build"""
class Datamodel1(Datamodel):
_name = "datamodel1"
@classmethod
def _complete_datamodel_build(cls):
# This method should be called after the Datamodel
# is built, and before it is pushed in the registry
cls._build_done = True
Datamodel1._build_datamodel(self.datamodel_registry)
# we inspect that our custom build has been executed
self.assertTrue(self.env.datamodels["datamodel1"]._build_done)
# pylint: disable=W8110
def test_inherit_attrs(self):
"""Check attributes inheritance of Datamodels with _inherit"""
class Datamodel1(Datamodel):
_name = "datamodel1"
msg = "ping"
def say(self):
return "foo"
class Datamodel2(Datamodel):
_name = "datamodel2"
_inherit = "datamodel1"
msg = "pong"
def say(self):
return super(Datamodel2, self).say() + " bar"
Datamodel1._build_datamodel(self.datamodel_registry)
Datamodel2._build_datamodel(self.datamodel_registry)
# we initialize the datamodels, normally we should pass
# an instance of WorkContext, but we don't need a real one
# for this test
datamodel1 = self.env.datamodels["datamodel1"](mock.Mock())
datamodel2 = self.env.datamodels["datamodel2"](mock.Mock())
self.assertEqual("ping", datamodel1.msg)
self.assertEqual("pong", datamodel2.msg)
self.assertEqual("foo", datamodel1.say())
self.assertEqual("foo bar", datamodel2.say())
def test_duplicate_datamodel(self):
"""Check that we can't have 2 datamodels with the same name"""
class Datamodel1(Datamodel):
_name = "datamodel1"
class Datamodel2(Datamodel):
_name = "datamodel1"
Datamodel1._build_datamodel(self.datamodel_registry)
msg = "Datamodel.*already exists.*"
with self.assertRaisesRegex(TypeError, msg):
Datamodel2._build_datamodel(self.datamodel_registry)
def test_no_parent(self):
"""Ensure we can't _inherit a non-existent datamodel"""
class Datamodel1(Datamodel):
_name = "datamodel1"
_inherit = "datamodel1"
msg = "Datamodel.*does not exist in registry.*"
with self.assertRaisesRegex(TypeError, msg):
Datamodel1._build_datamodel(self.datamodel_registry)
def test_no_parent2(self):
"""Ensure we can't _inherit by prototype a non-existent datamodel"""
class Datamodel1(Datamodel):
_name = "datamodel1"
class Datamodel2(Datamodel):
_name = "datamodel2"
_inherit = ["datamodel1", "datamodel3"]
Datamodel1._build_datamodel(self.datamodel_registry)
msg = "Datamodel.*inherits from non-existing datamodel.*"
with self.assertRaisesRegex(TypeError, msg):
Datamodel2._build_datamodel(self.datamodel_registry)
def test_add_inheritance(self):
"""Ensure we can add a new inheritance"""
class Datamodel1(Datamodel):
_name = "datamodel1"
class Datamodel2(Datamodel):
_name = "datamodel2"
class Datamodel2bis(Datamodel):
_name = "datamodel2"
_inherit = ["datamodel2", "datamodel1"]
Datamodel1._build_datamodel(self.datamodel_registry)
Datamodel2._build_datamodel(self.datamodel_registry)
Datamodel2bis._build_datamodel(self.datamodel_registry)
self.assertEqual(
(
Datamodel2bis,
Datamodel2,
self.env.datamodels["datamodel1"],
self.env.datamodels.registry.get("base"),
),
self.env.datamodels["datamodel2"].__bases__,
)
def test_add_inheritance_final_schema(self):
"""Ensure that the Marshmallow schema is updated if we add a
new inheritance"""
class Datamodel1(Datamodel):
_name = "datamodel1"
field_str1 = fields.String(load_default="str1")
class Datamodel2(Datamodel):
_name = "datamodel2"
field_str2 = fields.String(load_default="str2")
class Datamodel2bis(Datamodel):
_name = "datamodel2"
_inherit = ["datamodel2", "datamodel1"]
field_str3 = fields.String(load_default="str3")
Datamodel1._build_datamodel(self.datamodel_registry)
Datamodel2._build_datamodel(self.datamodel_registry)
Datamodel2bis._build_datamodel(self.datamodel_registry)
Datamodel2 = self.env.datamodels["datamodel2"]
self.assertDictEqual(
Datamodel2().dump(),
{"field_str1": "str1", "field_str2": "str2", "field_str3": "str3"},
)
def test_recursion(self):
class Datamodel1(Datamodel):
_name = "datamodel1"
field_str = fields.String()
Datamodel1._build_datamodel(self.datamodel_registry)
for _i in range(0, 1000):
self.env.datamodels["datamodel1"](field_str="1234")
def test_nested_model(self):
"""Test nested model serialization/deserialization"""
class Parent(Datamodel):
_name = "parent"
name = fields.String()
child = fields.NestedModel("child")
class Child(Datamodel):
_name = "child"
field_str = fields.String()
Parent._build_datamodel(self.datamodel_registry)
Child._build_datamodel(self.datamodel_registry)
Parent = self.env.datamodels["parent"]
Child = self.env.datamodels["child"]
instance = Parent(name="Parent", child=Child(field_str="My other string"))
res = instance.dump()
self.assertDictEqual(
res, {"child": {"field_str": "My other string"}, "name": "Parent"}
)
new_instance = instance.load(res)
self.assertEqual(new_instance.name, instance.name)
self.assertEqual(new_instance.child.field_str, instance.child.field_str)
def test_list_nested_model(self):
"""Test list model of nested model serialization/deserialization"""
class Parent(Datamodel):
_name = "parent"
name = fields.String()
list_child = fields.List(fields.NestedModel("child"))
class Child(Datamodel):
_name = "child"
field_str = fields.String()
Parent._build_datamodel(self.datamodel_registry)
Child._build_datamodel(self.datamodel_registry)
Parent = self.env.datamodels["parent"]
Child = self.env.datamodels["child"]
childs = [
Child(field_str="My 1st other string"),
Child(field_str="My 2nd other string"),
]
instance = Parent(name="Parent", list_child=childs)
res = instance.dump()
self.assertDictEqual(
res,
{
"list_child": [
{"field_str": "My 1st other string"},
{"field_str": "My 2nd other string"},
],
"name": "Parent",
},
)
new_instance = instance.load(res)
self.assertEqual(new_instance.name, instance.name)
self.assertEqual(new_instance.list_child, instance.list_child)
def test_many(self):
"""Test loads of many"""
class Item(Datamodel):
_name = "item"
idx = fields.Integer()
Item._build_datamodel(self.datamodel_registry)
Item = self.env.datamodels["item"]
items = Item.load([{"idx": 1}, {"idx": 2}], many=True)
self.assertTrue(len(items), 2)
self.assertEqual([i.idx for i in items], [1, 2])
def test_nested_many(self):
"""Tests loads and dump of model with array of nested model"""
class Parent(Datamodel):
_name = "parent"
items = fields.NestedModel("item", many=True)
class Item(Datamodel):
_name = "item"
idx = fields.Integer()
Parent._build_datamodel(self.datamodel_registry)
Item._build_datamodel(self.datamodel_registry)
Parent = self.env.datamodels["parent"]
Item = self.env.datamodels["item"]
instance = Parent.load({"items": [{"idx": 1}, {"idx": 2}]})
res = instance.dump()
self.assertEqual(res, {"items": [{"idx": 1}, {"idx": 2}]})
new_instance = Parent.load(res)
self.assertEqual(len(new_instance.items), 2)
self.assertEqual([i.idx for i in new_instance.items], [1, 2])
new_instance.items.append(Item(idx=3))
res = new_instance.dump()
self.assertEqual(res, {"items": [{"idx": 1}, {"idx": 2}, {"idx": 3}]})
def test_env(self):
"""
Tests that the current env is always available on datamodel instances
and schema
"""
class Parent(Datamodel):
_name = "parent"
items = fields.NestedModel("item", many=True)
class Item(Datamodel):
_name = "item"
idx = fields.Integer()
Parent._build_datamodel(self.datamodel_registry)
Item._build_datamodel(self.datamodel_registry)
Parent = self.env.datamodels["parent"]
p = Parent()
self.assertEqual(p.env, self.env)
schema = Parent.get_schema()
self.assertEqual(schema._env, self.env)
instance = Parent.load({"items": [{"idx": 1}, {"idx": 2}]})
self.assertEqual(instance.items[0].env, self.env)
schema = instance.items[0].get_schema()
self.assertEqual(schema._env, self.env)
another_env = api.Environment(self.env.registry.cursor(), SUPERUSER_ID, {})
new_p = another_env.datamodels["parent"]()
self.assertEqual(new_p.env, another_env)
class TestRegistryAccess(TransactionDatamodelCase):
def test_registry_access(self):
"""Check the access to the registry directly on tnv"""
base = self.env.datamodels["base"]
self.assertIsInstance(base(), MarshmallowModel)