oca-technical/odoo-bringout-oca-rest-framework-base_rest/base_rest/controllers/main.py
2025-08-29 15:43:03 +02:00

201 lines
7.2 KiB
Python

# Copyright 2018 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging
from contextlib import contextmanager
from werkzeug.exceptions import BadRequest
from odoo import models
from odoo.http import Controller, Response, request
from odoo.addons.component.core import WorkContext, _get_addon_name
from ..core import _rest_controllers_per_module
_logger = logging.getLogger(__name__)
class _PseudoCollection(object):
__slots__ = "_name", "env", "id"
def __init__(self, name, env):
self._name = name
self.env = env
self.id = None
class RestController(Controller):
"""Generic REST Controller
This controller is the base controller used by as base controller for all the REST
controller generated from the service components.
You must inherit of this controller into your code to register the root path
used to serve all the services defined for the given collection name.
This registration requires 2 parameters:
_root_path:
_collection_name:
Only one controller by _collection_name, _root_path should exists into an
odoo database. If more than one controller exists, a warning is issued into
the log at startup and the concrete controller used as base class
for the services registered into the collection name and served at the
root path is not predictable.
Module A:
class ControllerA(RestController):
_root_path='/my_path/'
_collection_name='my_services_collection'
Module B depends A: A
class ControllerB(ControllerA): / \
pass B C
/
Module C depends A: D
class ControllerC(ControllerA):
pass
Module D depends B:
class ControllerB(ControllerB):
pass
In the preceding illustration, services in module C will never be served
by controller D or B. Therefore if the generic dispatch method is overridden
in B or D, this override wil never apply to services in C since in Odoo
controllers are not designed to be inherited. That's why it's an error
to have more than one controller registered for the same root path and
collection name.
The following properties can be specified to define common properties to
apply to generated REST routes.
_default_auth: The default authentication to apply to all pre defined routes.
default: 'user'
_default_cors: The default Access-Control-Allow-Origin cors directive value.
default: None
_default_csrf: Whether CSRF protection should be enabled for the route.
default: False
_default_save_session: Whether session should be saved into the session store
default: True
"""
_root_path = None
_collection_name = None
# The default authentication to apply to all pre defined routes.
_default_auth = "user"
# The default Access-Control-Allow-Origin cors directive value.
_default_cors = None
# Whether CSRF protection should be enabled for the route.
_default_csrf = False
# Whether session should be saved into the session store
_default_save_session = True
_component_context_provider = "component_context_provider"
@classmethod
def __init_subclass__(cls):
super().__init_subclass__()
if "RestController" not in globals() or not any(
issubclass(b, RestController) for b in cls.__bases__
):
return
# register the rest controller into the rest controllers registry
root_path = getattr(cls, "_root_path", None)
collection_name = getattr(cls, "_collection_name", None)
if root_path and collection_name:
cls._module = _get_addon_name(cls.__module__)
_rest_controllers_per_module[cls._module].append(
{
"root_path": root_path,
"collection_name": collection_name,
"controller_class": cls,
}
)
_logger.debug(
"Added rest controller %s for module %s",
_rest_controllers_per_module[cls._module][-1],
cls._module,
)
def _get_component_context(self, collection=None):
"""
This method can be inherited to add parameter into the component
context
:return: dict of key value.
"""
work = WorkContext(
model_name="rest.service.registration",
collection=collection or self.default_collection,
request=request,
controller=self,
)
provider = work.component(usage=self._component_context_provider)
return provider._get_component_context()
def make_response(self, data):
if isinstance(data, Response):
# The response has been build by the called method...
return data
# By default return result as json
return request.make_json_response(data)
@property
def collection_name(self):
return self._collection_name
@property
def default_collection(self):
return _PseudoCollection(self.collection_name, request.env)
@contextmanager
def work_on_component(self, collection=None):
"""
Return the component that implements the methods of the requested
service.
:param service_name:
:return: an instance of base.rest.service component
"""
collection = collection or self.default_collection
component_ctx = self._get_component_context(collection=collection)
env = collection.env
collection.env = env(
context=dict(
env.context,
authenticated_partner_id=component_ctx.get("authenticated_partner_id"),
)
)
yield WorkContext(model_name="rest.service.registration", **component_ctx)
@contextmanager
def service_component(self, service_name, collection=None):
"""
Return the component that implements the methods of the requested
service.
:param service_name:
:return: an instance of base.rest.service component
"""
with self.work_on_component(collection=collection) as work:
service = work.component(usage=service_name)
yield service
def _validate_method_name(self, method_name):
if method_name.startswith("_"):
_logger.error(
"REST API called with an unallowed method "
"name: %s.\n Method can't start with '_'",
method_name,
)
raise BadRequest()
return True
def _process_method(
self, service_name, method_name, *args, collection=None, params=None
):
self._validate_method_name(method_name)
if isinstance(collection, models.Model) and not collection:
raise request.not_found()
with self.service_component(service_name, collection=collection) as service:
result = service.dispatch(method_name, *args, params=params)
return self.make_response(result)