Skip to content

Commit c0aab6d

Browse files
committed
Merge PR #569 into 19.0
Signed-off-by lmignon
2 parents 8a64ecc + f2e2390 commit c0aab6d

18 files changed

Lines changed: 538 additions & 479 deletions

.pre-commit-config.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ exclude: |
55
^base_rest_auth_api_key/|
66
^base_rest_pydantic/|
77
^extendable/|
8-
^fastapi/|
98
^pydantic/|
109
^rest_log/|
1110
# END NOT INSTALLABLE ADDONS

fastapi/README.rst

Lines changed: 317 additions & 316 deletions
Large diffs are not rendered by default.

fastapi/__manifest__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"name": "Odoo FastAPI",
66
"summary": """
77
Odoo FastAPI endpoint""",
8-
"version": "18.0.1.3.0",
8+
"version": "19.0.1.0.0",
99
"license": "LGPL-3",
1010
"author": "ACSONE SA/NV,Odoo Community Association (OCA)",
1111
"maintainers": ["lmignon"],
@@ -30,5 +30,5 @@
3030
]
3131
},
3232
"development_status": "Beta",
33-
"installable": False,
33+
"installable": True,
3434
}

fastapi/demo/fastapi_endpoint_demo.xml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
>
1111
<field name="name">My Demo Endpoint User</field>
1212
<field name="login">my_demo_app_user</field>
13-
<field name="groups_id" eval="[(6, 0, [])]" />
13+
<field name="group_ids" eval="[Command.set([])]" />
1414
</record>
1515

1616
<!-- This is the group that will be used to run the demo app
@@ -20,8 +20,11 @@
2020
-->
2121
<record id="my_demo_app_group" model="res.groups">
2222
<field name="name">My Demo Endpoint Group</field>
23-
<field name="users" eval="[(4, ref('my_demo_app_user'))]" />
24-
<field name="implied_ids" eval="[(4, ref('group_fastapi_endpoint_runner'))]" />
23+
<field name="user_ids" eval="[Command.link(ref('my_demo_app_user'))]" />
24+
<field
25+
name="implied_ids"
26+
eval="[Command.link(ref('group_fastapi_endpoint_runner'))]"
27+
/>
2528
</record>
2629

2730
<!-- This is the endpoint that will be used to run the demo app

fastapi/dependencies.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from odoo.api import Environment
77
from odoo.exceptions import AccessDenied
88

9-
from odoo.addons.base.models.res_partner import Partner
10-
from odoo.addons.base.models.res_users import Users
9+
from odoo.addons.base.models.res_partner import ResPartner
10+
from odoo.addons.base.models.res_users import ResUsers
1111

1212
from fastapi import Depends, Header, HTTPException, Query, status
1313
from fastapi.security import HTTPBasic, HTTPBasicCredentials
@@ -35,29 +35,31 @@ def odoo_env(company_id: Annotated[int | None, Depends(company_id)]) -> Environm
3535
yield env
3636

3737

38-
def authenticated_partner_impl() -> Partner:
38+
def authenticated_partner_impl() -> ResPartner:
3939
"""This method has to be overriden when you create your fastapi app
4040
to declare the way your partner will be provided. In some case, this
4141
partner will come from the authentication mechanism (ex jwt token) in other cases
4242
it could comme from a lookup on an email received into an HTTP header ...
4343
See the fastapi_endpoint_demo for an example"""
4444

4545

46-
def optionally_authenticated_partner_impl() -> Partner | None:
46+
def optionally_authenticated_partner_impl() -> ResPartner | None:
4747
"""This method has to be overriden when you create your fastapi app
4848
and you need to get an optional authenticated partner into your endpoint.
4949
"""
5050

5151

5252
def authenticated_partner_env(
53-
partner: Annotated[Partner, Depends(authenticated_partner_impl)],
53+
partner: Annotated[ResPartner, Depends(authenticated_partner_impl)],
5454
) -> Environment:
5555
"""Return an environment with the authenticated partner id in the context"""
5656
return partner.with_context(authenticated_partner_id=partner.id).env
5757

5858

