Skip to content

4.4 step4

Jean Cavallo edited this page Jan 22, 2019 · 5 revisions

Step 4 - User Interface

We now have a few models with data in them. We will now display it to the end user so that he can populate his database with informations.

In step 1, we talked about the module structure, and we created many xml files. There are two families of xml files in tryton:

  • Data xml files are those that are referenced in the tryton.cfg file, and include record to be included in the database on module installation / upgrade. We have only one such file for now, library.xml, about which we already talked a little
  • View xml files are located under the view folder in the module and contains view definition and layout. They are referenced from inside library.xml by the name attribute of the ir.ui.view records

Creating entry points

Before writing the contents of our views, we will want to make them accessible to the user through the tryton client. To do so, we will need to create entry points via the data xml file library.xml, so that our users can actually click somewhere to show up our views.

Add a menu

Open library.xml, and add the following inside the <data> tag, right after it is opened:

<!-- ############# -->
<!-- # Root Menu # -->
<!-- ############# -->
<menuitem name="Library" id="menu_library" sequence="1"/>

This creates a new menuitem in the database. A menuitem is an entry point, an entity that will appear in the main menu on the left side of the application. The name parameter is the string that will be used in the menu, the id is a unique identifier (like for all xml entries), and sequence is an integer used to order the different entry points. 1 will make it to the top (unless there is another module which adds entry points with a 0 sequence).

Now if you update your database and connect to it, you should see a new entry in the left-side menu named "Library".

Note: The <menuitem> accepts an optional icon attribute which allows you to set the icon of the menu entity to an image of your liking

Add an Author entry point

In library.xml, add the following after the view definitions (the ir.ui.view records) associated to the library.author model:

<record model="ir.action.act_window" id="act_author">
    <field name="name">Authors</field>
    <field name="res_model">library.author</field>
</record>
<record model="ir.action.act_window.view" id="act_author_view_list">
    <field name="sequence" eval="10"/>
    <field name="view" ref="author_view_list"/>
    <field name="act_window" ref="act_author"/>
</record>
<record model="ir.action.act_window.view" id="act_author_view_form">
    <field name="sequence" eval="20"/>
    <field name="view" ref="author_view_form"/>
    <field name="act_window" ref="act_author"/>
</record>
<menuitem parent="menu_library" sequence="1" action="act_author" id="menu_author"/>

We will first focus on the last line. The menuitem is the same than that of the "Library" menu we just created. We use the parent attribute to reference the main "Library" menu using its id menu_library. So we are in essence creating a new menu under the "Library" menu we already created. Here, the sequence attribute is used to order the menu among all menus that are children of the same parent, menu_library.

You will notice that this menuitem does not have a name. That is because it has a action attribute. "Actions" in tryton are used to describe well, actions, that the client is expected to perform when certain conditions are met. Here, the action will be triggered when the user activates (through double-click, or selecting then pressing the Enter key) the menu. When an action is set on a menu, the name that will be displayed to the user will be automatically calculated from the action definition.

Here, the action value is act_author. As with all references inside xml data files, it must match an id, and indeed we declared a record with this id above.

Warning: XML data file are read in the order they are defined. When a reference to an element is made, the target element must already be created, so it must be "above". That is why the menuitem is declared after the action

Let's decompose the action:

<record model="ir.action.act_window" id="act_author">
    <field name="name">Authors</field>
    <field name="res_model">library.author</field>
</record>

This is a record, the same as when we created views in step 1. The model is not the same though. It was ir.ui.view, here it is ir.action.act_window. This means that the entity we are creating is an action, of type "window". Window action will used to create a new tab in the tryton client for the user to see. The name attribute will be used as the tab's title. The res_model attribute is the __name__ of the model that will be displayed in the tab.

Note: There are other action types, for now we will only talk about ir.action.act_window

So here, we are:

  • Creating a new entry point which will be "under" the "Library" entry point
  • Instructing tryton to, when the user activates the entry point, open a new tab named Authors, which will be used to display library.author records

The two remaining entities are records of the ir.action.act_window.view model. If you remember the naming rules for models that we talked about in step 1, that sort of indicates that this model is a view linked to the ir.action.act_window model.

<record model="ir.action.act_window.view" id="act_author_view_list">
    <field name="sequence" eval="10"/>
    <field name="view" ref="author_view_list"/>
    <field name="act_window" ref="act_author"/>
</record>

We will focus on the first one, the second being very similar. The sequence attribute is used to order the views that will be used in a tab. Usually, you will have a list view, and a form view. You almost always want that when the user opens the tab, he sees the list view, and the form view when he opens a record. The sequence attribute allows to do that by ordering the different views. Why not <field name="sequence">10</field>? Because then the value would be a string, and the sequence attribute expects an integer. So we use the eval attribute to tell tryton to python evaluate the string as the value.

