From 2704bd47ff534b98b48a6405266defab45814dbe Mon Sep 17 00:00:00 2001 From: Piruin Panichphol Date: Thu, 29 Oct 2026 10:52:21 +0700 Subject: [PATCH] [FIX] ni_medication: read donor dosage views with sudo The dosage fields mixin splices fragments from donor form views in _get_view, which runs as the requesting user on a view-cache miss. Only base.group_system can read ir.ui.view, so medication users got an access error opening medication forms whenever no admin had loaded the form first in the same language (e.g. after a restart or upgrade). Tests now run as the lowest group each one needs, with the view cache cleared, so access regressions surface instead of being hidden by the superuser and the view cache. Fixes #178 --- ni_medication/README.md | 2 ++ .../models/ni_medication_dosage_fields_mixin.py | 4 +++- ni_medication/tests/test_ni_medication_dosage.py | 10 +++++++++- .../test_ni_medication_dosage_fields_mixin.py | 15 ++++++++++++++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/ni_medication/README.md b/ni_medication/README.md index da107f02e..ffa960cbc 100644 --- a/ni_medication/README.md +++ b/ni_medication/README.md @@ -49,6 +49,8 @@ flows. `ni_medication_request_view_form`). - Inheriting a donor view (e.g. an xpath adding a field to the dosage form) therefore only affects the donor form, not the other four, and the empty placeholders in the other forms cannot be targeted by xpath. +- Donor views must be read with `sudo()`: `_get_view` runs as the requesting user on a view-cache miss, and only + `base.group_system` can read `ir.ui.view`. ## Verification diff --git a/ni_medication/models/ni_medication_dosage_fields_mixin.py b/ni_medication/models/ni_medication_dosage_fields_mixin.py index 9409da03b..b7e5a17d9 100644 --- a/ni_medication/models/ni_medication_dosage_fields_mixin.py +++ b/ni_medication/models/ni_medication_dosage_fields_mixin.py @@ -43,7 +43,9 @@ def _get_view(self, view_id=None, view_type="form", **options): placeholders = arch.xpath(f"//*[@name='{name}' and not(node())]") if not placeholders: continue - donor_arch = etree.fromstring(self.env.ref(donor_xmlid).arch) + # ir.ui.view is readable by group_system only + donor_view = self.env.ref(donor_xmlid).sudo() + donor_arch = etree.fromstring(donor_view.arch) fragments = donor_arch.xpath(f"//*[@name='{name}' and node()]") if not fragments: continue diff --git a/ni_medication/tests/test_ni_medication_dosage.py b/ni_medication/tests/test_ni_medication_dosage.py index e1be5c4f1..4252dac8a 100644 --- a/ni_medication/tests/test_ni_medication_dosage.py +++ b/ni_medication/tests/test_ni_medication_dosage.py @@ -1,6 +1,6 @@ # Copyright (c) 2025 NSTDA -from odoo.tests import TransactionCase +from odoo.tests import TransactionCase, new_test_user class TestDosageTiming(TransactionCase): @@ -8,6 +8,14 @@ class TestDosageTiming(TransactionCase): EventTiming codes (ni.timing.event) and Timing.repeat fields (ni.timing.timing) that ni_timing already models.""" + @classmethod + def setUpClass(cls): + super().setUpClass() + user = new_test_user( + cls.env, login="medication_user", groups="ni_medication.group_user" + ) + cls.env = cls.env(user=user) + def setUp(self): super().setUp() self.Dosage = self.env["ni.medication.dosage"] diff --git a/ni_medication/tests/test_ni_medication_dosage_fields_mixin.py b/ni_medication/tests/test_ni_medication_dosage_fields_mixin.py index c7a98c8b8..3f3810420 100644 --- a/ni_medication/tests/test_ni_medication_dosage_fields_mixin.py +++ b/ni_medication/tests/test_ni_medication_dosage_fields_mixin.py @@ -1,7 +1,7 @@ # Copyright (c) 2025-2026 NSTDA from lxml import etree -from odoo.tests import TransactionCase +from odoo.tests import TransactionCase, new_test_user PLACEHOLDERS = ("dosage_page_content", "dosage_fields", "dosage_administration_fields") @@ -11,6 +11,19 @@ class TestDosageFieldsMixin(TransactionCase): placeholder into real content, with no leftover empty placeholder left in the arch.""" + @classmethod + def setUpClass(cls): + super().setUpClass() + viewer = new_test_user( + cls.env, login="medication_viewer", groups="ni_medication.group_viewer" + ) + cls.env = cls.env(user=viewer) + + def setUp(self): + super().setUp() + # a cached view would skip _get_view, hiding access errors (#178) + self.env.registry.clear_caches() + def _get_arch(self, model): arch = self.env[model].get_view(view_type="form")["arch"] tree = etree.fromstring(arch)