We all know that views are used to describe records to users. The user cannot get the information correctly without an appropriate vision. Odoo's view includes form, tree, kanban, graph, pivot, calendar, dashboard, search, grid, cohort, and many more.
Adding a new view is a more sophisticated topic. This guide focuses primarily on the most important stages.
1. Create the controller.
A controller's major function is to assist coordination between multiple view components such as 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 shows the control panel with Layout as well as the renderer.
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
A renderer's principal role is to provide a visual representation of data by displaying the view that contains 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's function is to retrieve and handle all of the data required in 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's function is to parse the arch view so that the view may access the 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 check how to add the custom_view to view lists. Here we are adding to the My tasks view in Project module of Odoo
<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>