QWeb is Odoo's primary templating engine which uses an XML-based syntax, making it easy to design layouts with placeholders for data. Within Odoo, QWeb seamlessly integrates with your data models to generate professional-looking PDFs, web pages, and other printable reports.
As a follow-up from the last article - Odoo Development: A Comprehensive Guide, where we've built a Cat Cattery app to manage predigreed kittens for adoption, we'll now create a printable PDF reports for kittens who have been adopted.
Prerequisites:
- Completion of the previous tutorial is not required to follow along, simply grab the starter files here. Otherwise, feel free to adapt the concepts to your own project.
- Odoo reports are just HTML pages that are then converted into PDF files. For this conversion, the wkhtmltopdf, short for Webkit HTML to PDF command-line tool is used. The most up-to-date Odoo information about wkhtmltopdf can be found at the following github repo.
- (Optional) In case you haven't changed the default company's information in Settings | Companies | Update Info, it is up to you to try adding a company's logo, addresses, etc. as these detail will be displayed in our reports. This is what mine looks like:
Great! Now we're good to go:
Step 1: Add Business Reports
One simple yet useful feature for a cat cattery app is to print out a report which shows the cats that have been adopted and how many remains in facility.
To implement this, we need to have report files which are stored in a /report subdirectory. First, create a cat_cattery_kitten_report.xml file inside the subdirectory, and let's not forget to declare it in the __mainifest__.py file.
Now we'll work on modifying this file.
1. Add the Report Action
The report action triggers the execution of a report, similarly to how window actions trigger web client view presentations. A report action is a record in the ir.actions.report XML model, and it can be inspected by using the Settings | Technical | Actions | Reports menu option.
To add the report action and trigger the execution of the report, edit the report/cat_cattery_kitten_report.xml file, as follows:
-
name
is the report action's title. -
model
is the technical name of the report's base model. -
report_type
is the type of document to generate. The options areqweb-pdf
,qweb-html
, orqweb-text
. -
report_name
is the XML ID of the QWeb template to be used to generate the report's content. Unlike other identifier references, it must be a complete reference that includes the module name; that is,<module_name>.<identifier_name>
. -
binding_model_id
is a many-to-one field for identifying the model where the report print option should be available. -
binding_type
should be set toreport
.
This report action makes the report available at the top of the Kittens view, on the Print button, next to the Action button:
[Print Action in box highlight]
2. Set a Paper Layout
Odoo proposes a few page formats out of the box, including European A4 and US Letter. Additional page formats can be added, including those for specific page orientations. The existing formats can be inspected using the Settings | Technical | Reporting | Paper Format menu option.
Let's choose an A4 Lanscape format for the adopted kitten report, by adding the following data record at the beginning of report/cat_cattery_kitten_report.xml file:
This is a copy of the European A4 format, defined by the base module, in the data/report_paperformat_data.xml file, with the orientation changed from portrait to landscape.
To use this paper format, stay in the report/cat_cattery_kitten_report.xml file, and add the paperfomat_id
field to the report action action_breeder_kitten_report
:
Step 2: Design Report Content
Odoo reports are generated using QWeb templates. QWeb generates HTML that can then be converted into a PDF document.
1. Add a Template
To ensure proper page formatting, let's first create a minimum viable template for the Qweb report with a few essential QWeb directives and flow controls.
In the /report subdirectory, add a new file - cat_cattery_kitten_report_templates.xml with the following:
- the
t-call
directives are used to call the standard report structures -web.html_container
andweb.external_layout
- The
web.html_container
template does the basic setup to support an HTML document. - The
web.external_layout
template handles the report header and footer using the corresponding company setup. - The
docs
variable represents the base record set to generate the report, and uses at-foreach
QWeb directive to iterate through each record.
With the basic skeleton for the report in place, it is time to start designing the report content.
2. Add Header Content
This content layout uses the Bootstrap 4 grid system, which was added with the <div class="container">
element.
The previous code adds a header row with column titles. After this, there is a t-foreach
loop to iterate through each record and render a row for each in the report content.
3. Add Report Content
Unlike Kanban views, the report QWeb templates are rendered on the server side and use the Python QWeb implementation. So, there are some differences to be aware of, compared to the JavaScript QWeb implementation.
Rows are added using a <div class="row">
element. A row contains cells, and each cell can span several columns so that the row takes up 12 columns. Each cell is added using a <div class="col-N">
element, where N is the number of columns it spans.
- the
t-field
attributes are being used to render field data. - The dot notation can be used to access fields from related data records. For example,
o.name
gets the value of the name field from the o record. -
t-options
is set with a dictionary-like data structure. The widget key can be used to represent the field data. i.e."widget": 'image'
to display theimage
field.
4. Add footer Content
It is common to have a final row which display certain summary statistics in business reports. For demonstration, let's have our report displaying the numbers of Total, Available, and Adopted kittens.
- The
t-out
can render the result of a (Python) code expression as an HTML-escaped value. - The
len()
Python function is used to count the number of elements in a collection.
Well done! Now, to see the report, in Cat Cattery | Kittens, select all kitten records, next to the Action button, you will see a Print button appears. Click on the button and you'll have a simple report that looks like this:
Step 3: Create Custom Reports
A report can not only be rendered using data available in the selected records, but also arbitrary data that's been prepared by specific business logic, this is all possible as custom reports.
A custom report example for our Cat Cattery app could be an Adopter report, where for each user, the report shows a list of kittens who have been adopted by this user.
Prior to creating the custom report, a few changes need to be made in the cat_cattery.kitten
model. In the models/kitten.py file:
First, add a new Relational Field:
-
adopter_id
is a many-to-one relational field linked to theres.parter
model
Second, to ensure only adopted
kittens can have their adopters, we need to create a Constraint logic that will raises an error message when there is an invalid entry to the field adopter_id
. Again, in the models/kitten.py file:
- The
ValidationError
object is used to report validation issues to the user. - The decorator
constains()
is invoked on records where the specification of theadopter_id
field's constraint (which depends on the value ofstate
) is not met and raises error.
Don't forget to make this new field available in the Form view, modify the form view in views/cat_cattery_kitten_views.xml file by adding this line:
And lastly, we need to register the adopters of our adopted kittens. For this example, our 3 fluffy kittens: Floki, Mango, and Saline will be fostered by an user in the res.partner
model named Addison Olson:
Now our data models are ready, let's move on the custom report!
1. Prepare Custom Report Data
The custom report will be available in the Contacts app where one or more partners can be selected, and the report will present the kittens adopted by each. Go to Apps and activate the module.
A custom report can add whatever data that's needed to the report rendering context. This is done using the AbstractModel
object which has no database representation and holds no data, followed by a naming convention of report.<module>.<report-name>
where a method - _get_report_values()
that returns a dictionary with the variables to add to the rendering context is implemented.
To do so, create a report/cat_cattery_adopter_report.py file and add the following:
- The abstract model's name following the convention is
report.cat_breeder.adopter_report
(thus the report identifier name will beadopter_report)
- The
_get_report_values()
method is decorated with theapi.model
- The
docids
argument is a list of the numeric IDs selected to print the report. The base model to run the report isres.partner
, so these will be partner IDs. - The business logic finds the adopted kittens by the adopters and group them by their new owners. The result is a list of tuples -
adopter_kittens
- The method returns a dictionary as the data structure which can be iterated in a loop for report template rendering.
For this file to be loaded by the module, it is also necessary to do the following:
- Add to the report/__init__.py file with a
from . import breeder_adopter_report
line.
- Add a
from . import report
line to the __init__.py top file.
2. Add the Report Template
Similar to regular reports, the next step is to create the QWeb template that's used to render the report.
We'll create an XML file that defines a report action and a report template. The template will have context variables available as whatever key/values are returned by the _get_report_values
method.
In the report subdirectory, make a breeder_adopter_report.py file and add to it:
The above XML includes two records – one for adding the Kittens by Adopter
report action and another for adding the adopter_report
template.
When running this report, the report engine will try finding a report.cat_breeder.adopter_report
model. If it exists, as is the case here, the _get_report_values()
method is used to add variables to the rendering context.
The QWeb template can then use the adopter_kittens
variable to access the added data. It is a list containing a tuple for each publisher. The first tuple element, group[0]
, is the adopter's Contact record that's used on the group header, while the second tuple element, group[1], is the record set containing the adopted kittens, presented using a second for loop.
To print the report, on Menu, go to Contacts, select the record of Addison Olson, then click on Print, select the report "Kittens by Adopter" that we've created, a PDF file will be downloaded.
And this is what the custom report looks like:
Hooray!
This tutorial provided you with the tools and techniques to implement printable reports which often can be important parts of a business application. Now, you can ensure that your business application doesn't fall short of your user's needs.
The source code of this tutorial is here.