Skip to content

Commit e48f6a3

Browse files
committed
[ADD] util/report: util.add_report
Add a new function that replaces util.add_to_migration_reports. The new function can automatically parse the provided data as a list of records. It can also limit the number of displayed records to prevent the upgrade report from growing too large. The structure of the note is now defined within the function itself, standardizing its appearance.
1 parent c2590fb commit e48f6a3

File tree

2 files changed

+144
-1
lines changed

2 files changed

+144
-1
lines changed

src/base/tests/test_util.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,3 +2664,63 @@ def test_assert_updated_combo(self):
26642664
):
26652665
p2.city = "Niflheim"
26662666
util.flush(p2)
2667+
2668+
2669+
class TestReportUtils(UnitTestCase):
2670+
@parametrize(
2671+
[
2672+
(
2673+
"Simple test with minimal arguments and no data.",
2674+
[],
2675+
("id", "name"),
2676+
"Partner {name} has id {id}",
2677+
None,
2678+
None,
2679+
100,
2680+
"Other",
2681+
"<summary>Simple test with minimal arguments and no data.</summary>",
2682+
),
2683+
(
2684+
"Testing links.",
2685+
[],
2686+
("id", "name"),
2687+
"Partner {partner_link}.",
2688+
{"partner_link": ("res.partner", "id", "name")},
2689+
None,
2690+
100,
2691+
"Other",
2692+
"<summary>Testing links.</summary>",
2693+
),
2694+
(
2695+
"Test with minimal data.",
2696+
[(1, "Partner One")],
2697+
("id", "name"),
2698+
"Partner {partner_link}.",
2699+
{"partner_link": ("res.partner", "id", "name")},
2700+
None,
2701+
100,
2702+
"Other",
2703+
"<summary>Test with minimal data.<details><i>The total number of affected records is 1.</i><ul>\n"
2704+
'<li>Partner <a target="_blank" href="/odoo/res.partner/1?debug=1">Partner One</a>.</li>\n'
2705+
"</ul></details></summary>",
2706+
),
2707+
(
2708+
"Test with limited data.",
2709+
[(1, "Partner One"), (2, "Partner Two"), (3, "Partner Three")],
2710+
("id", "name"),
2711+
"Partner {partner_link}.",
2712+
{"partner_link": ("res.partner", "id", "name")},
2713+
None,
2714+
2,
2715+
"Other",
2716+
"<summary>Test with limited data.<details><i>The total number of affected records is 3. This list is limited to 2 records.</i><ul>\n"
2717+
'<li>Partner <a target="_blank" href="/odoo/res.partner/1?debug=1">Partner One</a>.</li>\n'
2718+
'<li>Partner <a target="_blank" href="/odoo/res.partner/2?debug=1">Partner Two</a>.</li>\n'
2719+
"</ul></details></summary>",
2720+
),
2721+
]
2722+
)
2723+
def test_report_with_list(self, summary, data, columns, row_format, links, total, limit, category, expected):
2724+
self.assertEqual(
2725+
util.report_with_list(summary, data, columns, row_format, links, total, limit, category), expected
2726+
)

src/util/report.py

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def html_escape(text):
106106
}
107107

108108

109-
def add_to_migration_reports(message, category="Other", format="text"):
109+
def report(message, category="Other", format="text"):
110110
assert format in {"text", "html", "md", "rst"}
111111
if format == "md":
112112
message = md2html(dedent(message))
@@ -126,6 +126,89 @@ def add_to_migration_reports(message, category="Other", format="text"):
126126
_logger.warning("Upgrade report is growing suspiciously long: %s characters so far.", migration_reports_length)
127127

128128

129+
add_to_migration_reports = report
130+
131+
132+
def report_with_summary(summary, details, category="Other"):
133+
"""Append the upgrade report with a new entry.
134+
135+
:param str summary: Description of a report entry.
136+
:param str details: Detailed description that is going to be folded by default.
137+
:param str category: Title of a report entry.
138+
"""
139+
msg = (
140+
"<summary>{}<details>{}</details></summary>".format(summary, details)
141+
if details
142+
else "<summary>{}</summary>".format(summary)
143+
)
144+
report(message=msg, category=category, format="html")
145+
return msg
146+
147+
148+
def report_with_list(summary, data, columns, row_format, links=None, total=None, limit=100, category="Other"):
149+
"""Append the upgrade report with a new entry that displays a list of records.
150+
151+
The entry consists of a category (title) and a summary (body).
152+
The entry displays a list of records previously returned by SQL query, or any list.
153+
154+
.. example::
155+
156+
.. code-block:: python
157+
158+
total = cr.rowcount
159+
data = cr.fetchmany(20)
160+
util.report_with_list(
161+
summary="The following records were altered.",
162+
data=data,
163+
columns=("id", "name", "city", "comment", "company_id", "company_name"),
164+
row_format="Partner with id {partner_link} works at company {company_link} in {city}, ({comment})",
165+
links={"company_link": ("res.company", "company_id", "company_name"), "partner_link": ("res.partner", "id", "name")},
166+
total=total,
167+
category="Accounting"
168+
)
169+
170+
:param str summary: description of a report entry.
171+
:param list(tuple) data: data to report, each entry would be a row in the report.
172+
It could be empty, in which case only the summary is rendered.
173+
:param tuple(str) columns: columns in `data`, can be referenced in `row_format`.
174+
:param str row_format: format for rows, can use any name from `columns` or `links`, e.g.:
175+
"Partner {partner_link} that lives in {city} works at company {company_link}."
176+
:param dict(str, tuple(str, str, str)) links: optional model/record links spec,
177+
the keys can be referenced in `row_format`.
178+
:param int total: optional, total number of records.
179+
Taken as `len(data)` when `None` is passed.
180+
Useful when `data` was limited by the caller.
181+
:param int limit: maximum number of records to list in the report.
182+
If `data` contains more records the `total` number would be
183+
included in the report as well.
184+
:param str category: title of a report entry.
185+
"""
186+
187+
def row_to_html(row):
188+
row_dict = dict(zip(columns, row))
189+
if links:
190+
row_dict.update(
191+
{
192+
link: get_anchor_link_to_record(rec_model, row_dict[id_col], row_dict[name_col])
193+
for link, (rec_model, id_col, name_col) in links.items()
194+
}
195+
)
196+
return "<li>{}</li>".format(row_format.format(**row_dict))
197+
198+
if not data:
199+
row_to_html(columns) # Validate the format is correct, including links
200+
return report_with_summary(summary=summary, details="", category=category)
201+
202+
limit = min(limit, len(data))
203+
total = len(data) if total is None else total
204+
disclaimer = "The total number of affected records is {}.".format(total)
205+
if total > limit:
206+
disclaimer += " This list is limited to {} records.".format(limit)
207+
208+
rows = "<ul>\n" + "\n".join([row_to_html(row) for row in data[:limit]]) + "\n</ul>"
209+
return report_with_summary(summary, "<i>{}</i>{}".format(disclaimer, rows), category)
210+
211+
129212
def announce_release_note(cr):
130213
filepath = os.path.join(os.path.dirname(__file__), "release-note.xml")
131214
with open(filepath, "rb") as fp:

0 commit comments

Comments
 (0)