Skip to content
Snippets Groups Projects
model.py 6.04 KiB
Newer Older
##############################################################################
#
#    Converter Odoo module
#    Copyright (C) 2020 XCG Consulting <https://xcg-consulting.fr>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
import logging
Christophe de Vienne's avatar
Christophe de Vienne committed
from collections import OrderedDict
from collections.abc import Iterable, Mapping
Christophe de Vienne's avatar
Christophe de Vienne committed

import jsonschema
Christophe de Vienne's avatar
Christophe de Vienne committed

Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
from odoo import api, models
Christophe de Vienne's avatar
Christophe de Vienne committed

from .base import (
    ContextBuilder,
    Converter,
    Newinstance,
    NewinstanceType,
    Skip,
    build_context,
)
Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
from .validate import VALIDATION_SKIP, VALIDATION_STRICT, Validator
Christophe de Vienne's avatar
Christophe de Vienne committed

Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
_logger = logging.getLogger(__name__)

Christophe de Vienne's avatar
Christophe de Vienne committed

class Model(Converter):
    """A converter that takes a dict of key, used when a message has values"""

    def __init__(
        self,
        __type__: str,
        converters: Mapping[str, Converter],
Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
        json_schema: Optional[str] = None,
        # The validator is usually not given at this point but is common
        # throughout a project. That’s why it is a property
        validator: Optional[Validator] = None,
        merge_with: Optional[Iterable[Converter]] = None,
Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
        validation: str = VALIDATION_SKIP,
Christophe de Vienne's avatar
Christophe de Vienne committed
        context: Optional[ContextBuilder] = None,
    ):
        self._type: str = __type__
        self._converters: Mapping[str, Converter] = converters
        self._post_hooks_converters_key: list[str] = []
Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
        self._jsonschema: Optional[str] = json_schema
        self._get_instance: Converter = None
        """First converter whose `is_instance_getter` is true if any"""
        self.merge_with: Optional[Iterable[Converter]] = merge_with
        self.context: Optional[ContextBuilder] = context
Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
        self.validator: Optional[Validator] = validator
        self.validation = validation
Christophe de Vienne's avatar
Christophe de Vienne committed
        for key, converter in converters.items():
            if self._get_instance is None and converter.is_instance_getter():
                self._get_instance = key
            if hasattr(converter, "post_hook"):
                self._post_hooks_converters_key.append(key)

    def odoo_to_message(
        self, instance: models.Model, ctx: Optional[dict] = None
Christophe de Vienne's avatar
Christophe de Vienne committed
    ) -> Any:
        ctx = build_context(instance, ctx, self.context)

        message_data = {}
        if self._type:
            message_data["__type__"] = self._type

        for key in self._converters:
            value = self._converters[key].odoo_to_message(instance, ctx)
            if value is not Skip:
                message_data[key] = value

        if self.merge_with:
            for conv in self.merge_with:
                value = conv.odoo_to_message(instance, ctx)
                if value is Skip:
                    continue
                message_data.update(value)

Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
        if self.validation != VALIDATION_SKIP and self._jsonschema is not None:
            try:
                self.validator.validate(self._jsonschema, message_data)
            except jsonschema.exceptions.ValidationError as exception:
                _logger.warning("Validation failed", exc_info=1)
Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
                if self.validation == VALIDATION_STRICT:
                    raise exception

Christophe de Vienne's avatar
Christophe de Vienne committed
        return message_data

    def message_to_odoo(
        self,
Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
        odoo_env: api.Environment,
Christophe de Vienne's avatar
Christophe de Vienne committed
        phase: str,
Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
        message_value: Any,
Christophe de Vienne's avatar
Christophe de Vienne committed
        instance: models.Model,
        value_present: bool = True,
Christophe de Vienne's avatar
Christophe de Vienne committed
        values = OrderedDict()

        if self._type and message_value["__type__"] != self._type:
            raise Exception(
                "Expected __type__ {}, found {}".format(
                    self._type, message_value["__type__"]
                )
Christophe de Vienne's avatar
Christophe de Vienne committed
            )
        for key in self._converters:
            value = message_value.get(key, None) if message_value else None
            values.update(
                self._converters[key].message_to_odoo(
                    odoo_env,
                    phase,
                    value,
                    instance,
                    message_value and key in message_value,
                )
            )
        if self.merge_with:
            for conv in self.merge_with:
                value = conv.message_to_odoo(
                    odoo_env, phase, message_value, instance, value_present
                )
                if value is Skip:
                    continue
                values.update(value)

        return values

    def is_instance_getter(self) -> bool:
        return self._get_instance is not None

    def get_instance(
        self, odoo_env: api.Environment, message_data
    ) -> None | models.Model | NewinstanceType:
Christophe de Vienne's avatar
Christophe de Vienne committed
        """:return: an instance linked to the converter, if any"""
        if self._get_instance:
            instance = self._converters[self._get_instance].get_instance(
                odoo_env, message_data[self._get_instance]
            )
            if instance is None:
                instance = Newinstance
Christophe de Vienne's avatar
Christophe de Vienne committed
            return instance
        return None

    def post_hook(self, instance: models.Model, message_data):
        for key in self._post_hooks_converters_key:
            if key in message_data:
                self._converters[key].post_hook(instance, message_data[key])
        if self.merge_with:
            for converter in self.merge_with:
                if hasattr(converter, "post_hook"):
                    converter.post_hook(instance, message_data)
Christophe de Vienne's avatar
Christophe de Vienne committed

Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
        return {self._type}