How To Make a Custom PDF Data Report in Odoo 17/18 Using AbstractModel

February 20, 2026 by
How To Make a Custom PDF Data Report in Odoo 17/18 Using AbstractModel
Fazri Muhammad Yazid
| No comments yet

This approach separates business logic from the QWeb template by using models.AbstractModel.

Use this method when:

  • The report requires complex calculations
  • Multiple models must be combined
  • Aggregation or restructuring of data is required
  • You want a clean and scalable architecture

Module used in this example: custom_sgeede.


1. Report Action

File: report/sale_custom_report.xml

<odoo>
​<record id="action_report_custom_sale_order" model="ir.actions.report">
​<field name="name">Custom Sales Order PDF (Abstract)</field>
​<field name="model">sale.order</field>
​<field name="report_type">qweb-pdf</field>
​<field name="report_name">custom_sgeede.custom_sale_order_report</field>
​<field name="report_file">custom_sgeede.custom_sale_order_report</field>
​<field name="print_report_name">
​(object.name or 'SO') + ' - Custom Abstract.pdf'
​​</field>
​<field name="binding_model_id" ref="sale.model_sale_order"/>
​​<field name="binding_type">report</field>
​</record>
</odoo>

The structure remains the same as the basic implementation. The key difference is in the Python report class.


2. Create the Abstract Report Model

File: report/sale_custom_report.py

from odoo import models, api
​class ReportSimpleSale(models.AbstractModel):
​_name = 'report.custom_sgeede.simple_sale_report'
​_description = 'Sale Order Report'

​@api.model
​def _get_report_values(self, docids, data=None):
​orders = self.env['sale.order'].browse(docids)
​docs = []

​for order in orders:
​docs.append({
​'name': order.name,
​'customer': order.partner_id.name,
​'total': order.amount_total,
​})
​return {
​'doc_ids': docids,
​'doc_model': 'sale.order',
​'docs': docs,
​}

Critical structure rules:

  • _name must follow this format:
	​report.<module_name>.<report_name>

  • _get_report_values() is the entry point for QWeb.
  • docs is no longer a recordset. It is a list of dictionaries.
  • All data processing is handled in Python, not inside QWeb.


3. QWeb Template Using AbstractModel ​  Data

File: report/sale_custom_report.xml

<odoo>
    <template id="simple_sale_report">
        <t t-call="web.external_layout">
            <div class="page">
                <t t-foreach="docs" t-as="o">
                    <h2>Sales Order</h2>
                    <p>
                        <strong>Order:</strong>
                        <t t-esc="o['name']"/>
                    </p>
                    <p>
                        <strong>Customer:</strong>
                        <t t-esc="o['customer']"/>
                    </p>
                    <p>
                        <strong>Total:</strong>
                        <t t-esc="o['total']"/>
                    </p>
                    <div style="page-break-after: always;"/>
                </t>
            </div>
        </t>
    </template>
</odoo>

Core difference from the non-AbstractModel approach:

Without AbstractModel:

  • QWeb directly accesses sale.order recordsets.
  • Business logic is mixed inside the template.

With AbstractModel:

  • QWeb receives structured, ready-to-render data.
  • No calculations inside the template.
  • Clean separation of concerns.
  • Easier refactoring.
  • More testable Python logic.


5. When You Should Use AbstractModel

Use AbstractModel when:

  • The report combines 2 or more different models
  • Advanced grouping is required
  • Manual currency conversions are needed
  • Multi-company logic must be handled
  • The report structure differs from the database structure
  • Data originates from a wizard

In large Odoo projects, AbstractModel is the correct architectural pattern.

How To Make a Custom PDF Data Report in Odoo 17/18 Using AbstractModel
Fazri Muhammad Yazid February 20, 2026
Share this post
Archive
Sign in to leave a comment