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.