Skip to content

[16.0][MIG] controller_report_xls: Migration to 16.0. t#87779 #1700

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ffa5a7a
[IMP] Controller module to export QWeb reports into XLS format has la…
hbto Jan 29, 2015
bcc254e
[PYLINT]
hbto Jan 29, 2015
52ea7bc
[FIX] When we make a super we need to send the original values of the…
Jan 30, 2015
b0d36a8
[IMP] Giving proper filename to report in XLS format
hbto Jan 30, 2015
43637f7
[PYLINT]
hbto Jan 30, 2015
ff8355d
[FIX] Added new variables to work with the differents way to generate…
Feb 3, 2015
e36e15d
Add # -*- coding: utf-8 -*- may be it is necesary for pylint
nhomar Feb 6, 2015
2b5287a
[REF] controller_report_xls: Disable pylint error. More info here: ht…
moylop260 Feb 6, 2015
7fa5519
[IMP] <th/> tag sometimes is used as <td/> tag in some reports and they
hbto Mar 26, 2015
8fe553f
[ADD] Move controller_report_xls module description from the openerp …
zaoral Jul 17, 2015
64b29cf
[IMP] Add controller_report_xls module index.html file taking into ac…
zaoral Jul 23, 2015
99557aa
[IMP] Add missing controller_report_xls module icon. This icon is the…
zaoral Jul 23, 2015
9433e60
[IMP] changing versions forcing *.5 on versions to have them as refer…
nhomar Jul 25, 2015
1777829
[IMP] Move controller_report_xls module description from README.md to…
zaoral Jul 27, 2015
177bb0c
[FIX] external_dependency problem.
nhomar Aug 1, 2015
29b772a
[IMP] Update controller_report_xls module index.html file. New footer…
zaoral Aug 4, 2015
7b1805c
[IMP] Delete not used footer image in controller_report_xls module de…
zaoral Aug 4, 2015
42b85b2
[IMP] Update controller_report_xls module version +0.1 points to ensu…
zaoral Aug 4, 2015
fb248f5
[IMP][controller_report_xls]Change BeautifulSoup by lxml.html
luistorresm Aug 4, 2015
cb851b5
[IMP][controller_report_xls]Fixed pylint
luistorresm Aug 4, 2015
b9a80f8
[IMP][controller_report_xls]Reverted use to lxml by BeautifulSoup
luistorresm Aug 6, 2015
8cc8764
[REF] all py: Delete vim comment, add coding comment with pep8 style
moylop260 Sep 16, 2015
d48c601
[REF] all py: Remove magic comment interpreter
moylop260 Sep 16, 2015
53c95ed
'[REF] addons-vauxoo: Standardize project with odoo guidelines.'
moylop260 Sep 24, 2015
41c32cb
[IMP][controller_report_xls]Changed bs4 by lxml
luistorresm Oct 13, 2015
58ce157
[FIX] Wrong manifest version format
Apr 14, 2016
be0aeb8
[FIX] oca-autopep8: Fix bad-docstring-quotes and docstring-first-line…
Apr 25, 2016
aab91d2
[ADD] controller_report_xls: Add missing translation files. (#834)
KarenKawaii Apr 25, 2016
d01f55f
[MERGE] Improve controller report xls Vauxoo/addons-vauxoo#860 by @fe…
moylop260 May 18, 2016
18b1555
[IMP] Adding Number/Text Formatting
hbto Jun 15, 2016
4fd67fa
[REF] deactivate non-migrated to 10.0 modules
Dec 1, 2016
c15c562
[MIG] controller_report_xls: Migration to 11.0
Nov 24, 2020
dfefc87
[IMP] controller_report_xls: black, isort, prettier
carralc Sep 6, 2023
f8b572d
[MIG] controller_report_xls: Migration to 15.0
carralc Sep 6, 2023
2381027
[FIX] controller_report_xls: Get Odoo Style
CarmenMiranda Jan 23, 2024
b98d755
[FIX] controller_report_xls: Font Height
CarmenMiranda Jan 23, 2024
1ac9aea
[IMP] controller_report_xls: general improvements and and code cleanup
CDanielSanSeg Jul 3, 2025
f3a13fd
[IMP] controller_report_xls: Apply pre-commit-vauxoo fixes
CDanielSanSeg Jul 3, 2025
394cd8b
[REM] controller_report_xls: remove legacy controller
CDanielSanSeg Jul 4, 2025
e772ad8
[MIG] controller_report_xls: Migration to 16.0
CDanielSanSeg Jul 4, 2025
ca54b9c
[ADD] test-requirements.txt: add cssutils as requirement
CDanielSanSeg Jul 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions controller_report_xls/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
XLS Report Controller
=====================

This module does nothing by itself is meant to be imported by other modules to
enable them to export their QWeb reports into XLS format. The export tries to interpret
the cssstyle defined on qweb template and layouts into a XLWT Style. Not all the cssstyles
are mapped, so if you spot one, please contact us to add it. Special treatment on qweb
template and layouts is required to leverage from this module, meaning the style defined
should be flat, there is no support for cascading styles.

Contributors
------------

* Fekete Mihai <[email protected]>
1 change: 1 addition & 0 deletions controller_report_xls/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import reports
12 changes: 12 additions & 0 deletions controller_report_xls/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "XLS Report Controller",
"version": "16.0.1.0.0",
"author": "Vauxoo",
"category": "Tools",
"website": "https://www.vauxoo.com/",
"license": "LGPL-3",
"depends": [
"base",
],
"external_dependencies": {"python": ["cssutils"]},
}
16 changes: 16 additions & 0 deletions controller_report_xls/i18n/es.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * controller_report_xls
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-04 13:00+0000\n"
"PO-Revision-Date: 2025-07-04 13:00+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
1 change: 1 addition & 0 deletions controller_report_xls/reports/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import report_xls
188 changes: 188 additions & 0 deletions controller_report_xls/reports/report_xls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import base64
import logging
from io import BytesIO

