Enable Dark Mode!
how-to-create-a-custom-xlsx-export-from-list-view-in-odoo-19.jpg
By: Rahna Rasheed

How to Create a Custom XLSX Export from List View in Odoo 19

Technical Odoo 19 Odoo Enterprises Odoo Community

Odoo's standard export features are strong; they can fall short of meeting certain reporting needs, like complex data aggregation, custom formatting, or column names. This blog will discuss how to use OWL (Odoo Web Library) and a Python backend controller to add a specific download button to the List View in order to develop a custom Excel (XLSX) export capability in Odoo 19.

Step 1: Extending the List View Controller

To build our own export logic, we must first extend the standard ListController. The file download from our custom route will be initiated by the Odoo web core's download service.

Create a file at static/src/js/daily_timesheet_list.js:

How to Create a Custom XLSX Export from List View in Odoo 19-cybrosys
/** @odoo-module **/
import { ListController } from "@web/views/list/list_controller";
import { listView } from "@web/views/list/list_view";
import { registry } from "@web/core/registry";
import { download } from "@web/core/network/download";
export class DailyTimesheetListController extends ListController {
   async onDirectExportData() {
       const payload = {
           domain: this.model.root.domain,
       };
       if (!this.model.root.isDomainSelected && this.model.root.selection.length > 0) {
           const selectedIds = this.model.root.selection.map((r) => r.resId);
           payload.ids = selectedIds;
       }
       await download({
           data: {
               data: JSON.stringify(payload),
           },
           url: `/daily_timesheet/export_xlsx`,
       });
   }
}
DailyTimesheetListController.template = "daily_timesheet.ListView";
export const dailyTimesheetListView = {
   ...listView,
   Controller: DailyTimesheetListController,
};
registry.category("views").add("daily_timesheet_list", dailyTimesheetListView);

Step 2: Adding the Download Button to the UI

Next, we define the XML template to place our button in the Control Panel's additional actions area.

Create a file at static/src/xml/daily_timesheet_list.xml:

How to Create a Custom XLSX Export from List View in Odoo 19-cybrosys
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
   <t t-name="daily_timesheet.ListView" t-inherit="web.ListView" t-inherit-mode="primary">
       <xpath expr="//t[@t-set-slot='control-panel-additional-actions']" position="inside">
           <t t-if="props.context.from_daily_timesheet">
               <button type="button" class="btn btn-light o_button_export ms-1" style="font-size: 16px; padding: 4px 8px;"
                       t-on-click="onDirectExportData" title="Download">
                   <i class="fa fa-download"/>
               </button>
           </t>
       </xpath>
   </t>
</templates>

Step 3: Creating the Python Export Controller

After receiving the domain or chosen IDs, the backend controller will retrieve the data and create the prepared Excel file using the xlsxwriter library.

Create a file at controllers/daily_timesheet.py:

class DailyTimesheetExportController(http.Controller):
   @http.route('/daily_timesheet/export_xlsx', type='http', auth='user', methods=['POST'])
   def export_xlsx(self, data, **kwargs):
       params = json.loads(data)
       domain = params.get('domain', [])
       ids = params.get('ids', None)
       model = request.env['timesheets.analysis.report']
       if ids:
           domain = [('id', 'in', ids)]
       records = model.search(domain, order='date asc, employee_id asc')
       output = io.BytesIO()
       workbook = xlsxwriter.Workbook(output, {'in_memory': True})
       sheet = workbook.add_worksheet('Daily Timesheet')
       normal_header_fmt = workbook.add_format({
           'bold': True,
           'border': 1,
           'align': 'center',
           'valign': 'vcenter',
       })
       cell_fmt = workbook.add_format({
           'border': 1,
           'align': 'left',
           'valign': 'vcenter',
       })
       number_fmt = workbook.add_format({
           'border': 1,
           'align': 'right',
           'valign': 'vcenter',
           'num_format': '0.00',
       })
       COLUMNS = [
           ('Employee',       lambda r: r.employee_id.name or '', 20, cell_fmt,   False),
           ('Description',    lambda r: r.name or '', 30, cell_fmt,   False),
           ('Activity Group', lambda r: r.project_id.name or '', 25, cell_fmt,   True),
           ('Task',           lambda r: r.task_id.name or '', 25, cell_fmt,   False),
           ('Business Unit',  lambda r: r.company_id.name or '', 20, cell_fmt,   True),
           ('Department',     lambda r: r.department_id.name or '', 20, cell_fmt,   False),
           ('Time Spent',     lambda r: r.unit_amount, 12, number_fmt, False)                 ]
       sheet.set_row(0, 20)
       for col_idx, (label, _, width, _, is_yellow) in enumerate(COLUMNS):
           fmt = normal_header_fmt
           sheet.write(0, col_idx, label, fmt)
           sheet.set_column(col_idx, col_idx, width)
       for row_idx, record in enumerate(records, start=1):
           sheet.set_row(row_idx, 15)
           for col_idx, (_, accessor, _, fmt, _) in enumerate(COLUMNS):
               try:
                   value = accessor(record)
               except Exception:
                   value = ''
               if isinstance(value, float):
                   sheet.write_number(row_idx, col_idx, value, fmt)
               else:
                   sheet.write(row_idx, col_idx, value or '', fmt)
       workbook.close()
       output.seek(0)
       xlsx_data = output.read()
       return request.make_response(
           xlsx_data,
           headers=[
               ('Content-Type',
                'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'),
               ('Content-Disposition',
                content_disposition('daily_timesheet.xlsx')),
           ],
       )

Step 4: Registering the Custom View

Finally, we need to link our custom JS class to the Odoo List View using the js_class attribute.

Update your view XML:

<record id="timesheets_analysis_report_list_inherited" model="ir.ui.view">
   <field name="name">timesheets.analysis.report.list.inherited</field>
   <field name="model">timesheets.analysis.report</field>
   <field name="inherit_id" ref="hr_timesheet.timesheets_analysis_report_list"/>
   <field name="arch" type="xml">
       <xpath expr="//list" position="attributes">
           <attribute name="js_class">daily_timesheet_list</attribute>
       </xpath>
   </field>
</record>

Odoo 19 allows you to produce highly customized Excel outputs by combining a Python controller for data processing with OWL for frontend interactivity. This method preserves complete control over the format and content of the exported file while offering a smooth user experience.

To read more about How to Create a Custom XLSX Export from List View in Odoo 18, refer to our blog How to Create a Custom XLSX Export from List View in Odoo 18.


If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message