Skip to content

[18.0][IMP] web_form_banner: client-side fast path for simple rules#1

Open
dnplkndll wants to merge 7 commits into
18.0from
18.0-imp-web_form_banner-client-side
Open

[18.0][IMP] web_form_banner: client-side fast path for simple rules#1
dnplkndll wants to merge 7 commits into
18.0from
18.0-imp-web_form_banner-client-side

Conversation

@dnplkndll
Copy link
Copy Markdown

@dnplkndll dnplkndll commented May 17, 2026

Why

Every banner rule today fires a server RPC (compute_message) per trigger-field change. For typical conditions — state == 'draft' and amount_total > 1000 — Odoo's view compiler already evaluates exactly that kind of expression purely client-side via py.js in invisible= attributes. The round-trip isn't needed for the 80% case.

What

Per-rule client_side opt-in. When enabled:

  • Visibility is evaluated by Odoo's view compiler against the in-memory record. Zero RPC.
  • Message text uses inline <field name="..."/> tags (Odoo-native, reactive) plus the existing ${field_name} sugar that rewrites to those tags at get_view time.
  • The existing JS RPC machinery is untouched and continues to drive server-side rules.

Generated arch

For a rule with client_condition: not email and name, message Contact <strong>${name}</strong> has no email., target xpath //sheet, on res.partner form:

<form>
  <!-- ...standard partner header... -->
  <field name="email" invisible="True"/>   <!-- auto-injected so py.js can resolve -->
  <div class="alert alert-warning"
       invisible="not (not email and name)"
       role="status"
       data-rule-id="123"
       data-model="res.partner"
       data-client="1">
    Contact <strong><field name="name"/></strong> has no email.
  </div>
  <sheet>...</sheet>
</form>

Toggle email on/off in the form → banner appears/disappears instantly. Zero Network-tab activity, zero compute_message RPC.

Defensive auto-injection

If client_condition references a field that the form view doesn't declare, py.js raises Name 'X' is not defined at render time and crashes any browser test that opens the form. The get_view override parses the condition via ast.parse, extracts every leftmost-Name reference, filters out py.js reserved names + names not on the model + names already declared, and auto-injects the remaining as <field name="X" invisible="True"/> siblings. Any admin-defined rule referencing a non-loaded field now Just Works.

What works in client_condition

Comparisons, boolean ops, in/not in, attribute access (partner_id.email), built-ins (len, bool, min, max, set), ternary x if cond else y.

What does NOT work — keep server-side mode

Arbitrary method calls (.filtered(), .mapped()), slicing, lambdas, comprehensions, ORM searches, per-record severity overrides, ${X.Y} dotted interpolation.

Files

File Change
models/web_form_banner_rule.py client_side + client_condition fields, _check_client_condition constraint via ast.parse, _to_client_arch for ${var}<field/>
models/ir_model.py get_view branches on rule.client_side; _client_rule_missing_fields returns hidden field shims; banner built via lxml element API
static/src/js/web_form_banner.esm.js One-line filter in bannersIn() skips data-client="1" divs
views/web_form_banner_rule_views.xml New fields exposed, notebook page toggles between server- and client-side help
tests/test_web_form_banner.py 11 new test cases — arch emission, ${var} sugar, HTML escaping, missing/malformed condition rejection, hidden-field auto-injection with dedup, py.js-reserved-name skipping, dotted-${} rejection, quote-bearing conditions, position=after, multi-rule field sharing
demo/web_form_banner_rule_demo.xml One extra fast-path demo using base-only fields (not email and name)
readme/USAGE.md + ROADMAP.md + CONTRIBUTORS.md Client-side section, limitations, follow-ups, Ledoweb co-author
__manifest__.py Bump to 18.0.1.2.0, add Ledoweb co-author + maintainers=["dnplkndll"]

Notable correctness details

  • Banner element built via lxml's etree.Element API rather than f-string XML — single quotes in conditions like state == 'draft' would otherwise terminate the invisible='...' attribute mid-expression. Caught during review; covered by test_client_side_handles_quoted_string_literals.
  • ${var} sugar restricted to bare field names. Dotted paths like ${partner_id.email} would rewrite to invalid form arch. Covered by test_client_side_rejects_dotted_var_sugar. Documented in USAGE.

Verification

Local fresh-DB --test-enable: 20 tests, 0 failures.

Fork CI on ledoent self-hosted runners (hetzner-k3s-ledoent): all 5 jobs green (commit effeea0 on 18.0-imp-web_form_banner-client-side).

Practice run scope

PR opened against the fork (not OCA upstream) to exercise the review workflow. Will close after squashing/promoting to OCA/web.

Test plan

  • All 20 unit tests pass on a fresh DB
  • All 5 CI jobs pass on the fork (Detect deps, test with Odoo, test with OCB, both rebel variants)
  • Client-side rule with quoted string literal serializes correctly
  • Hidden-field auto-injection works for missing references
  • Server-driven rules continue to work unchanged
  • Pre-flight strip of >>> FORK-LOCAL BLOCK <<< before promoting upstream

dnplkndll added 7 commits May 16, 2026 19:49
test_position_relative_to_sheet asserts the partner_name_length demo
banner is immediately before <sheet>. Adding a second active demo
rule on res.partner (the new client-side demo) shifts the sibling
indexes by 1 and the assertion fails (3 != 4).

Match the existing pattern for rule_email / rule_tag: deactivate the
new rule_client_side in setUpClass so the position check stays
exact. The new client-side tests don't depend on the demo rule and
continue to work via _make_client_rule().
Adds a concurrency block to both fork workflows so pushing a new
commit to the same ref cancels still-running workflow instances.
With our iteration pattern (multiple empty commits to retrigger CI)
this drops ~3× the runner load.

Strip before opening upstream OCA PR — block is wrapped in
'>>> FORK-LOCAL BLOCK <<<' markers so a single sed strips both files.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant