In Odoo 17, the default "Export All" feature may export raw values for fields such as float, many2one, and date. If you're working with a model like hr.attendance and want to customize the export (e.g., format float as time, display many2one labels, etc.), this guide walks you through creating a custom Excel export controller while preserving the default behavior for other models.
Folder Structure
hr_plus/
├── controllers/
│ └── custom_ExportXlsx.py
├── __init__.py
└── __manifest__.py
Step 1: Override the Export Controller
in controllers/custom_ExportXlsx.py:
from odoo.addons.web.controllers.export import ExcelExport
from odoo import http
from odoo.http import request
import json
import io
import xlsxwriter
import logging
from datetime import datetime
_logger = logging.getLogger(__name__)
class CustomExcelExport(ExcelExport):
@http.route('/web/export/xlsx', type='http', auth="user", csrf=False, methods=['POST'], override=True)
def web_export_xlsx(self, data):
try:
return self.custom_base(data)
except Exception as exc:
_logger.exception("Exception during request handling.")
payload = json.dumps({
'code': 200,
'message': "Odoo Server Error",
'data': http.serialize_exception(exc)
})
raise http.InternalServerError(payload) from exc
def custom_base(self, data):
data = json.loads(data)
model = data.get('model')
fields = data.get('fields', [])
rows = data.get('rows', [])
groups = data.get('groups', [])
if model == 'hr.attendance':
# nếu rows rỗng thì fetch từ backend
if not rows:
return self.export_hr_attendance_backend(fields)
return self.export_hr_attendance(fields, rows)
# fallback cho model khác
return super().base(json.dumps(data))
def export_hr_attendance_backend(self, fields):
"""Export attendance data with formatted float time and date, and set column widths."""
model_name = "hr.attendance"
domain = [] # Có thể tùy chỉnh để lọc dữ liệu nếu cần
fields_names = [f["name"] for f in fields]
# Fetch records
records = request.env[model_name].search(domain, limit=1000)
rows = records.read(fields_names)
# Excel setup
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output)
worksheet = workbook.add_worksheet()
# Identify time & date fields
time_fields = ["start_time", "end_time", "overtime_hours"]
date_fields = [f["name"] for f in fields if f.get("type") == "date"]
# Optional: define column widths
width_map = {
"company_id": 20,
"department_id": 20,
"employee_id": 20,
"mobile_account": 15,
"check_in_date": 12,
"start_time": 10,
"end_time": 10,
"overtime_hours": 10,
"ndate": 12
}
# Write headers and set column widths
for col_index, field in enumerate(fields):
worksheet.write(0, col_index, field["label"])
field_name = field["name"]
width = width_map.get(field_name, 15)
worksheet.set_column(col_index, col_index, width)
# Write data rows
for row_index, record in enumerate(rows, start=1):
for col_index, field in enumerate(fields):
field_name = field["name"]
value = record.get(field_name)
# Format float time as HH:MM
if field_name in time_fields and isinstance(value, float):
hours = int(value)
minutes = int(round((value - hours) * 60))
time_str = f"{hours:02d}:{minutes:02d}"
worksheet.write(row_index, col_index, time_str)
# Format date string as "YYYY-MM-DD"
elif field_name in date_fields and value:
try:
worksheet.write(row_index, col_index, value.strftime("%Y-%m-%d"))
except Exception:
worksheet.write(row_index, col_index, value)
# Format many2one field as name
elif isinstance(value, tuple):
worksheet.write(row_index, col_index, value[1])
# Other fields
else:
worksheet.write(row_index, col_index, value or "")
# Finalize
workbook.close()
output.seek(0)
return request.make_response(
output.read(),
headers=[
('Content-Disposition', http.content_disposition('hr_attendance.xlsx')),
('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'),
`],`
)
Result:
When exporting the hr.attendance model using the Export All button, the Excel file will:
Format Start Time, End Time, and Overtime fields as HH:MM, matching the widget-style float time format.
Format Work Date (and any date fields) as YYYY-MM-DD.
Automatically adjust column widths for better readability.
If you export any other model, Odoo will fall back to the default export logic.
Reply