From df11ad794afad93eb774f0089934e66fc1d31ad5 Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Thu, 8 Oct 2026 11:23:40 +0800 Subject: [PATCH 1/7] test(spp_base_common): form New button must keep Odoo's canCreate guard (#582) Apply the web.FormView extension to the stock template with Odoo's own template_inheritance engine and assert the control-panel New button still reads canCreate. Fails today: the extension replaces the condition with !hideFormCreateButton, so forms opened with create disabled by action context, arch, ACL or preventCreate render a live New button. --- spp_base_common/tests/__init__.py | 1 + .../tests/test_form_create_button_template.py | 82 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 spp_base_common/tests/test_form_create_button_template.py diff --git a/spp_base_common/tests/__init__.py b/spp_base_common/tests/__init__.py index dd1dc2cfa..42c8af734 100644 --- a/spp_base_common/tests/__init__.py +++ b/spp_base_common/tests/__init__.py @@ -1,3 +1,4 @@ +from . import test_form_create_button_template from . import test_ir_module_module from . import test_pdf_backend from . import test_phone_number_validation diff --git a/spp_base_common/tests/test_form_create_button_template.py b/spp_base_common/tests/test_form_create_button_template.py new file mode 100644 index 000000000..9f29cc445 --- /dev/null +++ b/spp_base_common/tests/test_form_create_button_template.py @@ -0,0 +1,82 @@ +"""Guard the form-view "New" button extension in custom_list_create_template.xml. + +spp_base_common extends the ``web.FormView`` template so that any module can set +``this.hideFormCreateButton = true`` on its FormController patch to hide the +control-panel "New" button. Stock Odoo renders that button with +``t-if="canCreate"``, and ``canCreate`` is the single switch behind every way a +form can be opened without create: ``{'create': False}`` in the action context, +``create="0"`` on the form arch, a missing create ACL, and the ``preventCreate`` +prop. Replacing the condition instead of extending it rendered a live "New" +button in all four cases (#582). + +The web client applies template inheritance in the browser, which the test image +cannot run. Odoo ships the same xpath/position engine server-side in +``odoo.tools.template_inheritance``, so this test applies the extension to the +real stock template and checks the resulting condition. +""" + +import copy + +from lxml import etree + +from odoo.tests import TransactionCase +from odoo.tools.misc import file_open +from odoo.tools.template_inheritance import apply_inheritance_specs + +STOCK_FORM_TEMPLATES = "web/static/src/views/form/form_controller.xml" +EXTENSION_TEMPLATES = "spp_base_common/static/src/xml/custom_list_create_template.xml" +# Relative to the template element: an absolute "//" xpath on an lxml element +# searches the whole document, i.e. every template in the file. +CREATE_BUTTON_XPATH = ".//button[hasclass('o_form_button_create')]" + + +def _load_templates(path): + with file_open(path, "rb") as template_file: + return etree.fromstring(template_file.read()) + + +class TestFormCreateButtonTemplate(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.stock = _load_templates(STOCK_FORM_TEMPLATES) + cls.extension = _load_templates(EXTENSION_TEMPLATES) + + def _apply_extension(self, template_name): + """Return the stock template after every spec that inherits it is applied. + + The template is detached from its file first so that the spec's own + "//" xpath is scoped to this one template, as the web client scopes it. + """ + (source,) = self.stock.xpath(f"./t[@t-name='{template_name}']") + source = copy.deepcopy(source) + specs = [ + spec + for inherit in self.extension.xpath(f"./t[@t-inherit='{template_name}']") + for spec in inherit + if isinstance(spec.tag, str) + ] + return apply_inheritance_specs(source, specs) + + def test_control_panel_new_button_keeps_can_create(self): + form_view = self._apply_extension("web.FormView") + (button,) = form_view.xpath(CREATE_BUTTON_XPATH) + condition = button.get("t-if") + + self.assertRegex( + condition, + r"\bcanCreate\b", + 'the form New button must keep Odoo\'s canCreate guard (action context, create="0", ACL, preventCreate)', + ) + self.assertIn( + "!hideFormCreateButton", + condition, + "the hideFormCreateButton opt-out must still hide the button", + ) + + def test_dialog_new_button_is_not_touched(self): + """The extension targets web.FormView only; dialog footers keep stock markup.""" + self.assertFalse(self.extension.xpath("./t[@t-inherit='web.FormView.Buttons']")) + (source,) = self.stock.xpath("./t[@t-name='web.FormView.Buttons']") + (button,) = source.xpath(CREATE_BUTTON_XPATH) + self.assertRegex(button.get("t-if"), r"\bcanCreate\b") From 86695375a34dc8130f4488575ad3db11f9687846 Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Thu, 8 Oct 2026 11:25:00 +0800 Subject: [PATCH 2/7] fix(spp_base_common): keep canCreate on the form New button (#582) The web.FormView extension behind hideFormCreateButton replaced the button's t-if instead of extending it, so Odoo's canCreate guard was dropped and every form opened with create disabled (action context, create="0" on the arch, no create ACL, preventCreate) rendered a live New button. The condition is now canCreate and !hideFormCreateButton: the flag can still only hide the button. Dialog forms use the separate web.FormView.Buttons template and were never affected. spp_base_common 19.0.2.0.3. --- spp_base_common/__manifest__.py | 2 +- spp_base_common/readme/HISTORY.md | 4 ++++ .../static/src/xml/custom_list_create_template.xml | 8 ++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/spp_base_common/__manifest__.py b/spp_base_common/__manifest__.py index 09e67451d..550c135b5 100644 --- a/spp_base_common/__manifest__.py +++ b/spp_base_common/__manifest__.py @@ -5,7 +5,7 @@ { "name": "OpenSPP Base (Common)", "category": "OpenSPP/Core", - "version": "19.0.2.0.2", + "version": "19.0.2.0.3", "sequence": 1, "author": "OpenSPP.org", "website": "https://github.com/OpenSPP/OpenSPP2", diff --git a/spp_base_common/readme/HISTORY.md b/spp_base_common/readme/HISTORY.md index 2812444f2..5f5adda1e 100644 --- a/spp_base_common/readme/HISTORY.md +++ b/spp_base_common/readme/HISTORY.md @@ -1,3 +1,7 @@ +### 19.0.2.0.3 + +- fix(ui): the form view's **New** button respects Odoo's create guard again. The `web.FormView` extension that lets a module hide the button with `hideFormCreateButton` replaced the button's condition instead of extending it, dropping Odoo's `canCreate`. As a result every form opened with create disabled — `{'create': False}` in the action context, `create="0"` on the form arch, a role without create access, or `preventCreate` — still rendered a working **New** button that opened a blank editable record, or failed with an access error on save. The condition is now `canCreate and !hideFormCreateButton`: the flag can only hide the button, never show it, and modules using it keep working unchanged. Forms shown in dialogs use a separate template and were never affected (#582). + ### 19.0.2.0.2 - test: add a regression test guarding the PDF backend selected by `odoo.tools.pdf`. The Docker image accidentally shipped legacy PyPDF2 3.x next to pypdf; Odoo prefers PyPDF2 when importable, and its removed 1.x API (`numPages`/`getPage`) crashes multi-record PDF printing with a `DeprecationError` (OP#1168). The fix is in `docker/Dockerfile` (`--no-deps` on the Odoo editable install); this test fails on any image that regresses. diff --git a/spp_base_common/static/src/xml/custom_list_create_template.xml b/spp_base_common/static/src/xml/custom_list_create_template.xml index 2d517e986..e5a8d18e5 100644 --- a/spp_base_common/static/src/xml/custom_list_create_template.xml +++ b/spp_base_common/static/src/xml/custom_list_create_template.xml @@ -42,10 +42,14 @@ + FormController patch to hide the default "New" button. + The flag can only hide the button, never show it: Odoo's own + canCreate guard stays in the condition, so a form opened with + create disabled (action context {'create': False}, create="0" on + the arch, no create ACL, preventCreate) keeps hiding "New". --> - !hideFormCreateButton + canCreate and !hideFormCreateButton From 8e5bc639f022756b389355a23becb8528a04c64e Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Thu, 8 Oct 2026 11:38:27 +0800 Subject: [PATCH 3/7] test(e2e): assert the form New button is absent on read-only forms and for read-only roles (#582) Spec 01, test 20, as the HQ validator: an approval review form (the role has no create ACL on reviews) and the CR detail form (opened with context create: false). Both assert .o_form_button_create has DOM count 0 rather than being hidden, because spp_programs' FormController patch hides the same button with display:none and would mask a template that renders it anyway. --- e2e/tests/01-spp-starter-spmis.spec.ts | 30 +++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/e2e/tests/01-spp-starter-spmis.spec.ts b/e2e/tests/01-spp-starter-spmis.spec.ts index c13bd915b..1fc17e729 100644 --- a/e2e/tests/01-spp-starter-spmis.spec.ts +++ b/e2e/tests/01-spp-starter-spmis.spec.ts @@ -22,7 +22,10 @@ // 19 - Creates an HQ validator user (hqval@mail.com) with the "CR HQ Validator" role and sets its password // 20 - Logs in as the HQ validator and resolves all three pending change requests: approves // Edit Individual Information, rejects Edit Group Information (with a reason), and -// requests revision on Update ID Document (with revision notes) +// requests revision on Update ID Document (with revision notes). On the way, checks +// that forms the validator may not create from render no "New" button at all: an +// approval review form (the role has no create access on reviews) and the CR detail +// form (opened with create disabled in the action context) // 21 - Logs back in as admin and confirms the three resolutions actually took effect: the // approved name change is reflected on the registrant, and the Change Requests list // shows Rejected / Needs Changes for the other two @@ -1351,12 +1354,37 @@ test.describe.serial("OpenSPP Starter SP-MIS", () => { await expect(page.getByRole("row", {name: `${crNumber} Update ID`})).toBeVisible(); console.log("✅ Logged in as HQ validator (hqval@mail.com)"); + // The HQ validator role can read approval reviews but not create them, so + // Odoo derives canCreate = false from the ACL and must not render the form's + // "New" button. (Registrants are not a read-only model for this role: the + // change-request module grants it create on res.partner so that approving a + // request can create one.) Assert on the DOM count, not on visibility: + // spp_programs' FormController patch hides the same button with + // `display: none`, which would mask a template that renders it anyway (#582). + await page.getByRole("list").getByRole("menuitem", {name: "Approvals"}).click(); + await page.getByRole("menuitem", {name: "Pending Approvals"}).click(); + await expect(page.locator(".o_data_row").first()).toBeVisible(); + await page.locator(".o_data_row").first().click(); + await expect(page.locator(".o_form_view")).toBeVisible(); + await expect(page.locator(".o_form_button_create")).toHaveCount(0); + console.log( + "✅ No New button on the approval review form for a role without create access" + ); + + await page.getByRole("menuitem", {name: "Change Requests"}).click(); await page.getByRole("menuitem", {name: "All Requests"}).click(); await page.getByRole("cell", {name: "Edit Individual Information"}).click(); await expect( page.getByRole("row", {name: "Given Name Jose Miguel Jose"}) ).toBeVisible(); + // The CR detail form is opened with `context: {create: false}`, so Odoo must + // not render the form's "New" button at all. Assert on the DOM count, not on + // visibility: spp_programs' FormController patch hides the same button with + // `display: none`, which would mask a template that renders it anyway (#582). + await expect(page.locator(".o_form_button_create")).toHaveCount(0); + console.log("✅ No New button on the read-only CR detail form"); + await page.getByRole("button", {name: "Approve"}).click(); await expect(page.getByRole("heading", {name: "Confirmation"})).toBeVisible(); await page.getByRole("button", {name: "Ok"}).click(); From 8f0bf84458e8bf01f34a4b9e00430d4e9ee5516d Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Thu, 8 Oct 2026 11:46:56 +0800 Subject: [PATCH 4/7] test: address review findings on the #582 regression guards - Python test asserts the exact condition (a token check accepted 'canCreate or !hideFormCreateButton'). - e2e: open the My Approvals dropdown before Pending Approvals; the review-form comment names the real reason (create="0" on the arch and no create ACL); drop the CR-form assertion, which was vacuous (that form already sets hideFormCreateButton and has create="0"); assert the context case where it really applies, on the detail form the create wizard opens with create: False (test 16). - HISTORY: say where the button was visible. --- e2e/tests/01-spp-starter-spmis.spec.ts | 43 +++++++++---------- spp_base_common/readme/HISTORY.md | 2 +- .../tests/test_form_create_button_template.py | 16 +++---- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/e2e/tests/01-spp-starter-spmis.spec.ts b/e2e/tests/01-spp-starter-spmis.spec.ts index 1fc17e729..8e21bdb0c 100644 --- a/e2e/tests/01-spp-starter-spmis.spec.ts +++ b/e2e/tests/01-spp-starter-spmis.spec.ts @@ -16,16 +16,15 @@ // 13 - Creates an Approval Definition (Change request → HQ validator) and assigns it as the Approval Workflow for Edit Individual Information, Edit Group Information, and Update ID Document change request types // 14 - Manually defines 5 Change Request Document Type vocabulary codes for the Philippines (PSA Birth Certificate, PhilSys National ID, Barangay Certificate of Residency, PSA Marriage Certificate, Proof of Income), matching what spp_mis_demo_v2's demo generator seeds for "phl", by adding rows to the existing vocabulary's Codes tab via Settings > Vocabularies // 15 - Manually defines 5 more Change Request Document Type vocabulary codes for the Philippines (BIR Form 2316, Academic Calendar, Authorization Letter, Certificate of Enrolment, Valid ID of Parent), matching the e2e fixture PDFs in e2e/fixtures/, by adding rows to the same vocabulary's Codes tab as test 14 -// 16 - Creates an "Edit Individual Information" change request for Santos, Jose Miguel (as admin), uploads a supporting document, and submits it for approval +// 16 - Creates an "Edit Individual Information" change request for Santos, Jose Miguel (as admin), checks that the detail form the wizard opens with create disabled in its context renders no "New" button, uploads a supporting document, and submits it for approval // 17 - Creates an "Edit Group Information" change request for Santos Family (as admin), uploads a supporting document, and submits it for approval // 18 - Creates an "Update ID Document" change request for Santos, Jose Miguel (as admin), selecting the registrant by keyboard only and checking that the live region does not receive a late range announcement after the results are gone, sets a National ID with tomorrow's expiry date, uploads a supporting document, and submits it for approval // 19 - Creates an HQ validator user (hqval@mail.com) with the "CR HQ Validator" role and sets its password // 20 - Logs in as the HQ validator and resolves all three pending change requests: approves // Edit Individual Information, rejects Edit Group Information (with a reason), and -// requests revision on Update ID Document (with revision notes). On the way, checks -// that forms the validator may not create from render no "New" button at all: an -// approval review form (the role has no create access on reviews) and the CR detail -// form (opened with create disabled in the action context) +// requests revision on Update ID Document (with revision notes). First checks that an +// approval review form (create="0" on the arch, no create access for the role) renders +// no "New" button at all // 21 - Logs back in as admin and confirms the three resolutions actually took effect: the // approved name change is reflected on the registrant, and the Change Requests list // shows Rejected / Needs Changes for the other two @@ -1085,6 +1084,15 @@ test.describe.serial("OpenSPP Starter SP-MIS", () => { }); console.log("✅ Change request draft opened for editing"); + // The wizard opens this detail form with `create: False` in the action + // context (nothing on the arch or in a FormController patch hides the button + // here), so Odoo's canCreate is false and the form's "New" button must not be + // rendered at all. Assert on the DOM count, not on visibility: spp_programs' + // FormController patch hides the same button with `display: none`, which + // would mask a template that renders it anyway (#582). + await expect(page.locator(".o_form_button_create")).toHaveCount(0); + console.log("✅ No New button on the change request detail form"); + await page.getByRole("textbox", {name: "Given Name"}).fill("Jose Miguel Updated"); await page.getByRole("textbox", {name: "Address Line 1"}).fill("updated"); await page.getByRole("textbox", {name: "Address Line 2"}).fill("updated"); @@ -1354,22 +1362,20 @@ test.describe.serial("OpenSPP Starter SP-MIS", () => { await expect(page.getByRole("row", {name: `${crNumber} Update ID`})).toBeVisible(); console.log("✅ Logged in as HQ validator (hqval@mail.com)"); - // The HQ validator role can read approval reviews but not create them, so - // Odoo derives canCreate = false from the ACL and must not render the form's - // "New" button. (Registrants are not a read-only model for this role: the - // change-request module grants it create on res.partner so that approving a - // request can create one.) Assert on the DOM count, not on visibility: - // spp_programs' FormController patch hides the same button with - // `display: none`, which would mask a template that renders it anyway (#582). + // The approval review form declares create="0" on its arch, and the HQ + // validator role has no create ACL on reviews either, so Odoo's canCreate is + // false and the form's "New" button must not be rendered at all. Assert on + // the DOM count, not on visibility: spp_programs' FormController patch hides + // the same button with `display: none`, which would mask a template that + // renders it anyway (#582). await page.getByRole("list").getByRole("menuitem", {name: "Approvals"}).click(); + await page.getByRole("button", {name: "My Approvals"}).click(); await page.getByRole("menuitem", {name: "Pending Approvals"}).click(); await expect(page.locator(".o_data_row").first()).toBeVisible(); await page.locator(".o_data_row").first().click(); await expect(page.locator(".o_form_view")).toBeVisible(); await expect(page.locator(".o_form_button_create")).toHaveCount(0); - console.log( - "✅ No New button on the approval review form for a role without create access" - ); + console.log("✅ No New button on the approval review form"); await page.getByRole("menuitem", {name: "Change Requests"}).click(); await page.getByRole("menuitem", {name: "All Requests"}).click(); @@ -1378,13 +1384,6 @@ test.describe.serial("OpenSPP Starter SP-MIS", () => { page.getByRole("row", {name: "Given Name Jose Miguel Jose"}) ).toBeVisible(); - // The CR detail form is opened with `context: {create: false}`, so Odoo must - // not render the form's "New" button at all. Assert on the DOM count, not on - // visibility: spp_programs' FormController patch hides the same button with - // `display: none`, which would mask a template that renders it anyway (#582). - await expect(page.locator(".o_form_button_create")).toHaveCount(0); - console.log("✅ No New button on the read-only CR detail form"); - await page.getByRole("button", {name: "Approve"}).click(); await expect(page.getByRole("heading", {name: "Confirmation"})).toBeVisible(); await page.getByRole("button", {name: "Ok"}).click(); diff --git a/spp_base_common/readme/HISTORY.md b/spp_base_common/readme/HISTORY.md index 5f5adda1e..018422fe4 100644 --- a/spp_base_common/readme/HISTORY.md +++ b/spp_base_common/readme/HISTORY.md @@ -1,6 +1,6 @@ ### 19.0.2.0.3 -- fix(ui): the form view's **New** button respects Odoo's create guard again. The `web.FormView` extension that lets a module hide the button with `hideFormCreateButton` replaced the button's condition instead of extending it, dropping Odoo's `canCreate`. As a result every form opened with create disabled — `{'create': False}` in the action context, `create="0"` on the form arch, a role without create access, or `preventCreate` — still rendered a working **New** button that opened a blank editable record, or failed with an access error on save. The condition is now `canCreate and !hideFormCreateButton`: the flag can only hide the button, never show it, and modules using it keep working unchanged. Forms shown in dialogs use a separate template and were never affected (#582). +- fix(ui): the form view's **New** button respects Odoo's create guard again. The `web.FormView` extension that lets a module hide the button with `hideFormCreateButton` replaced the button's condition instead of extending it, dropping Odoo's `canCreate`. As a result every form opened with create disabled — `{'create': False}` in the action context, `create="0"` on the form arch, a role without create access, or `preventCreate` — still rendered a working **New** button that opened a blank editable record, or failed with an access error on save (visible on installs without `spp_programs`; with it, a workaround hid the rendered button after the fact). The condition is now `canCreate and !hideFormCreateButton`: the flag can only hide the button, never show it, and modules using it keep working unchanged. Forms shown in dialogs use a separate template and were never affected (#582). ### 19.0.2.0.2 diff --git a/spp_base_common/tests/test_form_create_button_template.py b/spp_base_common/tests/test_form_create_button_template.py index 9f29cc445..a795ee756 100644 --- a/spp_base_common/tests/test_form_create_button_template.py +++ b/spp_base_common/tests/test_form_create_button_template.py @@ -63,15 +63,15 @@ def test_control_panel_new_button_keeps_can_create(self): (button,) = form_view.xpath(CREATE_BUTTON_XPATH) condition = button.get("t-if") - self.assertRegex( + # Exact match: the condition is a one-line expression under our control, + # and a token check would accept "canCreate or !hideFormCreateButton", + # which reintroduces the bug. + self.assertEqual( condition, - r"\bcanCreate\b", - 'the form New button must keep Odoo\'s canCreate guard (action context, create="0", ACL, preventCreate)', - ) - self.assertIn( - "!hideFormCreateButton", - condition, - "the hideFormCreateButton opt-out must still hide the button", + "canCreate and !hideFormCreateButton", + "the form New button must keep Odoo's canCreate guard " + '(action context, create="0", ACL, preventCreate) and the ' + "hideFormCreateButton opt-out must still hide the button", ) def test_dialog_new_button_is_not_touched(self): From cf398db9597f37ae315ba79c05cf229f86e05188 Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Thu, 8 Oct 2026 11:57:28 +0800 Subject: [PATCH 5/7] docs(spp_base_common): regenerate README from fragments (CI generator output) --- spp_base_common/README.rst | 18 +++++++++++++++ spp_base_common/static/description/index.html | 23 +++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/spp_base_common/README.rst b/spp_base_common/README.rst index 9a77499d1..60a92830d 100644 --- a/spp_base_common/README.rst +++ b/spp_base_common/README.rst @@ -120,6 +120,24 @@ Dependencies Changelog ========= +19.0.2.0.3 +~~~~~~~~~~ + +- fix(ui): the form view's **New** button respects Odoo's create guard + again. The ``web.FormView`` extension that lets a module hide the + button with ``hideFormCreateButton`` replaced the button's condition + instead of extending it, dropping Odoo's ``canCreate``. As a result + every form opened with create disabled — ``{'create': False}`` in the + action context, ``create="0"`` on the form arch, a role without create + access, or ``preventCreate`` — still rendered a working **New** button + that opened a blank editable record, or failed with an access error on + save (visible on installs without ``spp_programs``; with it, a + workaround hid the rendered button after the fact). The condition is + now ``canCreate and !hideFormCreateButton``: the flag can only hide + the button, never show it, and modules using it keep working + unchanged. Forms shown in dialogs use a separate template and were + never affected (#582). + 19.0.2.0.2 ~~~~~~~~~~ diff --git a/spp_base_common/static/description/index.html b/spp_base_common/static/description/index.html index eabec4c9a..95f7d162d 100644 --- a/spp_base_common/static/description/index.html +++ b/spp_base_common/static/description/index.html @@ -495,6 +495,25 @@

