Skip to content

Commit 060e421

Browse files
committed
Refactor the XLSX report logic into a single function #1524
For re-usability: `get_xlsx_report` Signed-off-by: tdruez <[email protected]>
1 parent f4df518 commit 060e421

File tree

5 files changed

+58
-50
lines changed

5 files changed

+58
-50
lines changed

scanpipe/forms.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -297,15 +297,15 @@ class ProjectReportForm(BaseProjectActionForm):
297297
model_name = forms.ChoiceField(
298298
label="Choose the object type to include in the XLSX file",
299299
choices=[
300-
("discoveredpackage", "Packages"),
301-
("discovereddependency", "Dependencies"),
302-
("codebaseresource", "Resources"),
303-
("codebaserelation", "Relations"),
304-
("projectmessage", "Messages"),
300+
("package", "Packages"),
301+
("dependency", "Dependencies"),
302+
("resource", "Resources"),
303+
("relation", "Relations"),
304+
("message", "Messages"),
305305
("todo", "TODOs"),
306306
],
307307
required=True,
308-
initial="discoveredpackage",
308+
initial="package",
309309
widget=forms.RadioSelect,
310310
)
311311

scanpipe/management/commands/report.py

+1-14
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
from django.core.management import CommandError
2727
from django.core.management.base import BaseCommand
2828

29-
import xlsxwriter
30-
3129
from aboutcode.pipeline import humanize_time
3230
from scanpipe.models import Project
3331
from scanpipe.pipes import filename_now
@@ -76,7 +74,6 @@ def handle(self, *args, **options):
7674
labels = options["labels"]
7775
search = options["search"]
7876
sheet = options["sheet"]
79-
model_name = output.object_type_to_model_name.get(sheet)
8077

8178
if not (labels or search):
8279
raise CommandError(
@@ -97,23 +94,13 @@ def handle(self, *args, **options):
9794
msg = f"{project_count} project(s) will be included in the report."
9895
self.stdout.write(msg, self.style.SUCCESS)
9996

100-
worksheet_queryset = output.get_queryset(project=None, model_name=model_name)
101-
worksheet_queryset = worksheet_queryset.filter(project__in=project_qs)
102-
10397
filename = f"scancodeio-report-{filename_now()}.xlsx"
10498
if output_directory:
10599
output_file = Path(f"{output_directory}/{filename}")
106100
else:
107101
output_file = Path(filename)
108102

109-
with xlsxwriter.Workbook(output_file) as workbook:
110-
output.queryset_to_xlsx_worksheet(
111-
worksheet_queryset,
112-
workbook,
113-
exclude_fields=output.XLSX_EXCLUDE_FIELDS,
114-
prepend_fields=["project"],
115-
worksheet_name="TODOS",
116-
)
103+
output_file = output.get_xlsx_report(project_qs, sheet, output_file)
117104

118105
run_time = timer() - start_time
119106
if self.verbosity > 0:

scanpipe/pipes/output.py

+28
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import csv
2424
import decimal
25+
import io
2526
import json
2627
import re
2728
from operator import attrgetter
@@ -301,6 +302,7 @@ def to_json(project):
301302
"codebaseresource": "RESOURCES",
302303
"codebaserelation": "RELATIONS",
303304
"projectmessage": "MESSAGES",
305+
"todo": "TODOS",
304306
}
305307

306308
model_name_to_object_type = {
@@ -399,6 +401,32 @@ def add_xlsx_worksheet(workbook, worksheet_name, rows, fields):
399401
return errors_count
400402

401403

404+
# TODO: Add unit test
405+
def get_xlsx_report(project_qs, model_short_name, output_file=None):
406+
model_name = object_type_to_model_name.get(model_short_name)
407+
if not model_name:
408+
raise ValueError
409+
410+
worksheet_name = model_name_to_worksheet_name.get(model_short_name)
411+
412+
worksheet_queryset = get_queryset(project=None, model_name=model_name)
413+
worksheet_queryset = worksheet_queryset.filter(project__in=project_qs)
414+
415+
if not output_file:
416+
output_file = io.BytesIO()
417+
418+
with xlsxwriter.Workbook(output_file) as workbook:
419+
queryset_to_xlsx_worksheet(
420+
worksheet_queryset,
421+
workbook,
422+
exclude_fields=XLSX_EXCLUDE_FIELDS,
423+
prepend_fields=["project"],
424+
worksheet_name=worksheet_name,
425+
)
426+
427+
return output_file
428+
429+
402430
# Some scan attributes such as "copyrights" are list of dicts.
403431
#
404432
# 'authors': [{'end_line': 7, 'start_line': 7, 'author': 'John Doe'}],

scanpipe/tests/test_views.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
2121
# Visit https://github.com/nexB/scancode.io for support and download.
2222

23+
import io
2324
import json
2425
import shutil
2526
import uuid
@@ -34,6 +35,7 @@
3435
from django.urls import reverse
3536
from django.urls.exceptions import NoReverseMatch
3637

38+
import openpyxl
3739
import requests
3840

3941
from scanpipe.forms import BaseProjectActionForm
@@ -228,7 +230,12 @@ def test_scanpipe_views_project_action_report_view(self):
228230
"model_name": "todo",
229231
}
230232
response = self.client.post(url, data=data, follow=True)
231-
self.assertEqual("report.xlsx", response.filename)
233+
self.assertTrue(response.filename.startswith("scancodeio-report-"))
234+
self.assertTrue(response.filename.endswith(".xlsx"))
235+
236+
output_file = io.BytesIO(b"".join(response.streaming_content))
237+
workbook = openpyxl.load_workbook(output_file, read_only=True, data_only=True)
238+
self.assertEqual(["TODOS"], workbook.get_sheet_names())
232239

