fix(spp_approval): stop offering New on the approval review lists - #447
Conversation
My Pending Approvals showed a New button, which cannot do anything useful: a
review is created by the approval flow when a record is submitted, and its
create() builds the tier reviews from the definition. Making one by hand would
mean typing a model name, a record id and a definition into a blank form, and
the result would point at nothing (OP#1167).
Both views carry create="0" rather than one action carrying
context={'create': False}. For a plain list or form, activeActions.create comes
straight from the arch attribute, and a falsy create in an action's context is
not a separate mechanism — the framework rewrites that same attribute. Setting
it on the views covers My Pending Approvals, the sibling Approval Reviews list
that had the same button, the form's own breadcrumb, and any action added
later.
Scope of the report: only approval managers ever saw the button. Approvers hold
create=0 on spp.approval.review already, so nothing changes for them.
The test reads the combined arch through get_view rather than the view record's
own, because the multitier views inherit both of these and the merged result is
what the client renders. A second test pins the assumption the fix rests on —
that both actions still resolve to these views — and a third checks the views
stayed usable for the reviews the flow does create.
19.0.2.0.2 with its changelog entry, per the in-PR convention. The change is view definitions only, so the bump is what makes an existing database pick it up: without an upgrade the New button stays exactly where QA found it.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 19.0 #447 +/- ##
==========================================
- Coverage 76.59% 74.80% -1.80%
==========================================
Files 629 558 -71
Lines 42351 37725 -4626
==========================================
- Hits 32439 28219 -4220
+ Misses 9912 9506 -406
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
gonzalesedwin1123
left a comment
There was a problem hiding this comment.
Thorough review against OP#1167. The fix itself is correct, well-placed, and I verified every claim in the PR description against the code. One must-fix on the tests, then this is good to go.
Verified — the fix
create="0"on the root<list>and<form>archs is the right mechanism on Odoo 19:activeActions.createis read straight from the arch for plain list/form views, and an action context{'create': False}rewrites the same attribute rather than being a separate gate. The arch-level attribute therefore covers My Pending Approvals, Approval Reviews, the form breadcrumb, and any future action that reuses these views.- The multitier inherits (
approval_review_views_multitier.xml) xpath into//sheetand//field[@name='status']only — they never touch the root attributes, so the merged arch keepscreate="0". Test 1 asserting on the combinedget_viewarch (not the view record's own) is exactly the right pin for this. - ACL claim confirmed:
ir.model.access.csvgivesgroup_approval_approverperm_create=0onspp.approval.reviewand onlygroup_approval_managerperm_create=1— so indeed only managers ever saw the button, and the ticket's "across any user" framing correction is accurate. - Both actions (
approval_review_action,approval_review_my_pending_action) are the only entry points: no other menus, no dynamically-builtact_windowin Python references this model. - I also checked the OP#1171-style x2many trap the description mentions:
approval_review_idsis embedded in 6 other modules' forms (spp_change_request_v2, spp_drims, spp_event_data, spp_programs ×3) — every one isreadonly="1", so there is no "Add a line" exposure left anywhere. - Version chain: 19.0 is at 19.0.2.0.1 and none of the 3 commits the branch is behind (#435, #432, #444) touch spp_approval, so 19.0.2.0.2 is the correct next number and the branch merges cleanly. HISTORY.md / README.rst / index.html are consistent.
Must fix — the third test never runs in CI
test_a_review_still_reaches_the_list starts with search([], limit=1) on spp.approval.definition and skips when nothing is found — and in the CI database nothing is found. From the test (spp_approval) job log on this PR:
02:44:19 ... skipped TestApprovalReviewNoCreate.test_a_review_still_reaches_the_list : no approval definition available in this database
So the "views stayed usable" leg of the safety net is green-by-skip in exactly the environment that gates merges; it only ever ran on your local DB. Please create the definition inside the test instead of searching for one — then the test runs everywhere:
definition = self.env["spp.approval.definition"].create(
{"name": "Test Definition", "model": "spp.approval.definition"}
)(adjust required fields to the model). While you're in there, consider making the assertion earn its docstring: self.assertIn(review, search([])) proves ORM basics, not that the record "lists and opens". Reading the list through the action would guard what the test claims to guard, e.g. resolve approval_review_action, run search(action_domain) and assert the review is in it, and/or get_view + a web_search_read-shaped read of the list fields. Not a blocker if you keep it simple, but the CI-skip itself is.
Non-blocking observations (out of scope, noting so they aren't lost)
- Your own note stands: managers retain
unlinkon reviews, so an approval trail is still deletable. - While verifying the ACLs I noticed
rules.xmlattaches the reviewer-scoping rule (domain [('reviewer_id','=',user.id)], named "Approver Access") togroup_approval_officer, whilegroup_approval_approveronly impliesgroup_approval_viewer. Two smells: (a) a plain approver is in no ruled group, so no record rule scopes theirread=1/write=1ACL at all; (b)reviewer_idis only set at approve/reject time, so pending reviews would be invisible to anyone the rule does scope. Pre-existing, nothing to do with this PR — flagging for a follow-up ticket. - Related:
approval_review_my_pending_actionfilters onstatus = 'pending'only; the "my" in the name relies entirely on those record rules, so managers see everyone's pending reviews there. Also pre-existing.
test_a_review_still_reaches_the_list searched for an approval definition and skipped when it found none. The CI database ships none, so the "views stayed usable" half of the safety net was green-by-skip in exactly the environment that gates merges — it only ever ran against a local database that happened to have one. The definition is created in the test instead. The assertion now earns its docstring too: it reads the review through the action's own domain and the list arch's columns, rather than asserting a bare search([]) contains it, which proved ORM basics rather than that the record lists and opens.
|
Fixed in Must fix — the test now runs in CIYou were right about the mechanism and about why it mattered: the search-and-skip meant the only leg of the safety net that proves the views still work never executed in the environment that gates merges. The definition is created in the test instead: definition = self.env["spp.approval.definition"].create(
{
"name": "OP#1167 listing check",
"model_id": self.env["ir.model"]._get_id("res.partner"),
"approval_type": "group",
"approval_group_id": self.env.ref("base.group_user").id,
}
)
Verified on a fresh database (same shape as CI, not my local one): all three tests start, none skip. Took the optional half tooThe assertion no longer proves ORM basics. It reads the review the way the action does — resolving action = self.env.ref("spp_approval.approval_review_my_pending_action")
listed = self.env["spp.approval.review"].search(literal_eval(action.domain))
self.assertIn(review, listed, ...)
...
columns = [node.get("name") for node in arch.xpath("//field[@name]")]
self.assertTrue(review.read(columns), ...)One thing your review turned up indirectlyChecking for remaining skips, the same pathology exists elsewhere in this module — pre-existing and unrelated to this PR: That is the entire mixin suite silently not running in CI. Out of scope here; flagging it since it is the same class of problem and worth more than a footnote. Your Ready for re-review. |
gonzalesedwin1123
left a comment
There was a problem hiding this comment.
Approved — the single must-fix from the previous review is resolved, and verified end-to-end:
5badd53breplaces the search-and-skip with an in-testspp.approval.definitioncreate (name+model_idare the only required fields;res.partnersatisfies the mail-thread domain), so the test no longer depends on the database shipping a definition.- Confirmed in today's CI run (job 97672791306):
TestApprovalReviewNoCreate.test_a_review_still_reaches_the_liststarts and runs — no skip line — and the suite finishes 0 failed / 0 errors of 132 tests. The only skip in the module is the pre-existing, unrelatedTestApprovalMixinmodel-registration skip. - The strengthened assertion is a genuine improvement over what I asked for: reading through
approval_review_my_pending_action's own domain ([('status', '=', 'pending')], safe underliteral_eval, and the created review defaults topending) plus every column of the combined list arch proves the record reaches the list a user actually opens, not just thatsearch([])works. - Version chain re-verified after the 19.0 merge-in: base is at 19.0.2.0.1, this PR takes 19.0.2.0.2 with its HISTORY entry.
The three out-of-scope observations from the first review (manager unlink on reviews; the reviewer_id-scoping rule attached to officer-only while approvers go unscoped and pending reviews have no reviewer_id; the action domain relying entirely on those rules for "my") stand as follow-up candidates and do not block this fix.
Why is this change needed?
My Pending Approvals showed a New button, which cannot do anything useful (OP#1167). A review is created by the approval flow when a record is submitted —
create()builds the tier reviews from the definition — so making one by hand means typing a model name, a record id and a definition into a blank form, and the result points at nothing. Mark's note on the ticket puts it plainly: the view exists to show the status of change requests for the logged-in approver.One correction to the ticket's framing, since the title says "across any user": only approval managers ever saw the button. Approvers hold
create=0onspp.approval.reviewalready, so nothing changes for them. The fix is the same either way, but do not expect a visible difference when testing as an approver.How was the change implemented?
create="0"on both root nodes — the list and the form — inspp_approval/views/approval_review_views.xml.That covers more than the reported view: Approval Reviews lists the same records through the same views and had the same button, and the form's own breadcrumb New goes too. It also cannot be bypassed by a future action that forgets a context key.
The attribute is the authoritative gate here, unlike the x2many case in OP#1171: for a plain list or form
activeActions.createcomes straight from the arch (web/views/utils.js), and an action context of{'create': False}is not a separate mechanism — the framework rewrites that same attribute (web/views/view.js). So there is nolink-vs-createtrap in this one, and an arch-level test genuinely proves the behaviour.New unit tests
spp_approval/tests/test_approval_review_no_create.py:get_view, not the view record's own, becauseapproval_review_views_multitier.xmlinherits both views and the merged result is what the client renders;view_idthis fails rather than the suppression silently lapsing;Unit tests executed by the author
Full
spp_approvalsuite after merging19.0in: 132 tests, 0 failed, 0 errors.How to test manually
Related links
Reviewer notes
spp_approvalbumped to 19.0.2.0.2 with its changelog entry, per the in-PR convention. Load-bearing here: the change is view definitions only, so without the upgrade the button stays exactly where QA found it.unlinkon reviews, so an approval trail can still be deleted. Flagging it so it is not mistaken for part of this fix.