299 lines
14 KiB
Markdown
299 lines
14 KiB
Markdown
# Build PDF Reports
|
|
|
|
:::{important}
|
|
This tutorial is an extension of the {doc}`server_framework_101` tutorial. Make sure you have
|
|
completed it and use the `estate` module you have built as a base for the exercises in this
|
|
tutorial.
|
|
:::
|
|
|
|
We were previously {doc}`introduced to QWeb <server_framework_101/14_qwebintro>`
|
|
where it was used to build a kanban view. Now we will expand on one of QWeb's
|
|
other main uses: creating PDF reports. A common business requirement is the ability to create documents
|
|
to send to customers and to use internally. These reports can be used to summarize and display
|
|
information in an organized template to support the business in different ways. Odoo
|
|
can additionally add our company's header and footer to our reports with minimal extra effort.
|
|
|
|
The documentation related to this topic can be found in {ref}`reference/qweb`,
|
|
{ref}`reference/reports/report`, and the {ref}`reference/actions/report`
|
|
section of the Actions reference.
|
|
|
|
## File Structure
|
|
|
|
The bulk of a PDF report is its QWeb template. It also typically needs a corresponding
|
|
`ir.actions.report` to include the report within a module's business logic.
|
|
There is no strict rule for the file names or where they are located, but these two parts are
|
|
typically stored in 2 separate files within a `report` folder at the top level of your module's
|
|
directory. If a module has many or multiple long report templates, then they are often organized
|
|
logically across different files named after the report(s) they contain. All actions
|
|
for the reports are usually stored in the same file ending with `_reports.xml`, regardless of the
|
|
number of reports it contains.
|
|
|
|
Therefore, it is expected that your work tree will look something like this:
|
|
|
|
```bash
|
|
estate
|
|
├── models
|
|
│ ├── *.py
|
|
│ └── __init__.py
|
|
├── report
|
|
│ ├── estate_property_templates.xml
|
|
│ └── estate_property_reports.xml
|
|
├── security
|
|
│ └── ir.model.access.csv
|
|
├── views
|
|
│ └── *.xml
|
|
├── __init__.py
|
|
└── __manifest__.py
|
|
```
|
|
|
|
Don't forget to add whatever files your template and action view will be into to your `__manifest__.py`.
|
|
In this case, you will want to add the files to the `data` list and remember that the files listed in a manifest
|
|
are loaded sequentially!
|
|
|
|
## Basic Report
|
|
|
|
:::{note}
|
|
**Goal**: at the end of this section, we will be able to print a report that displays all offers for a
|
|
property.
|
|
|
|
```{image} pdf_reports/simple_report.png
|
|
:align: center
|
|
:alt: Simple PDF report
|
|
```
|
|
:::
|
|
|
|
In our real estate example there are many useful reports that we could create. One simple report we
|
|
can create is one that displays all of a property's offers.
|
|
|
|
### Report Data
|
|
|
|
Before we do anything we first need some data to populate our reports or else this tutorial
|
|
won't be very interesting. When creating reports, you will need some data to test your report code
|
|
and check that the resulting look is as expected. It is a good idea to test with data that will cover most
|
|
or all of your expected use cases. A good representation set for our simple report is:
|
|
|
|
- At least 3 properties where 1 is "sold", 1 is "offer received" and 1 is "new".
|
|
- At least 2-3 offers for our "sold" and "offer received" properties
|
|
|
|
If you don't have a set of data like this already, you can either:
|
|
|
|
- Complete the {doc}`define_module_data` tutorial (if you haven't done so already) and add the extra
|
|
cases to your demo data (you may need to create a new database to load in the demo data).
|
|
- Manually create the data in your database.
|
|
- Copy this [data file](https://github.com/odoo/technical-training-solutions/blob/{BRANCH}-J_reports/estate/data/estate_demo.xml)
|
|
into a new directory (data) in your estate module and copy [these lines](https://github.com/odoo/technical-training-solutions/blob/{BRANCH}-J_reports/estate/__manifest__.py#L21-L23)
|
|
into your \_\_manifest\_\_.py file (you may need to create a new database to load in the demo data).
|
|
|
|
Before continuing, click through your data in your database and make sure your data is as expected.
|
|
Of course you can add the data after you write your report code, but then you will not be able to
|
|
incrementally test portions of your code as you write it. This can make checking for mistakes and
|
|
debugging your code more difficult in the long run for complicated reports.
|
|
|
|
### Minimal Template
|
|
|
|
A minimal viable template is viewable under the "Minimal viable template" section of the
|
|
{ref}`reference/reports/templates` documentation. We can modify this example to build
|
|
our minimal property offers template file:
|
|
|
|
```xml
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<odoo>
|
|
<template id="report_property_offers">
|
|
<t t-foreach="docs" t-as="property">
|
|
<t t-call="web.html_container">
|
|
<t t-call="web.external_layout">
|
|
<div class="page">
|
|
<h2>
|
|
<span t-field="property.name"/>
|
|
</h2>
|
|
<div>
|
|
<strong>Expected Price: </strong>
|
|
<span t-field="property.expected_price"/>
|
|
</div>
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>Price</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<t t-set="offers" t-value="property.mapped('offer_ids')"/>
|
|
<tr t-foreach="offers" t-as="offer">
|
|
<td>
|
|
<span t-field="offer.price"/>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
</t>
|
|
</t>
|
|
</template>
|
|
</odoo>
|
|
```
|
|
|
|
Most of the Odoo specific (i.e. non-HTML) items in our file are explained in the minimal viable template section.
|
|
Some additional features in our template are:
|
|
|
|
- The use of the `class="table"` attribute, so our table has some nice formatting. Twitter Bootstrap
|
|
(we're using its table class in this case), and Font Awesome (useful for adding icons) classes can
|
|
be used in your report template.
|
|
- The use of `t-set`, `t-value`, `t-foreach`, and `t-as` so that we can loop over all the `offer_ids`.
|
|
|
|
If you are already familiar with website templating engines, then the QWeb directives (i.e. the `t-` commands)
|
|
probably don't need much explanation and you can just look at its {ref}`documentation <reference/qweb>` and
|
|
skip ahead to the next subsection.
|
|
|
|
Otherwise you are encouraged to read more about them (
|
|
[Wikipedia](https://en.wikipedia.org/wiki/Template_processor) has a good high level description), but
|
|
the general idea is that QWeb provides the ability to dynamically generate web code based on Odoo data and
|
|
simple commands. I.e. QWeb can access recordset data (and methods) and process simple programming operations
|
|
such as setting and accessing temporary variables. For example, in the above example:
|
|
|
|
- `t-set` creates a temporary variable called "offers" that has its value set by `t-value` to the current
|
|
`estate.property` recordset's `offer_ids`.
|
|
- The `t-foreach` and `t-as` usage is the equivalent to the Python:
|
|
|
|
```Python
|
|
for offer in offers:
|
|
```
|
|
|
|
### Report Action
|
|
|
|
Now that we have a template, we need to make it accessible in our app via a `ir.actions.report`.
|
|
A practical example of `ir.actions.report` is
|
|
[here](https://github.com/odoo/odoo/blob/0e12fa135882cd5095dbf15fe2f64231c6a84336/addons/event/report/event_event_reports.xml#L20-L30)
|
|
corresponding to
|
|
[this template](https://github.com/odoo/odoo/blob/0e12fa135882cd5095dbf15fe2f64231c6a84336/addons/event/report/event_event_templates.xml#L5).
|
|
Its contents are all explained in {ref}`the documentation <reference/actions/report>`.
|
|
|
|
An `ir.actions.report` is primarily used via the Print menu of a model's view. In the practical
|
|
example, the `binding_model_id` specifies which model's views the report should show, and Odoo
|
|
will auto-magically add it for you. Another common use case of the report action is to link it to
|
|
a button as we learned in {doc}`server_framework_101/09_actions`. This is handy for reports
|
|
that only make sense under specific conditions. For example, if we wanted to make a "Final Sale"
|
|
report, then we can link it to a "Print Sale Info" button that appears in the form view only when
|
|
the property is "Sold".
|
|
|
|
```{image} pdf_reports/print_menu.png
|
|
:align: center
|
|
:alt: Print Menu Button
|
|
```
|
|
|
|
You may have noticed or are wondered why our report template loops through a recordset. When our
|
|
template is passed more than one record, it can produce one PDF report for all the records.
|
|
Using the Print menu in the list view with multiple records selected will demonstrate this.
|
|
|
|
### Make a Report
|
|
|
|
Finally, you now know where to create your files and how the content of the files should look. Happy report making!
|
|
|
|
```{eval-rst}
|
|
.. exercise:: Make a report.
|
|
|
|
- Add the property offers report from the minimal template subsection to the Print menu of the Property views.
|
|
|
|
- Improve the report by adding more data. Refer to the **Goal** of this section to see what additional
|
|
data you can add and feel free to add even more.
|
|
|
|
- Bonus: Make an extra flexible report by adding in some logic so that when there are no offers on a property
|
|
then we don't create a table and instead write something about how there are no offers yet. Hint: you will
|
|
need to use ``t-if`` and ``t-else``.
|
|
|
|
Remember to check that your PDF reports match your data as expected.
|
|
|
|
```
|
|
|
|
## Sub-templates
|
|
|
|
:::{note}
|
|
**Goal**: at the end of this section, we will have a sub-template that we use in 2 reports.
|
|
|
|
```{image} pdf_reports/report_subtemplate.png
|
|
:align: center
|
|
:alt: Report using a subtemplate
|
|
```
|
|
:::
|
|
|
|
There are two main reasons for using sub-templates. One is to make the code easier to read when working with
|
|
extra-long or complicated templates. The other is to reuse code where possible. Our simple property offers
|
|
report is useful, but listing property offers information can be useful for more than just one report template.
|
|
One example is a report that lists all of a salesman's properties' offers.
|
|
|
|
See if you can understand how to call a sub-template by reading the
|
|
{ref}`documentation <reference/qweb/sub-templates>` on it and/or by looking at an
|
|
[example](https://github.com/odoo/odoo/blob/0e12fa135882cd5095dbf15fe2f64231c6a84336/addons/portal/static/src/xml/portal_chatter.xml#L147-L160)
|
|
(remember QWeb uses the same control flows regardless if it is for a report or a view in Odoo.)
|
|
|
|
```{eval-rst}
|
|
.. exercise:: Create and use a sub-template.
|
|
|
|
- Split the table portion of the offers into its own template. Remember to check that your
|
|
original report still prints correctly afterwards.
|
|
|
|
- Add a new report for ``res.users`` that allows you to print all of the Real Estate Properties
|
|
that are visible in their form view (i.e. in the "Settings" app). Include the offers for each
|
|
of those saleman's properties in the same report. Hint: since the ``binding_model_id`` in this
|
|
case will not be within the estate module, you will need to use ``ref="base.model_res_users"``.
|
|
|
|
Your end result should look similar to the image in the **Goal** of this section.
|
|
|
|
Remember to check that your reports match your data as expected!
|
|
```
|
|
|
|
## Report Inheritance
|
|
|
|
:::{note}
|
|
**Goal**: at the end of this section, we will inherit the property report in the `estate_account`
|
|
module.
|
|
|
|
```{image} pdf_reports/inherited_report.png
|
|
:align: center
|
|
:alt: An inherited report
|
|
```
|
|
:::
|
|
|
|
Inheritance in QWeb uses the same `xpath` elements as {ref}`views inheritance <reference/view_records/inheritance>`.
|
|
A QWeb template refers to its parent template in a different way though. It is even easier to do by just adding
|
|
the `inherit_id` attribute to the `template` element and setting it equal to the *module.parent_template_id*.
|
|
|
|
We didn't add any new fields to any of the estate models in `estate_account`, but we can still add information
|
|
to our existing property report. For example, we know that any "Sold" properties will already have an invoice
|
|
created for them, so we can add this information to our report.
|
|
|
|
```{eval-rst}
|
|
.. exercise:: Inherit a report.
|
|
|
|
- Extend the property report to include some information about the invoice. You can look at the **Goal** of this
|
|
section for inspiration (i.e. print a line when the property is Done, otherwise print nothing).
|
|
|
|
Again, remember to check that your reports match your data as expected!
|
|
```
|
|
|
|
## Additional Features
|
|
|
|
All the following extra features are described further in the {ref}`reference/reports/report`
|
|
documentation, including how to implement each of them.
|
|
|
|
### Translations
|
|
|
|
We all know Odoo is used in multiple languages thanks to automated and manual translating. QWeb reports are no
|
|
exception! Note that sometimes the translations do not work properly if there are unnecessary spaces in your
|
|
template's text content, so try to avoid them when possible (especially leading spaces).
|
|
|
|
### Reports are web pages
|
|
|
|
You probably are tired of hearing that QWeb creates HTML, but we're saying it again! One of the
|
|
neat features of reports being written in QWeb is they can be viewed within the web browser.
|
|
This can be useful if you want to embed a hyperlink that leads to a specific report. Note that
|
|
the usual security checks will still apply to prevent unauthorized users from accessing the reports.
|
|
|
|
### Barcodes
|
|
|
|
Odoo has a built-in barcode image creator that allows for barcodes to be embedded in your reports.
|
|
Check out the corresponding
|
|
[code](https://github.com/odoo/odoo/blob/0e12fa135882cd5095dbf15fe2f64231c6a84336/addons/web/controllers/main.py#L2044-L2046)
|
|
to see all the supported barcode types.
|
|
|