233240
def test_scanpipe_views_project_action_view_get_project_queryset(self):
234241
queryset = ProjectActionView.get_project_queryset(

scanpipe/views.py

+15-29
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
from scanpipe.models import RunInProgressError
9797
from scanpipe.pipes import compliance
9898
from scanpipe.pipes import count_group_by
99+
from scanpipe.pipes import filename_now
99100
from scanpipe.pipes import output
100101
from scanpipe.pipes import purldb
101102

@@ -453,24 +454,14 @@ def get_export_xlsx_queryset(self):
453454
def get_export_xlsx_filename(self):
454455
return f"{self.project.name}_{self.model._meta.model_name}.xlsx"
455456

456-
def get_export_xlsx_prepend_fields(self):
457-
return []
458-
459-
def get_export_xlsx_worksheet_name(self):
460-
return
461-
462457
def export_xlsx_file_response(self):
463458
output_file = io.BytesIO()
464459
queryset = self.get_export_xlsx_queryset()
465-
prepend_fields = self.get_export_xlsx_prepend_fields()
466-
worksheet_name = self.get_export_xlsx_worksheet_name()
467460
with xlsxwriter.Workbook(output_file) as workbook:
468461
output.queryset_to_xlsx_worksheet(
469462
queryset,
470463
workbook,
471464
exclude_fields=output.XLSX_EXCLUDE_FIELDS,
472-
prepend_fields=prepend_fields,
473-
worksheet_name=worksheet_name,
474465
)
475466

476467
output_file.seek(0)
@@ -1191,7 +1182,7 @@ def form_valid(self, form):
11911182

11921183

11931184
@method_decorator(require_POST, name="dispatch")
1194-
class ProjectActionView(ConditionalLoginRequired, ExportXLSXMixin, generic.ListView):
1185+
class ProjectActionView(ConditionalLoginRequired, generic.ListView):
11951186
"""Call a method for each instance of the selection."""
11961187

11971188
model = Project
@@ -1222,9 +1213,7 @@ def post(self, request, *args, **kwargs):
12221213
return self.download_outputs_zip_response(project_qs, action_form)
12231214

12241215
if action == "report":
1225-
self.action_form = action_form
1226-
self.project_qs = project_qs
1227-
return self.export_xlsx_file_response()
1216+
return self.xlsx_report_response(project_qs, action_form)
12281217

12291218
if action == "archive":
12301219
action_kwargs = action_form.get_action_kwargs()
@@ -1287,21 +1276,6 @@ def get_project_queryset(selected_project_ids=None, action_form=None):
12871276

12881277
raise Http404
12891278

1290-
def get_export_xlsx_queryset(self):
1291-
model_name = self.action_form.cleaned_data["model_name"]
1292-
queryset = output.get_queryset(project=None, model_name=model_name)
1293-
return queryset.filter(project__in=self.project_qs)
1294-
1295-
def get_export_xlsx_prepend_fields(self):
1296-
return ["project"]
1297-
1298-
def get_export_xlsx_worksheet_name(self):
1299-
if self.action_form.cleaned_data.get("model_name") == "todo":
1300-
return "TODOS"
1301-
1302-
def get_export_xlsx_filename(self):
1303-
return "report.xlsx"
1304-
13051279
@staticmethod
13061280
def download_outputs_zip_response(project_qs, action_form):
13071281
output_format = action_form.cleaned_data["output_format"]
@@ -1323,6 +1297,18 @@ def download_outputs_zip_response(project_qs, action_form):
13231297
filename="scancodeio_output_files.zip",
13241298
)
13251299

1300+
@staticmethod
1301+
def xlsx_report_response(project_qs, action_form):
1302+
model_short_name = action_form.cleaned_data["model_name"]
1303+
filename = f"scancodeio-report-{filename_now()}.xlsx"
1304+
output_file = output.get_xlsx_report(project_qs, model_short_name)
1305+
output_file.seek(0)
1306+
return FileResponse(
1307+
output_file,
1308+
as_attachment=True,
1309+
filename=filename,
1310+
)
1311+
13261312

13271313
class HTTPResponseHXRedirect(HttpResponseRedirect):
13281314
status_code = 200

0 commit comments

Comments
 (0)