Skip to content

Commit 547a287

Browse files
authored
feat: add support for OpenDocument format in report export #363 (#478)
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent 7d35abf commit 547a287

12 files changed

+124
-9
lines changed

dje/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1880,6 +1880,7 @@ class DownloadableMixin:
18801880
"doc": "application/msword",
18811881
"html": "text/html",
18821882
"json": "application/json",
1883+
"ods": "application/vnd.oasis.opendocument.spreadsheet",
18831884
"xls": "application/vnd.ms-excel",
18841885
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
18851886
"yaml": "application/x-yaml",
19 KB
Loading

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,10 @@ dependencies = [
154154
"annotated-types==0.7.0",
155155
"semantic-version==2.10.0",
156156
# OpenVEX
157-
"msgspec==0.20.0"
157+
"msgspec==0.20.0",
158+
# OpenDocument Format
159+
"odfdo==3.20.2",
160+
"lxml==6.0.2",
158161
]
159162

160163
[project.optional-dependencies]

reporting/templates/reporting/report_run.html

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ <h1 class="header-title">
5252
</div>
5353
<div class="col-auto">
5454
<select id="id_format" class="form-select form-select-sm" name="format">
55-
<option value="doc">doc</option>
56-
<option value="html">html</option>
57-
<option value="json">json</option>
58-
<option value="xls">xls</option>
59-
<option value="xlsx" selected="selected">xlsx</option>
60-
<option value="yaml">yaml</option>
55+
<option value="html">Web Page (.html)</option>
56+
<option value="json">JSON (.json)</option>
57+
<option value="ods">OpenDocument (.ods)</option>
58+
<option value="doc">Microsoft Word (.doc)</option>
59+
<option value="xls">Microsoft Excel 97-2003 (.xls)</option>
60+
<option value="xlsx" selected="selected">Microsoft Excel (.xlsx)</option>
61+
<option value="yaml">YAML (.yaml)</option>
6162
</select>
6263
</div>
6364
<div class="col-auto">

reporting/tests/test_views.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ def test_report_view_get_json_response(self):
429429
[0, 1, 2], list(self.column_template.fields.all().values_list("seq", flat=True))
430430
)
431431

432-
# In case of a change: print repr(response.content)
432+
# In case of a change: print(repr(response.content))
433433
expected = (
434434
"{\n "
435435
'"key": "license_126",\n '
@@ -448,10 +448,19 @@ def test_report_view_get_yaml_response(self):
448448
)
449449
self.assertEqual(response["Content-Type"], "application/x-yaml")
450450

451-
# In case of a change: >>> print repr(response.content)
451+
# In case of a change: >>> print(repr(response.content))
452452
expected = "- key: license_138\n short_name: license_138\n name: license_138\n"
453453
self.assertContains(response, expected)
454454

455+
def test_report_view_get_ods_response(self):
456+
self.client.login(username="test", password="t3st")
457+
url = self.report.get_absolute_url() + "?format=ods"
458+
response = self.client.get(url)
459+
self.assertEqual(
460+
response["Content-Disposition"], 'attachment; filename="license-list-for-analysis.ods"'
461+
)
462+
self.assertEqual(response["Content-Type"], "application/vnd.oasis.opendocument.spreadsheet")
463+
455464
def test_run_report_view_runtime_parameters_value_fields(self):
456465
self.client.login(username="test", password="t3st")
457466

reporting/views.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from django.views.generic.detail import SingleObjectMixin
2525
from django.views.generic.list import MultipleObjectMixin
2626

27+
import odfdo
2728
import saneyaml
2829
import xlsxwriter
2930

@@ -232,6 +233,28 @@ def get_yaml_response(self, **response_kwargs):
232233
dump = self.get_dump(saneyaml.dump)
233234
return HttpResponse(dump, **response_kwargs)
234235

236+
def get_ods_response(self, **response_kwargs):
237+
"""Return the results as ods format."""
238+
context = self.get_context_data(**self.kwargs)
239+
report_data = [context["headers"]] + context["output"]
240+
241+
document = odfdo.Document("spreadsheet")
242+
table = odfdo.Table("Report")
243+
244+
for row_data in report_data:
245+
row = odfdo.Row()
246+
for cell_value in row_data:
247+
row.append(odfdo.Cell(normalize_newlines(cell_value), cell_type="string"))
248+
table.append(row)
249+
250+
document.body.clear()
251+
document.body.append(table)
252+
253+
file_output = io.BytesIO()
254+
document.save(file_output)
255+
256+
return HttpResponse(file_output.getvalue(), **response_kwargs)
257+
235258
def get_xlsx_response(self, **response_kwargs):
236259
"""Return the results as `xlsx` format."""
237260
context = self.get_context_data(**self.kwargs)
Binary file not shown.
Binary file not shown.

thirdparty/dist/lxml-6.0.2.tar.gz

3.88 MB
Binary file not shown.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
about_resource: lxml-6.0.2.tar.gz
2+
name: lxml
3+
version: 6.0.2
4+
download_url: https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz
5+
description: |
6+
Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API.
7+
lxml is a Pythonic, mature binding for the libxml2 and libxslt libraries.
8+
It provides safe and convenient access to these libraries using the
9+
ElementTree API.
10+
11+
It extends the ElementTree API significantly to offer support for XPath,
12+
RelaxNG, XML Schema, XSLT, C14N and much more.
13+
14+
To contact the project, go to the `project home page <https://lxml.de/>`_
15+
or see our bug tracker at https://launchpad.net/lxml
16+
17+
In case you want to use the current in-development version of lxml,
18+
you can get it from the github repository at
19+
https://github.com/lxml/lxml . Note that this requires Cython to
20+
build the sources, see the build instructions on the project home page.
21+
22+
23+
After an official release of a new stable series, bug fixes may become available at
24+
https://github.com/lxml/lxml/tree/lxml-6.0 .
25+
Running ``pip install https://github.com/lxml/lxml/archive/refs/heads/lxml-6.0.tar.gz``
26+
will install the unreleased branch state as soon as a maintenance branch has been established.
27+
Note that this requires Cython to be installed at an appropriate version for the build.
28+
29+
6.0.2 (2025-09-21)
30+
==================
31+
32+
Bugs fixed
33+
----------
34+
35+
* LP#2125278: Compilation with libxml2 2.15.0 failed.
36+
Original patch by Xi Ruoyao.
37+
38+
* Setting ``decompress=True`` in the parser had no effect in libxml2 2.15.
39+
40+
* Binary wheels on Linux and macOS use the library version libxml2 2.14.6.
41+
See https://gitlab.gnome.org/GNOME/libxml2/-/releases/v2.14.6
42+
43+
* Test failures in libxml2 2.15.0 were fixed.
44+
45+
Other changes
46+
-------------
47+
48+
* Binary wheels for Py3.9-3.11 on the ``riscv64`` architecture were added.
49+
50+
* Error constants were updated to match libxml2 2.15.0.
51+
52+
* Built using Cython 3.1.4.
53+
homepage_url: https://lxml.de/
54+
package_url: pkg:pypi/lxml@6.0.2
55+
license_expression: bsd-new
56+
copyright: Copyright Rick Jelliffe and Academia Sinica Computing Center
57+
attribute: yes
58+
checksum_md5: ac9a945976227fd854d3e9e034e52ca1
59+
checksum_sha1: 2b37a3d8ad8afe74b7ec616dc695ccc25a73bd97
60+
licenses:
61+
- key: bsd-new
62+
name: BSD-3-Clause
63+
file: bsd-new.LICENSE

0 commit comments

Comments
 (0)