5959
def optionally_authenticated_partner_env(
60-
partner: Annotated[Partner | None, Depends(optionally_authenticated_partner_impl)],
60+
partner: Annotated[
61+
ResPartner | None, Depends(optionally_authenticated_partner_impl)
62+
],
6163
env: Annotated[Environment, Depends(odoo_env)],
6264
) -> Environment:
6365
"""Return an environment with the authenticated partner id in the context if
@@ -69,9 +71,9 @@ def optionally_authenticated_partner_env(
6971

7072

7173
def authenticated_partner(
72-
partner: Annotated[Partner, Depends(authenticated_partner_impl)],
74+
partner: Annotated[ResPartner, Depends(authenticated_partner_impl)],
7375
partner_env: Annotated[Environment, Depends(authenticated_partner_env)],
74-
) -> Partner:
76+
) -> ResPartner:
7577
"""If you need to get access to the authenticated partner into your
7678
endpoint, you can add a dependency into the endpoint definition on this
7779
method.
@@ -85,9 +87,11 @@ def authenticated_partner(
8587

8688

8789
def optionally_authenticated_partner(
88-
partner: Annotated[Partner | None, Depends(optionally_authenticated_partner_impl)],
90+
partner: Annotated[
91+
ResPartner | None, Depends(optionally_authenticated_partner_impl)
92+
],
8993
partner_env: Annotated[Environment, Depends(optionally_authenticated_partner_env)],
90-
) -> Partner | None:
94+
) -> ResPartner | None:
9195
"""If you need to get access to the authenticated partner if the call is
9296
authenticated, you can add a dependency into the endpoint definition on this
9397
method.
@@ -110,21 +114,20 @@ def paging(
110114
def basic_auth_user(
111115
credential: Annotated[HTTPBasicCredentials, Depends(HTTPBasic())],
112116
env: Annotated[Environment, Depends(odoo_env)],
113-
) -> Users:
117+
) -> ResUsers:
114118
username = credential.username
115119
password = credential.password
116120
try:
117121
response = (
118122
env["res.users"]
119123
.sudo()
120124
.authenticate(
121-
db=env.cr.dbname,
122125
credential={
123126
"type": "password",
124127
"login": username,
125128
"password": password,
126129
},
127-
user_agent_env=None,
130+
user_agent_env={"interactive": False},
128131
)
129132
)
130133
return env["res.users"].browse(response.get("uid"))
@@ -137,9 +140,9 @@ def basic_auth_user(
137140

138141

139142
def authenticated_partner_from_basic_auth_user(
140-
user: Annotated[Users, Depends(basic_auth_user)],
143+
user: Annotated[ResUsers, Depends(basic_auth_user)],
141144
env: Annotated[Environment, Depends(odoo_env)],
142-
) -> Partner:
145+
) -> ResPartner:
143146
return env["res.partner"].browse(user.sudo().partner_id.id)
144147

145148

fastapi/error_handlers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@ def convert_exception_to_status_body(exc: Exception) -> tuple[int, dict]:
2929
status_code = exc.status_code
3030
details = exc.detail
3131
elif isinstance(exc, RequestValidationError):
32-
status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
32+
status_code = status.HTTP_422_UNPROCESSABLE_CONTENT
3333
details = jsonable_encoder(exc.errors())
3434
elif isinstance(exc, WebSocketRequestValidationError):
3535
status_code = status.WS_1008_POLICY_VIOLATION
3636
details = jsonable_encoder(exc.errors())
3737
elif isinstance(exc, AccessDenied | AccessError):
3838
status_code = status.HTTP_403_FORBIDDEN
39-
details = "AccessError"
39+
details = exc.args[0]
4040
elif isinstance(exc, MissingError):
4141
status_code = status.HTTP_404_NOT_FOUND
42-
details = "MissingError"
42+
details = exc.args[0]
4343
elif isinstance(exc, UserError):
4444
status_code = status.HTTP_400_BAD_REQUEST
4545
details = exc.args[0]

fastapi/models/fastapi_endpoint.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
from starlette.middleware import Middleware
1111
from starlette.routing import Mount
1212

13-
from odoo import _, api, exceptions, fields, models, tools
13+
from odoo import api, exceptions, fields, models, tools
14+
from odoo.tools import convert
1415

1516
from fastapi import APIRouter, Depends, FastAPI
1617

@@ -88,7 +89,7 @@ def _check_root_path(self):
8889
for rec in self:
8990
if rec.root_path in self._blacklist_root_paths:
9091
raise exceptions.UserError(
91-
_(
92+
self.env._(
9293
"`%(name)s` uses a blacklisted root_path = `%(root_path)s`",
9394
name=rec.name,
9495
root_path=rec.root_path,
@@ -338,3 +339,19 @@ def _get_fastapi_app_middlewares(self) -> list[Middleware]:
338339
def _get_fastapi_app_dependencies(self) -> list[Depends]:
339340
"""Return the dependencies to use for the fastapi app."""
340341
return [Depends(dependencies.accept_language)]
342+
343+
# test utility
344+
@api.model
345+
def has_demo_data(self):
346+
return (
347+
self.env.ref("fastapi.fastapi_endpoint_demo", raise_if_not_found=False)
348+
is not None
349+
)
350+
351+
def _load_demo_data(self):
352+
if self.has_demo_data():
353+
return
354+
# Load demo data
355+
convert.convert_file(
356+
self.env, "fastapi", "demo/fastapi_endpoint_demo.xml", None, mode="init"
357+
)

fastapi/models/fastapi_endpoint_demo.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
# License LGPL-3.0 or later (http://www.gnu.org/licenses/LGPL).
33
from typing import Annotated, Any
44

5-
from odoo import _, api, fields, models
5+
from odoo import api, fields, models
66
from odoo.api import Environment
77
from odoo.exceptions import ValidationError
88

9-
from odoo.addons.base.models.res_partner import Partner
9+
from odoo.addons.base.models.res_partner import ResPartner
1010

1111
from fastapi import APIRouter, Depends, HTTPException, status
1212
from fastapi.security import APIKeyHeader
@@ -40,7 +40,7 @@ def _valdiate_demo_auth_method(self):
4040
for rec in self:
4141
if rec.app == "demo" and not rec.demo_auth_method:
4242
raise ValidationError(
43-
_(
43+
self.env._(
4444
"The authentication method is required for app %(app)s",
4545
app=rec.app,
4646
)
@@ -90,7 +90,7 @@ def api_key_based_authenticated_partner_impl(
9090
),
9191
],
9292
env: Annotated[Environment, Depends(odoo_env)],
93-
) -> Partner:
93+
) -> ResPartner:
9494
"""A dummy implementation that look for a user with the same login
9595
as the provided api key
9696
"""

fastapi/routers/demo_router.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from odoo.exceptions import AccessError, MissingError, UserError, ValidationError
1515
from odoo.service.model import MAX_TRIES_ON_CONCURRENCY_FAILURE
1616

17-
from odoo.addons.base.models.res_partner import Partner
17+
from odoo.addons.base.models.res_partner import ResPartner
1818

1919
from fastapi import APIRouter, Depends, File, HTTPException, Query, status
2020
from fastapi.responses import JSONResponse
@@ -67,7 +67,7 @@ async def get_lang(env: Annotated[Environment, Depends(odoo_env)]):
6767

6868
@router.get("/demo/who_ami")
6969
async def who_ami(
70-
partner: Annotated[Partner, Depends(authenticated_partner)],
70+
partner: Annotated[ResPartner, Depends(authenticated_partner)],
7171
) -> DemoUserInfo:
7272
"""Who am I?
7373

