mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
fix(admin): the Logs download survives any row and stops at a size it can build
- A failed login writes the name it was tried with, as typed: a control character in it made every download holding that row fail, as the workbook cannot hold one. Workbooks now write it as \x01; the VM report shares the code. - A download held every matching row in memory: it now takes the newest 100000, and its About sheet says when the search should be narrowed.
This commit is contained in:
parent
0b5eb990ed
commit
5ef3437291
3 changed files with 28 additions and 2 deletions
|
|
@ -3,6 +3,7 @@ only, says the year and time zone, links a VM's rows to the VM, and downloads
|
|||
what it shows as an Excel workbook."""
|
||||
|
||||
from io import BytesIO
|
||||
from unittest.mock import patch
|
||||
|
||||
from computes.models import Compute
|
||||
from django.contrib.auth import get_user_model
|
||||
|
|
@ -85,3 +86,18 @@ class LogsPageTests(TestCase):
|
|||
def test_only_an_administrator_downloads(self):
|
||||
self.client.force_login(get_user_model().objects.create_user("lg-user", password="pw"))
|
||||
self.assertEqual(self.client.get(reverse("admin:logs_export")).status_code, 403)
|
||||
|
||||
def test_a_control_character_in_a_row_is_shown_not_a_failure(self):
|
||||
# a failed login writes the name it was tried with, as typed
|
||||
Logs.objects.create(user="eve\x01", instance="", message="Login", success=False)
|
||||
response = self.client.get(reverse("admin:logs_export"), {"q": "eve"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(load_workbook(BytesIO(response.content)).active.cell(row=2, column=2).value, "eve\\x01")
|
||||
|
||||
def test_the_download_stops_at_its_row_limit_and_says_so(self):
|
||||
with patch("admin.views.EXPORT_ROWS", 3):
|
||||
book = load_workbook(BytesIO(self.client.get(reverse("admin:logs_export")).content))
|
||||
self.assertEqual(book.active.max_row, 4) # the header and three rows
|
||||
notes = [row[0] for row in book["About"].values]
|
||||
self.assertIn("Only the newest 3 rows: narrow the search for the others", notes)
|
||||
|
||||
|
|
|
|||
|
|
@ -260,6 +260,10 @@ def user_unblock(request, pk):
|
|||
return redirect("admin:user_list")
|
||||
|
||||
|
||||
# rows in one Logs download: the workbook is built in memory
|
||||
EXPORT_ROWS = 100000
|
||||
|
||||
|
||||
def _logs(request):
|
||||
"""The Logs rows the search and the failed-only box choose, newest first,
|
||||
and the query that chooses them again."""
|
||||
|
|
@ -299,10 +303,13 @@ def logs_export(request):
|
|||
rows, text, failed, _query = _logs(request)
|
||||
zone = timezone.get_current_timezone_name()
|
||||
header = [f"Date ({zone})", "User", "Host", "VM", "VM UUID", "Message", "Result", "IP"]
|
||||
logs = list(rows[:EXPORT_ROWS + 1])
|
||||
data = [[timezone.localtime(log.date).strftime("%Y-%m-%d %H:%M:%S"), log.user, log.host, log.instance,
|
||||
log.instance_uuid, log.message, str(_result(log.success)), log.ip or ""] for log in rows.iterator()]
|
||||
log.instance_uuid, log.message, str(_result(log.success)), log.ip or ""] for log in logs[:EXPORT_ROWS]]
|
||||
notes = [f"Search: {text}" if text else "Search: none", "Failed only" if failed else "All results",
|
||||
f"Rows: {len(data)}", f"Times in {zone}"]
|
||||
if len(logs) > EXPORT_ROWS:
|
||||
notes.append(f"Only the newest {EXPORT_ROWS} rows: narrow the search for the others")
|
||||
response = HttpResponse(workbook("Logs", header, data, "About", notes),
|
||||
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
name = f"webvirtcloud-logs-{timezone.localtime():%Y%m%d-%H%M%S}.xlsx"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
from io import BytesIO
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
|
||||
from openpyxl.styles import Font
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
|
|
@ -17,7 +18,9 @@ def workbook(title, header, rows, notes_title, notes):
|
|||
sheet = book.active
|
||||
sheet.title = title
|
||||
for values in [header, *rows]:
|
||||
sheet.append(values)
|
||||
# a control character (a name typed at a failed login) cannot be in the file: written as \x01
|
||||
sheet.append([ILLEGAL_CHARACTERS_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", value)
|
||||
if isinstance(value, str) else value for value in values])
|
||||
for row in sheet.iter_rows():
|
||||
for cell in row:
|
||||
if isinstance(cell.value, str):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue