The pages given by the previous examples are not integrated into the Odoo website: we have no page footer, menu, and so on. The Odoo website
addon module conveniently provides all these features so that we don't have to worry about them ourselves.
To use it, we should start by installing the website
addon module in our work instance, and then add it as a dependency to our module. The __manifest__.py
key depends
should look like this:
'depends': ['todo_kanban', 'website'],
To use the website, we also need to modify the controller and the template.
The controller needs an additional website=True
argument on the route:
@http.route('/hello', auth='public', website=True)
def hello(self, **kwargs):
return request.render('todo_website.hello')
And the template needs to be inserted inside the website general layout:
<template id="hello" name="Hello World"> <t t-call="website.layout"> <h1>Hello World!</h1> </t> </template>
With this, the Hello World! example we used before should now be shown inside an Odoo website page.
Our website pages might need some additional CSS or JavaScript assets. This aspect of the web pages is managed by the website, so we need a way to tell it to also use our files.
We will add some CSS to add a simple strikeout effect for the done tasks. For that, create the todo_website/static/src/css/index.css
file with this content:
.todo-app-done { text-decoration: line-through; }
Next we need to have it included in the website pages. This is done by adding them in the website.assets_frontend
template responsible for loading website-specific assets. Edit the todo_website/views/todo_templates.xml
data file, to extend that template:
<odoo>
<template id="assets_frontend"
name="todo_website_assets"
inherit_id="website.assets_frontend">
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/css"
href="/todo_website/static/src/css/index.css"/>
</xpath>
</template>
</odoo>
We will soon be using this new todo-app-done
style class. Of course, JavaScript assets can also be added using a similar approach.
Now that we went through the basics, let's work on our Todo Task list. We will have a /todo
URL showing us a web page with a list of Todo Tasks.
For that, we need a controller method, preparing the data to present, and a QWeb template to present that list to the user.
Edit the todo_website/controllers/main.py
file, to add this method:
#class Main(http.Controller): @http.route('/todo', auth='user' , website=True) def index(self, **kwargs): TodoTask = request.env['todo.task'] tasks = TodoTask.search([]) return request.render( 'todo_website.index', {'tasks': tasks})
The controller retrieves the data to be used and makes it available to the rendered template. In this case the controller requires an authenticated session, since the route has the auth='user'
attribute. Even if that is the default value, it's a good practice to explicitly state that a user session is required.
With this, the Todo Task search()
statement will run with the current session user.
The data accessible to public users is very limited, when using that type of route, we often need to use sudo()
to elevate access and make the page data available that otherwise would not be accessible.
This can also be a security risk, so be careful on the validation of the input parameters and on the actions made. Also keep the sudo()
recordset usage limited to the minimum operations possible.
The request.render()
method expects the identifier of the QWeb template to render, and a dictionary with the context available for the template evaluation.
The QWeb template should be added by a data file, and we can add it to the existing todo_website/views/todo_templates.xml
data file:
<template id="index" name="Todo List"> <t t-call="website.layout"> <div id="wrap" class="container"> <h1>Todo Tasks</h1> <!-- List of Tasks --> <t t-foreach="tasks" t-as="task"> <div class="row"> <input type="checkbox" disabled="True" t-att-checked=" 'checked' if task.is_done else {}" /> <a t-attf-href="/todo/{{slug(task)}}"> <span t-field="task.name" t-att-class="'todo-app-done' if task.is_done else ''" /> </a> </div> </t> <!-- Add a new Task --> <div class="row"> <a href="/todo/add" class="btn btn-primary btn-lg"> Add </a> </div> </div> </t> </template>
The preceding code uses the t-foreach
directive to render a list of tasks. The t-att
directive used on the input checkbox allows us to add, or not, a checked
attribute depending on the is_done
value.
We have a checkbox input, and want it to be checked if the task is done. In HTML, a checkbox is checked depending on it having or not a checked
attribute. For this we use the t-att-NAME
directive to dynamically render the checked
attribute depending on an expression. In this case, the expression evaluates to None
, QWeb will omit the attribute, which is convenient for this case.
When rendering the task name, the t-attf
directive is used to dynamically create the URL to open the detail form for each specific task. We used the special function slug()
to generate a human-readable URL for each record. The link won't work for now, since we are still to create the corresponding controller.
On each task we also use the t-att
directive to set the todo-app-done
style only for the tasks that are done.
Finally, we have an Add button to open a page with a form to create a new Todo Task. We will use it to introduce web form handling next.
Each item in the Todo list is a link to a detail page. We should implement a controller for those links, and a QWeb template for their presentation. At this point, this should be a straightforward exercise.
In the todo_website/controllers/main.py
file add the method:
#class Main(http.Controller): @http.route('/todo/<model("todo.task"):task>', website=True) def index(self, task, **kwargs): return http.request.render( 'todo_website.detail', {'task': task})
Notice that the route is using a placeholder with the model("todo.task")
converter, mapping to the task variable. It captures a Task identifier from the URL, either a simple ID number or a slug representation, and converts it into the corresponding browse record object.
And for the QWeb template add following code to the todo_website/views/todo_web.xml
data file:
<template id="detail" name="Todo Task Detail"> <t t-call="website.layout"> <div id="wrap" class="container"> <h1 t-field="task.name" /> <p>Responsible: <span t-field="task.user_id" /></p> <p>Deadline: <span t-field="task.date_deadline" /></p> </div> </t> </template>
Noteworthy here is the usage of the <t t-field>
element. It handles the proper representation of the field value, just like in the backend. It correctly presents date values and many-to-one values, for example.