mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-18 03:12:04 +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
46
odoo-bringout-oca-rest-framework-datamodel/README.md
Normal file
46
odoo-bringout-oca-rest-framework-datamodel/README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Datamodel
|
||||
|
||||
Odoo addon: datamodel
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-rest-framework-datamodel
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Datamodel
|
||||
- **Version**: 16.0.1.0.2
|
||||
- **Category**: N/A
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/rest-framework](https://github.com/OCA/rest-framework) branch 16.0, addon `datamodel`.
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
178
odoo-bringout-oca-rest-framework-datamodel/datamodel/README.rst
Normal file
178
odoo-bringout-oca-rest-framework-datamodel/datamodel/README.rst
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
=========
|
||||
Datamodel
|
||||
=========
|
||||
|
||||
..
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:220702c6d930c27e2dbb4016e1f6bef6e1b9c7b96cff4b3c5ad27fdc5733ad26
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||
:target: https://odoo-community.org/page/development-status
|
||||
:alt: Beta
|
||||
.. |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%2Frest--framework-lightgray.png?logo=github
|
||||
:target: https://github.com/OCA/rest-framework/tree/16.0/datamodel
|
||||
:alt: OCA/rest-framework
|
||||
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
|
||||
:target: https://translation.odoo-community.org/projects/rest-framework-16-0/rest-framework-16-0-datamodel
|
||||
: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/rest-framework&target_branch=16.0
|
||||
:alt: Try me on Runboat
|
||||
|
||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||
|
||||
This addon allows you to define simple data models supporting serialization/deserialization
|
||||
to/from json
|
||||
|
||||
Datamodels are `Marshmallow models <https://github.com/sv-tools/marshmallow-objects>`_ classes that can be inherited as Odoo
|
||||
Models.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
To define your own datamodel you just need to create a class that inherits from
|
||||
``odoo.addons.datamodel.core.Datamodel``
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from marshmallow import fields
|
||||
|
||||
from odoo.addons.base_rest import restapi
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.datamodel.core import Datamodel
|
||||
|
||||
|
||||
class PartnerShortInfo(Datamodel):
|
||||
_name = "partner.short.info"
|
||||
|
||||
id = fields.Integer(required=True, allow_none=False)
|
||||
name = fields.String(required=True, allow_none=False)
|
||||
|
||||
class PartnerInfo(Datamodel):
|
||||
_name = "partner.info"
|
||||
_inherit = "partner.short.info"
|
||||
|
||||
street = fields.String(required=True, allow_none=False)
|
||||
street2 = fields.String(required=False, allow_none=True)
|
||||
zip_code = fields.String(required=True, allow_none=False)
|
||||
city = fields.String(required=True, allow_none=False)
|
||||
phone = fields.String(required=False, allow_none=True)
|
||||
is_componay = fields.Boolean(required=False, allow_none=False)
|
||||
|
||||
|
||||
As for odoo models, you can extend the `base` datamodel by inheriting of `base`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Base(Datamodel):
|
||||
_inherit = "base"
|
||||
|
||||
def _my_method(self):
|
||||
pass
|
||||
|
||||
Datamodels are available through the `datamodels` registry provided by the Odoo's environment.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ResPartner(Model):
|
||||
_inherit = "res.partner"
|
||||
|
||||
def _to_partner_info(self):
|
||||
PartnerInfo = self.env.datamodels["partner.info"]
|
||||
partner_info = PartnerInfo(partial=True)
|
||||
partner_info.id = partner.id
|
||||
partner_info.name = partner.name
|
||||
partner_info.street = partner.street
|
||||
partner_info.street2 = partner.street2
|
||||
partner_info.zip_code = partner.zip
|
||||
partner_info.city = partner.city
|
||||
partner_info.phone = partner.phone
|
||||
partner_info.is_company = partner.is_company
|
||||
return partner_info
|
||||
|
||||
The Odoo's environment is also available into the datamodel instance.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyDataModel(Datamodel):
|
||||
_name = "my.data.model"
|
||||
|
||||
def _my_method(self):
|
||||
partners = self.env["res.partner"].search([])
|
||||
|
||||
.. warning::
|
||||
|
||||
The `env` property into a Datamodel instance is mutable. IOW, you can't rely
|
||||
on information (context, user) provided by the environment. The `env` property
|
||||
is a helper property that give you access to the odoo's registry and must
|
||||
be use with caution.
|
||||
|
||||
Known issues / Roadmap
|
||||
======================
|
||||
|
||||
The `roadmap <https://github.com/OCA/rest-framework/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement+label%3Adatamodel>`_
|
||||
and `known issues <https://github.com/OCA/rest-framework/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Adatamodel>`_ can
|
||||
be found on GitHub.
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/rest-framework/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/rest-framework/issues/new?body=module:%20datamodel%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
|
||||
~~~~~~~
|
||||
|
||||
* ACSONE SA/NV
|
||||
|
||||
Contributors
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* Laurent Mignon <laurent.mignon@acsone.eu>
|
||||
* `Tecnativa <https://www.tecnativa.com>`_:
|
||||
|
||||
* Carlos Roca
|
||||
|
||||
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-lmignon| image:: https://github.com/lmignon.png?size=40px
|
||||
:target: https://github.com/lmignon
|
||||
:alt: lmignon
|
||||
|
||||
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|
||||
|
||||
|maintainer-lmignon|
|
||||
|
||||
This module is part of the `OCA/rest-framework <https://github.com/OCA/rest-framework/tree/16.0/datamodel>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
from . import builder
|
||||
from . import datamodels
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2019 ACSONE SA/NV
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
{
|
||||
"name": "Datamodel",
|
||||
"summary": """
|
||||
This addon allows you to define simple data models supporting
|
||||
serialization/deserialization""",
|
||||
"version": "16.0.1.0.2",
|
||||
"license": "LGPL-3",
|
||||
"development_status": "Beta",
|
||||
"author": "ACSONE SA/NV, " "Odoo Community Association (OCA)",
|
||||
"maintainers": ["lmignon"],
|
||||
"website": "https://github.com/OCA/rest-framework",
|
||||
"external_dependencies": {
|
||||
"python": ["marshmallow<4.0.0", "marshmallow-objects>=2.0.0"]
|
||||
},
|
||||
"installable": True,
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# Copyright 2019 ACSONE SA/NV
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
"""
|
||||
|
||||
Datamodels Builder
|
||||
==================
|
||||
|
||||
Build the datamodels at the build of a registry.
|
||||
|
||||
"""
|
||||
from odoo import models, modules
|
||||
|
||||
from .core import DEFAULT_CACHE_SIZE, DatamodelRegistry, _datamodel_databases
|
||||
|
||||
|
||||
class DatamodelBuilder(models.AbstractModel):
|
||||
"""Build the datamodel classes
|
||||
|
||||
And register them in a global registry.
|
||||
|
||||
Every time an Odoo registry is built, the know datamodels are cleared and
|
||||
rebuilt as well. The Datamodel classes are built using the same mechanism
|
||||
than Odoo's Models: a final class is created, taking every Datamodels with
|
||||
a ``_name`` and applying Datamodels with an ``_inherits`` upon them.
|
||||
|
||||
The final Datamodel classes are registered in global registry.
|
||||
|
||||
This class is an Odoo model, allowing us to hook the build of the
|
||||
datamodels 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 datamodels Classes and in the correct
|
||||
order.
|
||||
|
||||
"""
|
||||
|
||||
_name = "datamodel.builder"
|
||||
_description = "Datamodel Builder"
|
||||
|
||||
_datamodels_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 rebuild the datamodels. We use a new
|
||||
# registry so we have an empty cache and we'll add datamodels in it.
|
||||
datamodels_registry = self._init_global_registry()
|
||||
self.build_registry(datamodels_registry)
|
||||
datamodels_registry.ready = True
|
||||
|
||||
def _init_global_registry(self):
|
||||
datamodels_registry = DatamodelRegistry(
|
||||
cachesize=self._datamodels_registry_cache_size
|
||||
)
|
||||
_datamodel_databases[self.env.cr.dbname] = datamodels_registry
|
||||
return datamodels_registry
|
||||
|
||||
def build_registry(self, datamodels_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 datamodels following the order
|
||||
# of the addons' dependencies
|
||||
graph = 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_datamodels(module.name, datamodels_registry=datamodels_registry)
|
||||
|
||||
def load_datamodels(self, module, datamodels_registry=None):
|
||||
"""Build every datamodel known by MetaDatamodel for an odoo module
|
||||
|
||||
The final datamodel (composed by all the Datamodel classes in this
|
||||
module) will be pushed into the registry.
|
||||
|
||||
:param module: the name of the addon for which we want to load
|
||||
the datamodels
|
||||
:type module: str | unicode
|
||||
:param registry: the registry in which we want to put the Datamodel
|
||||
:type registry: :py:class:`~.core.DatamodelRegistry`
|
||||
"""
|
||||
datamodels_registry = (
|
||||
datamodels_registry or _datamodel_databases[self.env.cr.dbname]
|
||||
)
|
||||
datamodels_registry.load_datamodels(module)
|
||||
428
odoo-bringout-oca-rest-framework-datamodel/datamodel/core.py
Normal file
428
odoo-bringout-oca-rest-framework-datamodel/datamodel/core.py
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# Copyright 2019 ACSONE SA/NV
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
import logging
|
||||
from collections import OrderedDict, defaultdict
|
||||
from contextlib import ExitStack
|
||||
|
||||
from marshmallow import INCLUDE
|
||||
|
||||
from odoo.api import Environment
|
||||
from odoo.tools import LastOrderedSet, OrderedSet
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import marshmallow
|
||||
from marshmallow_objects.models import Model as MarshmallowModel, ModelMeta
|
||||
except ImportError:
|
||||
_logger.debug("Cannot import 'marshmallow_objects'.")
|
||||
|
||||
# The Cache size represents the number of items, so the number
|
||||
# of datamodels (include abstract datamodels) 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 = module_parts[2]
|
||||
else:
|
||||
addon_name = module_parts[0]
|
||||
return addon_name
|
||||
|
||||
|
||||
def _get_nested_schemas(schema):
|
||||
res = [schema]
|
||||
for field in schema.fields.values():
|
||||
if getattr(field, "schema", None):
|
||||
res += _get_nested_schemas(field.schema)
|
||||
return res
|
||||
|
||||
|
||||
class DatamodelDatabases(dict):
|
||||
"""Holds a registry of datamodels for each database"""
|
||||
|
||||
|
||||
class DatamodelRegistry(object):
|
||||
"""Store all the datamodel and allow to retrieve them by name
|
||||
|
||||
The key is the ``_name`` of the datamodels.
|
||||
|
||||
This is an OrderedDict, because we want to keep the registration order of
|
||||
the datamodels, addons loaded first have their datamodels found first.
|
||||
|
||||
The :attr:`ready` attribute must be set to ``True`` when all the datamodels
|
||||
are loaded.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, cachesize=DEFAULT_CACHE_SIZE):
|
||||
self._datamodels = OrderedDict()
|
||||
self._loaded_modules = set()
|
||||
self.ready = False
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._datamodels[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._datamodels[key] = value
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._datamodels
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self._datamodels.get(key, default)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._datamodels)
|
||||
|
||||
def load_datamodels(self, module):
|
||||
if module in self._loaded_modules:
|
||||
return
|
||||
for datamodel_class in MetaDatamodel._modules_datamodels[module]:
|
||||
datamodel_class._build_datamodel(self)
|
||||
self._loaded_modules.add(module)
|
||||
|
||||
|
||||
# We will store a DatamodeltRegistry per database here,
|
||||
# it will be cleared and updated when the odoo's registry is rebuilt
|
||||
_datamodel_databases = DatamodelDatabases()
|
||||
|
||||
|
||||
@marshmallow.post_load
|
||||
def __make_object__(self, data, **kwargs):
|
||||
datamodel = self._env.datamodels[self._datamodel_name]
|
||||
return datamodel(__post_load__=True, __schema__=self, **data)
|
||||
|
||||
|
||||
class MetaDatamodel(ModelMeta):
|
||||
"""Metaclass for Datamodel
|
||||
|
||||
Every new :class:`Datamodel` will be added to ``_modules_datamodels``,
|
||||
that will be used by the datamodel builder.
|
||||
|
||||
"""
|
||||
|
||||
_modules_datamodels = defaultdict(list)
|
||||
|
||||
def __init__(self, name, bases, attrs):
|
||||
|
||||
if not self._register:
|
||||
self._register = True
|
||||
super(MetaDatamodel, self).__init__(name, bases, attrs)
|
||||
|
||||
return
|
||||
|
||||
# If datamodels are declared in tests, exclude them from the
|
||||
# "datamodels of the addon" list. If not, when we use the
|
||||
# "load_datamodels" method, all the test datamodels 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 datamodels for the purpose of the test, then a
|
||||
# second tests uses the "load_datamodels" to load all the addons of the
|
||||
# module: it will load the datamodel of the previous test.
|
||||
if "tests" in self.__module__.split("."):
|
||||
return
|
||||
|
||||
if not hasattr(self, "_module"):
|
||||
self._module = _get_addon_name(self.__module__)
|
||||
|
||||
self._modules_datamodels[self._module].append(self)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Allow to set any field (including 'dump_only') at instantiation
|
||||
This is not an issue thanks to cleanup during (de)serialization
|
||||
"""
|
||||
kwargs["unknown"] = kwargs.get("unknown", INCLUDE)
|
||||
return super().__call__(*args, **kwargs)
|
||||
|
||||
|
||||
class Datamodel(MarshmallowModel, metaclass=MetaDatamodel):
|
||||
"""Main Datamodel Model
|
||||
|
||||
All datamodels have a Python inheritance either on
|
||||
:class:`Datamodel`.
|
||||
|
||||
Inheritance mechanism
|
||||
The inheritance mechanism is like the Odoo's one for Models. Each
|
||||
datamodel has a ``_name``. This is the absolute minimum in a Datamodel
|
||||
class.
|
||||
|
||||
::
|
||||
|
||||
from marshmallow import fields
|
||||
from odoo.addons.datamodel.core import Datamodel
|
||||
|
||||
class MyDatamodel(Datamodel):
|
||||
_name = 'my.datamodel'
|
||||
|
||||
name = fields.String()
|
||||
|
||||
Every datamodel implicitly inherit from the `'base'` datamodel.
|
||||
|
||||
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 datamodel we want to extend. With
|
||||
the following example, ``my.datamodel`` is now able to speak and to
|
||||
yell.
|
||||
|
||||
::
|
||||
|
||||
class MyDatamodel(Datamodel): # name of the class does not matter
|
||||
_inherit = 'my.datamodel'
|
||||
|
||||
|
||||
The second has a different ``_name``, it creates a new datamodel,
|
||||
including the behavior of the inherited datamodel, but without
|
||||
modifying it.
|
||||
|
||||
::
|
||||
|
||||
class AnotherDatamodel(Datamodel):
|
||||
_name = 'another.datamodel'
|
||||
_inherit = 'my.datamodel'
|
||||
|
||||
age = fields.Int()
|
||||
"""
|
||||
|
||||
_register = False
|
||||
_env = None # Odoo Environment
|
||||
|
||||
# used for inheritance
|
||||
_name = None #: Name of the datamodel
|
||||
|
||||
#: Name or list of names of the datamodel(s) to inherit from
|
||||
_inherit = None
|
||||
|
||||
def __init__(self, context=None, partial=None, env=None, **kwargs):
|
||||
self._env = env or type(self)._env
|
||||
super().__init__(context=context, partial=partial, **kwargs)
|
||||
|
||||
@property
|
||||
def env(self):
|
||||
return self._env
|
||||
|
||||
@classmethod
|
||||
def get_schema(cls, **kwargs):
|
||||
"""
|
||||
Get a marshmallow schema instance
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
return cls.__get_schema_class__(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data, context=None, many=None, partial=None, unknown=None):
|
||||
schema = cls.__get_schema_class__(
|
||||
context=context, partial=partial, unknown=unknown
|
||||
)
|
||||
all_schemas = _get_nested_schemas(schema)
|
||||
with ExitStack() as stack:
|
||||
# propagate 'unknown' to each nested schema during validate
|
||||
for nested_schema in all_schemas:
|
||||
stack.enter_context(cls.propagate_unknwown(nested_schema, unknown))
|
||||
return schema.validate(data, many=many, partial=partial)
|
||||
|
||||
@classmethod
|
||||
def _build_datamodel(cls, registry):
|
||||
"""Instantiate a given Datamodel in the datamodels registry.
|
||||
|
||||
This method is called at the end of the Odoo's registry build. The
|
||||
caller is :meth:`datamodel.builder.DatamodelBuilder.load_datamodels`.
|
||||
|
||||
It generates new classes, which will be the Datamodel classes we will
|
||||
be using. The new classes are generated following the inheritance
|
||||
of ``_inherit``. It ensures that the ``__bases__`` of the generated
|
||||
Datamodel classes follow the ``_inherit`` chain.
|
||||
|
||||
Once a Datamodel class is created, it adds it in the Datamodel Registry
|
||||
(:class:`DatamodelRegistry`), so it will be available for
|
||||
lookups.
|
||||
|
||||
At the end of new class creation, a hook method
|
||||
:meth:`_complete_datamodel_build` is called, so you can customize
|
||||
further the created datamodels.
|
||||
|
||||
The following code is roughly the same than the Odoo's one for
|
||||
building Models.
|
||||
|
||||
"""
|
||||
|
||||
# In the simplest case, the datamodel's registry class inherits from
|
||||
# cls and the other classes that define the datamodel in a flat
|
||||
# hierarchy. The registry contains the instance ``datamodel`` (on the
|
||||
# left). Its class, ``DatamodelClass``, carries inferred metadata that
|
||||
# is shared between all the datamodel's instances for this registry
|
||||
# only.
|
||||
#
|
||||
# class A1(Datamodel): Datamodel
|
||||
# _name = 'a' / | \
|
||||
# A3 A2 A1
|
||||
# class A2(Datamodel): \ | /
|
||||
# _inherit = 'a' DatamodelClass
|
||||
#
|
||||
# class A3(Datamodel):
|
||||
# _inherit = 'a'
|
||||
#
|
||||
# When a datamodel is extended by '_inherit', its base classes are
|
||||
# modified to include the current class and the other inherited
|
||||
# datamodel classes.
|
||||
# Note that we actually inherit from other ``DatamodelClass``, so that
|
||||
# extensions to an inherited datamodel are immediately visible in the
|
||||
# current datamodel class, like in the following example:
|
||||
#
|
||||
# class A1(Datamodel):
|
||||
# _name = 'a' Datamodel
|
||||
# / / \ \
|
||||
# class B1(Datamodel): / A2 A1 \
|
||||
# _name = 'b' / \ / \
|
||||
# B2 DatamodelA B1
|
||||
# class B2(Datamodel): \ | /
|
||||
# _name = 'b' \ | /
|
||||
# _inherit = ['b', 'a'] \ | /
|
||||
# DatamodelB
|
||||
# class A2(Datamodel):
|
||||
# _inherit = 'a'
|
||||
|
||||
# determine inherited datamodels
|
||||
parents = cls._inherit
|
||||
if isinstance(parents, str):
|
||||
parents = [parents]
|
||||
elif parents is None:
|
||||
parents = []
|
||||
|
||||
if cls._name in registry and not parents:
|
||||
raise TypeError(
|
||||
"Datamodel %r (in class %r) already exists. "
|
||||
"Consider using _inherit instead of _name "
|
||||
"or using a different _name." % (cls._name, cls)
|
||||
)
|
||||
|
||||
# determine the datamodel's name
|
||||
name = cls._name or (len(parents) == 1 and parents[0])
|
||||
|
||||
if not name:
|
||||
raise TypeError("Datamodel %r must have a _name" % cls)
|
||||
|
||||
# all datamodels except 'base' implicitly inherit from 'base'
|
||||
if name != "base":
|
||||
parents = list(parents) + ["base"]
|
||||
|
||||
# create or retrieve the datamodel's class
|
||||
if name in parents:
|
||||
if name not in registry:
|
||||
raise TypeError("Datamodel %r does not exist in registry." % name)
|
||||
|
||||
# determine all the classes the datamodel should inherit from
|
||||
bases = LastOrderedSet([cls])
|
||||
for parent in parents:
|
||||
if parent not in registry:
|
||||
raise TypeError(
|
||||
"Datamodel %r inherits from non-existing datamodel %r."
|
||||
% (name, parent)
|
||||
)
|
||||
parent_class = registry[parent]
|
||||
if parent == name:
|
||||
for base in parent_class.__bases__:
|
||||
bases.add(base)
|
||||
else:
|
||||
bases.add(parent_class)
|
||||
parent_class._inherit_children.add(name)
|
||||
|
||||
if name in parents:
|
||||
DatamodelClass = registry[name]
|
||||
# Add the new bases to the existing model since the class into
|
||||
# the registry could already be used into an inherit
|
||||
DatamodelClass.__bases__ = tuple(bases)
|
||||
# We must update the marshmallow schema on the existing datamodel
|
||||
# class to include those inherited
|
||||
parent_schemas = []
|
||||
for parent in bases:
|
||||
if issubclass(parent, MarshmallowModel):
|
||||
parent_schemas.append(parent.__schema_class__)
|
||||
schema_class = type(name + "Schema", tuple(parent_schemas), {})
|
||||
DatamodelClass.__schema_class__ = schema_class
|
||||
else:
|
||||
attrs = {
|
||||
"_name": name,
|
||||
"_register": False,
|
||||
# names of children datamodel
|
||||
"_inherit_children": OrderedSet(),
|
||||
}
|
||||
if name == "base":
|
||||
attrs["_registry"] = registry
|
||||
DatamodelClass = type(name, tuple(bases), attrs)
|
||||
|
||||
setattr(DatamodelClass.__schema_class__, "_registry", registry) # noqa: B010
|
||||
setattr(DatamodelClass.__schema_class__, "_datamodel_name", name) # noqa: B010
|
||||
setattr( # noqa: B010
|
||||
DatamodelClass.__schema_class__, "__make_object__", __make_object__
|
||||
)
|
||||
DatamodelClass._complete_datamodel_build()
|
||||
|
||||
registry[name] = DatamodelClass
|
||||
|
||||
return DatamodelClass
|
||||
|
||||
@classmethod
|
||||
def _complete_datamodel_build(cls):
|
||||
"""Complete build of the new datamodel class
|
||||
|
||||
After the datamodel 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 Datamodel, but a Datamodel can inherit
|
||||
the method to add its own behavior.
|
||||
"""
|
||||
|
||||
|
||||
# makes the datamodels registry available on env
|
||||
|
||||
|
||||
class DataModelFactory(object):
|
||||
"""Factory for datamodels
|
||||
|
||||
This factory ensures the propagation of the environment to the
|
||||
instanciated datamodels and related schema.
|
||||
"""
|
||||
|
||||
__slots__ = ("env", "registry")
|
||||
|
||||
def __init__(self, env, registry):
|
||||
self.env = env
|
||||
self.registry = registry
|
||||
|
||||
def __getitem__(self, key):
|
||||
model = self.registry[key]
|
||||
model._env = self.env
|
||||
|
||||
@classmethod
|
||||
def __get_schema_class__(cls, **kwargs):
|
||||
cls = cls.__schema_class__(**kwargs)
|
||||
cls._env = self.env
|
||||
return cls
|
||||
|
||||
model.__get_schema_class__ = __get_schema_class__
|
||||
return model
|
||||
|
||||
|
||||
@property
|
||||
def datamodels(self):
|
||||
if not hasattr(self, "_datamodels_factory"):
|
||||
factory = DataModelFactory(self, _datamodel_databases.get(self.cr.dbname))
|
||||
self._datamodels_factory = factory
|
||||
return self._datamodels_factory
|
||||
|
||||
|
||||
Environment.datamodels = datamodels
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import base
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# Copyright 2019 ACSONE SA/NV
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from ..core import Datamodel
|
||||
|
||||
|
||||
class BaseDatamodel(Datamodel):
|
||||
"""This is the base datamodel for every datamodel
|
||||
|
||||
It is implicitely inherited by all datamodels.
|
||||
|
||||
All your base are belong to us
|
||||
"""
|
||||
|
||||
_name = "base"
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
# Copyright 2019 ACSONE SA/NV
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
"""
|
||||
|
||||
Fields
|
||||
=====
|
||||
|
||||
Create a single place for all fields defined for datamodels
|
||||
|
||||
"""
|
||||
import logging
|
||||
|
||||
from .core import Datamodel
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from marshmallow.fields import * # noqa: F403,F401
|
||||
from marshmallow.fields import Nested
|
||||
except ImportError:
|
||||
Nested = object
|
||||
_logger.debug("Cannot import 'marshmallow'.")
|
||||
|
||||
|
||||
class NestedModel(Nested):
|
||||
def __init__(self, nested, **kwargs):
|
||||
self.datamodel_name = nested
|
||||
super(NestedModel, self).__init__(None, **kwargs)
|
||||
|
||||
@property
|
||||
def schema(self):
|
||||
if not self.nested:
|
||||
# Get the major parent to avoid error of _env does not exist
|
||||
super_parent = None
|
||||
parent = self
|
||||
while not super_parent:
|
||||
if not hasattr(parent, "parent"):
|
||||
super_parent = parent
|
||||
break
|
||||
parent = parent.parent
|
||||
self.nested = super_parent._env.datamodels[
|
||||
self.datamodel_name
|
||||
].__schema_class__
|
||||
self.nested._env = super_parent._env
|
||||
return super(NestedModel, self).schema
|
||||
|
||||
def _deserialize(self, value, attr, data, **kwargs):
|
||||
if isinstance(value, Datamodel):
|
||||
return value
|
||||
return super(NestedModel, self)._deserialize(value, attr, data, **kwargs)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * datamodel
|
||||
#
|
||||
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: datamodel
|
||||
#: model:ir.model,name:datamodel.model_datamodel_builder
|
||||
msgid "Datamodel Builder"
|
||||
msgstr "Konstruktor modela podataka"
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * datamodel
|
||||
#
|
||||
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: datamodel
|
||||
#: model:ir.model,name:datamodel.model_datamodel_builder
|
||||
msgid "Datamodel Builder"
|
||||
msgstr ""
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * datamodel
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-01-15 11:35+0000\n"
|
||||
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
|
||||
"Language-Team: none\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: datamodel
|
||||
#: model:ir.model,name:datamodel.model_datamodel_builder
|
||||
msgid "Datamodel Builder"
|
||||
msgstr "Costruttore datamodel"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
* Laurent Mignon <laurent.mignon@acsone.eu>
|
||||
* `Tecnativa <https://www.tecnativa.com>`_:
|
||||
|
||||
* Carlos Roca
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
This addon allows you to define simple data models supporting serialization/deserialization
|
||||
to/from json
|
||||
|
||||
Datamodels are `Marshmallow models <https://github.com/sv-tools/marshmallow-objects>`_ classes that can be inherited as Odoo
|
||||
Models.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
The `roadmap <https://github.com/OCA/rest-framework/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement+label%3Adatamodel>`_
|
||||
and `known issues <https://github.com/OCA/rest-framework/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Adatamodel>`_ can
|
||||
be found on GitHub.
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
To define your own datamodel you just need to create a class that inherits from
|
||||
``odoo.addons.datamodel.core.Datamodel``
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from marshmallow import fields
|
||||
|
||||
from odoo.addons.base_rest import restapi
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.datamodel.core import Datamodel
|
||||
|
||||
|
||||
class PartnerShortInfo(Datamodel):
|
||||
_name = "partner.short.info"
|
||||
|
||||
id = fields.Integer(required=True, allow_none=False)
|
||||
name = fields.String(required=True, allow_none=False)
|
||||
|
||||
class PartnerInfo(Datamodel):
|
||||
_name = "partner.info"
|
||||
_inherit = "partner.short.info"
|
||||
|
||||
street = fields.String(required=True, allow_none=False)
|
||||
street2 = fields.String(required=False, allow_none=True)
|
||||
zip_code = fields.String(required=True, allow_none=False)
|
||||
city = fields.String(required=True, allow_none=False)
|
||||
phone = fields.String(required=False, allow_none=True)
|
||||
is_componay = fields.Boolean(required=False, allow_none=False)
|
||||
|
||||
|
||||
As for odoo models, you can extend the `base` datamodel by inheriting of `base`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Base(Datamodel):
|
||||
_inherit = "base"
|
||||
|
||||
def _my_method(self):
|
||||
pass
|
||||
|
||||
Datamodels are available through the `datamodels` registry provided by the Odoo's environment.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ResPartner(Model):
|
||||
_inherit = "res.partner"
|
||||
|
||||
def _to_partner_info(self):
|
||||
PartnerInfo = self.env.datamodels["partner.info"]
|
||||
partner_info = PartnerInfo(partial=True)
|
||||
partner_info.id = partner.id
|
||||
partner_info.name = partner.name
|
||||
partner_info.street = partner.street
|
||||
partner_info.street2 = partner.street2
|
||||
partner_info.zip_code = partner.zip
|
||||
partner_info.city = partner.city
|
||||
partner_info.phone = partner.phone
|
||||
partner_info.is_company = partner.is_company
|
||||
return partner_info
|
||||
|
||||
The Odoo's environment is also available into the datamodel instance.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyDataModel(Datamodel):
|
||||
_name = "my.data.model"
|
||||
|
||||
def _my_method(self):
|
||||
partners = self.env["res.partner"].search([])
|
||||
|
||||
.. warning::
|
||||
|
||||
The `env` property into a Datamodel instance is mutable. IOW, you can't rely
|
||||
on information (context, user) provided by the environment. The `env` property
|
||||
is a helper property that give you access to the odoo's registry and must
|
||||
be use with caution.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
|
|
@ -0,0 +1,515 @@
|
|||
<!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>Datamodel</title>
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
Despite the name, some widely supported CSS2 features are used.
|
||||
|
||||
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: gray; } /* 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, pre.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="datamodel">
|
||||
<h1 class="title">Datamodel</h1>
|
||||
|
||||
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:220702c6d930c27e2dbb4016e1f6bef6e1b9c7b96cff4b3c5ad27fdc5733ad26
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.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/rest-framework/tree/16.0/datamodel"><img alt="OCA/rest-framework" src="https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/rest-framework-16-0/rest-framework-16-0-datamodel"><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/rest-framework&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 addon allows you to define simple data models supporting serialization/deserialization
|
||||
to/from json</p>
|
||||
<p>Datamodels are <a class="reference external" href="https://github.com/sv-tools/marshmallow-objects">Marshmallow models</a> classes that can be inherited as Odoo
|
||||
Models.</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="#known-issues-roadmap" id="toc-entry-2">Known issues / Roadmap</a></li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">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>To define your own datamodel you just need to create a class that inherits from
|
||||
<tt class="docutils literal">odoo.addons.datamodel.core.Datamodel</tt></p>
|
||||
<pre class="code python literal-block">
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">marshmallow</span><span class="w"> </span><span class="kn">import</span> <span class="n">fields</span><span class="w">
|
||||
|
||||
</span><span class="kn">from</span><span class="w"> </span><span class="nn">odoo.addons.base_rest</span><span class="w"> </span><span class="kn">import</span> <span class="n">restapi</span><span class="w">
|
||||
</span><span class="kn">from</span><span class="w"> </span><span class="nn">odoo.addons.component.core</span><span class="w"> </span><span class="kn">import</span> <span class="n">Component</span><span class="w">
|
||||
</span><span class="kn">from</span><span class="w"> </span><span class="nn">odoo.addons.datamodel.core</span><span class="w"> </span><span class="kn">import</span> <span class="n">Datamodel</span><span class="w">
|
||||
|
||||
|
||||
</span><span class="k">class</span><span class="w"> </span><span class="nc">PartnerShortInfo</span><span class="p">(</span><span class="n">Datamodel</span><span class="p">):</span><span class="w">
|
||||
</span> <span class="n">_name</span> <span class="o">=</span> <span class="s2">"partner.short.info"</span><span class="w">
|
||||
|
||||
</span> <span class="nb">id</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">Integer</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">allow_none</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span><span class="w">
|
||||
</span> <span class="n">name</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">allow_none</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span><span class="w">
|
||||
|
||||
</span><span class="k">class</span><span class="w"> </span><span class="nc">PartnerInfo</span><span class="p">(</span><span class="n">Datamodel</span><span class="p">):</span><span class="w">
|
||||
</span> <span class="n">_name</span> <span class="o">=</span> <span class="s2">"partner.info"</span><span class="w">
|
||||
</span> <span class="n">_inherit</span> <span class="o">=</span> <span class="s2">"partner.short.info"</span><span class="w">
|
||||
|
||||
</span> <span class="n">street</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">allow_none</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span><span class="w">
|
||||
</span> <span class="n">street2</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">allow_none</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span><span class="w">
|
||||
</span> <span class="n">zip_code</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">allow_none</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span><span class="w">
|
||||
</span> <span class="n">city</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">allow_none</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span><span class="w">
|
||||
</span> <span class="n">phone</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">allow_none</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span><span class="w">
|
||||
</span> <span class="n">is_componay</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">Boolean</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">allow_none</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
|
||||
</pre>
|
||||
<p>As for odoo models, you can extend the <cite>base</cite> datamodel by inheriting of <cite>base</cite>.</p>
|
||||
<pre class="code python literal-block">
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">Base</span><span class="p">(</span><span class="n">Datamodel</span><span class="p">):</span><span class="w">
|
||||
</span> <span class="n">_inherit</span> <span class="o">=</span> <span class="s2">"base"</span><span class="w">
|
||||
|
||||
</span> <span class="k">def</span><span class="w"> </span><span class="nf">_my_method</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span><span class="w">
|
||||
</span> <span class="k">pass</span>
|
||||
</pre>
|
||||
<p>Datamodels are available through the <cite>datamodels</cite> registry provided by the Odoo’s environment.</p>
|
||||
<pre class="code python literal-block">
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">ResPartner</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span><span class="w">
|
||||
</span> <span class="n">_inherit</span> <span class="o">=</span> <span class="s2">"res.partner"</span><span class="w">
|
||||
|
||||
</span> <span class="k">def</span><span class="w"> </span><span class="nf">_to_partner_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span><span class="w">
|
||||
</span> <span class="n">PartnerInfo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">datamodels</span><span class="p">[</span><span class="s2">"partner.info"</span><span class="p">]</span><span class="w">
|
||||
</span> <span class="n">partner_info</span> <span class="o">=</span> <span class="n">PartnerInfo</span><span class="p">(</span><span class="n">partial</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span><span class="w">
|
||||
</span> <span class="n">partner_info</span><span class="o">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">partner</span><span class="o">.</span><span class="n">id</span><span class="w">
|
||||
</span> <span class="n">partner_info</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">partner</span><span class="o">.</span><span class="n">name</span><span class="w">
|
||||
</span> <span class="n">partner_info</span><span class="o">.</span><span class="n">street</span> <span class="o">=</span> <span class="n">partner</span><span class="o">.</span><span class="n">street</span><span class="w">
|
||||
</span> <span class="n">partner_info</span><span class="o">.</span><span class="n">street2</span> <span class="o">=</span> <span class="n">partner</span><span class="o">.</span><span class="n">street2</span><span class="w">
|
||||
</span> <span class="n">partner_info</span><span class="o">.</span><span class="n">zip_code</span> <span class="o">=</span> <span class="n">partner</span><span class="o">.</span><span class="n">zip</span><span class="w">
|
||||
</span> <span class="n">partner_info</span><span class="o">.</span><span class="n">city</span> <span class="o">=</span> <span class="n">partner</span><span class="o">.</span><span class="n">city</span><span class="w">
|
||||
</span> <span class="n">partner_info</span><span class="o">.</span><span class="n">phone</span> <span class="o">=</span> <span class="n">partner</span><span class="o">.</span><span class="n">phone</span><span class="w">
|
||||
</span> <span class="n">partner_info</span><span class="o">.</span><span class="n">is_company</span> <span class="o">=</span> <span class="n">partner</span><span class="o">.</span><span class="n">is_company</span><span class="w">
|
||||
</span> <span class="k">return</span> <span class="n">partner_info</span>
|
||||
</pre>
|
||||
<p>The Odoo’s environment is also available into the datamodel instance.</p>
|
||||
<pre class="code python literal-block">
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">MyDataModel</span><span class="p">(</span><span class="n">Datamodel</span><span class="p">):</span><span class="w">
|
||||
</span> <span class="n">_name</span> <span class="o">=</span> <span class="s2">"my.data.model"</span><span class="w">
|
||||
|
||||
</span> <span class="k">def</span><span class="w"> </span><span class="nf">_my_method</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span><span class="w">
|
||||
</span> <span class="n">partners</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="p">[</span><span class="s2">"res.partner"</span><span class="p">]</span><span class="o">.</span><span class="n">search</span><span class="p">([])</span>
|
||||
</pre>
|
||||
<div class="admonition warning">
|
||||
<p class="first admonition-title">Warning</p>
|
||||
<p class="last">The <cite>env</cite> property into a Datamodel instance is mutable. IOW, you can’t rely
|
||||
on information (context, user) provided by the environment. The <cite>env</cite> property
|
||||
is a helper property that give you access to the odoo’s registry and must
|
||||
be use with caution.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="known-issues-roadmap">
|
||||
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
|
||||
<p>The <a class="reference external" href="https://github.com/OCA/rest-framework/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement+label%3Adatamodel">roadmap</a>
|
||||
and <a class="reference external" href="https://github.com/OCA/rest-framework/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Adatamodel">known issues</a> can
|
||||
be found on GitHub.</p>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
|
||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/rest-framework/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/rest-framework/issues/new?body=module:%20datamodel%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-4">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>ACSONE SA/NV</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
|
||||
<ul>
|
||||
<li><p class="first">Laurent Mignon <<a class="reference external" href="mailto:laurent.mignon@acsone.eu">laurent.mignon@acsone.eu</a>></p>
|
||||
</li>
|
||||
<li><p class="first"><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:</p>
|
||||
<blockquote>
|
||||
<ul class="simple">
|
||||
<li>Carlos Roca</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2><a class="toc-backref" href="#toc-entry-7">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/lmignon"><img alt="lmignon" src="https://github.com/lmignon.png?size=40px" /></a></p>
|
||||
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/rest-framework/tree/16.0/datamodel">OCA/rest-framework</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 @@
|
|||
from . import test_build_datamodel
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
U[Users] -->|HTTP| V[Views and QWeb Templates]
|
||||
V --> C[Controllers]
|
||||
V --> W[Wizards – Transient Models]
|
||||
C --> M[Models and ORM]
|
||||
W --> M
|
||||
M --> R[Reports]
|
||||
DX[Data XML] --> M
|
||||
S[Security – ACLs and Groups] -. enforces .-> M
|
||||
|
||||
subgraph Datamodel Module - datamodel
|
||||
direction LR
|
||||
M:::layer
|
||||
W:::layer
|
||||
C:::layer
|
||||
V:::layer
|
||||
R:::layer
|
||||
S:::layer
|
||||
DX:::layer
|
||||
end
|
||||
|
||||
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
|
||||
```
|
||||
|
||||
Notes
|
||||
- Views include tree/form/kanban templates and report templates.
|
||||
- Controllers provide website/portal routes when present.
|
||||
- Wizards are UI flows implemented with `models.TransientModel`.
|
||||
- Data XML loads data/demo records; Security defines groups and access.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for datamodel. Configure related models, access rights, and options as needed.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Controllers
|
||||
|
||||
This module does not define custom HTTP controllers.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Dependencies
|
||||
|
||||
No explicit module dependencies declared.
|
||||
4
odoo-bringout-oca-rest-framework-datamodel/doc/FAQ.md
Normal file
4
odoo-bringout-oca-rest-framework-datamodel/doc/FAQ.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# FAQ
|
||||
|
||||
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
|
||||
- Q: How to enable? A: Start server with --addon datamodel or install in UI.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Install
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-rest-framework-datamodel"
|
||||
# or
|
||||
uv pip install odoo-bringout-oca-rest-framework-datamodel"
|
||||
```
|
||||
11
odoo-bringout-oca-rest-framework-datamodel/doc/MODELS.md
Normal file
11
odoo-bringout-oca-rest-framework-datamodel/doc/MODELS.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Models
|
||||
|
||||
Detected core models and extensions in datamodel.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
```
|
||||
|
||||
Notes
|
||||
- Classes show model technical names; fields omitted for brevity.
|
||||
- Items listed under _inherit are extensions of existing models.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Overview
|
||||
|
||||
Packaged Odoo addon: datamodel. Provides features documented in upstream Odoo 16 under this addon.
|
||||
|
||||
- Source: OCA/OCB 16.0, addon datamodel
|
||||
- License: LGPL-3
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Reports
|
||||
|
||||
This module does not define custom reports.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Security
|
||||
|
||||
This module does not define custom security rules or access controls beyond Odoo defaults.
|
||||
|
||||
Default Odoo security applies:
|
||||
- Base user access through standard groups
|
||||
- Model access inherited from dependencies
|
||||
- No custom row-level security rules
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Troubleshooting
|
||||
|
||||
- Ensure Python and Odoo environment matches repo guidance.
|
||||
- Check database connectivity and logs if startup fails.
|
||||
- Validate that dependent addons listed in DEPENDENCIES.md are installed.
|
||||
7
odoo-bringout-oca-rest-framework-datamodel/doc/USAGE.md
Normal file
7
odoo-bringout-oca-rest-framework-datamodel/doc/USAGE.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Usage
|
||||
|
||||
Start Odoo including this addon (from repo root):
|
||||
|
||||
```bash
|
||||
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon datamodel
|
||||
```
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Wizards
|
||||
|
||||
This module does not include UI wizards.
|
||||
43
odoo-bringout-oca-rest-framework-datamodel/pyproject.toml
Normal file
43
odoo-bringout-oca-rest-framework-datamodel/pyproject.toml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-rest-framework-datamodel"
|
||||
version = "16.0.0"
|
||||
description = "Datamodel -
|
||||
This addon allows you to define simple data models supporting
|
||||
serialization/deserialization"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">= 3.11"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://github.com/bringout/0"
|
||||
repository = "https://github.com/bringout/0"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["datamodel"]
|
||||
|
||||
[tool.rye]
|
||||
managed = true
|
||||
dev-dependencies = [
|
||||
"pytest>=8.4.1",
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue