Skip to main content

The QWeb template language

The QWeb template language

The QWeb parser looks for special directives in the templates and replaces them with dynamically generated HTML. These directives are XML element attributes and can be used in any valid tag or element, such as <div>, <span>, or <field>.
Sometimes, we may want to use a QWeb directive but we don't want to place it in any of the XML elements in our template. For those cases, we have a <t> special element that can have QWeb directives, such as t-if or t-foreach, but is silent and won't have any output on the final XML/HTML produced.
The QWeb directives will frequently make use of evaluated expressions to produce different results, depending on the current record values. There are two different QWeb implementations: client-side JavaScript and server-side Python.
The reports and website pages use the server-side Python implementation of QWeb.
Kanban views use the client-side JavaScript implementation. This means that the QWeb expression used in Kanban views should be written using the JavaScript syntax, not Python.
When displaying a Kanban view, the internal steps are roughly as follows:
  1. Get the XML for the templates to render.
  2. Call the server read() method to get the data for the fields mentioned in the templates.
  3. Locate the kanban-box template and parse it using QWeb to output the final HTML fragments.
  4. Inject the HTML in the browser display (the DOM).
This is not meant to be technically exact. It's just a mind map that can be useful to understand how things work in Kanban views.

Next, we'll learn about QWeb expression evaluation and explore the available QWeb directives, using examples that enhance the checkout Kanban card.

The QWeb JavaScript evaluation context

Many of the QWeb directives use expressions that are evaluated to produce some result. When used from the client side, as is the case for Kanban views, these expressions are written in JavaScript. They're evaluated in a context that has a few useful variables available.
A record object is available, representing the current record, with the fields requested from the server. The field values can be accessed using either the raw_value or value attributes:
  • raw_value is the value returned by the read() server method, so it's more suitable for use in condition expressions.
  • value is formatted according to the user settings and is meant to be used for display in the user interface. This is typically relevant for date/datetime, float/monetary, and relational fields.
The QWeb evaluation context also has references available for the JavaScript web client instance. To make use of them, a good understanding of the web client architecture is needed, but we won't be able to go into that in detail. For reference purposes, the following identifiers are available in QWeb expression evaluation:
  • widget is a reference to the current KanbanRecord() widget object, responsible for the rendering of the current record into a Kanban card. It exposes some helper functions we can use.
  • record is a shortcut for widget.record and provides access to the fields available, using dot notation.
  • read_only_mode indicates whether the current view is in read mode (and not in edit mode). It's a shortcut for widget.view.options.read_only_mode.
  • instance is a reference to the full web client instance.


It's also noteworthy that some characters are not allowed inside expressions. The lower than sign (<) is such a case. This is because of the XML standard, where such characters have special meaning and shouldn't be used on the XML content. A negated >= operator is a valid alternative, but the common practice is to use the following alternative symbols that are available for inequality operations:
  • lt is for less than.
  • lte is for less than or equal to.
  • gt is for greater than.
  • gte is for greater than or equal to.

Note

The preceding comparison symbols are specific to Odoo and were introduced to overcome limitations in the XML format. They are not part of the XML standard.

Dynamic attributes by string substitution – t-attf

Our Kanban card is using the t-attf QWeb directive to dynamically set a class on the top <div> element so that the card is colored depending on the color field value. For this, the t-attf- QWeb directive was used.
The t-attf- directive dynamically generates tag attributes using string substitution. This allows for parts of larger strings generated dynamically, such as a URL address or CSS class names.
The directive looks for expression blocks that will be evaluated and replaced by the results. These are delimited either by {{ and }} or by #{ and }. The content of the blocks can be any valid JavaScript expression and can use any of the variables available for QWeb expressions, such as record and widget.
In our case, we also used the kanban_color() JavaScript function, specially provided to map color index numbers into the CSS class color names.


As an elaborate example, we'll use this directive to dynamically change the color of the user, to be in red font if the priority is high. For this, replace <field name="user_id"/> in our Kanban card with the following:
<li t-attf-class="oe_kanban_text_{{ 
  record.user_id.raw_value lt '2' 
  ? 'black' : 'red' }}"> 
  <field name="user_id"/> 
</li> 
This results in either class="oe_kanban_text_red" or class="oe_kanban_text_black", depending on the checkout's priority value. Please note that, while the oe_kanban_text_red CSS class is available in Kanban views, the oe_kanban_text_black CSS class does not exist and was used to explain the point.

Note

Notice the lt symbol used in the JavaScript expression. It's an escape expression for the < sign, not allowed in XML.

Dynamic attributes by expressions – t-att

The t-att- QWeb directive dynamically generates an attribute value by evaluating an expression.
Our Kanban card uses it to dynamically set some attributes on the <img> tag; the title attribute is dynamically rendered using the following:
t-att-title="record.member_id.value"
The  .value field returns its value representation as it should be shown on the screen. For many-to-one fields, this is usually the related record's name value. For users, this is the username. As a result, when hovering the mouse pointer over the image, you will see the corresponding username.
When the expression evaluates to a false equivalent value, the attribute is not rendered at all. This is important for special HTML attributes such as the  checked input field, which can have an effect even without an attribute value.

