Forms, lists, and search views are defined using the arch
XML structures. To extend views, we need a way to modify this XML. This means locating XML elements and then introducing modifications at those points.
Inherited views allow just that. An inherited view declaration looks like this:
<record id="view_form_todo_task_inherited" model="ir.ui.view"> <field name="name">Todo Task form - User extension</field> <field name="model">todo.task</field> <field name="inherit_id" ref="todo_app.view_form_todo_task"/> <field name="arch" type="xml"> <!-- ...match and extend elements here! ... --> </field </record>
The inherit_id
field identifies the view to be extended by referring to its external identifier using the special ref
attribute. External identifiers will be discussed in more detail in
Chapter 4
, Module Data.
Being XML, the best way to locate elements in XML is to use XPath expressions. For example, taking the form view defined in the previous chapter, one XPath expression to locate the <field name="is_done">
element is //field[@name]='is_done'
. This expression finds any field
element with a name
attribute that is equal to is_done
. You can find more information on XPath at https://docs.python.org/2/library/xml.etree.elementtree.html#xpath-support.
If an XPath expression matches multiple elements, only the first one will be modified. So they should be made as specific as possible, using unique attributes. Using the name
attribute is the easiest way to ensure we find the exact elements we want to use an extension point. Thus, it is important to set them on our view XML elements.
Once the extension point is located, you can either modify it or have XML elements added near it. As a practical example, to add the date_deadline
field before the is_done
field, we would write the following in arch
:
<xpath expr="//field[@name]='is_done'" position="before"> <field name="date_deadline" /> </xpath>
Fortunately, Odoo provides shortcut notation for this, so most of the time we can avoid the XPath syntax entirely. Instead of the preceding XPath element, we can just use information related to the type of element type to locate and its distinctive attributes, and instead of the preceding XPath, we write this:
<field name="is_done" position="before"> <field name="date_deadline" /> </field>
Just be aware that if the field appears more than once in the same view, you should always use the XPath syntax. This is because Odoo will stop at the first occurrence of the field and it may apply your changes to the wrong field.
Often, we want to add new fields next to the existing ones, so the <field>
tag will be used as the locator frequently. But any other tag can be used: <sheet>
, <group>
, <div>
, and so on. The name
attribute is usually the best choice for matching elements, but sometimes, we may need to use something else: the CSS class
element, for example. Odoo will find the first element that has at least all the attributes specified.
The position
attribute used with the locator element is optional and can have the following values:
after
adds the content to the parent element, after the matched node.before
adds the content, before the matched node.inside
(default value) appended the content inside matched node.replace
replaces the matched node. If used with empty content, it deletes an element. Since Odoo 10 it also allows to wrap an element with other markup, by using $0 in the content to represent the element being replaced.attributes
modifies the XML attributes of the matched element. This is done using in the element content <attribute name="attr-name">
elements with the new attribute values to set.For Example, in the Task form, we have the active
field, but having it visible is not that useful. We could hide it from the user. This can be done by setting its invisible
attribute:
<field name="active" position="attributes"> <attribute name="invisible">1</attribute> </field>
Setting the invisible
attribute to hide an element is a good alternative to using the replace
locator to remove nodes. Removing nodes should be avoided since it can break depending modules that may be depending on the deleted node as a placeholder to add other elements.
Putting together all the previous form elements, we can add the new fields and hide the active
field. The complete inheritance view to extend the to-do tasks form is this:
<record id="view_form_todo_task_inherited"
model="ir.ui.view">
<field name="name">Todo Task form - User
extension</field>
<field name="model">todo.task</field>
<field name="inherit_id"
ref="todo_app.view_form_todo_task"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="user_id">
</field>
<field name="is_done" position="before">
<field name="date_deadline" />
</field>
<field name="active" position="attributes">
<attribute name="invisible">1</attribute>
</field>
</field>
</record>
This should be added to a views/todo_task.xml
file in our module, inside the <odoo>
element, as shown in the previous chapter.
Also, we should not forget to add the data
attribute to the __manifest__.py
descriptor file:
'data': ['views/todo_task.xml'],
Tree and search view extensions are also defined using the arch
XML structure, and they can be extended in the same way as form views. We will continue our example by extending the list and search views.
For the list view, we want to add the user
field to it:
<record id="view_tree_todo_task_inherited" model="ir.ui.view"> <field name="name">Todo Task tree - User extension</field> <field name="model">todo.task</field> <field name="inherit_id" ref="todo_app.view_tree_todo_task"/> <field name="arch" type="xml"> <field name="name" position="after"> <field name="user_id" /> </field> </field </record>
For the search view, we will add the search by the user and predefined filters for the user's own tasks and the tasks not assigned to anyone:
<record id="view_filter_todo_task_inherited" model="ir.ui.view"> <field name="name">Todo Task tree - User extension</field> <field name="model">todo.task</field> <field name="inherit_id" ref="todo_app.view_filter_todo_task"/> <field name="arch" type="xml"> <field name="name" position="after"> <field name="user_id" /> <filter name="filter_my_tasks" string="My Tasks" domain="[('user_id','in',[uid,False])]" /> <filter name="filter_not_assigned" string="Not Assigned" domain="[('user_id','=',False)]" /> </field> </field </record>
Don't worry too much about the specific syntax for these views. We'll cover them in more detail in Chapter 6 , Views - Designing the User Interface.