import cssutils
import lxml.html
import xlwt
from cssutils import parseString

from odoo import models

from ..reports.xfstyle import css2excel

_logger = logging.getLogger(__name__)


def get_css_style(csstext, style):
cssstyle = ""
if csstext:
cssutils.log.setLevel(logging.CRITICAL)
cssnode = parseString(csstext)
stylesheet = cssnode.cssRules
for rule in stylesheet:
if rule.selectorText.replace(".", "") == style:
cssstyle = str(rule.style.cssText)
return cssstyle


def get_odoo_style(html, style, node):
if node.attrib.get("class", False):
for class_style in node.attrib.get("class", False).split():
for style_element in html.xpath('//style[@type="text/css"]'):
styleclass = get_css_style(style_element.text, class_style)
style.update(dict(item.split(":") for item in text_adapt(styleclass).split(";") if item))
if node.attrib.get("style", False):
style_nodes = [style_node.strip() for style_node in node.attrib.get("style").split(";") if style_node.strip()]
style.update(dict(item.split(":", 1) for item in style_nodes))
return style


def write_tables_to_excel(sheet, row, col, tables, html, table_styles):
for table in tables:
table_styles = get_odoo_style(html, table_styles, table)
headers = table.xpath("thead")
if not headers:
headers = table.xpath("table_header")
if headers:
for header in headers:
head_style = get_odoo_style(html, table_styles, header)
rows = header.xpath("tr")
if rows:
row = write_rows_to_excel(sheet, row, col, rows, html, head_style)
bodies = table.xpath("tbody")
if not bodies:
bodies = table.xpath("table_body")
if bodies:
for body in bodies:
body_style = get_odoo_style(html, table_styles, body)
rows = body.xpath("tr")
if rows:
row = write_rows_to_excel(sheet, row, col, rows, html, body_style)
if not headers and not bodies:
rows = table.xpath("tr")
if rows:
row = write_rows_to_excel(sheet, row, col, rows, html, table_styles)
row += 1
return row