Loops – t-foreach

A block of HTML can be repeated by iterating through a loop. We can use it to add the avatars of the record followers.
Let's start by rendering just the partner IDs of the record, as follows:
<t t-foreach="record.message_partner_ids.raw_value" t-as="rec"> 
  <t t-esc="rec" />; 
</t> 
The t-foreach directive accepts a JavaScript expression evaluating to a collection to iterate. In most cases, this will be just the name of a to-many relation field. It's used with a t-as directive to set the name to be used to refer to each item in the iteration.
The t-esc directive used next evaluates the provided expression, just the rec variable name in this case, and renders it as safely escaped HTML.
In the previous example, we loop through the followers stored in the message_parter_ids field. Since there is limited space on the Kanban card, we could have used the slice() JavaScript function to limit the number of followers to display, as shown in the following:
t-foreach="record.message_partner_ids.raw_value.slice(0, 3)" 
The rec variable holds each iteration value, a partner ID in this case. With this, we can rewrite the follower loop as follows:
<t t-foreach="record.message_parter_ids.raw_value.slice(0, 3)" 
  t-as="rec"> 
  <img t-att-src="kanban_image('res.partner', 'image_small', rec)" 
    class="oe_avatar" width="24" height="24" /> 
</t> 
For example, this could be added next to the responsible user image, in the right-hand footer.
A few helper variables are also available. Their name has the variable name defined in t-as as a prefix. In our example, we used rec, so the helper variables available are as follows:
  • rec_index is the iteration index, starting from zero
  • rec_size is the number of elements of the collection
  • rec_first is true on the first element of the iteration
  • rec_last is true on the last element of the iteration
  • rec_even is true on even indexes
  • rec_odd is true on odd indexes
  • rec_parity is either odd or even, depending on the current index
  • rec_all represents the object being iterated over
  • rec_value, when iterating through a {key:value} dictionary, holds the value (rec holds the key name)
For example, we could make use of the following to avoid a trailing comma on our ID list:
<t t-foreach="record.message_parter_ids.raw_value.slice(0, 3)" 
  t-as="rec"> 
  <t t-esc="rec" />
  <t t-if="!rec_last">;</t> 
</t> 

Conditionals – t-if

Our Kanban view used the t-if directive in the card option menu to make some options available depending on some conditions. The t-if directive expects an expression to be evaluated in JavaScript when rendering Kanban views on the client side. The tag and its content will be rendered only if the condition evaluates to true.
As an example, to only display the checkout's number of books borrowed if it has a value, add the following after the request_date field:
<t t-if="record.num_books.raw_value gt 0"> 
  <li> <field name="num_books"/> books</li> 
</t> 
We used a <t t-if="..."> element so that if the condition is false, the element produces no output. If it's true, only the contained <li> element is rendered to the output. Notice that the condition expression used the gt symbol instead of > to represent the greater than operator.
The else if and else conditions are also supported with the t-elif and t-else directives. Here is an example of their usage:
<t t-if="record.num_books.raw_value == 0"> 
  <li>No books.</li> 
</t>
<t t-elif="record.num_books.raw_value gt 9"> 
  <li>A lot of books!</li> 
</t> 
<t t-else=""> 
  <li> <field name="num_books"/> books.</li> 
</t>
In Javascript expressions, the AND and OR operators are && and ||. But the ampersand symbol is not allowed in XML. We can work around this using the and and or operators.

Rendering values – t-esc and t-raw

We used the <field> element to render the field content. But field values can also be presented directly without a <field> tag.
The t-esc directive evaluates an expression and renders it as an HTML-escaped value, as shown in the following:
<t t-esc="record.message_parter_ids.raw_value" />
In some cases, and if the source data is guaranteed to be safe, t-raw can be used to render the field raw value without any escaping, as shown in the following example:
<t t-raw="record.message_parter_ids.raw_value" />

Note

For security reasons, it's important to avoid using t-raw as much as possible. Its usage should be strictly reserved for outputting HTML data that was specifically prepared without any user data in it or where any user data was escaped explicitly for HTML special characters.

Set values on variables – t-set

For more complex logic, we can store the result of an expression into a variable to use it later in the template. This is to be done using the t-set directive, naming the variable to set followed by the t-value directive, with the expression calculating the value to assign.
As an example, the following code renders missed deadlines in red, just as in the previous section, but uses a red_or_black variable for the CSS class to use, as shown in the following:
<t t-set="red_or_black"
   t-value="
     record.priority.raw_value gte '2' 
     ? 'oe_kanban_text_red' : ''" /> 
<li t-att-class="red_or_black"> 
  <field name="user_id" /> 