Changelog

+

19.0.2.0.3

+
    +
  • fix(ui): the form view’s New button respects Odoo’s create guard +again. The web.FormView extension that lets a module hide the +button with hideFormCreateButton replaced the button’s condition +instead of extending it, dropping Odoo’s canCreate. As a result +every form opened with create disabled — {'create': False} in the +action context, create="0" on the form arch, a role without create +access, or preventCreate — still rendered a working New button +that opened a blank editable record, or failed with an access error on +save (visible on installs without spp_programs; with it, a +workaround hid the rendered button after the fact). The condition is +now canCreate and !hideFormCreateButton: the flag can only hide +the button, never show it, and modules using it keep working +unchanged. Forms shown in dialogs use a separate template and were +never affected (#582).
  • +
+
+

19.0.2.0.2

  • test: add a regression test guarding the PDF backend selected by @@ -506,7 +525,7 @@

    19.0.2.0.2

    this test fails on any image that regresses.
-
+

19.0.2.0.1

  • fix(security): add groups="base.group_system" to the existing @@ -522,7 +541,7 @@

    19.0.2.0.1

    group_ids to empty.
-
+

19.0.2.0.0

  • Initial migration to OpenSPP2
  • From 88bf6cbb0d686054e7336d5b7712da38a623cb6d Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Thu, 8 Oct 2026 12:13:24 +0800 Subject: [PATCH 6/7] docs/test(spp_base_common): preventCreate was never exposed; assert the template is shipped (#582) Second review round: preventCreate reaches FormController only through FormViewDialog, which disables the control panel, so that path never rendered the button. Drop it from the list of affected cases in HISTORY, the template comment and the test docstring. Add a test that the manifest still lists the template in web.assets_backend, so removing the asset cannot leave the inheritance test green. --- spp_base_common/readme/HISTORY.md | 2 +- .../static/src/xml/custom_list_create_template.xml | 2 +- .../tests/test_form_create_button_template.py | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/spp_base_common/readme/HISTORY.md b/spp_base_common/readme/HISTORY.md index 018422fe4..87966eab0 100644 --- a/spp_base_common/readme/HISTORY.md +++ b/spp_base_common/readme/HISTORY.md @@ -1,6 +1,6 @@ ### 19.0.2.0.3 -- fix(ui): the form view's **New** button respects Odoo's create guard again. The `web.FormView` extension that lets a module hide the button with `hideFormCreateButton` replaced the button's condition instead of extending it, dropping Odoo's `canCreate`. As a result every form opened with create disabled — `{'create': False}` in the action context, `create="0"` on the form arch, a role without create access, or `preventCreate` — still rendered a working **New** button that opened a blank editable record, or failed with an access error on save (visible on installs without `spp_programs`; with it, a workaround hid the rendered button after the fact). The condition is now `canCreate and !hideFormCreateButton`: the flag can only hide the button, never show it, and modules using it keep working unchanged. Forms shown in dialogs use a separate template and were never affected (#582). +- fix(ui): the form view's **New** button respects Odoo's create guard again. The `web.FormView` extension that lets a module hide the button with `hideFormCreateButton` replaced the button's condition instead of extending it, dropping Odoo's `canCreate`. As a result every form opened with create disabled — `{'create': False}` in the action context, `create="0"` on the form arch, or a role without create access — still rendered a working **New** button that opened a blank editable record, or failed with an access error on save (visible on installs without `spp_programs`; with it, a workaround hid the rendered button after the fact). The condition is now `canCreate and !hideFormCreateButton`: the flag can only hide the button, never show it, and modules using it keep working unchanged. Forms shown in dialogs use a separate template and were never affected (#582). ### 19.0.2.0.2 diff --git a/spp_base_common/static/src/xml/custom_list_create_template.xml b/spp_base_common/static/src/xml/custom_list_create_template.xml index e5a8d18e5..eaea4254b 100644 --- a/spp_base_common/static/src/xml/custom_list_create_template.xml +++ b/spp_base_common/static/src/xml/custom_list_create_template.xml @@ -46,7 +46,7 @@ The flag can only hide the button, never show it: Odoo's own canCreate guard stays in the condition, so a form opened with create disabled (action context {'create': False}, create="0" on - the arch, no create ACL, preventCreate) keeps hiding "New". --> + the arch, no create ACL) keeps hiding "New". --> canCreate and !hideFormCreateButton diff --git a/spp_base_common/tests/test_form_create_button_template.py b/spp_base_common/tests/test_form_create_button_template.py index a795ee756..5a18f40c1 100644 --- a/spp_base_common/tests/test_form_create_button_template.py +++ b/spp_base_common/tests/test_form_create_button_template.py @@ -5,9 +5,10 @@ control-panel "New" button. Stock Odoo renders that button with ``t-if="canCreate"``, and ``canCreate`` is the single switch behind every way a form can be opened without create: ``{'create': False}`` in the action context, -``create="0"`` on the form arch, a missing create ACL, and the ``preventCreate`` -prop. Replacing the condition instead of extending it rendered a live "New" -button in all four cases (#582). +``create="0"`` on the form arch, and a missing create ACL. Replacing the +condition instead of extending it rendered a live "New" button in all three +cases (#582). The fourth input to ``canCreate``, the ``preventCreate`` prop, is +only passed by form dialogs, which do not render the control panel at all. The web client applies template inheritance in the browser, which the test image cannot run. Odoo ships the same xpath/position engine server-side in @@ -19,6 +20,7 @@ from lxml import etree +from odoo.modules.module import get_manifest from odoo.tests import TransactionCase from odoo.tools.misc import file_open from odoo.tools.template_inheritance import apply_inheritance_specs @@ -74,6 +76,11 @@ def test_control_panel_new_button_keeps_can_create(self): "hideFormCreateButton opt-out must still hide the button", ) + def test_extension_is_shipped_in_the_backend_bundle(self): + """The template only takes effect if the manifest still lists it in web.assets_backend.""" + manifest = get_manifest("spp_base_common") + self.assertIn(EXTENSION_TEMPLATES, manifest["assets"]["web.assets_backend"]) + def test_dialog_new_button_is_not_touched(self): """The extension targets web.FormView only; dialog footers keep stock markup.""" self.assertFalse(self.extension.xpath("./t[@t-inherit='web.FormView.Buttons']")) From d7ef2bac3ef9886313ece0c303ed49acd67e64de Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Thu, 8 Oct 2026 12:21:31 +0800 Subject: [PATCH 7/7] docs(spp_base_common): regenerate README from fragments (CI generator output) --- spp_base_common/README.rst | 18 +++++++++--------- spp_base_common/static/description/index.html | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/spp_base_common/README.rst b/spp_base_common/README.rst index 60a92830d..b1c3fe1b5 100644 --- a/spp_base_common/README.rst +++ b/spp_base_common/README.rst @@ -128,15 +128,15 @@ Changelog button with ``hideFormCreateButton`` replaced the button's condition instead of extending it, dropping Odoo's ``canCreate``. As a result every form opened with create disabled — ``{'create': False}`` in the - action context, ``create="0"`` on the form arch, a role without create - access, or ``preventCreate`` — still rendered a working **New** button - that opened a blank editable record, or failed with an access error on - save (visible on installs without ``spp_programs``; with it, a - workaround hid the rendered button after the fact). The condition is - now ``canCreate and !hideFormCreateButton``: the flag can only hide - the button, never show it, and modules using it keep working - unchanged. Forms shown in dialogs use a separate template and were - never affected (#582). + action context, ``create="0"`` on the form arch, or a role without + create access — still rendered a working **New** button that opened a + blank editable record, or failed with an access error on save (visible + on installs without ``spp_programs``; with it, a workaround hid the + rendered button after the fact). The condition is now + ``canCreate and !hideFormCreateButton``: the flag can only hide the + button, never show it, and modules using it keep working unchanged. + Forms shown in dialogs use a separate template and were never affected + (#582). 19.0.2.0.2 ~~~~~~~~~~ diff --git a/spp_base_common/static/description/index.html b/spp_base_common/static/description/index.html index 95f7d162d..800e72f2b 100644 --- a/spp_base_common/static/description/index.html +++ b/spp_base_common/static/description/index.html @@ -502,15 +502,15 @@

    19.0.2.0.3

    button with hideFormCreateButton replaced the button’s condition instead of extending it, dropping Odoo’s canCreate. As a result every form opened with create disabled — {'create': False} in the -action context, create="0" on the form arch, a role without create -access, or preventCreate — still rendered a working New button -that opened a blank editable record, or failed with an access error on -save (visible on installs without spp_programs; with it, a -workaround hid the rendered button after the fact). The condition is -now canCreate and !hideFormCreateButton: the flag can only hide -the button, never show it, and modules using it keep working -unchanged. Forms shown in dialogs use a separate template and were -never affected (#582). +action context, create="0" on the form arch, or a role without +create access — still rendered a working New button that opened a +blank editable record, or failed with an access error on save (visible +on installs without spp_programs; with it, a workaround hid the +rendered button after the fact). The condition is now +canCreate and !hideFormCreateButton: the flag can only hide the +button, never show it, and modules using it keep working unchanged. +Forms shown in dialogs use a separate template and were never affected +(#582).