What Changed in Odoo 19 Views
If you are coming from an earlier version, these are the critical changes:
- Odoo 18+:
<tree>renamed to<list>— using<tree>still works but is deprecated - Odoo 17+:
attrsattribute removed — useinvisible,readonly,requireddirectly as view attributes - Odoo 19:
web_editormodule removed, replaced byhtml_editor+html_builder
Form Views
<record id="equipment_view_form" model="ir.ui.view">
<field name="name">equipment.form</field>
<field name="model">equipment.item</field>
<field name="arch" type="xml">
<form>
<header>
<button name="action_confirm" type="object"
string="Confirm" class="btn-primary"
invisible="state != 'draft'"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,confirmed,done"/>
</header>
<sheet>
<div class="oe_title">
<h1><field name="name" placeholder="Equipment Name"/></h1>
</div>
<group>
<group string="Details">
<field name="serial_number"/>
<field name="department_id"/>
<field name="assigned_to"/>
</group>
<group string="Dates">
<field name="purchase_date"/>
<field name="warranty_expiry"/>
<field name="days_until_warranty"
invisible="not warranty_expiry"/>
</group>
</group>
<notebook>
<page string="Maintenance History" name="maintenance">
<field name="maintenance_ids">
<list>
<field name="date"/>
<field name="type"/>
<field name="description"/>
<field name="cost"/>
</list>
</field>
</page>
<page string="Notes" name="notes">
<field name="notes" placeholder="Internal notes..."/>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>Key Form View Elements
| Element | Purpose |
|---|---|
<header> | Action buttons and statusbar |
<sheet> | Main content area with white background |
<div class="oe_title"> | Large title field at the top |
<group> | Two-column layout container |
<notebook> / <page> | Tabbed sections |
<div class="oe_chatter"> | Discussion thread and activities |
List Views (Formerly Tree)
<record id="equipment_view_list" model="ir.ui.view">
<field name="name">equipment.list</field>
<field name="model">equipment.item</field>
<field name="arch" type="xml">
<list decoration-danger="days_until_warranty < 30"
decoration-warning="days_until_warranty < 90">
<field name="name"/>
<field name="serial_number"/>
<field name="department_id"/>
<field name="assigned_to"/>
<field name="state" widget="badge"
decoration-success="state == 'active'"
decoration-info="state == 'maintenance'"/>
<field name="warranty_expiry"/>
<field name="days_until_warranty" optional="hide"/>
</list>
</field>
</record>Important: Use <list> not <tree> in Odoo 18+. <tree> still works but is deprecated.
Kanban Views
<record id="equipment_view_kanban" model="ir.ui.view">
<field name="name">equipment.kanban</field>
<field name="model">equipment.item</field>
<field name="arch" type="xml">
<kanban default_group_by="state">
<templates>
<t t-name="card">
<field name="name" class="fw-bold fs-5"/>
<field name="serial_number" class="text-muted"/>
<div class="d-flex justify-content-between mt-2">
<field name="department_id"/>
<field name="assigned_to" widget="many2one_avatar"/>
</div>
</t>
</templates>
</kanban>
</field>
</record>Search Views
<record id="equipment_view_search" model="ir.ui.view">
<field name="name">equipment.search</field>
<field name="model">equipment.item</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="serial_number"/>
<field name="department_id"/>
<field name="assigned_to"/>
<filter name="active" string="Active"
domain="[('state', '=', 'active')]"/>
<filter name="maintenance" string="In Maintenance"
domain="[('state', '=', 'maintenance')]"/>
<filter name="warranty_expiring" string="Warranty Expiring"
domain="[('days_until_warranty', '<', 90)]"/>
<separator/>
<group string="Group By">
<filter name="group_department" string="Department"
context="{'group_by': 'department_id'}"/>
<filter name="group_state" string="Status"
context="{'group_by': 'state'}"/>
<filter name="group_assigned" string="Assigned To"
context="{'group_by': 'assigned_to'}"/>
</group>
</search>
</field>
</record>View Inheritance
<!-- Add a field to an existing view -->
<record id="equipment_view_form_inherit" model="ir.ui.view">
<field name="name">equipment.form.inherit</field>
<field name="model">equipment.item</field>
<field name="inherit_id" ref="equipment_view_form"/>
<field name="arch" type="xml">
<!-- Add after a specific field -->
<xpath expr="//field[@name='serial_number']" position="after">
<field name="manufacturer"/>
</xpath>
<!-- Add a new page to the notebook -->
<xpath expr="//notebook" position="inside">
<page string="Warranty Info" name="warranty">
<group>
<field name="warranty_provider"/>
<field name="warranty_type"/>
</group>
</page>
</xpath>
</field>
</record>Common XPath Patterns
| XPath | Matches |
|---|---|
//field[@name='x'] | Field named 'x' anywhere |
//group[@string='Details'] | Group with string 'Details' |
//page[@name='notes'] | Notebook page named 'notes' |
//notebook | The notebook element |
//sheet | The sheet element |
Position Attribute
| Position | Effect |
|---|---|
after | Insert after the matched element |
before | Insert before the matched element |
inside | Insert as last child of matched element |
replace | Replace the matched element entirely |
attributes | Modify attributes of matched element |
Conditional Visibility (Odoo 17+)
<!-- OLD (Odoo 16 and earlier) — DO NOT USE IN v17+ -->
<field name="discount" attrs="{'invisible': [('state', '!=', 'draft')]}"/>
<!-- NEW (Odoo 17+) — use direct attribute -->
<field name="discount" invisible="state != 'draft'"/>
<field name="approval_note" readonly="state == 'done'"/>
<field name="partner_id" required="state == 'confirmed'"/>
<!-- Complex conditions -->
<field name="discount" invisible="state != 'draft' or not is_manager"/>Getting Started
Use Claude Code with an Odoo 19 CLAUDE.md to generate views with correct syntax. Deploy and test on DeployMonkey — the AI agent helps diagnose view rendering issues.