def write_rows_to_excel(sheet, row, col, nodes, html, styles):
for tr in nodes:
new_styles = get_odoo_style(html, styles, tr)
rowspan = 0
if tr.attrib.get("rowspan", False):
rowspan = int(tr.attrib.get("rowspan")) - 1
cols = tr.xpath("td")
if not cols:
cols = tr.xpath("th")
if not cols:
continue
if cols:
row = write_cols_to_excel(sheet, row, col, rowspan, cols, html, new_styles)
row += rowspan + 1
return row


def write_cols_to_excel(sheet, row, col, rowspan, nodes, html, styles):
for td in nodes:
new_styles = get_odoo_style(html, styles, td)
# Check tables in column
tables = td.xpath("table")
if tables:
row = write_tables_to_excel(sheet, row, col, tables, html, new_styles)
else:
new_text = ""
colspan = 0
if td.attrib.get("colspan", False):
colspan = int(td.attrib.get("colspan")) - 1
text = text_adapt(" ".join(list(td.itertext())))
try:
new_text = float(text)
except ValueError:
new_text = text
cell_styles = css2excel(new_styles)
sheet.write_merge(row, row + rowspan, col, col + colspan, new_text, cell_styles)
col += colspan + 1
return row


def text_adapt(text):
new_text = text.replace("\n", " ").replace("\r", "")
new_text = new_text.replace("&nbsp;", " ").replace(" ", "")
return new_text.replace("; ", ";").replace(": ", ":").strip()


def write_cell_to_excel(sheet, row, rowspan, col, colspan, node, styles):
# Should implement if column have many classes with different styles,
# but write_rich_text doesn't support write to multiple rows and columns
# taken from rowspan and colspan
cell_styles = css2excel(styles)
rich_text = []
for line in node.iter():
text = text_adapt(" ".join(list(line.itertext())))
try:
new_text = float(text)
except ValueError:
new_text = text
new_style = get_odoo_style(styles, line)
if new_text:
rich_text.append(new_text)
text_style = css2excel(new_style)
rich_text.append(text_style)
sheet.write_rich_text(row, col, tuple(rich_text), cell_styles)
return True


def get_xls(html):
wb = xlwt.Workbook(style_compression=2)
ws = wb.add_sheet("Sheet 1")
root = lxml.html.fromstring(html)
html = root
row = 0
col = 0
table_styles = {}
table_styles["background-color"] = "#FFFFFF"
# Check header tables
tables = root.xpath('//div[@class="header"]/table')
if tables:
row = write_tables_to_excel(ws, row, col, tables, html, table_styles)
# Check page tables
tables = root.xpath('//div[@class="page"]/table')
if tables:
row = write_tables_to_excel(ws, row, col, tables, html, table_styles)
# Check footer tables
tables = root.xpath('//div[@class="footer"]/table')
if tables:
row = write_tables_to_excel(ws, row, col, tables, html, table_styles)
stream = BytesIO()
wb.save(stream)
return stream.getvalue()


class ActionReport(models.Model):
_inherit = "ir.actions.report"

def report_action(self, docids, data=None, config=True):
response = super().report_action(docids, data, config)
context = self.env.context
if response is None or not context.get("xls_report"):
return response
html = self._render_qweb_html(docids, data=data)[0]
html = html.decode("utf-8")
xls_stream = get_xls(html)
attachment = self.env["ir.attachment"].create(
{
"name": "%s.xls" % response["name"],
"datas": base64.encodebytes(xls_stream),
"type": "binary",
"mimetype": "application/vnd.ms-excel",
"description": response["name"],
}
)
return {
"name": attachment.name,
"type": "ir.actions.act_url",
"url": "web/content/?id=" + str(attachment.id) + "&download=true&filename=" + attachment.name,
"target": "new",
}
Loading