How to Implement XLSX Report in Odoo?


✅ Why Use XLSX Reports?

While QWeb PDF is great for print-ready documents, Excel (XLSX) is preferred when:

  • Users want to manipulate data
  • Reports involve calculations, filters, or pivot tables
  • Bulk downloads (like inventory, sales, or attendance summaries) are needed

Odoo provides no native XLSX engine, so we use Python’s xlsxwriter library, often in-memory using io.BytesIO.


🔧 Key Libraries Used

import io

import xlsxwriter

from odoo.http import request, content_disposition

  • xlsxwriter: Generates the Excel content
  • io.BytesIO: Keeps the file in memory (no disk I/O)
  • content_disposition: Used for HTTP download headers


📘 Example Use Case: Export Sale Order Lines

Let’s say you have a wizard that exports selected sale orders’ line items to Excel.


1. Add a Button in Wizard​


def action_export_xlsx(self):

    return {

        'type': 'ir.actions.report',

        'report_type': 'xlsx',

        'data': {'model': 'sale.order', 'options': self.ids}

    }



2. Create Controller for XLSX Report


from odoo import http

from odoo.http import request, content_disposition

import io

import xlsxwriter


class XLSXReportController(http.Controller):


    @http.route('/report/sale_order_xlsx', type='http', auth='user')

    def sale_order_xlsx(self, **kwargs):

        buffer = io.BytesIO()

        workbook = xlsxwriter.Workbook(buffer, {'in_memory': True})

        worksheet = workbook.add_worksheet('Orders')


        # Add header

        headers = ['Order', 'Customer', 'Product', 'Qty', 'Price']

        for col, name in enumerate(headers):

            worksheet.write(0, col, name)


        # Fetch and write data

        row = 1

        orders = request.env['sale.order'].search([], limit=50)

        for order in orders:

            for line in order.order_line:

                worksheet.write(row, 0, order.name)

                worksheet.write(row, 1, order.partner_id.name)

                worksheet.write(row, 2, line.product_id.name)

                worksheet.write(row, 3, line.product_uom_qty)

                worksheet.write(row, 4, line.price_unit)

                row += 1


        workbook.close()

        buffer.seek(0)


        return request.make_response(

            buffer.read(),

            headers=[

                ('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'),

                ('Content-Disposition', content_disposition('sale_orders.xlsx'))

            ]

        )



📌 Key Features in This Approach


Feature

Description

In-memory

No disk writes, faster and safer

Headers

Set manually using worksheet.write()

Styles

workbook.add_format({...}) to format fonts, numbers, borders

Download-ready

Delivered as HTTP response with correct headers



✅ Summary Table


Topic

Description

Report Format

.xlsx

Library

xlsxwriter

Storage

In-memory via io.BytesIO

Use Case

Custom data export, analytical reporting

Route Type

type='http', auth='user'

Alternative

Use ir.actions.report (PDF/QWeb) for printable reports


🧠 Best Practices

  • Use field names as column headers for dynamic sheets.
  • Use formats for currency, bold headers, or alignment.
  • Handle large data exports in batches (with pagination or generators).
  • Secure route with auth='user' or tokens if public.