Quick Answer
Default Odoo views don't match your workflow. Build custom views: Form views for detailed editing, List views for table scanning, Kanban boards for visual workflow progress, Pivot tables for analytics. Get views right: 90%+ staff adoption within days. Get them wrong: $40,000-$100,000 in lost productivity and frustrated users.
The View Design Problem
Your D2C brand has thousands of orders. You need to see them:
❏ In a detailed form for editing individual order details
❏ In a list for quick scanning of all orders
❏ In a kanban board for visual workflow (ordered → shipped → delivered)
❏ In a pivot table for analyzing revenue by product category and month
Each view serves a different purpose. Each requires different XML configuration. Get them right, and your team works efficiently. Get them wrong, and your team struggles with a confusing, poorly-organized interface.
We've implemented 150+ Odoo systems. The ones with well-designed views? Staff adoption is 90%+ within days. The ones with poorly-designed views? Staff fights with the interface, creates workarounds, eventually gives up on Odoo. That's $40,000-$100,000 in lost productivity and frustrated users.
View 1: Form View (Detailed Record Editor)
What it is: Single record with all fields visible. Used for editing and creating records.
Real D2C example: Editing a customer profile
<record id="view_customer_form" model="ir.ui.view">
<field name="name">Customer Form</field>
<field name="model">res.partner</field>
<field name="arch" type="xml">
<!-- FORM CONTAINER -->
<form>
<!-- HEADER (Status bar, buttons) -->
<header>
<button name="action_confirm" type="object" string="Confirm"
class="btn-primary"/>
<button name="action_archive" type="object" string="Archive"/>
<field name="status" widget="statusbar"
statusbar_visible="draft,active,archived"/>
</header>
<!-- BODY (Fields, groups, pages) -->
<sheet>
<!-- Title area -->
<div class="oe_title">
<h1>
<field name="name" placeholder="Customer Name"/>
</h1>
</div>
<!-- Contact Information Section -->
<group string="Contact Information">
<field name="email" widget="email"/>
<field name="phone" widget="phone"/>
<field name="mobile" widget="phone"/>
<field name="website" widget="url"/>
</group>
<!-- Address Section -->
<group string="Address">
<field name="street" colspan="2"/>
<field name="street2" colspan="2"/>
<field name="city"/>
<field name="state_id"/>
<field name="zip"/>
<field name="country_id"/>
</group>
<!-- Custom Fields Section -->
<group string="Customer Profile">
<field name="x_customer_type"
options="{'no_quick_create': True}"/>
<field name="x_loyalty_tier"
selection="[('bronze','Bronze'),('silver','Silver'),('gold','Gold')]"/>
<field name="x_lifetime_value" widget="monetary"/>
</group>
</sheet>
</form>
</field>
</record>
Form Widgets (Field Display Options)
<!-- TEXT FIELDS -->
<field name="name" widget="char"/> <!-- Plain text -->
<field name="email" widget="email"/> <!-- Email with validation -->
<field name="website" widget="url"/> <!-- URL with validation -->
<field name="phone" widget="phone"/> <!-- Phone formatter -->
<!-- SELECTION/DROPDOWN -->
<field name="status" widget="selection"/> <!-- Dropdown -->
<field name="tags" widget="many2many_tags"/> <!-- Tag field -->
<!-- DATE/TIME -->
<field name="date" widget="date"/> <!-- Date picker -->
<field name="datetime" widget="datetime"/> <!-- Date + time picker -->
<field name="duration" widget="float_time"/> <!-- Duration (00:30:00) -->
<!-- MONETARY -->
<field name="amount" widget="monetary"/> <!-- Currency-aware -->
<!-- BOOLEAN -->
<field name="is_active" widget="boolean"/> <!-- Checkbox -->
<!-- RELATIONAL -->
<field name="customer_id" widget="many2one"/> <!-- Dropdown for linked record -->
<field name="items" widget="one2many"/> <!-- Sub-table of related records -->
<field name="tags" widget="many2many_tags"/> <!-- Multiple selection tags -->
View 2: List View (Table of Records)
What it is: All records in table format. Fast scanning, sorting, filtering, bulk actions.
Real D2C example: View all orders
<record id="view_order_list" model="ir.ui.view">
<field name="name">Order List</field>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<!-- TREE = List view in Odoo terminology -->
<tree editable="bottom" decoration-danger="state == 'draft'">
<!-- COLUMNS -->
<field name="name" string="Order #"/>
<field name="order_date"/>
<field name="customer_id"/>
<field name="amount_total" sum="Total" widget="monetary"/>
<field name="x_priority" optional="show"/>
<field name="state" decoration-success="state == 'done'"
decoration-warning="state == 'draft'"/>
</tree>
</field>
</record>
<!-- SEARCH VIEW (Filters, search, grouping) -->
<record id="view_order_search" model="ir.ui.view">
<field name="name">Order Search</field>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<search>
<!-- SEARCHABLE FIELDS -->
<field name="name" string="Order #"/>
<field name="customer_id"/>
<field name="amount_total"/>
<!-- FILTERS -->
<filter name="filter_draft" string="Draft Orders"
domain="[('state', '=', 'draft')]" />
<filter name="filter_done" string="Completed"
domain="[('state', '=', 'done')]" />
<filter name="filter_priority" string="High Priority"
domain="[('x_priority', '=', 'high')]" />
<!-- GROUPING OPTIONS -->
<group expand="0" string="Group By">
<filter name="group_status" string="Status"
context="{'group_by': 'state'}"/>
<filter name="group_customer" string="Customer"
context="{'group_by': 'customer_id'}"/>
<filter name="group_date" string="Order Date"
context="{'group_by': 'order_date'}"/>
</group>
</search>
</field>
</record>
View 3: Kanban View (Visual Workflow Cards)
What it is: Records as cards in columns. Perfect for visualizing workflow progress (Draft → Ordered → Shipped).
Real D2C example: Orders by status
<record id="view_order_kanban" model="ir.ui.view">
<field name="name">Order Kanban</field>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<!-- KANBAN BOARD -->
<kanban default_group_by="state"
quick_create="bottom"
on_create="quick_create">
<!-- TEMPLATES = Card design -->
<templates>
<!-- CARD CONTENT -->
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<!-- CARD TITLE -->
<div class="oe_kanban_details">
<strong>
<field name="name"/>
- <field name="customer_id"/>
</strong>
</div>
<!-- AMOUNT -->
<div class="oe_kanban_subtitle">
Amount:
<field name="amount_total" widget="monetary"/>
</div>
<!-- PRIORITY BADGE -->
<div class="oe_kanban_footer">
<t t-if="record.x_priority.raw_value == 'high'">
<span class="badge badge-danger">
<field name="x_priority"/>
</span>
</t>
</div>
<!-- ORDER DATE -->
<small class="text-muted">
<field name="order_date"/>
</small>
<!-- PROGRESSBAR -->
<div class="oe_kanban_progbar">
<field name="state" widget="progressbar"
options="{'editable': true}"/>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
View 4: Pivot View (Data Analysis Table)
What it is: Aggregated data in crosstab format. Analyze totals by multiple dimensions (revenue by product × month).
Real D2C example: Revenue analysis
<record id="view_order_pivot" model="ir.ui.view">
<field name="name">Order Pivot</field>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<!-- PIVOT TABLE -->
<pivot string="Order Analysis">
<!-- ROWS (left axis) -->
<field name="customer_id" type="row"/>
<field name="product_id" type="row" interval="day"/>
<!-- COLUMNS (top axis) -->
<field name="state" type="col"/>
<field name="order_date" type="col" interval="month"/>
<!-- VALUES (numbers in cells) -->
<field name="amount_total" type="measure" aggregate="sum"/>
<field name="quantity" type="measure" aggregate="sum"/>
<field name="id" type="measure" aggregate="count"
string="Number of Orders"/>
</pivot>
</field>
</record>
Real-World D2C Pivot Table
| Month | Draft | Ordered | Shipped | Delivered |
|---|---|---|---|---|
| January | $4,200 | $12,500 | $18,300 | $45,600 |
| February | $2,100 | $9,800 | $22,100 | $51,200 |
| March | $5,600 | $14,200 | $19,800 | $48,900 |
View 5: Graph View (Charts)
<!-- BAR CHART (Default) -->
<graph type="bar" stacked="False">
<field name="product_id" type="row"/>
<field name="amount_total" type="measure" aggregate="sum"/>
</graph>
<!-- LINE CHART -->
<graph type="line">
<field name="order_date" type="row" interval="month"/>
<field name="amount_total" type="measure" aggregate="sum"/>
</graph>
<!-- PIE CHART -->
<graph type="pie">
<field name="state" type="row"/>
<field name="id" type="measure" aggregate="count"/>
</graph>
Extending Views (Inheritance)
Add a field to an existing view without modifying the original:
<!-- INHERIT existing customer form -->
<record id="view_customer_form_extended" model="ir.ui.view">
<field name="name">Customer Form - Extended</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<!-- ADD FIELD AFTER existing field -->
<xpath expr="//field[@name='email']" position="after">
<field name="x_loyalty_points"/>
<field name="x_preferred_contact"/>
</xpath>
<!-- REPLACE (modify) existing field -->
<xpath expr="//field[@name='phone']" position="replace">
<field name="phone" widget="phone" required="true"/>
</xpath>
</field>
</record>
Performance Optimization (For Large Datasets)
Problem: List view with 100,000 orders loads slowly.
Solutions:
# 1. Use search limits
class SaleOrder(models.Model):
_name = 'sale.order'
# In list view, only load 80 records at a time
_order = 'order_date DESC'
# 2. Add database indexes
class SaleOrder(models.Model):
_name = 'sale.order'
# Index frequently-searched fields
customer_id = fields.Many2one(index=True) # Fast filtering
order_date = fields.Date(index=True) # Fast sorting
state = fields.Selection(index=True) # Fast filtering
<!-- ACTION with limit -->
<record id="action_order" model="ir.actions.act_window">
<field name="name">Orders</field>
<field name="res_model">sale.order</field>
<field name="view_mode">list,form</field>
<field name="limit">80</field> <!-- Load 80 records per page -->
</record>
View Types Comparison
| View Type | Best For | Key Features | User Action |
|---|---|---|---|
| Form View | Detailed editing, creating records | Widgets, groups, pages, header buttons | Edit single record |
| List View | Fast scanning, sorting, filtering | Columns, filters, groups, bulk actions | View multiple records |
| Kanban View | Visual workflow, status tracking | Cards, drag-drop, badges, progress bars | Move through workflow |
| Pivot View | Data analysis, cross-tabulation | Rows, columns, measures, aggregation | Analyze trends |
| Graph View | Visual analytics, charts | Bar, line, pie charts | Visualize data |
Action Items: Build Your Custom Views
For Form Views
❏ Add header with status bar and buttons
❏ Group related fields logically
❏ Use appropriate widgets (email, phone, date, etc.)
❏ Show related records (One2Many) as sub-tables
For List Views
❏ Define searchable fields
❏ Add filters for common use cases
❏ Add grouping options
❏ Use color-coding (decoration-danger, decoration-success)
For Kanban Views
❏ Choose grouping field (status, priority, etc.)
❏ Design card layout with key information
❏ Add badges/colors for important data
❏ Enable quick-create for fast card creation
For Pivot/Graph Views
❏ Identify analysis dimensions (rows, columns)
❏ Choose aggregation (sum, count, avg)
❏ Pick appropriate chart type
Frequently Asked Questions
What's the difference between tree and list views in Odoo?
They're the same. <tree> is the XML tag name (legacy terminology), but it creates a list view (table of records). Use <tree editable="bottom"> for inline editing or <tree> for read-only lists.
Can I add custom widgets to Odoo form views?
Yes. Odoo includes built-in widgets (email, phone, monetary, etc.). For custom widgets, create a JavaScript widget using OWL framework and register it. Use <field name="field_name" widget="your_custom_widget"/>.
How do I inherit and extend an existing Odoo view without breaking updates?
Use inherit_id with XPath expressions. Set <field name="inherit_id" ref="module.view_id"/> and use <xpath expr="//field[@name='field']" position="after"> to add fields. This doesn't modify the original view, so updates won't break your customizations.
What's the cost of poorly designed views vs. well-designed views?
Well-designed views: 90%+ staff adoption within days, efficient workflows. Poorly-designed views: Staff fights the interface, creates workarounds, $40,000-$100,000 in lost productivity. Redesigning views post-launch costs $8,000-$20,000.
Free View Design Workshop
Stop guessing about view structure. We'll assess your current view structure, design optimal views for your workflows, optimize views for large datasets, create inherited views without breaking updates, and review performance bottlenecks. Most D2C brands discover their views are poorly designed after launch. Redesigning views costs $8,000-$20,000 in consulting and user confusion. Getting views right upfront eliminates this cost entirely.
