diff --git a/base_rest/README.rst b/base_rest/README.rst index eb0d0058..087dc691 100644 --- a/base_rest/README.rst +++ b/base_rest/README.rst @@ -2,10 +2,13 @@ Base Rest ========= -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:01ad6c170e472506c6762db5cfc93c21a508bad9e2175c1dc2bd16d86697ca45 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -19,31 +22,31 @@ Base Rest .. |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-base_rest :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/271/16.0 - :alt: Try me on Runbot +.. |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| +|badge1| |badge2| |badge3| |badge4| |badge5| This addon is deprecated and not fully supported anymore on Odoo 16. -Please migrate to the FastAPI migration module. -See https://github.com/OCA/rest-framework/pull/291. +Please migrate to the FastAPI migration module. See +https://github.com/OCA/rest-framework/pull/291. This addon provides the basis to develop high level REST APIs for Odoo. -As Odoo becomes one of the central pieces of enterprise IT systems, it often -becomes necessary to set up specialized service interfaces, so existing -systems can interact with Odoo. +As Odoo becomes one of the central pieces of enterprise IT systems, it +often becomes necessary to set up specialized service interfaces, so +existing systems can interact with Odoo. While the XML-RPC interface of Odoo comes handy in such situations, it requires a deep understanding of Odoo’s internal data model. When used -extensively, it creates a strong coupling between Odoo internals and client -systems, therefore increasing maintenance costs. +extensively, it creates a strong coupling between Odoo internals and +client systems, therefore increasing maintenance costs. -Once developed, an `OpenApi `_ documentation -is generated from the source code and available via a -`Swagger UI `_ served by your odoo server -at `https://my_odoo_server/api-docs`. +Once developed, an `OpenApi `__ +documentation is generated from the source code and available via a +`Swagger UI `__ served by your +odoo server at https://my_odoo_server/api-docs. **Table of contents** @@ -53,314 +56,328 @@ at `https://my_odoo_server/api-docs`. Configuration ============= -If an error occurs when calling a method of a service (ie missing parameter, -..) the system returns only a general description of the problem without -details. This is done on purpose to ensure maximum opacity on implementation -details and therefore lower security issue. +If an error occurs when calling a method of a service (ie missing +parameter, ..) the system returns only a general description of the +problem without details. This is done on purpose to ensure maximum +opacity on implementation details and therefore lower security issue. This restriction can be problematic when the services are accessed by an -external system in development. To know the details of an error it is indeed -necessary to have access to the log of the server. It is not always possible -to provide this kind of access. That's why you can configure the server to run -these services in development mode. +external system in development. To know the details of an error it is +indeed necessary to have access to the log of the server. It is not +always possible to provide this kind of access. That's why you can +configure the server to run these services in development mode. To run the REST API in development mode you must add a new section -'**[base_rest]**' with the option '**dev_mode=True**' in the server config -file. +'**[base_rest]**' with the option '**dev_mode=True**' in the server +config file. -.. code-block:: cfg +.. code:: cfg - [base_rest] - dev_mode=True + [base_rest] + dev_mode=True -When the REST API runs in development mode, the original description and a -stack trace is returned in case of error. **Be careful to not use this mode -in production**. +When the REST API runs in development mode, the original description and +a stack trace is returned in case of error. **Be careful to not use this +mode in production**. Usage ===== To add your own REST service you must provides at least 2 classes. -* A Component providing the business logic of your service, -* A Controller to register your service. +- A Component providing the business logic of your service, +- A Controller to register your service. The business logic of your service must be implemented into a component (``odoo.addons.component.core.Component``) that inherit from 'base.rest.service' -Initially, base_rest expose by default all public methods defined in a service. -The conventions for accessing methods via HTTP were as follows: - -* The method ``def get(self, _id)`` if defined, is accessible via HTTP GET routes ``/`` and ``//get``. -* The method ``def search(self, **params)`` if defined, is accessible via the HTTP GET routes ``/`` and ``/search``. -* The method ``def delete(self, _id)`` if defined, is accessible via the HTTP DELETE route ``/``. -* The ``def update(self, _id, **params)`` method, if defined, is accessible via the HTTP PUT route ``/``. -* Other methods are only accessible via HTTP POST routes ```` or ``/`` or ``/`` or ``//`` - -.. code-block:: python - - from odoo.addons.component.core import Component - - - class PingService(Component): - _inherit = 'base.rest.service' - _name = 'ping.service' - _usage = 'ping' - _collection = 'my_module.services' - - - # The following method are 'public' and can be called from the controller. - def get(self, _id, message): - return { - 'response': 'Get called with message ' + message} - - def search(self, message): - return { - 'response': 'Search called search with message ' + message} - - def update(self, _id, message): - return {'response': 'PUT called with message ' + message} - - # pylint:disable=method-required-super - def create(self, **params): - return {'response': 'POST called with message ' + params['message']} - - def delete(self, _id): - return {'response': 'DELETE called with id %s ' % _id} - - # Validator - def _validator_search(self): - return {'message': {'type': 'string'}} - - # Validator - def _validator_get(self): - # no parameters by default - return {} - - def _validator_update(self): - return {'message': {'type': 'string'}} - - def _validator_create(self): - return {'message': {'type': 'string'}} - -Once you have implemented your services (ping, ...), you must tell to Odoo -how to access to these services. This process is done by implementing a -controller that inherits from ``odoo.addons.base_rest.controllers.main.RestController`` - -.. code-block:: python - - from odoo.addons.base_rest.controllers import main - - class MyRestController(main.RestController): - _root_path = '/my_services_api/' - _collection_name = my_module.services - -In your controller, _'root_path' is used to specify the root of the path to -access to your services and '_collection_name' is the name of the collection -providing the business logic for the requested service/ - - -By inheriting from ``RestController`` the following routes will be registered -to access to your services - -.. code-block:: python - - @route([ - ROOT_PATH + '', - ROOT_PATH + '/search', - ROOT_PATH + '/', - ROOT_PATH + '//get' - ], methods=['GET'], auth="user", csrf=False) - def get(self, _service_name, _id=None, **params): - method_name = 'get' if _id else 'search' - return self._process_method(_service_name, method_name, _id, params) - - @route([ - ROOT_PATH + '', - ROOT_PATH + '/', - ROOT_PATH + '/', - ROOT_PATH + '//' - ], methods=['POST'], auth="user", csrf=False) - def modify(self, _service_name, _id=None, method_name=None, **params): - if not method_name: - method_name = 'update' if _id else 'create' - if method_name == 'get': - _logger.error("HTTP POST with method name 'get' is not allowed. " - "(service name: %s)", _service_name) - raise BadRequest() - return self._process_method(_service_name, method_name, _id, params) - - @route([ - ROOT_PATH + '/', - ], methods=['PUT'], auth="user", csrf=False) - def update(self, _service_name, _id, **params): - return self._process_method(_service_name, 'update', _id, params) - - @route([ - ROOT_PATH + '/', - ], methods=['DELETE'], auth="user", csrf=False) - def delete(self, _service_name, _id): - return self._process_method(_service_name, 'delete', _id) - - -As result an HTTP GET call to 'http://my_odoo/my_services_api/ping' will be -dispatched to the method ``PingService.search`` - -In addition to easily exposing your methods, the module allows you to define -data schemas to which the exchanged data must conform. These schemas are defined -on the basis of `Cerberus schemas `_ -and associated to the methods using the -following naming convention. For a method `my_method`: - -* ``def _validator_my_method(self):`` will be called to get the schema required to - validate the input parameters. -* ``def _validator_return_my_method(self):`` if defined, will be called to get - the schema used to validate the response. +Initially, base_rest expose by default all public methods defined in a +service. The conventions for accessing methods via HTTP were as follows: + +- The method ``def get(self, _id)`` if defined, is accessible via HTTP + GET routes ``/`` and + ``//get``. +- The method ``def search(self, **params)`` if defined, is accessible + via the HTTP GET routes ``/`` and + ``/search``. +- The method ``def delete(self, _id)`` if defined, is accessible via + the HTTP DELETE route ``/``. +- The ``def update(self, _id, **params)`` method, if defined, is + accessible via the HTTP PUT route + ``/``. +- Other methods are only accessible via HTTP POST routes + ```` or + ``/`` or + ``/`` or + ``//`` + +.. code:: python + + from odoo.addons.component.core import Component + + + class PingService(Component): + _inherit = 'base.rest.service' + _name = 'ping.service' + _usage = 'ping' + _collection = 'my_module.services' + + + # The following method are 'public' and can be called from the controller. + def get(self, _id, message): + return { + 'response': 'Get called with message ' + message} + + def search(self, message): + return { + 'response': 'Search called search with message ' + message} + + def update(self, _id, message): + return {'response': 'PUT called with message ' + message} + + # pylint:disable=method-required-super + def create(self, **params): + return {'response': 'POST called with message ' + params['message']} + + def delete(self, _id): + return {'response': 'DELETE called with id %s ' % _id} + + # Validator + def _validator_search(self): + return {'message': {'type': 'string'}} + + # Validator + def _validator_get(self): + # no parameters by default + return {} + + def _validator_update(self): + return {'message': {'type': 'string'}} + + def _validator_create(self): + return {'message': {'type': 'string'}} + +Once you have implemented your services (ping, ...), you must tell to +Odoo how to access to these services. This process is done by +implementing a controller that inherits from +``odoo.addons.base_rest.controllers.main.RestController`` + +.. code:: python + + from odoo.addons.base_rest.controllers import main + + class MyRestController(main.RestController): + _root_path = '/my_services_api/' + _collection_name = my_module.services + +In your controller, \_'root_path' is used to specify the root of the +path to access to your services and '\_collection_name' is the name of +the collection providing the business logic for the requested service/ + +By inheriting from ``RestController`` the following routes will be +registered to access to your services + +.. code:: python + + @route([ + ROOT_PATH + '', + ROOT_PATH + '/search', + ROOT_PATH + '/', + ROOT_PATH + '//get' + ], methods=['GET'], auth="user", csrf=False) + def get(self, _service_name, _id=None, **params): + method_name = 'get' if _id else 'search' + return self._process_method(_service_name, method_name, _id, params) + + @route([ + ROOT_PATH + '', + ROOT_PATH + '/', + ROOT_PATH + '/', + ROOT_PATH + '//' + ], methods=['POST'], auth="user", csrf=False) + def modify(self, _service_name, _id=None, method_name=None, **params): + if not method_name: + method_name = 'update' if _id else 'create' + if method_name == 'get': + _logger.error("HTTP POST with method name 'get' is not allowed. " + "(service name: %s)", _service_name) + raise BadRequest() + return self._process_method(_service_name, method_name, _id, params) + + @route([ + ROOT_PATH + '/', + ], methods=['PUT'], auth="user", csrf=False) + def update(self, _service_name, _id, **params): + return self._process_method(_service_name, 'update', _id, params) + + @route([ + ROOT_PATH + '/', + ], methods=['DELETE'], auth="user", csrf=False) + def delete(self, _service_name, _id): + return self._process_method(_service_name, 'delete', _id) + +As result an HTTP GET call to 'http://my_odoo/my_services_api/ping' will +be dispatched to the method ``PingService.search`` + +In addition to easily exposing your methods, the module allows you to +define data schemas to which the exchanged data must conform. These +schemas are defined on the basis of `Cerberus +schemas `__ and associated +to the methods using the following naming convention. For a method +\`my_method\`: + +- ``def _validator_my_method(self):`` will be called to get the schema + required to validate the input parameters. +- ``def _validator_return_my_method(self):`` if defined, will be called + to get the schema used to validate the response. In order to offer even more flexibility, a new API has been developed. -This new API replaces the implicit approach used to expose a service by the use -of a python decorator to explicitly mark a method as being available via the -REST API: ``odoo.addons.base_rest.restapi.method``. - - -.. code-block:: python - - class PartnerNewApiService(Component): - _inherit = "base.rest.service" - _name = "partner.new_api.service" - _usage = "partner" - _collection = "base.rest.demo.new_api.services" - _description = """ - Partner New API Services - Services developed with the new api provided by base_rest - """ - - @restapi.method( - [(["//get", "/"], "GET")], - output_param=restapi.CerberusValidator("_get_partner_schema"), - auth="public", - ) - def get(self, _id): - return {"name": self.env["res.partner"].browse(_id).name} - - def _get_partner_schema(self): - return { - "name": {"type": "string", "required": True} - } - - @restapi.method( - [(["/list", "/"], "GET")], - output_param=restapi.CerberusListValidator("_get_partner_schema"), - auth="public", - ) - def list(self): - partners = self.env["res.partner"].search([]) - return [{"name": p.name} for p in partners] - -Thanks to this new api, you are now free to specify your own routes but also -to use other object types as parameter or response to your methods. -For example, `base_rest_datamodel` allows you to use Datamodel object instance -into your services. - -.. 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 PartnerSearchParam(Datamodel): - _name = "partner.search.param" - - id = fields.Integer(required=False, allow_none=False) - name = fields.String(required=False, allow_none=False) - - - class PartnerShortInfo(Datamodel): - _name = "partner.short.info" - - id = fields.Integer(required=True, allow_none=False) - name = fields.String(required=True, allow_none=False) - - - class PartnerNewApiService(Component): - _inherit = "base.rest.service" - _name = "partner.new_api.service" - _usage = "partner" - _collection = "base.rest.demo.new_api.services" - _description = """ - Partner New API Services - Services developed with the new api provided by base_rest - """ - - @restapi.method( - [(["/", "/search"], "GET")], - input_param=restapi.Datamodel("partner.search.param"), - output_param=restapi.Datamodel("partner.short.info", is_list=True), - auth="public", - ) - def search(self, partner_search_param): - """ - Search for partners - :param partner_search_param: An instance of partner.search.param - :return: List of partner.short.info - """ - domain = [] - if partner_search_param.name: - domain.append(("name", "like", partner_search_param.name)) - if partner_search_param.id: - domain.append(("id", "=", partner_search_param.id)) - res = [] - PartnerShortInfo = self.env.datamodels["partner.short.info"] - for p in self.env["res.partner"].search(domain): - res.append(PartnerShortInfo(id=p.id, name=p.name)) - return res +This new API replaces the implicit approach used to expose a service by +the use of a python decorator to explicitly mark a method as being +available via the REST API: ``odoo.addons.base_rest.restapi.method``. + +.. code:: python + + class PartnerNewApiService(Component): + _inherit = "base.rest.service" + _name = "partner.new_api.service" + _usage = "partner" + _collection = "base.rest.demo.new_api.services" + _description = """ + Partner New API Services + Services developed with the new api provided by base_rest + """ + + @restapi.method( + [(["//get", "/"], "GET")], + output_param=restapi.CerberusValidator("_get_partner_schema"), + auth="public", + ) + def get(self, _id): + return {"name": self.env["res.partner"].browse(_id).name} + + def _get_partner_schema(self): + return { + "name": {"type": "string", "required": True} + } + + @restapi.method( + [(["/list", "/"], "GET")], + output_param=restapi.CerberusListValidator("_get_partner_schema"), + auth="public", + ) + def list(self): + partners = self.env["res.partner"].search([]) + return [{"name": p.name} for p in partners] + +Thanks to this new api, you are now free to specify your own routes but +also to use other object types as parameter or response to your methods. +For example, base_rest_datamodel allows you to use Datamodel object +instance into your services. + +.. code:: 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 PartnerSearchParam(Datamodel): + _name = "partner.search.param" + + id = fields.Integer(required=False, allow_none=False) + name = fields.String(required=False, allow_none=False) + + + class PartnerShortInfo(Datamodel): + _name = "partner.short.info" + + id = fields.Integer(required=True, allow_none=False) + name = fields.String(required=True, allow_none=False) + + + class PartnerNewApiService(Component): + _inherit = "base.rest.service" + _name = "partner.new_api.service" + _usage = "partner" + _collection = "base.rest.demo.new_api.services" + _description = """ + Partner New API Services + Services developed with the new api provided by base_rest + """ + + @restapi.method( + [(["/", "/search"], "GET")], + input_param=restapi.Datamodel("partner.search.param"), + output_param=restapi.Datamodel("partner.short.info", is_list=True), + auth="public", + ) + def search(self, partner_search_param): + """ + Search for partners + :param partner_search_param: An instance of partner.search.param + :return: List of partner.short.info + """ + domain = [] + if partner_search_param.name: + domain.append(("name", "like", partner_search_param.name)) + if partner_search_param.id: + domain.append(("id", "=", partner_search_param.id)) + res = [] + PartnerShortInfo = self.env.datamodels["partner.short.info"] + for p in self.env["res.partner"].search(domain): + res.append(PartnerShortInfo(id=p.id, name=p.name)) + return res The BaseRestServiceContextProvider provides context for your services, -including authenticated_partner_id. -You are free to redefine the method _get_authenticated_partner_id() to pass the -authenticated_partner_id based on the authentication mechanism of your choice. -See base_rest_auth_jwt for an example. +including authenticated_partner_id. You are free to redefine the method +\_get_authenticated_partner_id() to pass the authenticated_partner_id +based on the authentication mechanism of your choice. See +base_rest_auth_jwt for an example. -In addition, authenticated_partner_id is available in record rule evaluation context. +In addition, authenticated_partner_id is available in record rule +evaluation context. Known issues / Roadmap ====================== -The `roadmap `_ -and `known issues `_ can -be found on GitHub. +The +`roadmap `__ +and `known +issues `__ +can be found on GitHub. Changelog ========= 12.0.2.0.1 -~~~~~~~~~~ +---------- -* _validator_...() methods can now return a cerberus ``Validator`` object - instead of a schema dictionnary, for additional flexibility (e.g. allowing - validator options such as ``allow_unknown``). +- \_validator\_...() methods can now return a cerberus ``Validator`` + object instead of a schema dictionnary, for additional flexibility + (e.g. allowing validator options such as ``allow_unknown``). 12.0.2.0.0 -~~~~~~~~~~ +---------- -* Licence changed from AGPL-3 to LGPL-3 +- Licence changed from AGPL-3 to LGPL-3 12.0.1.0.1 -~~~~~~~~~~ +---------- -* Fix issue when rendering the jsonapi documentation if no documentation is - provided on a method part of the REST api. +- Fix issue when rendering the jsonapi documentation if no + documentation is provided on a method part of the REST api. 12.0.1.0.0 -~~~~~~~~~~ +---------- First official version. The addon has been incubated into the -`Shopinvader repository `_ from +`Shopinvader +repository `__ from Akretion. For more information you need to look at the git log. Bug Tracker @@ -368,7 +385,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us smashing it by providing a detailed and welcomed +If you spotted it first, help us to smash it by providing a detailed and welcomed `feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -377,18 +394,18 @@ Credits ======= Authors -~~~~~~~ +------- * ACSONE SA/NV Contributors -~~~~~~~~~~~~ +------------ -* Laurent Mignon -* Sébastien Beau +- Laurent Mignon +- Sébastien Beau Maintainers -~~~~~~~~~~~ +----------- This module is maintained by the OCA. diff --git a/base_rest/readme/CONFIGURE.md b/base_rest/readme/CONFIGURE.md new file mode 100644 index 00000000..e10b17f6 --- /dev/null +++ b/base_rest/readme/CONFIGURE.md @@ -0,0 +1,20 @@ +If an error occurs when calling a method of a service (ie missing parameter, ..) the +system returns only a general description of the problem without details. This is done +on purpose to ensure maximum opacity on implementation details and therefore lower +security issue. + +This restriction can be problematic when the services are accessed by an external system +in development. To know the details of an error it is indeed necessary to have access to +the log of the server. It is not always possible to provide this kind of access. That's +why you can configure the server to run these services in development mode. + +To run the REST API in development mode you must add a new section '**\[base_rest\]**' +with the option '**dev_mode=True**' in the server config file. + +```cfg +[base_rest] +dev_mode=True +``` + +When the REST API runs in development mode, the original description and a stack trace +is returned in case of error. **Be careful to not use this mode in production**. diff --git a/base_rest/readme/CONFIGURE.rst b/base_rest/readme/CONFIGURE.rst deleted file mode 100644 index e35bc3ae..00000000 --- a/base_rest/readme/CONFIGURE.rst +++ /dev/null @@ -1,23 +0,0 @@ -If an error occurs when calling a method of a service (ie missing parameter, -..) the system returns only a general description of the problem without -details. This is done on purpose to ensure maximum opacity on implementation -details and therefore lower security issue. - -This restriction can be problematic when the services are accessed by an -external system in development. To know the details of an error it is indeed -necessary to have access to the log of the server. It is not always possible -to provide this kind of access. That's why you can configure the server to run -these services in development mode. - -To run the REST API in development mode you must add a new section -'**[base_rest]**' with the option '**dev_mode=True**' in the server config -file. - -.. code-block:: cfg - - [base_rest] - dev_mode=True - -When the REST API runs in development mode, the original description and a -stack trace is returned in case of error. **Be careful to not use this mode -in production**. diff --git a/base_rest/readme/CONTRIBUTORS.md b/base_rest/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..82cc4582 --- /dev/null +++ b/base_rest/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- Laurent Mignon \<\> +- Sébastien Beau \<\> diff --git a/base_rest/readme/CONTRIBUTORS.rst b/base_rest/readme/CONTRIBUTORS.rst deleted file mode 100644 index 50885333..00000000 --- a/base_rest/readme/CONTRIBUTORS.rst +++ /dev/null @@ -1,2 +0,0 @@ -* Laurent Mignon -* Sébastien Beau diff --git a/base_rest/readme/DESCRIPTION.md b/base_rest/readme/DESCRIPTION.md new file mode 100644 index 00000000..548cf604 --- /dev/null +++ b/base_rest/readme/DESCRIPTION.md @@ -0,0 +1,18 @@ +This addon is deprecated and not fully supported anymore on Odoo 16. Please migrate to +the FastAPI migration module. See . + +This addon provides the basis to develop high level REST APIs for Odoo. + +As Odoo becomes one of the central pieces of enterprise IT systems, it often becomes +necessary to set up specialized service interfaces, so existing systems can interact +with Odoo. + +While the XML-RPC interface of Odoo comes handy in such situations, it requires a deep +understanding of Odoo’s internal data model. When used extensively, it creates a strong +coupling between Odoo internals and client systems, therefore increasing maintenance +costs. + +Once developed, an [OpenApi](https://spec.openapis.org/oas/v3.0.3) documentation is +generated from the source code and available via a +[Swagger UI](https://swagger.io/tools/swagger-ui/) served by your odoo server at +https://my_odoo_server/api-docs. diff --git a/base_rest/readme/DESCRIPTION.rst b/base_rest/readme/DESCRIPTION.rst deleted file mode 100644 index 28c8759f..00000000 --- a/base_rest/readme/DESCRIPTION.rst +++ /dev/null @@ -1,19 +0,0 @@ -This addon is deprecated and not fully supported anymore on Odoo 16. -Please migrate to the FastAPI migration module. -See https://github.com/OCA/rest-framework/pull/291. - -This addon provides the basis to develop high level REST APIs for Odoo. - -As Odoo becomes one of the central pieces of enterprise IT systems, it often -becomes necessary to set up specialized service interfaces, so existing -systems can interact with Odoo. - -While the XML-RPC interface of Odoo comes handy in such situations, it -requires a deep understanding of Odoo’s internal data model. When used -extensively, it creates a strong coupling between Odoo internals and client -systems, therefore increasing maintenance costs. - -Once developed, an `OpenApi `_ documentation -is generated from the source code and available via a -`Swagger UI `_ served by your odoo server -at `https://my_odoo_server/api-docs`. diff --git a/base_rest/readme/HISTORY.md b/base_rest/readme/HISTORY.md new file mode 100644 index 00000000..a2b3a8ea --- /dev/null +++ b/base_rest/readme/HISTORY.md @@ -0,0 +1,20 @@ +## 12.0.2.0.1 + +- \_validator\_...() methods can now return a cerberus `Validator` object instead of a + schema dictionnary, for additional flexibility (e.g. allowing validator options such + as `allow_unknown`). + +## 12.0.2.0.0 + +- Licence changed from AGPL-3 to LGPL-3 + +## 12.0.1.0.1 + +- Fix issue when rendering the jsonapi documentation if no documentation is provided on + a method part of the REST api. + +## 12.0.1.0.0 + +First official version. The addon has been incubated into the +[Shopinvader repository](https://github.com/akretion/odoo-shopinvader) from Akretion. +For more information you need to look at the git log. diff --git a/base_rest/readme/HISTORY.rst b/base_rest/readme/HISTORY.rst deleted file mode 100644 index 5c44213b..00000000 --- a/base_rest/readme/HISTORY.rst +++ /dev/null @@ -1,24 +0,0 @@ -12.0.2.0.1 -~~~~~~~~~~ - -* _validator_...() methods can now return a cerberus ``Validator`` object - instead of a schema dictionnary, for additional flexibility (e.g. allowing - validator options such as ``allow_unknown``). - -12.0.2.0.0 -~~~~~~~~~~ - -* Licence changed from AGPL-3 to LGPL-3 - -12.0.1.0.1 -~~~~~~~~~~ - -* Fix issue when rendering the jsonapi documentation if no documentation is - provided on a method part of the REST api. - -12.0.1.0.0 -~~~~~~~~~~ - -First official version. The addon has been incubated into the -`Shopinvader repository `_ from -Akretion. For more information you need to look at the git log. diff --git a/base_rest/readme/ROADMAP.md b/base_rest/readme/ROADMAP.md new file mode 100644 index 00000000..6e292e8b --- /dev/null +++ b/base_rest/readme/ROADMAP.md @@ -0,0 +1,5 @@ +The +[roadmap](https://github.com/OCA/rest-framework/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement+label%3Abase_rest) +and +[known issues](https://github.com/OCA/rest-framework/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Abase_rest) +can be found on GitHub. diff --git a/base_rest/readme/ROADMAP.rst b/base_rest/readme/ROADMAP.rst deleted file mode 100644 index e27624f2..00000000 --- a/base_rest/readme/ROADMAP.rst +++ /dev/null @@ -1,3 +0,0 @@ -The `roadmap `_ -and `known issues `_ can -be found on GitHub. diff --git a/base_rest/readme/USAGE.md b/base_rest/readme/USAGE.md new file mode 100644 index 00000000..7705af5b --- /dev/null +++ b/base_rest/readme/USAGE.md @@ -0,0 +1,246 @@ +To add your own REST service you must provides at least 2 classes. + +- A Component providing the business logic of your service, +- A Controller to register your service. + +The business logic of your service must be implemented into a component +(`odoo.addons.component.core.Component`) that inherit from 'base.rest.service' + +Initially, base_rest expose by default all public methods defined in a service. The +conventions for accessing methods via HTTP were as follows: + +- The method `def get(self, _id)` if defined, is accessible via HTTP GET routes + `/` and `//get`. +- The method `def search(self, **params)` if defined, is accessible via the HTTP GET + routes `/` and `/search`. +- The method `def delete(self, _id)` if defined, is accessible via the HTTP DELETE route + `/`. +- The `def update(self, _id, **params)` method, if defined, is accessible via the HTTP + PUT route `/`. +- Other methods are only accessible via HTTP POST routes `` or + `/` or `/` or + `//` + +```python +from odoo.addons.component.core import Component + + +class PingService(Component): + _inherit = 'base.rest.service' + _name = 'ping.service' + _usage = 'ping' + _collection = 'my_module.services' + + + # The following method are 'public' and can be called from the controller. + def get(self, _id, message): + return { + 'response': 'Get called with message ' + message} + + def search(self, message): + return { + 'response': 'Search called search with message ' + message} + + def update(self, _id, message): + return {'response': 'PUT called with message ' + message} + + # pylint:disable=method-required-super + def create(self, **params): + return {'response': 'POST called with message ' + params['message']} + + def delete(self, _id): + return {'response': 'DELETE called with id %s ' % _id} + + # Validator + def _validator_search(self): + return {'message': {'type': 'string'}} + + # Validator + def _validator_get(self): + # no parameters by default + return {} + + def _validator_update(self): + return {'message': {'type': 'string'}} + + def _validator_create(self): + return {'message': {'type': 'string'}} +``` + +Once you have implemented your services (ping, ...), you must tell to Odoo how to access +to these services. This process is done by implementing a controller that inherits from +`odoo.addons.base_rest.controllers.main.RestController` + +```python +from odoo.addons.base_rest.controllers import main + +class MyRestController(main.RestController): + _root_path = '/my_services_api/' + _collection_name = my_module.services +``` + +In your controller, \_'root_path' is used to specify the root of the path to access to +your services and '\_collection_name' is the name of the collection providing the +business logic for the requested service/ + +By inheriting from `RestController` the following routes will be registered to access to +your services + +```python +@route([ + ROOT_PATH + '', + ROOT_PATH + '/search', + ROOT_PATH + '/', + ROOT_PATH + '//get' +], methods=['GET'], auth="user", csrf=False) +def get(self, _service_name, _id=None, **params): + method_name = 'get' if _id else 'search' + return self._process_method(_service_name, method_name, _id, params) + +@route([ + ROOT_PATH + '', + ROOT_PATH + '/', + ROOT_PATH + '/', + ROOT_PATH + '//' +], methods=['POST'], auth="user", csrf=False) +def modify(self, _service_name, _id=None, method_name=None, **params): + if not method_name: + method_name = 'update' if _id else 'create' + if method_name == 'get': + _logger.error("HTTP POST with method name 'get' is not allowed. " + "(service name: %s)", _service_name) + raise BadRequest() + return self._process_method(_service_name, method_name, _id, params) + +@route([ + ROOT_PATH + '/', +], methods=['PUT'], auth="user", csrf=False) +def update(self, _service_name, _id, **params): + return self._process_method(_service_name, 'update', _id, params) + +@route([ + ROOT_PATH + '/', +], methods=['DELETE'], auth="user", csrf=False) +def delete(self, _service_name, _id): + return self._process_method(_service_name, 'delete', _id) +``` + +As result an HTTP GET call to '' will be dispatched +to the method `PingService.search` + +In addition to easily exposing your methods, the module allows you to define data +schemas to which the exchanged data must conform. These schemas are defined on the basis +of [Cerberus schemas](https://docs.python-cerberus.org/en/stable/) and associated to the +methods using the following naming convention. For a method \`my_method\`: + +- `def _validator_my_method(self):` will be called to get the schema required to + validate the input parameters. +- `def _validator_return_my_method(self):` if defined, will be called to get the schema + used to validate the response. + +In order to offer even more flexibility, a new API has been developed. + +This new API replaces the implicit approach used to expose a service by the use of a +python decorator to explicitly mark a method as being available via the REST API: +`odoo.addons.base_rest.restapi.method`. + +```python +class PartnerNewApiService(Component): + _inherit = "base.rest.service" + _name = "partner.new_api.service" + _usage = "partner" + _collection = "base.rest.demo.new_api.services" + _description = """ + Partner New API Services + Services developed with the new api provided by base_rest + """ + + @restapi.method( + [(["//get", "/"], "GET")], + output_param=restapi.CerberusValidator("_get_partner_schema"), + auth="public", + ) + def get(self, _id): + return {"name": self.env["res.partner"].browse(_id).name} + + def _get_partner_schema(self): + return { + "name": {"type": "string", "required": True} + } + + @restapi.method( + [(["/list", "/"], "GET")], + output_param=restapi.CerberusListValidator("_get_partner_schema"), + auth="public", + ) + def list(self): + partners = self.env["res.partner"].search([]) + return [{"name": p.name} for p in partners] +``` + +Thanks to this new api, you are now free to specify your own routes but also to use +other object types as parameter or response to your methods. For example, +base_rest_datamodel allows you to use Datamodel object instance into your services. + +```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 PartnerSearchParam(Datamodel): + _name = "partner.search.param" + + id = fields.Integer(required=False, allow_none=False) + name = fields.String(required=False, allow_none=False) + + +class PartnerShortInfo(Datamodel): + _name = "partner.short.info" + + id = fields.Integer(required=True, allow_none=False) + name = fields.String(required=True, allow_none=False) + + +class PartnerNewApiService(Component): + _inherit = "base.rest.service" + _name = "partner.new_api.service" + _usage = "partner" + _collection = "base.rest.demo.new_api.services" + _description = """ + Partner New API Services + Services developed with the new api provided by base_rest + """ + + @restapi.method( + [(["/", "/search"], "GET")], + input_param=restapi.Datamodel("partner.search.param"), + output_param=restapi.Datamodel("partner.short.info", is_list=True), + auth="public", + ) + def search(self, partner_search_param): + """ + Search for partners + :param partner_search_param: An instance of partner.search.param + :return: List of partner.short.info + """ + domain = [] + if partner_search_param.name: + domain.append(("name", "like", partner_search_param.name)) + if partner_search_param.id: + domain.append(("id", "=", partner_search_param.id)) + res = [] + PartnerShortInfo = self.env.datamodels["partner.short.info"] + for p in self.env["res.partner"].search(domain): + res.append(PartnerShortInfo(id=p.id, name=p.name)) + return res +``` + +The BaseRestServiceContextProvider provides context for your services, including +authenticated_partner_id. You are free to redefine the method +\_get_authenticated_partner_id() to pass the authenticated_partner_id based on the +authentication mechanism of your choice. See base_rest_auth_jwt for an example. + +In addition, authenticated_partner_id is available in record rule evaluation context. diff --git a/base_rest/readme/USAGE.rst b/base_rest/readme/USAGE.rst deleted file mode 100644 index bb33bf90..00000000 --- a/base_rest/readme/USAGE.rst +++ /dev/null @@ -1,247 +0,0 @@ -To add your own REST service you must provides at least 2 classes. - -* A Component providing the business logic of your service, -* A Controller to register your service. - -The business logic of your service must be implemented into a component -(``odoo.addons.component.core.Component``) that inherit from -'base.rest.service' - -Initially, base_rest expose by default all public methods defined in a service. -The conventions for accessing methods via HTTP were as follows: - -* The method ``def get(self, _id)`` if defined, is accessible via HTTP GET routes ``/`` and ``//get``. -* The method ``def search(self, **params)`` if defined, is accessible via the HTTP GET routes ``/`` and ``/search``. -* The method ``def delete(self, _id)`` if defined, is accessible via the HTTP DELETE route ``/``. -* The ``def update(self, _id, **params)`` method, if defined, is accessible via the HTTP PUT route ``/``. -* Other methods are only accessible via HTTP POST routes ```` or ``/`` or ``/`` or ``//`` - -.. code-block:: python - - from odoo.addons.component.core import Component - - - class PingService(Component): - _inherit = 'base.rest.service' - _name = 'ping.service' - _usage = 'ping' - _collection = 'my_module.services' - - - # The following method are 'public' and can be called from the controller. - def get(self, _id, message): - return { - 'response': 'Get called with message ' + message} - - def search(self, message): - return { - 'response': 'Search called search with message ' + message} - - def update(self, _id, message): - return {'response': 'PUT called with message ' + message} - - # pylint:disable=method-required-super - def create(self, **params): - return {'response': 'POST called with message ' + params['message']} - - def delete(self, _id): - return {'response': 'DELETE called with id %s ' % _id} - - # Validator - def _validator_search(self): - return {'message': {'type': 'string'}} - - # Validator - def _validator_get(self): - # no parameters by default - return {} - - def _validator_update(self): - return {'message': {'type': 'string'}} - - def _validator_create(self): - return {'message': {'type': 'string'}} - -Once you have implemented your services (ping, ...), you must tell to Odoo -how to access to these services. This process is done by implementing a -controller that inherits from ``odoo.addons.base_rest.controllers.main.RestController`` - -.. code-block:: python - - from odoo.addons.base_rest.controllers import main - - class MyRestController(main.RestController): - _root_path = '/my_services_api/' - _collection_name = my_module.services - -In your controller, _'root_path' is used to specify the root of the path to -access to your services and '_collection_name' is the name of the collection -providing the business logic for the requested service/ - - -By inheriting from ``RestController`` the following routes will be registered -to access to your services - -.. code-block:: python - - @route([ - ROOT_PATH + '', - ROOT_PATH + '/search', - ROOT_PATH + '/', - ROOT_PATH + '//get' - ], methods=['GET'], auth="user", csrf=False) - def get(self, _service_name, _id=None, **params): - method_name = 'get' if _id else 'search' - return self._process_method(_service_name, method_name, _id, params) - - @route([ - ROOT_PATH + '', - ROOT_PATH + '/', - ROOT_PATH + '/', - ROOT_PATH + '//' - ], methods=['POST'], auth="user", csrf=False) - def modify(self, _service_name, _id=None, method_name=None, **params): - if not method_name: - method_name = 'update' if _id else 'create' - if method_name == 'get': - _logger.error("HTTP POST with method name 'get' is not allowed. " - "(service name: %s)", _service_name) - raise BadRequest() - return self._process_method(_service_name, method_name, _id, params) - - @route([ - ROOT_PATH + '/', - ], methods=['PUT'], auth="user", csrf=False) - def update(self, _service_name, _id, **params): - return self._process_method(_service_name, 'update', _id, params) - - @route([ - ROOT_PATH + '/', - ], methods=['DELETE'], auth="user", csrf=False) - def delete(self, _service_name, _id): - return self._process_method(_service_name, 'delete', _id) - - -As result an HTTP GET call to 'http://my_odoo/my_services_api/ping' will be -dispatched to the method ``PingService.search`` - -In addition to easily exposing your methods, the module allows you to define -data schemas to which the exchanged data must conform. These schemas are defined -on the basis of `Cerberus schemas `_ -and associated to the methods using the -following naming convention. For a method `my_method`: - -* ``def _validator_my_method(self):`` will be called to get the schema required to - validate the input parameters. -* ``def _validator_return_my_method(self):`` if defined, will be called to get - the schema used to validate the response. - -In order to offer even more flexibility, a new API has been developed. - -This new API replaces the implicit approach used to expose a service by the use -of a python decorator to explicitly mark a method as being available via the -REST API: ``odoo.addons.base_rest.restapi.method``. - - -.. code-block:: python - - class PartnerNewApiService(Component): - _inherit = "base.rest.service" - _name = "partner.new_api.service" - _usage = "partner" - _collection = "base.rest.demo.new_api.services" - _description = """ - Partner New API Services - Services developed with the new api provided by base_rest - """ - - @restapi.method( - [(["//get", "/"], "GET")], - output_param=restapi.CerberusValidator("_get_partner_schema"), - auth="public", - ) - def get(self, _id): - return {"name": self.env["res.partner"].browse(_id).name} - - def _get_partner_schema(self): - return { - "name": {"type": "string", "required": True} - } - - @restapi.method( - [(["/list", "/"], "GET")], - output_param=restapi.CerberusListValidator("_get_partner_schema"), - auth="public", - ) - def list(self): - partners = self.env["res.partner"].search([]) - return [{"name": p.name} for p in partners] - -Thanks to this new api, you are now free to specify your own routes but also -to use other object types as parameter or response to your methods. -For example, `base_rest_datamodel` allows you to use Datamodel object instance -into your services. - -.. 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 PartnerSearchParam(Datamodel): - _name = "partner.search.param" - - id = fields.Integer(required=False, allow_none=False) - name = fields.String(required=False, allow_none=False) - - - class PartnerShortInfo(Datamodel): - _name = "partner.short.info" - - id = fields.Integer(required=True, allow_none=False) - name = fields.String(required=True, allow_none=False) - - - class PartnerNewApiService(Component): - _inherit = "base.rest.service" - _name = "partner.new_api.service" - _usage = "partner" - _collection = "base.rest.demo.new_api.services" - _description = """ - Partner New API Services - Services developed with the new api provided by base_rest - """ - - @restapi.method( - [(["/", "/search"], "GET")], - input_param=restapi.Datamodel("partner.search.param"), - output_param=restapi.Datamodel("partner.short.info", is_list=True), - auth="public", - ) - def search(self, partner_search_param): - """ - Search for partners - :param partner_search_param: An instance of partner.search.param - :return: List of partner.short.info - """ - domain = [] - if partner_search_param.name: - domain.append(("name", "like", partner_search_param.name)) - if partner_search_param.id: - domain.append(("id", "=", partner_search_param.id)) - res = [] - PartnerShortInfo = self.env.datamodels["partner.short.info"] - for p in self.env["res.partner"].search(domain): - res.append(PartnerShortInfo(id=p.id, name=p.name)) - return res - -The BaseRestServiceContextProvider provides context for your services, -including authenticated_partner_id. -You are free to redefine the method _get_authenticated_partner_id() to pass the -authenticated_partner_id based on the authentication mechanism of your choice. -See base_rest_auth_jwt for an example. - -In addition, authenticated_partner_id is available in record rule evaluation context. diff --git a/base_rest/static/description/index.html b/base_rest/static/description/index.html index cec663a2..5e9456e8 100644 --- a/base_rest/static/description/index.html +++ b/base_rest/static/description/index.html @@ -3,7 +3,7 @@ - + Base Rest