Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
60 changes: 60 additions & 0 deletions src/base/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2677,3 +2677,63 @@ def test_assert_updated_combo(self):
):
p2.city = "Niflheim"
util.flush(p2)


class TestReportUtils(UnitTestCase):
@parametrize(
[
(
"Simple test with minimal arguments and no data.",
[],
("id", "name"),
"Partner {name} has id {id}",
None,
None,
100,
"Other",
Comment on lines +2692 to +2693
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to pass the default arguments as regular ones, because of how the tested method is called in test_report_with_list. Simply leaving them out would obviously result in

Traceback (most recent call last):
  File "/home/odoo/src/upgrade-util/src/testing.py", line 174, in wrapped
    return func(self, *args)
TypeError: TestReportUtils.test_report_with_list() missing 2 required positional arguments: 'category' and 'expected'

Passing them as None also wouldn't work, because since Python differentiates between "no argument passed" and None, they would actually be set to None instead of taking the default value. And if we pass category=None, then add_to_migration_reports will break on running len(None).

"<summary>Simple test with minimal arguments and no data.</summary>",
),
(
"Testing links.",
[],
("id", "name"),
"Partner {partner_link}.",
{"partner_link": ("res.partner", "id", "name")},
None,
100,
"Other",
"<summary>Testing links.</summary>",
),
(
"Test with minimal data.",
[(1, "Partner One")],
("id", "name"),
"Partner {partner_link}.",
{"partner_link": ("res.partner", "id", "name")},
None,
100,
"Other",
"<summary>Test with minimal data.<details><i>The total number of affected records is 1.</i><ul>\n"
'<li>Partner <a target="_blank" href="/odoo/res.partner/1?debug=1">Partner One</a>.</li>\n'
"</ul></details></summary>",
),
(
"Test with limited data.",
[(1, "Partner One"), (2, "Partner Two"), (3, "Partner Three")],
("id", "name"),
"Partner {partner_link}.",
{"partner_link": ("res.partner", "id", "name")},
None,
2,
"Other",
"<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"
'<li>Partner <a target="_blank" href="/odoo/res.partner/1?debug=1">Partner One</a>.</li>\n'
'<li>Partner <a target="_blank" href="/odoo/res.partner/2?debug=1">Partner Two</a>.</li>\n'
"</ul></details></summary>",
),
]
)
def test_report_with_list(self, summary, data, columns, row_format, links, total, limit, category, expected):
self.assertEqual(
util.report_with_list(summary, data, columns, row_format, links, total, limit, category), expected
)
85 changes: 84 additions & 1 deletion src/util/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def html_escape(text):
}


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


add_to_migration_reports = report


def report_with_summary(summary, details, category="Other"):
"""Append the upgrade report with a new entry.

:param str summary: Description of a report entry.
:param str details: Detailed description that is going to be folded by default.
:param str category: Title of a report entry.
"""
msg = (
"<summary>{}<details>{}</details></summary>".format(summary, details)
if details
else "<summary>{}</summary>".format(summary)
)
report(message=msg, category=category, format="html")
return msg


def report_with_list(summary, data, columns, row_format, links=None, total=None, limit=100, category="Other"):
"""Append the upgrade report with a new entry that displays a list of records.

The entry consists of a category (title) and a summary (body).
The entry displays a list of records previously returned by SQL query, or any list.

.. example::

.. code-block:: python

total = cr.rowcount
data = cr.fetchmany(20)
util.report_with_list(
summary="The following records were altered.",
data=data,
columns=("id", "name", "city", "comment", "company_id", "company_name"),
row_format="Partner with id {partner_link} works at company {company_link} in {city}, ({comment})",
links={"company_link": ("res.company", "company_id", "company_name"), "partner_link": ("res.partner", "id", "name")},
total=total,
category="Accounting"
)

:param str summary: description of a report entry.
:param list(tuple) data: data to report, each entry would be a row in the report.
It could be empty, in which case only the summary is rendered.
:param tuple(str) columns: columns in `data`, can be referenced in `row_format`.
:param str row_format: format for rows, can use any name from `columns` or `links`, e.g.:
"Partner {partner_link} that lives in {city} works at company {company_link}."
:param dict(str, tuple(str, str, str)) links: optional model/record links spec,
the keys can be referenced in `row_format`.
:param int total: optional, total number of records.
Taken as `len(data)` when `None` is passed.
Useful when `data` was limited by the caller.
:param int limit: maximum number of records to list in the report.
If `data` contains more records the `total` number would be
included in the report as well.
:param str category: title of a report entry.
"""

def row_to_html(row):
row_dict = dict(zip(columns, row))
if links:
row_dict.update(
{
link: get_anchor_link_to_record(rec_model, row_dict[id_col], row_dict[name_col])
for link, (rec_model, id_col, name_col) in links.items()
}
)
return "<li>{}</li>".format(row_format.format(**row_dict))

if not data:
row_to_html(columns) # Validate the format is correct, including links
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will fail. column doesn't have the expected type.

(a.k.a tests are missing)

return report_with_summary(summary=summary, details="", category=category)

limit = min(limit, len(data))
total = len(data) if total is None else total
disclaimer = "The total number of affected records is {}.".format(total)
if total > limit:
disclaimer += " This list is limited to {} records.".format(limit)

rows = "<ul>\n" + "\n".join([row_to_html(row) for row in data[:limit]]) + "\n</ul>"
return report_with_summary(summary, "<i>{}</i>{}".format(disclaimer, rows), category)


def announce_release_note(cr):
filepath = os.path.join(os.path.dirname(__file__), "release-note.xml")
with open(filepath, "rb") as fp:
Expand Down