fastapi/security/ir_rule+acl.xml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
<field name="name">Fastapi: Running user rule</field>
1919
<field name="model_id" ref="base.model_res_users" />
2020
<field name="domain_force"> [('id', '=', user.id)]</field>
21-
<field name="groups" eval="[(4, ref('group_fastapi_endpoint_runner'))]" />
21+
<field
22+
name="groups"
23+
eval="[Command.link(ref('group_fastapi_endpoint_runner'))]"
24+
/>
2225
</record>
2326

2427
<!-- give access to the user running the demo app to the res.partner model -->
@@ -40,7 +43,10 @@
4043
<field
4144
name="domain_force"
4245
> ['|', ('user_ids', '=', user.id), ('id', '=', authenticated_partner_id)]</field>
43-
<field name="groups" eval="[(4, ref('group_fastapi_endpoint_runner'))]" />
46+
<field
47+
name="groups"
48+
eval="[Command.link(ref('group_fastapi_endpoint_runner'))]"
49+
/>
4450
</record>
4551

4652
<!-- give access by the user running the demo app to the fastapi.enddoint model -->
@@ -59,6 +65,9 @@
5965
<field name="name">Fastapi: Running user rule</field>
6066
<field name="model_id" ref="fastapi.model_fastapi_endpoint" />
6167
<field name="domain_force"> [('user_id', '=', user.id)]</field>
62-
<field name="groups" eval="[(4, ref('group_fastapi_endpoint_runner'))]" />
68+
<field
69+
name="groups"
70+
eval="[Command.link(ref('group_fastapi_endpoint_runner'))]"
71+
/>
6372
</record>
6473
</odoo>

0 commit comments

Comments
 (0)