+
+
+- `env`: Odoo environment for ORM access.
+- `user`: Current user (env.user).
+- `ctx`: Copy of the current context (dict(env.context)).
+- `record`: Current record (the form’s record).
+- `draft`: The persisted field values of the ORM record (before
+applying the current form’s unsaved changes) + the current unsaved
+changes on trigger fields. Should be used instead of record when your
+rule is triggered dynamically by an update to a trigger field. It
+doesn’t include any values from complex fields (one2many/reference,
+etc).
+- `record_id`: Integer id of the record being edited, or False if the
+form is creating a new record.
+- `model`: Shortcut to the current model (env[record._name]).
+- `url_for(obj): Helper that returns a backend form URL for `obj.
+- `context_today(ts=None)`: User-timezone “today” (date) for reliable
+date comparisons.
+- time, `datetime`: Standard Python time/datetime modules.
+- `dateutil`: { “parser”: dateutil.parser, “relativedelta”:
+dateutil.relativedelta }
+- `timezone`: pytz.timezone for TZ handling.
+- float_compare, float_is_zero, `float_round`: Odoo float utils for
+precision-safe comparisons/rounding.
+
+
All of the above are injected by the module to the safe_eval locals.
+
+
+
Trigger Fields is an optional list of model fields that, when changed
+in the open form, cause the banner to recompute live. If left empty,
+the banner does not auto-refresh as the user edits the form.
+
When a trigger fires, the module sends the current draft values to the
+server, sanitizes them, builds an evaluation record, and re-runs your
+message_value_code.
+
You should use draft instead of record to access the current form values
+if your rule is triggered based on an update to a trigger field.
+
+
+
A) Missing email on contact (warning)
+
+- Model: res.partner
+- Message: This contact has no email.
+- Message Value Code:
+
+
+{"visible": not bool(record.email)}
+
+
B) Show partner comment if available
+
+- Model: purchase.order
+- Message: Vendor Comments: ${comment}
+- Message Value Code (single expression):
+
+
+{
+ "visible": bool(record.partner_id.comment),
+ "values": {"comment": record.partner_id.comment},
+}
+
+
It is also possible to use “convenience placeholders” without an
+explicit values key:
+
+{
+ "visible": bool(record.partner_id.comment),
+ "comment": record.partner_id.comment,
+}
+
+
C) High-value sale order (dynamic severity)
+
+- Model: sale.order
+- Message: High-value order: ${amount_total}
+- Message Value Code:
+
+
+{
+ "visible": record.amount_total >= 30000,
+ "severity": "danger" if record.amount_total >= 100000 else "warning",
+ "values": {"amount_total": record.amount_total},
+}
+
+
D) Quotation past validity date
+
+- Model: sale.order
+- Message: This quotation is past its validity date (${validity_date}).
+- Message Value Code:
+
+
+{
+ "visible": bool(record.validity_date and context_today() > record.validity_date and record.state in ["draft", "sent"]),
+ "values": {"validity_date": record.validity_date},
+}
+
+
E) Pending activities on a task (uses `env`)
+
+- Model: project.task
+- Message: There are ${cnt} pending activities.
+- Message Value Code (multi-line with result):
+
+
+cnt = env["mail.activity"].search_count([("res_model","=",record._name),("res_id","=",record.id)])
+result = {"visible": cnt > 0, "values": {"cnt": cnt}}
+
+
F) Product is missing internal reference (uses trigger fields)
+
+- Model: product.template
+- Trigger Fields: default_code
+- Message: Make sure to set an internal reference!
+- Message Value Code:
+
+
+{"visible": not bool(draft.default_code)}
+
+
G) HTML banner linking to the customer’s last sales order (uses
+trigger fields)
+
+- Model: sale.order
+- Trigger Fields: partner_id
+- Message: (leave blank; html provided by Message Value Code)
+- Message Value Code (multi-line with result):
+
+
+domain = [("partner_id", "=", draft.partner_id.id)]
+if record_id:
+ domain += [("id", "<", record_id)]
+last = model.search(domain, order="date_order desc, id desc", limit=1)
+if last:
+ html = "<strong>Previous order:</strong> <a href='%s'>%s</a>" % (url_for(last), last.name)
+ result = {"visible": True, "html": html}
+else:
+ result = {"visible": False}
+
+
If we set up the rules for a partner record as shown below:
+

+

+
The banners will be displayed in the partner form view:
+

+
Once the values are filled in, the banners will disappear:
+

+