Skip to content

Commit

Permalink
Download Product comparison as XLSX #7 (#51)
Browse files Browse the repository at this point in the history
* Download Product comparison as XLSX #7

Signed-off-by: tdruez <[email protected]>

* Refine code and add unit test #7

Signed-off-by: tdruez <[email protected]>

---------

Signed-off-by: tdruez <[email protected]>
  • Loading branch information
tdruez authored Feb 16, 2024
1 parent f8eb1b4 commit 573916b
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ Release notes
data is fetched from the PurlDB to initialize the form.
https://github.com/nexB/dejacode/issues/47

- If you select two versions of the same Product in the Product list, or two different
Products, and click the Compare button, you can now download the results of the
comparison to a .xlsx file, making it easy to share the information with your
colleagues.
https://github.com/nexB/dejacode/issues/7

- Add dark theme support in UI.
https://github.com/nexB/dejacode/issues/25

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ <h1 class="header-title">
Comparison: {{ left_product }} <i class="fas fa-exchange-alt mx-2"></i> {{ right_product }}
</h1>
</div>
<div class="col-auto">
<a href="?download_xlsx=1" target="_blank" class="btn btn-outline-dark">
<i class="fas fa-download me-1"></i>Download as XLSX
</a>
</div>
</div>
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions product_portfolio/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,12 @@ def test_product_portfolio_product_tree_comparison_view_proper(self):
self.assertContains(response, "Added (2)")
self.assertContains(response, "Removed (1)")

response = self.client.get(url, data={"download_xlsx": True})
expected_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
self.assertEqual(expected_type, response.headers.get("Content-Type"))
expected_disposition = "attachment; filename=product_comparison.xlsx"
self.assertEqual(expected_disposition, response.headers.get("Content-Disposition"))

def test_product_portfolio_product_tree_comparison_view_secured_access(self):
self.client.login(username=self.basic_user.username, password="secret")
url = resolve_url(
Expand Down
78 changes: 78 additions & 0 deletions product_portfolio/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@

from crispy_forms.utils import render_crispy_form
from guardian.shortcuts import get_perms as guardian_get_perms
from openpyxl import Workbook
from openpyxl.styles import Alignment
from openpyxl.styles import Border
from openpyxl.styles import Font
from openpyxl.styles import NamedStyle
from openpyxl.styles import Side

from component_catalog.forms import ComponentAjaxForm
from component_catalog.license_expression_dje import build_licensing
Expand Down Expand Up @@ -972,6 +978,75 @@ def get_queryset(self):
)


def comparison_download_xlsx(request, rows, left_product, right_product):
wb = Workbook()
ws = wb.active
ws.title = "Product comparison"
header = ["Changes", str(left_product), str(right_product)]
compare_data = [header]

def get_relation_data(relation, diff, is_left):
if not relation:
return ""

data = [
str(relation),
"",
f"Review status: {relation.review_status or ''}",
f"License: {relation.license_expression or ''}",
]
if relation.purpose:
data.append(f"Purpose: {relation.purpose or ''}")

if diff:
data.append("")
for field, values in diff.items():
diff_line = (
f"{'-' if is_left else '+'} {field.verbose_name.title()}: "
f"{values[0] if is_left else values[1]}"
)
data.append(diff_line)

return "\n".join(data)

# Iterate over each row and populate the Excel worksheet
for action, left, right, diff in rows:
compare_data.append(
[
action,
get_relation_data(left, diff, is_left=True),
get_relation_data(right, diff, is_left=False),
]
)

for row in compare_data:
ws.append(row)

# Styling
header = NamedStyle(name="header")
header.font = Font(bold=True)
header.border = Border(bottom=Side(border_style="thin"))
header.alignment = Alignment(horizontal="center", vertical="center")
header_row = ws[1]
for cell in header_row:
cell.style = header

# Freeze first header row
ws.freeze_panes = "A2"

# Columns width
ws.column_dimensions["B"].width = 40
ws.column_dimensions["C"].width = 40

# Prepare response
xlsx_content_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
response = HttpResponse(content_type=xlsx_content_type)
response["Content-Disposition"] = "attachment; filename=product_comparison.xlsx"
wb.save(response)

return response


@login_required
def product_tree_comparison_view(request, left_uuid, right_uuid):
guarded_qs = Product.objects.get_queryset(request.user)
Expand Down Expand Up @@ -1057,6 +1132,9 @@ def sort_by_name_version(row):

rows.sort(key=sort_by_name_version)

if request.GET.get("download_xlsx"):
return comparison_download_xlsx(request, rows, left_product, right_product)

action_filters = [
{"value": "added", "count": len(added), "checked": True},
{"value": "changed", "count": len(changed), "checked": True},
Expand Down

0 comments on commit 573916b

Please sign in to comment.