Views are essential in Odoo for presenting records in a user-friendly
manner. Without a well-structured view, users may struggle to
interpret the information effectively. Odoo supports a variety of
view types, including form, list, kanban, graph, pivot, calendar,
dashboard, search, grid, cohort, and others.
While adding a new view can be complex, this guide highlights the key
steps involved in the process.
1. Create the controller.
The primary role of a controller is to coordinate the interaction
between various view components, including the Renderer, Model, and
Layout.
Create custom_controller.js
/** @odoo-module */
import { Layout } from "@web/search/layout";
import { useService } from "@web/core/utils/hooks";
import { Component, onWillStart, useState} from "@odoo/owl";
export class CustomController extends Component {
setup() {
this.orm = useService("orm");
// The controller create the model and make it reactive so whenever this.model is
// accessed and edited then it'll cause a rerendering
this.model = useState(
new this.props.Model(
this.orm,
this.props.resModel,
this.props.fields,
this.props.archInfo,
this.props.domain
)
);
onWillStart(async () => {
await this.model.load();
});
}
}
CustomController.template = "custom_view.View";
CustomController.components = { Layout };
The Controller template displays the control panel along with the
Layout and the Renderer components.
Create custom_controller.xml
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="custom_view.View">
<Layout display="props.display" className="'h-100 overflow-auto'">
<t t-component="props.Renderer" records="model.records" propsYouWant="'Hello world'"/>
</Layout>
</t>
</templates>
2. Create the renderer.
Create custom_renderer.js
The primary role of a renderer is to visually present data by
rendering the view that displays the records.
/** @odoo-module */
import { Component } from "@odoo/owl";
export class CustomRenderer extends Component {}
CustomRenderer.template = "custom_view.Renderer";
Create custom_renderer.xml
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="my_module.Renderer">
<t t-esc="props.propsYouWant"/>
</t>
</templates>
3. Create the model.
The model is responsible for fetching and managing all the data
needed for the view.
Create custom_model.js
/** @odoo-module */
import { KeepLast } from "@web/core/utils/concurrency";
export class CustomModel {
constructor(orm, resModel, fields, archInfo, domain) {
this.orm = orm;
this.resModel = resModel;
// We can access arch information parsed by the beautiful arch parser
const { fieldFromTheArch } = archInfo;
this.fieldFromTheArch = fieldFromTheArch;
this.fields = fields;
this.domain = domain;
this.keepLast = new KeepLast();
}
async load() {
// The keeplast protect against concurrency call
const { length, records } = await this.keepLast.add(
this.orm.webSearchRead(this.resModel, this.domain, [this.fieldsFromTheArch], {})
);
this.records = records;
this.recordsLength = length;
}
}
4. Create the arch parser.
The arch parser is responsible for parsing the arch (architecture) of
the view, enabling the view to interpret and access its defined
structure and information.
Create custom_arch_parser.js
/** @odoo-module */
import { XMLParser } from "@web/core/utils/xml";
export class GanttArchParser {
parse(arch) {
const xmlDoc = this.parseXML(arch);
const fieldFromTheArch = xmlDoc.getAttribute("fieldFromTheArch");
return {
fieldFromTheArch,
};
}
}
5. Create the view by assembling all of its components, and then
registering it in the views registry.
Create custom_view.js
/** @odoo-module */
import { registry } from "@web/core/registry";
import { CustomController } from "./custom_controller";
import { CustomArchParser } from "./custom_arch_parser";
import { CustomModel } from "./custom_model";
import { CustomRenderer } from "./custom_renderer";
export const customView = {
type: "custom_view",
display_name: "Custom",
icon: "fa fa-picture-o", // the icon that will be displayed in the Layout panel
multiRecord: true,
Controller: CustomController,
ArchParser: CustomArchParser,
Model: CustomModel,
Renderer: CustomRenderer,
props(genericProps, view) {
const { ArchParser } = view;
const { arch } = genericProps;
const archInfo = new ArchParser().parse(arch);
return {
...genericProps,
Model: view.Model,
Renderer: view.Renderer,
archInfo,
};
},
};
registry.category("views").add("custom_view", customView);
6. Add it to the manifest file
'assets': {
'web.assets_backend': [
'custom_view/static/src/js/custom_arch_parser.js',
'custom_view/static/src/js/custom_controller.js',
'custom_view/static/src/js/custom_model.js',
'custom_view/static/src/js/custom_renderer.js',
'custom_view/static/src/js/custom_view.js',
'custom_view/static/src/xml/custom_controller.xml',
'custom_view/static/src/xml/custom_renderer.xml',
],
},
7. Add the created view to the view mode
from odoo import fields, models
class IrActionsActWindowView(models.Model):
_inherit = 'ir.actions.act_window.view'
view_mode = fields.Selection(
selection_add=[('custom_view', "Custom")],
ondelete={'custom_view': 'cascade'}
)
8. Add to the type in ir.ui.view
class IrUiView(models.Model):
_inherit = 'ir.ui.view'
type = fields.Selection(
selection_add=[('custom_view', "Custom")]
)
Now, let's explore how to add a custom_view to the list of available
views. In this example, we'll integrate the custom view into the My
Tasks view within Odoo's Project module.
<record id="project_task_custom_view" model="ir.ui.view" >
<field name="name">project.task.custom_view</field >
<field name="model">project.task</field >
<field name="arch" type="xml" >
<custom_view/ >
</field >
</record >
<record id="action_view_all_task"
model="ir.actions.act_window" >
<field name="view_mode">kanban,tree,form,custom_view </field >
</record >