+
+
+- 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}
+
+