We'll add website capabilities to the library addon developed in Chapter 4, Application models, and following. What we're interested in is allowing users to browse through the library and, if they are logged in with the appropriate permissions, enable them to edit book content right from the website interface.
As we make use of the library.book
model, get chapter 4's code for my_module
. For convenience, this recipe's code contains a copy of it.
We'll need to define a couple of controllers and views:
views/library_book_templates.xml
.<odoo> <template id="books"> <t t-call="website.layout"> <section> This is an editable text before the list of books. </section> <t t-foreach="books" t-as="book"> <article itemscope="itemscope" itemtype="http://schema.org/Book" t-attf-class="row book-#{book_parity}"> <h2 t-field="book.name" class="col-md-12" /> <t t-if="book.date_release"> <div class="col-md-2" t-att-dateCreated="book.date_release" t-field="book.date_release" /> </t> <ul class="col-md-10"> <li t-foreach="book.author_ids" t-as="author" itemprop="author"> <t t-esc="author.name" /> </li> </ul> </article> </t> <section contenteditable="False"> This is a non-editable text after the list of books. </section> </t> </template> </odoo>
controllers/main.py
.:@http.route('/books', type='http', auth="user", website=True) def route(self): return request.render( 'ch13_r02.books', { 'books': request.env['library.book'].search([]), })
With this code in place, users can query existing books and see their details. Given appropriate permissions, users will also be offered to change book details and a couple of other texts.
First, we create a template called books
that is used to generate the HTML necessary to display a list of books. All of the code is wrapped in a t
element with the t-call attribute set, which makes Odoo render the website.layout template and insert our content at the place the called template has designated for it. This way, we get a full Odoo web page with the menu, footer, and so on, without having to repeat any code. If you leave out this call, you'll have to generate this or similar code yourself.
For working on record sets, you need a construct to loop through lists. Have a look at the inside of the t-call
element, where the actual content generation happens. The template expects to be rendered with a context that has a variable called books
set and iterates through it in the t-foreach element. Iteration can happen in a t element, in which case its contents are repeated for every member of the iterable that was passed in the t-foreach
attribute. You can also place a t-foreach
and t-as attribute in some arbitrary element; then this element and its contents will be repeated for every item in the iterable. The t-as
attribute is mandatory and will be used as the name of the iterator variable to use for accessing the iterated data. While the most common use for this construction is to iterate over record sets, you can use it on any Python object that is iterable.
We use as many of the semantic HTML5 elements as possible, which is why the actual data is wrapped in an article
tag. For machine readability, some item*
properties are attached. You can read more about this in this recipe's See also section.
Now focus on the t-attf-class attribute here. This will be evaluated by QWeb to be the attribute class
with the contents evaluated as a format string. That is, the string row book-
is passed as it is and the contents of the #{...}
snippet is evaluated as Python code. This is different from t-att-dateCreated (note the missing 'f', the first is an attribute format string, the latter an evaluation) used below, where the whole content is evaluated as Python code. Both forms just pass the part of their name after the second dash as the name of the attribute to be constructed.
It is pretty much up to you when to choose a t-attf-* construction and when to use t-att-*, the rule of thumb is to use a format string if the result contains a lot of string literals anyway and an evaluation otherwise.
The h2
and div
tags that follow use the t-field attribute. This attribute needs to be passed a field within a record set with length one and transparently allows the user to change the content displayed when the website's edit mode is activated. Of course, this is subject to a permission check and only allowed if the current user has write permissions on the displayed record. With an optional t-field-options attribute, you can give a dictionary of options to be passed to the field renderer, including the widget to be used. Currently, there's no vast amount of widgets for the backend, so the choices are a bit limited here.
Note that the division showing the publication date is wrapped by a t
element with the t-if
attribute set. This attribute is evaluated as Python code and the element only rendered if the result is truthy . In the example, we only show the div
class if there is actually a publication date set.
Changes made by a user will either be propagated to the connected database record if the change happened within a t-field
node, or to the view in question if it is some other node marked as editable. Content nodes like section
elements don't need to be marked as editable, they are editable by default. To turn an editable element read-only, use the attribute contenteditable=False
.
Note that editing a view via the website editor sets the noupdate flag on this view. This means that subsequent code changes will never make it into your customer's database. In order to also get the ease of use of inline editing and the possibility of updating your HTML code in subsequent releases, create one view that contains the semantic HTML elements and a second one that injects editable elements. Then only the latter view will be noupdate and you can still change the former.
For the CSS classes used here, consult bootstrap's documentation, as linked in this recipe's See also section.
The controller in step 2 just renders the template, please refer to the Chapter 13, Web Server Development, recipe Modify an existing handler for details.
Given the way t-call
elements are evaluated, you can use t-set
elements to pass information to the template you call. This can be very useful if you have some generalized templates like
website.layout or report.external_layout, which display data you want to see almost always. Adapt them not to print certain elements if some variable is set and set it via a t-set
element on the page or report where you want to suppress the element in question. This helps to avoid a lot of code duplication.
Within t-foreach
loops, you've got access to a couple of variables whose names are derived from the accompanying t-as
attribute. As it is book
in the example above, we have access to the variable book_parity
which contains the value even for even indices while iterating and odd for odd ones. In this example, we use this to be able to have alternating background colors in our list. Other interesting variables in this case would be book_index
, which returns the current (zero-based) index in the iteration, book_first
and book_last,
which are True
if this is the first or last iteration respectively, and book_value,
which would contain the item's value if the book variable we iterate over were a dictionary; in this case, book
would iterate through the dictionary's keys.
The template element is a shorthand for a record element that sets some properties on the record for you. While there's never a reason not to use the convenience of the template element, you should know what happens under the hood: the element creates a record of the model ir.ui.view with type qweb
. Then, depending on the template
element's name and inherit_id
attributes, the inherit_id
field on the view record will be set. In case the template has the attribute primary
set to True
, the view record's mode
field will be set to primary
. Finally, an arch
field is crafted with the correct root element (t for non-inheriting views, data
for inheriting views) and a t-name
attribute is set for primary views.
Keep in mind that QWeb is a perfectly generic templating engine, so whenever you need to generate content in an Odoo context, use a Qweb view that you render by calling env['ir.ui.view'].render('xmlid', parameters)
. Especially for generating XML documents, this is convenient, but you can generate every other kind of text too.
For a more in-depth discussion of controllers, see the Chapter 13, Web Server Development, recipes Make a path accessible from the network and Restrict access to web accessible paths.
For details on view inheritance, see the Chapter 8, Backend Views, recipe Changing existing views: View inheritance.
The code generated here makes use of microdata (https://html.spec.whatwg.org/multipage/microdata.html) using definitions from http://schema.org – it is advised you do the same for your publicly accessible websites in order to simplify reading your pages for machines. This also makes your content more accessible for search engines.
Odoo as a whole makes extensive use of bootstrap (http://getbootstrap.com), which you should use to get adaptive designs without much effort.