Maintainers
+Maintainers
This module is maintained by the OCA.
@@ -1837,6 +1837,5 @@ diff --git a/fastapi/README.rst b/fastapi/README.rst index 417ab2d2f..e6a5701f5 100644 --- a/fastapi/README.rst +++ b/fastapi/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ============ Odoo FastAPI ============ @@ -17,7 +13,7 @@ Odoo FastAPI .. |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/license-LGPL--3-blue.png +.. |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 @@ -251,6 +247,12 @@ Now, you can start your Odoo server, install your addon and create a new endpoin instance for your app. Once it's done click on the docs url to access the interactive documentation of your app. +**Note**: FastAPI automatically exposes three services to easily interact with +the APIs and their structure (Swagger, Redoc, OpenAPI). +When exposing your Odoo instance to the open internet you might want to disable +these services. To do so toggle the "Expose FastAPI docs" checkbox in the FastAPI +Endpoint form view. + Before trying to test your app, you need to define on the endpoint instance the user that will be used to run the app. You can do it by setting the **'user_id'** field. This information is the most important one because it's the basis for diff --git a/fastapi/models/fastapi_endpoint.py b/fastapi/models/fastapi_endpoint.py index 25e3dc468..992f5098b 100644 --- a/fastapi/models/fastapi_endpoint.py +++ b/fastapi/models/fastapi_endpoint.py @@ -65,6 +65,7 @@ class FastapiEndpoint(models.Model): "unexpecteed disk space consumption.", default=True, ) + expose_doc_urls = fields.Boolean("Expose FastAPI docs", default=True) @api.depends("root_path") def _compute_root_path(self): @@ -150,7 +151,7 @@ def _handle_route_updates(self, vals): @api.model def _fastapi_app_fields(self) -> List[str]: """The list of fields requiring to refresh the fastapi app if modified""" - return [] + return ["expose_doc_urls"] def _make_routing_rule(self, options=None): """Generator of rule""" @@ -258,12 +259,15 @@ def _get_app_dependencies_overrides(self) -> Dict[Callable, Callable]: def _prepare_fastapi_app_params(self) -> Dict[str, Any]: """Return the params to pass to the Fast API app constructor""" - return { + to_return = { "title": self.name, "description": self.description, "middleware": self._get_fastapi_app_middlewares(), "dependencies": self._get_fastapi_app_dependencies(), } + if not self.expose_doc_urls: + to_return |= {"docs_url": None, "redoc_url": None, "openapi_url": None} + return to_return def _get_fastapi_routers(self) -> List[APIRouter]: """Return the api routers to use for the instance. diff --git a/fastapi/readme/USAGE.rst b/fastapi/readme/USAGE.rst index f03942a38..9069f6b3a 100644 --- a/fastapi/readme/USAGE.rst +++ b/fastapi/readme/USAGE.rst @@ -170,6 +170,12 @@ Now, you can start your Odoo server, install your addon and create a new endpoin instance for your app. Once it's done click on the docs url to access the interactive documentation of your app. +**Note**: FastAPI automatically exposes three services to easily interact with +the APIs and their structure (Swagger, Redoc, OpenAPI). +When exposing your Odoo instance to the open internet you might want to disable +these services. To do so toggle the "Expose FastAPI docs" checkbox in the FastAPI +Endpoint form view. + Before trying to test your app, you need to define on the endpoint instance the user that will be used to run the app. You can do it by setting the **'user_id'** field. This information is the most important one because it's the basis for diff --git a/fastapi/static/description/index.html b/fastapi/static/description/index.html index 1acafecb0..ca6329172 100644 --- a/fastapi/static/description/index.html +++ b/fastapi/static/description/index.html @@ -3,7 +3,7 @@
-This addon provides the basis to smoothly integrate the FastAPI framework into Odoo.
This integration allows you to use all the goodies from FastAPI to build custom @@ -458,9 +453,9 @@
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.7+ based on standard Python type hints. This addons let’s you keep advantage of the fastapi framework and use it with Odoo.
@@ -510,9 +505,9 @@Then, you need to declare your app by defining a model that inherits from ‘fastapi.endpoint’ and add your app name into the app field. For example:
-from odoo import fields, models +from odoo import fields, models -class FastapiEndpoint(models.Model): +class FastapiEndpoint(models.Model): _inherit = "fastapi.endpoint" @@ -535,10 +530,10 @@What’s building an API with fas
Now, you can create your first router. For that, you need to define a global variable into your fastapi_endpoint module called for example ‘demo_api_router’
-from fastapi import APIRouter -from odoo import fields, models +from fastapi import APIRouter +from odoo import fields, models -class FastapiEndpoint(models.Model): +class FastapiEndpoint(models.Model): _inherit = "fastapi.endpoint" @@ -552,10 +547,10 @@What’s building an API with fas
To make your router available to your app, you need to add it to the list of routers returned by the _get_fastapi_routers method of your fastapi_endpoint model.
-from fastapi import APIRouter -from odoo import api, fields, models +from fastapi import APIRouter +from odoo import api, fields, models -class FastapiEndpoint(models.Model): +class FastapiEndpoint(models.Model): _inherit = "fastapi.endpoint" @@ -563,7 +558,7 @@What’s building an API with fas
selection_add=[("demo", "Demo Endpoint")], ondelete={"demo": "cascade"} ) - def _get_fastapi_routers(self): + def _get_fastapi_routers(self): if self.app == "demo": return [demo_api_router] return super()._get_fastapi_routers() @@ -574,17 +569,17 @@What’s building an API with fas
Now, you can start adding routes to your router. For example, let’s add a route that returns a list of partners.
-from typing import Annotated +from typing import Annotated -from fastapi import APIRouter -from pydantic import BaseModel +from fastapi import APIRouter +from pydantic import BaseModel -from odoo import api, fields, models -from odoo.api import Environment +from odoo import api, fields, models +from odoo.api import Environment -from odoo.addons.fastapi.dependencies import odoo_env +from odoo.addons.fastapi.dependencies import odoo_env -class FastapiEndpoint(models.Model): +class FastapiEndpoint(models.Model): _inherit = "fastapi.endpoint" @@ -592,7 +587,7 @@What’s building an API with fas
selection_add=[("demo", "Demo Endpoint")], ondelete={"demo": "cascade"} ) - def _get_fastapi_routers(self): + def _get_fastapi_routers(self): if self.app == "demo": return [demo_api_router] return super()._get_fastapi_routers() @@ -600,12 +595,12 @@What’s building an API with fas
# create a router demo_api_router = APIRouter() -class PartnerInfo(BaseModel): +class PartnerInfo(BaseModel): name: str email: str @demo_api_router.get("/partners", response_model=list[PartnerInfo]) -def get_partners(env: Annotated[Environment, Depends(odoo_env)]) -> list[PartnerInfo]: +def get_partners(env: Annotated[Environment, Depends(odoo_env)]) -> list[PartnerInfo]: return [ PartnerInfo(name=partner.name, email=partner.email) for partner in env["res.partner"].search([]) @@ -614,6 +609,11 @@What’s building an API with fas
Now, you can start your Odoo server, install your addon and create a new endpoint instance for your app. Once it’s done click on the docs url to access the interactive documentation of your app.
+Note: FastAPI automatically exposes three services to easily interact with +the APIs and their structure (Swagger, Redoc, OpenAPI). +When exposing your Odoo instance to the open internet you might want to disable +these services. To do so toggle the “Expose FastAPI docs” checkbox in the FastAPI +Endpoint form view.
Before trying to test your app, you need to define on the endpoint instance the user that will be used to run the app. You can do it by setting the ‘user_id’ field. This information is the most important one because it’s the basis for @@ -664,19 +664,19 @@
What’s building an API with fas
The ‘odoo.addons.fastapi.dependencies’ module provides a set of functions that you can use to inject reusable dependencies into your routes. For example, the ‘odoo_env’ function returns the current odoo environment. You can use it to access the odoo models and the database from your route handlers.
-from typing import Annotated +from typing import Annotated -from odoo.api import Environment -from odoo.addons.fastapi.dependencies import odoo_env +from odoo.api import Environment +from odoo.addons.fastapi.dependencies import odoo_env @demo_api_router.get("/partners", response_model=list[PartnerInfo]) -def get_partners(env: Annotated[Environment, Depends(odoo_env)]) -> list[PartnerInfo]: +def get_partners(env: Annotated[Environment, Depends(odoo_env)]) -> list[PartnerInfo]: return [ PartnerInfo(name=partner.name, email=partner.email) for partner in env["res.partner"].search([]) @@ -712,7 +712,7 @@Dealing with the odoo environment
The ‘odoo_env’ dependency relies on a simple implementation that retrieves the current odoo environment from ContextVar variable initialized at the start of the request processing by the specific request dispatcher processing the @@ -724,7 +724,7 @@
-def fastapi_endpoint_id() -> int: +def fastapi_endpoint_id() -> int: """This method is overriden by default to make the fastapi.endpoint record available for your endpoint method. To get the fastapi.endpoint record in your method, you just need to add a dependency on the fastapi_endpoint method @@ -732,7 +732,7 @@The dependency injection mechanis """
-def fastapi_endpoint( +def fastapi_endpoint( _id: Annotated[int, Depends(fastapi_endpoint_id)], env: Annotated[Environment, Depends(odoo_env)], ) -> "FastapiEndpoint": @@ -748,7 +748,7 @@The dependency injection mechanis registering a specific method that returns the id of the current fastapi endpoint model instance for the original method.
-def _get_app(self) -> FastAPI: +def _get_app(self) -> FastAPI: app = FastAPI(**self._prepare_fastapi_endpoint_params()) for router in self._get_fastapi_routers(): app.include_router(prefix=self.root_path, router=router) @@ -762,18 +762,18 @@The dependency injection mechanis endpoint configuration.
To make our app not tightly coupled with a specific authentication mechanism, we will use the ‘authenticated_partner’ dependency. As for the ‘fastapi_endpoint’ this dependency depends on an abstract dependency.
When you define a route handler, you can inject the ‘authenticated_partner’ dependency as a parameter of your route handler.
-from odoo.addons.base.models.res_partner import Partner +from odoo.addons.base.models.res_partner import Partner @demo_api_router.get("/partners", response_model=list[PartnerInfo]) -def get_partners( +def get_partners( env: Annotated[Environment, Depends(odoo_env)], partner: Annotated[Partner, Depends(authenticated_partner)] ) -> list[PartnerInfo]: return [ @@ -789,7 +789,7 @@The authentication mechanism< ‘odoo.addons.fastapi.dependencies’ module and relies on functionalities provided by the ‘fastapi.security’ module.
-def authenticated_partner( +def authenticated_partner( env: Annotated[Environment, Depends(odoo_env)], security: Annotated[HTTPBasicCredentials, Depends(HTTPBasic())], ) -> "res.partner": @@ -827,9 +827,9 @@The authentication mechanism< authentication by using an api key or via basic auth. Since basic auth is already implemented, we will only implement the api key authentication mechanism.
-from fastapi.security import APIKeyHeader +from fastapi.security import APIKeyHeader -def api_key_based_authenticated_partner_impl( +def api_key_based_authenticated_partner_impl( api_key: Annotated[str, Depends( APIKeyHeader( name="api-key", @@ -854,9 +854,9 @@The authentication mechanism< can allows the user to select one of these authentication mechanisms by adding a selection field on the fastapi endpoint model.
-from odoo import fields, models +from odoo import fields, models -class FastapiEndpoint(models.Model): +class FastapiEndpoint(models.Model): _inherit = "fastapi.endpoint" @@ -879,8 +879,8 @@The authentication mechanism< provide the right implementation of the ‘authenticated_partner’ dependency when the app is instantiated.
-from odoo.addons.fastapi.dependencies import authenticated_partner -class FastapiEndpoint(models.Model): +from odoo.addons.fastapi.dependencies import authenticated_partner +class FastapiEndpoint(models.Model): _inherit = "fastapi.endpoint" @@ -892,7 +892,7 @@The authentication mechanism<
string="Authenciation method", ) - def _get_app(self) -> FastAPI: + def _get_app(self) -> FastAPI: app = super()._get_app() if self.app == "demo": # Here we add the overrides to the authenticated_partner_impl method @@ -923,7 +923,7 @@The authentication mechanism<
As we have seen in the previous section, you can add configuration fields on the fastapi endpoint model to allow the user to configure your app (as for any odoo model you extend). When you need to access these configuration fields @@ -931,10 +931,10 @@
-from pydantic import BaseModel, Field -from odoo.addons.fastapi.dependencies import fastapi_endpoint +from pydantic import BaseModel, Field +from odoo.addons.fastapi.dependencies import fastapi_endpoint -class EndpointAppInfo(BaseModel): +class EndpointAppInfo(BaseModel): id: str name: str app: str @@ -948,7 +948,7 @@Managing configuration parameters
response_model=EndpointAppInfo, dependencies=[Depends(authenticated_partner)], ) - async def endpoint_app_info( + async def endpoint_app_info( endpoint: Annotated[FastapiEndpoint, Depends(fastapi_endpoint)], ) -> EndpointAppInfo: """Returns the current endpoint configuration""" @@ -967,7 +967,7 @@Managing configuration parameters name of the fields that impact the instantiation of the app into the returned list.
-class FastapiEndpoint(models.Model): +class FastapiEndpoint(models.Model): _inherit = "fastapi.endpoint" @@ -980,14 +980,14 @@Managing configuration parameters
) @api.model - def _fastapi_app_fields(self) -> List[str]: + def _fastapi_app_fields(self) -> List[str]: fields = super()._fastapi_app_fields() fields.append("demo_auth_method") return fields
The fastapi addon parses the Accept-Language header of the request to determine the language to use. This parsing is done by respecting the RFC 7231 specification. That means that the language is determined by the first language found in the header that is @@ -999,7 +999,7 @@
When you develop a fastapi app, in a native python app it’s not possible to extend an existing one. This limitation doesn’t apply to the fastapi addon because the fastapi endpoint model is designed to be extended. However, the @@ -1027,7 +1027,7 @@
Let’s say that you want to change the implementation of the route handler ‘/demo/echo’. Since a route handler is just a python method, it could seems a tedious task since we are not into a model method and therefore we can’t @@ -1040,16 +1040,16 @@
-from pydantic import BaseModel -from fastapi import Depends, APIRouter -from odoo import models -from odoo.addons.fastapi.dependencies import odoo_env +from pydantic import BaseModel +from fastapi import Depends, APIRouter +from odoo import models +from odoo.addons.fastapi.dependencies import odoo_env -class FastapiEndpoint(models.Model): +class FastapiEndpoint(models.Model): _inherit = "fastapi.endpoint" - def _get_fastapi_routers(self) -> List[APIRouter]: + def _get_fastapi_routers(self) -> List[APIRouter]: routers = super()._get_fastapi_routers() routers.append(demo_api_router) return routers @@ -1061,29 +1061,29 @@Changing the implementation of th
response_model=EchoResponse, dependencies=[Depends(odoo_env)], ) -async def echo( +async def echo( message: str, odoo_env: Annotated[Environment, Depends(odoo_env)], ) -> EchoResponse: """Echo the message""" return EchoResponse(message=odoo_env["demo.fastapi.endpoint"].echo(message)) -class EchoResponse(BaseModel): +class EchoResponse(BaseModel): message: str -class DemoEndpoint(models.AbstractModel): +class DemoEndpoint(models.AbstractModel): _name = "demo.fastapi.endpoint" _description = "Demo Endpoint" - def echo(self, message: str) -> str: + def echo(self, message: str) -> str: return message -class DemoEndpointInherit(models.AbstractModel): +class DemoEndpointInherit(models.AbstractModel): _inherit = "demo.fastapi.endpoint" - def echo(self, message: str) -> str: + def echo(self, message: str) -> str: return f"Hello {message}"
As you’ve previously seen, the dependency injection mechanism of fastapi is very powerful. By designing your route handler to rely on dependencies with a specific functional scope, you can easily change the implementation of the @@ -1108,20 +1108,20 @@
Let’s say that you want to add a new route handler ‘/demo/echo2’. You could be tempted to add this new route handler in your new addons by importing the router of the existing app and adding the new route handler to it.
-from odoo.addons.fastapi.models.fastapi_endpoint_demo import demo_api_router +from odoo.addons.fastapi.models.fastapi_endpoint_demo import demo_api_router @demo_api_router.get( "/echo2", response_model=EchoResponse, dependencies=[Depends(odoo_env)], ) -async def echo2( +async def echo2( message: str, odoo_env: Annotated[Environment, Depends(odoo_env)], ) -> EchoResponse: @@ -1136,11 +1136,11 @@Adding a new route handler returned by the method ‘_get_fastapi_routers’ of the model ‘fastapi.endpoint’ you are inheriting from into your new addon.
-class FastapiEndpoint(models.Model): +class FastapiEndpoint(models.Model): _inherit = "fastapi.endpoint" - def _get_fastapi_routers(self) -> List[APIRouter]: + def _get_fastapi_routers(self) -> List[APIRouter]: routers = super()._get_fastapi_routers() if self.app == "demo": routers.append(additional_demo_api_router) @@ -1153,7 +1153,7 @@Adding a new route handler
response_model=EchoResponse, dependencies=[Depends(odoo_env)], ) -async def echo2( +async def echo2( message: str, odoo_env: Annotated[Environment, Depends(odoo_env)], ) -> EchoResponse: @@ -1165,7 +1165,7 @@Adding a new route handler the app is called for a database where your new addon is installed.
The fastapi python library uses the pydantic library to define the models. By default, once a model is defined, it’s not possible to extend it. However, a companion python library called @@ -1179,10 +1179,10 @@
When you want to allow other addons to extend a pydantic model, you must first define the model as an extendable model by using a dedicated metaclass
-from pydantic import BaseModel -from extendable_pydantic import ExtendableModelMeta +from pydantic import BaseModel +from extendable_pydantic import ExtendableModelMeta -class Partner(BaseModel, metaclass=ExtendableModelMeta): +class Partner(BaseModel, metaclass=ExtendableModelMeta): name = 0.1 model_config = ConfigDict(from_attributes=True)@@ -1195,7 +1195,7 @@
If you need to add a new field into the model ‘Partner’, you can extend it in your new addon by defining a new model that inherits from the model ‘Partner’.
-from typing import Optional -from odoo.addons.fastapi.models.fastapi_endpoint_demo import Partner +from typing import Optional +from odoo.addons.fastapi.models.fastapi_endpoint_demo import Partner -class PartnerExtended(Partner, extends=Partner): +class PartnerExtended(Partner, extends=Partner): email: Optional[str]
If your new addon is installed in a database, a call to the route handler @@ -1233,7 +1233,7 @@
By default the route handlers are processed using the user configured on the ‘fastapi.endpoint’ model instance. (default is the Public user). You have seen previously how to define a dependency that will be used to enforce @@ -1303,7 +1303,7 @@
Thanks to the starlette test client, it’s possible to test your fastapi app in a very simple way. With the test client, you can call your route handlers as if they were real http endpoints. The test client is available in the @@ -1325,20 +1325,20 @@
With this base class, writing a test for a route handler is as simple as:
-from odoo.fastapi.tests.common import FastAPITransactionCase +from odoo.fastapi.tests.common import FastAPITransactionCase -from odoo.addons.fastapi import dependencies -from odoo.addons.fastapi.routers import demo_router +from odoo.addons.fastapi import dependencies +from odoo.addons.fastapi.routers import demo_router -class FastAPIDemoCase(FastAPITransactionCase): +class FastAPIDemoCase(FastAPITransactionCase): @classmethod - def setUpClass(cls) -> None: + def setUpClass(cls) -> None: super().setUpClass() cls.default_fastapi_running_user = cls.env.ref("fastapi.my_demo_app_user") cls.default_fastapi_authenticated_partner = cls.env["res.partner"].create({"name": "FastAPI Demo"}) - def test_hello_world(self) -> None: + def test_hello_world(self) -> None: with self._create_test_client(router=demo_router) as test_client: response: Response = test_client.get("/demo/") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1348,20 +1348,20 @@How to test your fastapi app have created a test client for the whole app by not specifying the router but the app instead.
-from odoo.fastapi.tests.common import FastAPITransactionCase +from odoo.fastapi.tests.common import FastAPITransactionCase -from odoo.addons.fastapi import dependencies -from odoo.addons.fastapi.routers import demo_router +from odoo.addons.fastapi import dependencies +from odoo.addons.fastapi.routers import demo_router -class FastAPIDemoCase(FastAPITransactionCase): +class FastAPIDemoCase(FastAPITransactionCase): @classmethod - def setUpClass(cls) -> None: + def setUpClass(cls) -> None: super().setUpClass() cls.default_fastapi_running_user = cls.env.ref("fastapi.my_demo_app_user") cls.default_fastapi_authenticated_partner = cls.env["res.partner"].create({"name": "FastAPI Demo"}) - def test_hello_world(self) -> None: + def test_hello_world(self) -> None: demo_endpoint = self.env.ref("fastapi.fastapi_endpoint_demo") with self._create_test_client(app=demo_endpoint._get_app()) as test_client: response: Response = test_client.get(f"{demo_endpoint.root_path}/demo/") @@ -1370,7 +1370,7 @@How to test your fastapi app
Developing a fastapi app requires to follow some good practices to ensure that the app is robust and easy to maintain. Here are some of them:
The ‘odoo-addon-fastapi’ module provides 2 useful piece of code to help you be consistent when writing a route handler for a search route.
-from typing import Annotated -from pydantic import BaseModel +from typing import Annotated +from pydantic import BaseModel -from odoo.api import Environment -from odoo.addons.fastapi.dependencies import paging, authenticated_partner_env -from odoo.addons.fastapi.schemas import PagedCollection, Paging +from odoo.api import Environment +from odoo.addons.fastapi.dependencies import paging, authenticated_partner_env +from odoo.addons.fastapi.schemas import PagedCollection, Paging -class SaleOrder(BaseModel): +class SaleOrder(BaseModel): id: int name: str model_config = ConfigDict(from_attributes=True) @@ -1514,7 +1514,7 @@Development of a search route ha
response_model=PagedCollection[SaleOrder], response_model_exclude_unset=True, ) -def get_sale_orders( +def get_sale_orders( paging: Annotated[Paging, Depends(paging)], env: Annotated[Environment, Depends(authenticated_partner_env)], ) -> PagedCollection[SaleOrder]: @@ -1534,7 +1534,7 @@Development of a search route ha
The error handling is a very important topic in the design of the fastapi integration with odoo. By default, when instantiating the fastapi app, the fastapi library declare a default exception handler that will catch any exception raised by the @@ -1552,7 +1552,7 @@
When you develop a new addon to expose an api with fastapi, it’s a good practice to follow the same directory structure and naming convention for the files related to the api. It will help you to easily find the files related to the api @@ -1605,15 +1605,15 @@
In the ‘__init__.py’ file, you will import the router and add it to the global router or your addon.
-from fastapi import APIRouter +from fastapi import APIRouter -from .items import router as items_router +from .items import router as items_router router = APIRouter() router.include_router(items_router) @@ -1626,22 +1626,22 @@FastAPI addons directory structu For example, in your ‘my_model.py’ file, you will define a model like this:
-from pydantic import BaseModel +from pydantic import BaseModel -class MyModel(BaseModel): +class MyModel(BaseModel): name: str description: str = NoneIn the ‘__init__.py’ file, you will import the model’s classes from the files in the directory.
-from .my_model import MyModel +from .my_model import MyModelThis will allow to always import the models from the schemas module whatever the models are spread across different files or defined in the ‘schemas.py’ file.
-from x_api_addon.schemas import MyModel +from x_api_addon.schemas import MyModelThe ‘dependencies.py’ file contains the custom dependencies that you @@ -1656,14 +1656,14 @@
FastAPI addons directory structu
-What’s next?
+What’s next?
The ‘odoo-addon-fastapi’ module is still in its early stage of development. It will evolve over time to integrate your feedback and to provide the missing features. It’s now up to you to try it and to provide your feedback.
The roadmap and known issues can be found on GitHub.
@@ -1675,9 +1675,9 @@Features
Bugfixes
Fix issue with the retry of a POST request with a body content.
@@ -1699,7 +1699,7 @@Bugfixes
This change is a complete rewrite of the way the transactions are managed when @@ -1729,7 +1729,7 @@
Bugfixes
Fix compatibility issues with the latest Odoo version
@@ -1741,7 +1741,7 @@Bugfixes
Odoo has done an update and now, it checks domains of ir.rule on creation and modification.
@@ -1752,7 +1752,7 @@Bugfixes
In case of exception in endpoint execution, close the database cursor after rollback.
@@ -1762,7 +1762,7 @@Bugfixes
Bugfixes
Fix a typo in the Field declaration of the ‘count’ attribute of the ‘PagedCollection’ schema.
@@ -1786,7 +1786,7 @@Features
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 to smash it by providing a detailed and welcomed @@ -1808,21 +1808,21 @@
Do not contact contributors directly about support or help with technical issues.