The second attribute is view. We need to tell tryton which view we want to use. We already declared in step 1 the views we will need, so we just need to reference them through their id using the ref keyword. Here, author_view_list is used to match the id of the ir.ui.view record we created before.

The third attribute is used to link the record we are creating to its parent, the ir.action.act_window instance with the id act_author that we just defined.

All in all, those two ir.action.act_window.view records are used to explain to tryton that the tab we want to open should use the two views that we defined earlier, first the list view, and second the form view.

You can now update your database, restart your server and client, and you should see a new antry point named Authors after you expand the Library entry point. Double-clicking it should open a new ampty tab named Authors. Nothing is displayed here, because we did not (yet) add any contents to our views, we are coming to that now.

Writing views

We are now going to flesh up our views, so that we can actually start to use our library application.

Tree / List views

View definition

Open the view/author_list.xml file, and write the following inside the <tree> tag:

<field name="name"/>

That's it. Tree views are the simplest, because the only thing we define in them is the list of columns we want to display. We usually only define <field/> tags in them, with a sole attribute, name, whose value is the name of the field we want to display.

There are some options you can use. Rewrite the previous line with the following:

<field name="name" expand="1"/>

The expand attribute in tree views tells the client to expand the column size as much as possible. Since we only have one column, we should do so. If many columns have the expand attribute set:

  • The columns without it will be shrinked as much as possible (depending on the contents size so as to still be readable)
  • The remaining space will be equally divided for the expanded columns

Filtering / sorting

Tree views can usually be filtered on their columns. You cannot filter on a field that is not displayed in the view. If you want to make it possible to do so anyway, you can add the field in the view with the tree_invisible attribute set:

<field name="my_invisible_field" tree_invisible="1"/>

Doing so will make the client load the field, and allow filtering from the client side.

It is possible to sort a list by clicking on the columns headers.

Form view

Basic form

Edit view/author_form.xml and add the following inside the <form/> tag:

<label name="name"/>
<field name="name"/>
<label name="gender"/>
<field name="gender"/>

The <field> tag is similar to that of the tree view. The <label> tag is used to display the field's string as a text to the user. Remember, in all field definition, there is a "nice text" whose purpose is to describe the field to the end user. That's where it is used.

The <label> and <field> are the building blocks of all form views. Form views without <field> tags are totally static, because they will not fetch any data linked to the record a user is consulting.

Right now, you can restart your server. The Authors entry point will now, when opened, display a list view with a column named Name. You can create a new author by Hitting "Ctrl+n" or clicking the "New" button in the toolbar. This will switch you to the form view, in which you will be able to set the name and gender fields, save (note how the client blocks you from saving if you do not set the name field), then switch back to the list view (by hitting "Ctrl+l" or clicking the "Switch" button) and see your newly created author.

Slightly more complex form

Reopen view/author_form.xml and add the following after the gender field:

<separator id="dates" string="Dates" colspan="4"/>
<label name="birth_date"/>
<field name="birth_date"/>
<label name="death_date"/>
<field name="death_date"/>
<field name="books" colspan="4"/>

The <label> and <field> tags should be clear enough by now. Let's focus on the separator tag.

A separator is used to separate (...) elements in the view. It is displayed as an horizontal line, with (eventually) a string in it. Here we set a string ("Dates") and an id since it is not linked to a field. The complicated part is the colspan attributes.

Tryton forms are basically a grid. By default (it can be changed), this grid has four columns, which are filled up from left to right, top to bottom. When you write:

<label name="birth_date"/>
<field name="birth_date"/>

you fill up one cell of the grid with the label, and the next one with the field. If the label is the four-th element of a row, the field will be the first element of the next row.

Column sizes cannot be controlled, they are automatically calculated bu the client so that the bigger cell in the column fits. So if the same column has a very big label, for instance if you defined a field like the following:

my_field = fields.Char('This is a very long field especially designed for problems')

the column in which the <label name="my_field"/> will be made long enough to acommodate this length, whcih may no be very aesthetic.

Back to our separator. The colspan attribute is used to control how many columns our element should take in the row. Here we are setting colspan to 4, so four columns. As said earlier, the default views have four columns, so we want it to take the full width of the view.

Note: If we require four columns but our element starts at column 2, it will be moved at the beginning of the next line. Using colspan does not risk for the client to somehow "split" your element across two rows

You notice that we use colspan as well for the books field. This is because the default widget for One2Many fields is bigger than the standard Char or Selection widgets, and multiline, so for readability it is common to set its width to the full screen, or half of that.

Restart the server / client, then check out the new form and see how the xml file contents reflects in the view layout.

Complex form

Open the view/book_form.xml file, and complete it inside the <form> tag:

<group id="left" string="" colspan="1" xexpand="0" xfill="1">
    <field name="cover" widget="image"/>
</group>
<group id="right" string="" colspan="3" col="2" yfill="1">
    <label name="title"/>
    <field name="title"/>
    <label name="author"/>
    <field name="author"/>
    <label name="genre"/>
    <field name="genre"/>
    <label name="editor"/>
    <field name="editor"/>
