Skip to content
Snippets Groups Projects
Commit 37f39264 authored by Martin Mareš's avatar Martin Mareš
Browse files

web.table: Podpora pro streamování exportu tabulek

Table.rows může být cokoliv iterovatelného, z čeho padají řádky v podobě
slovníků, tedy i generátor.

Funkce send_as jde zavolat se streaming=True a pak odpověď streamuje.
To nicméně není default, protože je hezké, aby krátké odpovědi měly
Content-Length.

Také jsem vylepšil definici sloupečků, aby pouze key byl povinný
a vše ostatní defaultovalo.
parent f2667df7
No related branches found
No related tags found
No related merge requests found
......@@ -4,7 +4,7 @@ from flask import Response, url_for
from html import escape
import io
from markupsafe import Markup
from typing import Sequence, Optional
from typing import Sequence, Optional, Iterable
import werkzeug.exceptions
import mo.csv
......@@ -16,7 +16,12 @@ from mo.web import app
class Column:
key: str # Jméno klíče ve slovnících
name: str # Název v hlavičce CSV
title: Optional[str] # Titulek v HTML tabulkách
title: str # Titulek v HTML tabulkách
def __init__(self, key: str, name: Optional[str] = None, title: Optional[str] = None):
self.key = key
self.name = name or key
self.title = title or self.name
class Cell:
......@@ -46,10 +51,10 @@ class CellLink(Cell):
class Table:
columns: Sequence[Column]
rows: Sequence[dict]
rows: Iterable[dict]
filename: str
def __init__(self, columns: Sequence[Column], rows: Sequence[dict], filename: str):
def __init__(self, columns: Sequence[Column], rows: Iterable[dict], filename: str):
self.columns = columns
self.rows = rows
self.filename = filename
......@@ -58,7 +63,7 @@ class Table:
tab = ['<table class=data>', '<tr>']
for c in self.columns:
tab.append(f'\t<th>{c.title or c.name}')
tab.append(f'\t<th>{c.title}')
for r in self.rows:
tab.append('<tr>')
......@@ -87,19 +92,45 @@ class Table:
return out.getvalue()
def send_as(self, format: str) -> Response:
def to_csv_stream(self, dialect: str) -> Iterable[str]:
out = io.StringIO()
writer = csv.writer(out, dialect=dialect)
header = [c.name for c in self.columns]
writer.writerow(header)
nrows = 0
for row in self.rows:
r = [row.get(c.key) for c in self.columns]
writer.writerow(r)
nrows += 1
if nrows >= 100:
yield out.getvalue()
out.seek(0)
out.truncate()
nrows = 0
yield out.getvalue()
def send_as(self, format: str, streaming: bool = False) -> Response:
if format == 'csv':
out = self.to_csv(dialect='excel')
dialect = 'excel'
ctype = 'text/csv; charset=utf=8'
filename = self.filename + '.csv'
elif format == 'tsv':
out = self.to_csv(dialect='tsv')
dialect = 'tsv'
ctype = 'text/tab-separated-values; charset=utf=8'
filename = self.filename + '.tsv'
else:
raise werkzeug.exceptions.NotFound()
if streaming:
resp = Response(self.to_csv_stream(dialect=dialect))
else:
out = self.to_csv(dialect=dialect)
resp = app.make_response(out)
resp.content_type = ctype
resp.headers.add('Content-Disposition', f'attachment; filename={filename}')
return resp
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment