Skip to content

Commit ec8ffc4

Browse files
committed
fix(spp_api_v2_change_request): fix 3 bugs and promote to Beta
- Fix path routing: replace /{reference} with /{p1}/{p2}/{p3} on all 8 reference-based endpoints since CR references (CR/2026/00001) contain slashes that Starlette splits into separate path segments - Fix mail.thread AccessError: add mail_create_nolog and tracking_disable context to service env, preventing flush_all() from triggering message_partner_ids writes as Public user (uid=3) - Fix detail serialization: exclude mail.thread fields (message_ids, message_is_follower, has_message, etc.) from API responses - Promote development_status from Alpha to Beta
1 parent 5a04d3a commit ec8ffc4

File tree

3 files changed

+63
-19
lines changed

3 files changed

+63
-19
lines changed

spp_api_v2_change_request/__manifest__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"author": "OpenSPP.org",
77
"website": "https://github.com/OpenSPP/OpenSPP2",
88
"license": "LGPL-3",
9-
"development_status": "Alpha",
9+
"development_status": "Beta",
1010
"maintainers": ["jeremi", "gonzalesedwin1123", "reichie020212"],
1111
"depends": [
1212
"spp_api_v2",

spp_api_v2_change_request/routers/change_request.py

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040
change_request_router = APIRouter(tags=["ChangeRequest"], prefix="/ChangeRequest")
4141

4242

43+
def _build_reference(p1: str, p2: str, p3: str) -> str:
44+
"""Reconstruct CR reference from path segments (e.g., CR/2026/00001)."""
45+
return f"{p1}/{p2}/{p3}"
46+
47+
4348
@change_request_router.post(
4449
"",
4550
response_model=ChangeRequestResponse,
@@ -87,9 +92,11 @@ async def create_change_request(
8792
return service.to_api_schema(cr)
8893

8994

90-
@change_request_router.get("/{reference}", response_model=ChangeRequestResponse)
95+
@change_request_router.get("/{p1}/{p2}/{p3}", response_model=ChangeRequestResponse)
9196
async def read_change_request(
92-
reference: Annotated[str, Path(description="CR reference (e.g., CR/2024/00001)")],
97+
p1: Annotated[str, Path(description="Reference part 1 (e.g., CR)")],
98+
p2: Annotated[str, Path(description="Reference part 2 (e.g., 2026)")],
99+
p3: Annotated[str, Path(description="Reference part 3 (e.g., 00001)")],
93100
env: Annotated[Environment, Depends(odoo_env)],
94101
api_client: Annotated[dict, Depends(get_authenticated_client)],
95102
response: Response,
@@ -106,6 +113,7 @@ async def read_change_request(
106113
detail="Client does not have permission to read change requests",
107114
)
108115

116+
reference = _build_reference(p1, p2, p3)
109117
service = ChangeRequestService(env)
110118
cr = service.find_by_reference(reference)
111119

@@ -224,9 +232,11 @@ async def search_change_requests(
224232
)
225233

226234

227-
@change_request_router.put("/{reference}", response_model=ChangeRequestResponse)
235+
@change_request_router.put("/{p1}/{p2}/{p3}", response_model=ChangeRequestResponse)
228236
async def update_change_request(
229-
reference: Annotated[str, Path()],
237+
p1: Annotated[str, Path(description="Reference part 1 (e.g., CR)")],
238+
p2: Annotated[str, Path(description="Reference part 2 (e.g., 2026)")],
239+
p3: Annotated[str, Path(description="Reference part 3 (e.g., 00001)")],
230240
update_data: ChangeRequestUpdate,
231241
env: Annotated[Environment, Depends(odoo_env)],
232242
api_client: Annotated[dict, Depends(get_authenticated_client)],
@@ -245,6 +255,7 @@ async def update_change_request(
245255
detail="Client does not have permission to update change requests",
246256
)
247257

258+
reference = _build_reference(p1, p2, p3)
248259
service = ChangeRequestService(env)
249260
cr = service.find_by_reference(reference)
250261

@@ -284,9 +295,11 @@ async def update_change_request(
284295
# === Actions ===
285296

286297

287-
@change_request_router.post("/{reference}/$submit", response_model=ChangeRequestResponse)
298+
@change_request_router.post("/{p1}/{p2}/{p3}/$submit", response_model=ChangeRequestResponse)
288299
async def submit_change_request(
289-
reference: Annotated[str, Path()],
300+
p1: Annotated[str, Path(description="Reference part 1 (e.g., CR)")],
301+
p2: Annotated[str, Path(description="Reference part 2 (e.g., 2026)")],
302+
p3: Annotated[str, Path(description="Reference part 3 (e.g., 00001)")],
290303
env: Annotated[Environment, Depends(odoo_env)],
291304
api_client: Annotated[dict, Depends(get_authenticated_client)],
292305
):
@@ -301,6 +314,7 @@ async def submit_change_request(
301314
detail="Client does not have permission to submit change requests",
302315
)
303316

317+
reference = _build_reference(p1, p2, p3)
304318
service = ChangeRequestService(env)
305319
cr = service.find_by_reference(reference)
306320

@@ -321,9 +335,11 @@ async def submit_change_request(
321335
return service.to_api_schema(cr)
322336

323337

324-
@change_request_router.post("/{reference}/$approve", response_model=ChangeRequestResponse)
338+
@change_request_router.post("/{p1}/{p2}/{p3}/$approve", response_model=ChangeRequestResponse)
325339
async def approve_change_request(
326-
reference: Annotated[str, Path()],
340+
p1: Annotated[str, Path(description="Reference part 1 (e.g., CR)")],
341+
p2: Annotated[str, Path(description="Reference part 2 (e.g., 2026)")],
342+
p3: Annotated[str, Path(description="Reference part 3 (e.g., 00001)")],
327343
env: Annotated[Environment, Depends(odoo_env)],
328344
api_client: Annotated[dict, Depends(get_authenticated_client)],
329345
action_data: ApproveActionData | None = None,
@@ -339,6 +355,7 @@ async def approve_change_request(
339355
detail="Client does not have permission to approve change requests",
340356
)
341357

358+
reference = _build_reference(p1, p2, p3)
342359
service = ChangeRequestService(env)
343360
cr = service.find_by_reference(reference)
344361

@@ -360,9 +377,11 @@ async def approve_change_request(
360377
return service.to_api_schema(cr)
361378

362379

363-
@change_request_router.post("/{reference}/$reject", response_model=ChangeRequestResponse)
380+
@change_request_router.post("/{p1}/{p2}/{p3}/$reject", response_model=ChangeRequestResponse)
364381
async def reject_change_request(
365-
reference: Annotated[str, Path()],
382+
p1: Annotated[str, Path(description="Reference part 1 (e.g., CR)")],
383+
p2: Annotated[str, Path(description="Reference part 2 (e.g., 2026)")],
384+
p3: Annotated[str, Path(description="Reference part 3 (e.g., 00001)")],
366385
action_data: RejectActionData,
367386
env: Annotated[Environment, Depends(odoo_env)],
368387
api_client: Annotated[dict, Depends(get_authenticated_client)],
@@ -378,6 +397,7 @@ async def reject_change_request(
378397
detail="Client does not have permission to reject change requests",
379398
)
380399

400+
reference = _build_reference(p1, p2, p3)
381401
service = ChangeRequestService(env)
382402
cr = service.find_by_reference(reference)
383403

@@ -398,9 +418,11 @@ async def reject_change_request(
398418
return service.to_api_schema(cr)
399419

400420

401-
@change_request_router.post("/{reference}/$request-revision", response_model=ChangeRequestResponse)
421+
@change_request_router.post("/{p1}/{p2}/{p3}/$request-revision", response_model=ChangeRequestResponse)
402422
async def request_revision_change_request(
403-
reference: Annotated[str, Path()],
423+
p1: Annotated[str, Path(description="Reference part 1 (e.g., CR)")],
424+
p2: Annotated[str, Path(description="Reference part 2 (e.g., 2026)")],
425+
p3: Annotated[str, Path(description="Reference part 3 (e.g., 00001)")],
404426
action_data: RequestRevisionActionData,
405427
env: Annotated[Environment, Depends(odoo_env)],
406428
api_client: Annotated[dict, Depends(get_authenticated_client)],
@@ -416,6 +438,7 @@ async def request_revision_change_request(
416438
detail="Client does not have permission to request revision",
417439
)
418440

441+
reference = _build_reference(p1, p2, p3)
419442
service = ChangeRequestService(env)
420443
cr = service.find_by_reference(reference)
421444

@@ -436,9 +459,11 @@ async def request_revision_change_request(
436459
return service.to_api_schema(cr)
437460

438461

439-
@change_request_router.post("/{reference}/$apply", response_model=ChangeRequestResponse)
462+
@change_request_router.post("/{p1}/{p2}/{p3}/$apply", response_model=ChangeRequestResponse)
440463
async def apply_change_request(
441-
reference: Annotated[str, Path()],
464+
p1: Annotated[str, Path(description="Reference part 1 (e.g., CR)")],
465+
p2: Annotated[str, Path(description="Reference part 2 (e.g., 2026)")],
466+
p3: Annotated[str, Path(description="Reference part 3 (e.g., 00001)")],
442467
env: Annotated[Environment, Depends(odoo_env)],
443468
api_client: Annotated[dict, Depends(get_authenticated_client)],
444469
):
@@ -453,6 +478,7 @@ async def apply_change_request(
453478
detail="Client does not have permission to apply change requests",
454479
)
455480

481+
reference = _build_reference(p1, p2, p3)
456482
service = ChangeRequestService(env)
457483
cr = service.find_by_reference(reference)
458484

@@ -473,9 +499,11 @@ async def apply_change_request(
473499
return service.to_api_schema(cr)
474500

475501

476-
@change_request_router.post("/{reference}/$reset", response_model=ChangeRequestResponse)
502+
@change_request_router.post("/{p1}/{p2}/{p3}/$reset", response_model=ChangeRequestResponse)
477503
async def reset_change_request(
478-
reference: Annotated[str, Path()],
504+
p1: Annotated[str, Path(description="Reference part 1 (e.g., CR)")],
505+
p2: Annotated[str, Path(description="Reference part 2 (e.g., 2026)")],
506+
p3: Annotated[str, Path(description="Reference part 3 (e.g., 00001)")],
479507
env: Annotated[Environment, Depends(odoo_env)],
480508
api_client: Annotated[dict, Depends(get_authenticated_client)],
481509
):
@@ -490,6 +518,7 @@ async def reset_change_request(
490518
detail="Client does not have permission to reset change requests",
491519
)
492520

521+
reference = _build_reference(p1, p2, p3)
493522
service = ChangeRequestService(env)
494523
cr = service.find_by_reference(reference)
495524

spp_api_v2_change_request/services/change_request_service.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ class ChangeRequestService:
1818
"""Service for Change Request resource CRUD and mapping."""
1919

2020
def __init__(self, env: Environment):
21-
self.env = env
21+
ctx = dict(env.context, mail_create_nolog=True, tracking_disable=True)
22+
self.env = env(context=ctx)
2223

2324
def find_by_reference(self, reference: str):
2425
"""
@@ -161,7 +162,7 @@ def _serialize_detail(self, detail) -> dict[str, Any]:
161162
# Get fields from the model
162163
model_fields = detail._fields
163164

164-
# Skip internal and computed fields
165+
# Skip internal, computed, and mail.thread fields
165166
skip_fields = {
166167
"id",
167168
"create_uid",
@@ -175,6 +176,20 @@ def _serialize_detail(self, detail) -> dict[str, Any]:
175176
"registrant_id",
176177
"approval_state",
177178
"is_applied",
179+
# mail.thread fields
180+
"message_ids",
181+
"message_follower_ids",
182+
"message_partner_ids",
183+
"message_is_follower",
184+
"has_message",
185+
"message_needaction",
186+
"message_needaction_counter",
187+
"message_has_error",
188+
"message_has_error_counter",
189+
"message_attachment_count",
190+
"message_has_sms_error",
191+
"message_main_attachment_id",
192+
"website_message_ids",
178193
}
179194

180195
for field_name, field in model_fields.items():

0 commit comments

Comments
 (0)