</group>
<notebook colspan="4">
    <page name="summary" col="2">
        <label name="description"/>
        <field name="description"/>
        <separator name="summary" colspan="2"/>
        <field name="summary" colspan="2"/>
    </page>
    <page id="other_data" string="Other informations">
        <label name="page_count"/>
        <field name="page_count"/>
        <label name="edition_stopped"/>
        <field name="edition_stopped"/>
    </page>
</notebook>

Note: You will not be able to consult this view until you do your homework. Do not hesitate to come back here once it is done to see how its contents are displayed

This view uses other tags and parameters, we will detail them now:

  • The <group> tag is used to create a sub-grid in the main grid. It must be identified by either an id and string to match, or a name (in which case the string will be that of the field whose name in the name tag)
  • The xexpand and xfill attributes are often used together to control how the group (or field, those attributes are available on multiple elements) tries to use the available space in the view. The same as expand in the tree view, elements with the xexpand attribute set will try to take the maximum available space in terms of column size. The xfill attribute will usually be set to "1", it is used to tell the contents of the group to take as much space as possible inside the group
  • There are yexpand and yfill attributes as well, which work similarly on the "y" axis
  • The widget attribute is used to force a field to be displayed using a widget. Every field type has a default widget that is usually good as is, however you can force it to a compatible widget. Here the cover field is a Binary field. The default widget allows to upload a file, retrieve it, or open it. We assume that the cover files will be picture, so we force the image widget so that they are directly shown to the user
  • The right group has a new col attribute. Remember that group are basically "sub-grids" of the main 4-column grid you are designing your form in. This attribute controls how many columns there will be in the sub-grid. By the way, modifying the number of columns in the main grid is done by setting the col attribute on the form tag

Here the combination of the left and right groups is used to have a small group on the top-left part of the view which will contain the cover field, and a big group on the right with more data. The relative horizontal size of the groups is controlled by the combination of the xexpand and colspan attributes on both groups. The yfill attribute is used so that the right group contents are not vertically centered but rather at the top.

notebook elements, used in conjunction with the page element, arre used to describe sets of tabs to better organize your data. Here we want a "main" page to contain a short description of the book and a more complete summary, and another one for less frequently used informations.

More thoughts on views

Tryton's views may take a while to get accustomed to, and feel a little limited at first, but with a little practice it becomes possible to rather quickly design and test views which will "do the job". You may not have pixel control, but it should suit most of your needs anyway.

What may be bothersome is writing all this xml, if you do not already do so, you should consider investigating snippets for your text editor, which will make your life all the easier when creating / editing views.

Note: We only covered the basics of views, there are various options / widgets that tryton supports which may suit some more specific needs. Please read the documentation for more information about those

Be careful when choosing the id of your xml entries. There is usually a "standard" pattern for each model:

  • <model>_view_<view_type> for views
  • act_<model> for actions
  • menu_<model> for entry points etc.

Those are not hard rules, more guidelines that you should follow unless there is a reason that forbid you to (duplicate, readability, etc.)

Regarding the development process, here are the rules:

  • When modifying a XML data file (in our case, library.xml), you MUST update your database and restart the server in order for your modifications to be visible
  • When modifying a view, just restarting the server, and reopening the view should be enough. Reopening means closing all tabs where the view may be displayed, and opening them again. If it does not work, you can try restarting the server

Record name

Something you will have to worry about when writing views is how your records will appear when they are presented as relation. A Char field contents will obviously be its value, but what about a Many2One field ?

Tryton has an internal hidden field named rec_name which stands for "record name", and is used to display the record to the user. By default, the rec_name of a model will be:

  • If the field has field named name, this field's value will be used. So no worries for library.editor, library.genre and library.author, which will be displayed using their names
  • Else, the id field is used, so the displayed value to the user will be an integer

Fortunately, tryton allows to modify this. The easiest way to do so is by setting the _rec_name variable on a model. Add this line right under the __name__ = 'library.book' line:

_rec_name = 'title'

By doing so, you instruct tryton to use the contents of the title field as the rec_name. So from now on, every time a library.book will be displayed as a Many2One to the end user, it will be as the book's title.

Homework

  • Add the missing _rec_name in library.py
  • Add a "Configuration" entry point under the existing "Library" entry point
  • Create the "Editor" and "Genre" entry points under this entry point
  • Add a "Book" entry point below "Authors"
  • Fill up all the views. There is no "better" way, the correction will only be a reasonable version. You may also improve the views we wrote above
  • Try to modify the views with various attributes, to get a feel of how things work. Design the view on paper, then try to make it work. Ask around if you need help on how you can achieve what you want

You can compare your code with that of the step4_homework branch for differences. You can then read here about what you should have done, and why.

What's next

We are starting to have a usable library management module. The next step will be dedicated to function fields, which are critical to make a good tryton application.