</li> 
Variables can also be assigned HTML content, as in the following example:
<t t-set="calendar_sign"> 
  <i class="fa fa-calendar"/> 
</t> 
<t t-raw="calendar_sign" />

Call and reuse other templates – t-call

QWeb templates can be reusable HTML snippets that can be inserted into other templates. Instead of repeating the same HTML blocks over and over again, we can design building blocks to compose more complex user interface views.
Reusable templates are defined inside the <templates> tag and identified by a top element with a t-nameother than kanban-box. These other templates can then be included using the t-call directive. This is true for the templates declared in the same Kanban view, somewhere else in the same add-on module or in a different add-on.
The follower avatar list is something that could be isolated in a reusable snippet. Let's rework it to use a sub-template. We should start by adding another template to our XML file, inside the <templates> element, after the <t t-name="kanban-box"> node, as shown in the following:
<t t-name="follower_avatars">   <div> 
    <t t-foreach="record.message_parter_ids.raw_value.slice(0, 3)" 
      t-as="rec"> 
      <img t-att-src="kanban_image(
        'res.partner', 'image_small', rec)" 
        class="oe_avatar" width="24" height="24" /> 
    </t> 
  </div> 
</t>


Calling it from the kanban-box main template is quite straightforward. Instead of the <div> element containing the for each directive, we should use the following:
<t t-call="follower_avatars" /> 
To call templates defined in other add-on modules, we need to use the module.name full identifier, as we do with the other views. For instance, this snippet can be referred using the library_checkout.follower_avatars full identifier.
The called template runs in the same context as the caller, so any variable names available in the caller are also available when processing the called template.
A more elegant alternative is to pass arguments to the called template. This is done by setting variables inside the t-call tag. These will be evaluated and made available in the sub-template context only and won't exist in the caller context.
We could use this to have the maximum number of follower avatars set by the caller instead of being hardcoded in the sub-template. First, we need to replace the fixed value, 3, with a variable, arg_max, example:
<t t-name="follower_avatars"> 
  <div> 
    <t t-foreach="record.message_parter_ids.raw_value.slice(0, arg_max)"
      t-as="rec"> 
      <img t-att-src="kanban_image('res.partner', 'image_small', rec)" 
        class="oe_avatar" width="24" height="24" /> 
    </t> 
  </div> 
</t> 
Then, define that variable's value when performing the sub-template call as follows:
<t t-call="follower_avatars"> 
  <t t-set="arg_max" t-value="3" /> 
</t> 
The entire content inside the t-call element is also available to the sub-template through the  0 magic variable. Instead of argument variables, we can define an HTML code fragment that can be used in the sub-template with <t t-raw="0" />. This is especially useful for building layouts and combining/nesting QWeb templates in a modular way.

Dynamic attributes using dictionaries and lists

We've gone through the most important QWeb directives, but there are a few more we should be aware of. Let's look at a short explanation of them.
We have seen the t-att-NAME and t-attf-NAME style dynamic tag attributes. Additionally, the fixed t-attdirective can be used. It accepts either a key-value dictionary mapping or a pair (a two-element list).
Use the following mapping:
<p t-att="{'class': 'oe_bold', 'name': 'Hello'}" /> 
This results in the following:
<p class="oe_bold" name="Hello" /> 
Use the following pair:
<p t-att="['class', 'oe_bold']" /> 
This results in the following:
<p class="oe_bold" /> 

Comments

Post a Comment

Popular posts from this blog

The message and activity features

The message and activity features Odoo has available global  messaging  and activity planning features, provided by the  Discuss  application, with technical name mail. The mail module provides the  mail.thread  abstract class that makes it simple to add the messaging features to any model, and the  mail.activity.mixin  that adds planned activity features. This was done in  Chapter 4 ,  Extending Modules , to explain how to inherit features from mixin abstract classes. To add these features, we need to add the mail dependency to the add-on module,  library_checkout , and then have the library checkout model class inherit from the abstract classes providing the following features. Edit the  'depends'  key in the  library_checkout/__manifest__.py   file, to add the mail module, shown as follows: Copy 'depends': ['library_member' , 'mail' ], And edit the  library_checkout/m...

Setting up an nginx reverse proxy

Setting up an nginx reverse proxy While Odoo itself can serve web pages, it's strongly recommended that there is a  reverse  proxy positioned in front of it. A reverse proxy acts as an intermediary that manages the traffic between clients sending requests and the Odoo servers responding to them. Using a reverse proxy has several benefits. On the security side, it can do the following: Handle (and enforce) HTTPS protocols to encrypt traffic Hide the internal network characteristics Act as an application firewall, limiting the URLs accepted for processing Also, on the performance side, it can provide the following significant improvements: Cached static content, hence reducing the load on the Odoo servers Compressed content to speed up loading time Act as a load balancer, distributing load between several servers Apache is a popular choice when considering a reverse proxy, although  nginx  is a recent alternative with good technical argumen...