As we have seen in previous chapters, form views can follow a simple layout or a business document layout, similar to a paper document.
We will now see how to design these business document views and how to use the elements and widgets available. We would normally do this by inheriting and extending the todo_app
views. But for the sake of clarity, we will instead create completely new views to override the original ones.
The same model can have more than one view of the same type. This can be useful since an window action can tell the specific view that should be used, through its XML ID. So we have the flexibility to have two different menu items to open the same model using different views. This is done adding a view_id
attribute to the window action, with the XML ID of the view to use. For example, we could have used this in the todo_app.action_todo_task
action, with something similar to: view_id="view_form_todo_task_ui"
.
But what happens if no specific view is define? In that case the one used will be the first one returned when querying for views. This will be the one with the lower priority . If we add a new view and set it with a lower priority than the existing ones, it will be the one used. The final effect is that it looks like this new view is overriding the original one.
Since the default value for the view priority is 16, any lower value would do, so a 15 priority will work.
It's not the most commonly used route, to help keeping our examples as readable as possible, we will use the priority approach in our next examples.
Business applications are often systems of record – for products in a warehouse, invoices in an accounting department, and many more. Most of the recorded data can be represented as a paper document. For a better user experience, form views can mimic these paper documents. For example, in our app, we could think of a To-Do Task as something that has a simple paper form to fill out. We will provide a form view that follows this design.
To add a view XML with the basic skeleton of a business document view, we should edit the views/todo_views.xml
file and add it to the top:
<record id="view_form_todo_task_ui" model="ir.ui.view"> <field name="model">todo.task</field> <field name="priority">15</field> <field name="arch" type="xml"> <form> <header> <!-- To add buttons and status widget --> </header> <sheet> <!-- To add form content --> </sheet> <!-- Discuss widgets for history and communication: --> <div class="oe_chatter"> <field name="message_follower_ids" widget="mail_followers" /> <field name="message_ids" widget="mail_thread" /> </div> </form> </field> </record>
The view name is optional and automatically generated if missing. For simplicity, we took advantage of that and omitted the <field name="name">
element from the view record.
We can see that business document views usually use three main areas: the header status bar, the sheet for the main content, and a bottom history and communication section, also known as chatter.
The history and communication section, at the bottom, uses the social network widgets provided by the mail addon module. To be able to use them, our model should inherit the mail.thread
mixin model, as we saw in Chapter 3, Inheritance - Extending Existing Applications.
The header at the top usually features the life cycle or steps that the document will move through and the action buttons.
These action buttons are regular form buttons, and the most important next steps can be highlighted, using class="oe_highlight"
.
The document life cycle uses the statusbar
widget on a field that represents the point in the life cycle where the document is currently at. This is usually a State selection field or a Stage many-to-one field. These two fields can be found across several Odoo core modules.
The stage is a many-to-one field which uses a supporting model to set up the steps of the process. Because of this it can be dynamically configured by end users to fit their specific business process, and is perfect to support kanban boards.
The state is a selection list featuring a few, rather stable, steps in a process, such as New
, In Progress
, and Done
. It is not configurable by end users but, since it is static, it is much easier to be used in business logic. The view fields even have special support for it: the state attribute allows a field to be available to the user or not, depending on the document state.
Historically, stages were introduced later than states. Both have coexisted, but the trend in the Odoo core is for stages to replace states. But as seen in the preceding explanation, states still provide some features that stages don't.
It is still possible to benefit from the best of both worlds, by mapping the stages into states. This was what we did in the previous chapter, by adding a state field in the task Stages model, and making it also available in the To-do Task documents through a computed field, enabling the use of the state field attribute.
In the views/todo_view.xml
file we can now expand the basic header to add a status bar:
<header> <field name="state" invisible="True" /> <button name="do_toggle_done" type="object" attrs="{'invisible':[('state','in',['draft'])]}" string="Toggle Done" class="oe_highlight" /> <field name="stage_id" widget="statusbar" clickable="True" options="{'fold_field': 'fold'}" /> </header>
Here we add state
as a hidden field. We need this to force the client to also include that field in the data requests sent to the server. Otherwise it won't be available to be used in expressions.
Next a button is added to the status bar, to let the user toggle the Task's Done flag.
The buttons displayed in the status bar should change based on the where in the life cycle the current document is.
We used the attrs
attribute to hide the button when the document is in the draft
state. The condition to do this uses the state
field, not shown on the form, which is why we had to add it as a hidden field.
If we have a state
selection field, we can instead use the states
attribute. In this case we do, and the same effect could be achieved using states="open,done"
. While it's not as flexible as attrs
attribute, it is more concise.
These visibility features can be also used on other view elements, such as fields. We will explore them in more detail later in this chapter.
The clickable
attribute allows the user to change the document stage by clicking on the status bar. We usually want to enable this, but there are also cases where we don't, such as when we need more control over the workflow, and require the users to progress through the stages using only the available action buttons, so that these can perform validations before moving between stages.
When using a status bar widget with stages, we can have the seldom used stages hidden in a More stage group. For this, the stages model must have a flag to configure the ones to hide, usually named fold
. And the statusbar
widget should use an options
attribute, as shown in the preceding code, to provide that field name to the fold_field
option.
When using the status bar widget with a state field, a similar effect can be achieved with the statusbar_visible
attribute, used to list states that should be always visible and hide exception states necessary for less common cases. For example:
<field name="stage_id" widget="statusbar" clickable="True" statusbar_visible="draft,open" />
The sheet canvas is the main area of the form where the actual data elements are placed. It is designed to look like an actual paper document, and it is common to see that the records in Odoo are referred to as documents.
Usually, a document sheet structure will have these areas:
Let's go through each of these areas.
Fields outside a <group>
element don't automatically have labels rendered for them. This will be the case for the title elements, so the <label for"..."/>
element should be used to render it. At the expense of some extra work, this has the advantage of giving more control over the label display.
Regular HTML, including CSS-style elements, can also be used to make the title shine. For best results, the title should be inside a <div>
with the oe_title
class.
Here is the <sheet>
element expanded to include the title plus some additional fields as subtitles:
<sheet> <div class="oe_title"> <label for="name" class="oe_edit_only"/> <h1><field name="name"/></h1> <h3> <span class="oe_read_only>By</span> <label for="user_id" class="oe_edit_only"/> <field name="user_id" class="oe_inline" /> </h3> </div> <!-- More elements will be added from here... --> </sheet>
Here we can see that we use regular HTML elements, such as div
, span
, h1
, and h2
. The <label>
element allows us to control when and where it will be shown. The for
attribute identifies the field we should get the label text from. Another possibility is to use the string
attribute to provide a specific text to use for the label. Our example also uses the class="oe_edit_only"
attribute so that it is visible only in edit mode.
In some cases, such as Partners or Products, a representative image is shown at the top-left corner. Supposing we had a my_image
binary field, we could add before the <div class="oe_title">
line, using:
<field name="my_image" widget="image" class="oe_avatar"/>
The top-right area can have an invisible box where buttons can be placed. The version 8.0 introduced smart buttons, shown as rectangles with a statistic indicator that can be followed through when clicked.
We can add the button box right after the end of the oe_title
DIV, with the following:
<div name="buttons" class="oe_right oe_button_box"> <!-- Smart buttons here … --> </div>
The container for the buttons is a div
with the oe_button_box
class and also oe_right
, to align it to the right-hand side of the form. We will be discussing buttons in more detail in a later section, so we will wait until then to add actual buttons in this box.
The main content of the form should be organized using <group>
tags. The group
tag inserts two columns in the canvas, and inside it, by default, fields will be displayed with labels.
A field value and field label takes two columns, so adding fields inside a group will have them stacked vertically. If we nest two <group>
elements inside a top group, we will be able to get two columns of fields with labels, side by side.
Continuing with our form view, we can now add the main content after the smart buttons box:
<group name="group_top"> <group name="group_left"> <field name="date_deadline" /> <separator string="Reference" /> <field name="refers_to" /> </group> <group name="group_right"> <field name="tag_ids" widget="many2many_tags"/> </group> </group>
It is a good practice to assign a name
to group tags so that it's easier to reference them later to extend the view (either by you or another developer). The string
attribute is also allowed, and if set, is used to display section title.
Inside a group, a <newline>
element will force a new line and the next element will be rendered in the group's first column. Additional section titles can be added inside a group using the <separator>
element.
We can have greater control over the layout of a group element using the col
and colspan
attributes.
The col
attribute can be used on <group>
elements to customize the number of columns it will contain. The default is 2
, but it can be changed to any other number. Even numbers work better since by default each field added takes up two columns, for the label plus the field value.
The elements placed inside the group, including <field>
elements, can use a colspan
attribute to set a specific number of columns they should take. By default one column is taken up.
Another way to organize content is using the
notebook
element, containing multiple tabbed sections, called pages. These can be used to keep less used data out of sight until needed, or to organize a large number of fields by topic.
We won't need to add this to our To-do Task form, but here is an example that could be added to a Task Stages form:
<notebook> <page string="Whiteboard" name="whiteboard"> <field name="docs" /> </page> <page> <!-- Second page content --> </page> </notebook>