Select Git revision
-
Martin Mareš authored
Nepovinné parametry u již známých soutěžících při přidávání do soutěže See merge request !93
Martin Mareš authoredNepovinné parametry u již známých soutěžících při přidávání do soutěže See merge request !93
table.py 5.56 KiB
import csv
from dataclasses import dataclass
from flask import Response, url_for
from html import escape
import io
from markupsafe import Markup
from typing import Any, Dict, Sequence, Optional, Iterable, Union
import urllib.parse
import werkzeug.exceptions
from mo.csv import FileFormat
import mo.db as db
from mo.web import app
@dataclass
class Column:
key: str # Jméno klíče ve slovnících
name: str # Název v hlavičce CSV
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:
"""Buňka tabulky může mít dvě verze: textovou a HTML."""
text: str
def __init__(self, text: str):
self.text = text
def __str__(self) -> str:
return self.text
def to_html(self) -> str:
return '<td>' + escape(self.text)
class Row:
"""Řádek tabulky, definuje klíče. Může definovat HTML atributy řádku."""
keys: Dict[str, Any]
html_attr: Dict[str, str]
def __init__(self, keys: Dict[str, Any], html_attr: Dict[str, str] = {}):
self.keys = keys
self.html_attr = html_attr
def get(self, key: str) -> Any:
return self.keys.get(key)
class CellLink(Cell):
url: str
hint: Optional[str]
def __init__(self, text: str, url: str, hint: Optional[str] = None):
Cell.__init__(self, text)
self.url = url
self.hint = hint
def to_html(self) -> str:
a = '<td><a href="' + escape(self.url) + '"'
if self.hint:
a += ' title="' + escape(self.hint) + '"'
return a + '>' + escape(self.text) + '</a>'
class CellCheckbox(Cell):
name: str
value: str
checked: bool
def __init__(self, name: str, value: str, checked: bool = False):
Cell.__init__(self, "")
self.name = name
self.value = value
self.checked = checked
def to_html(self) -> str:
ch = f'<td><input type="checkbox" name="{self.name}" value="{self.value}"'
if self.checked:
ch += ' checked'
return ch + '>'
class Table:
columns: Sequence[Column]
rows: Iterable[Row]
filename: str
show_downlink: bool
table_class: str
def __init__(
self, columns: Sequence[Column], rows: Iterable[Row],
filename: str, show_downlink: bool = True,
table_class: str = "data"
):
self.columns = columns
self.rows = rows
self.filename = filename
self.show_downlink = show_downlink
self.table_class = table_class
def to_html(self) -> str:
tab = [f'<table class="{self.table_class}">', '<thead>', '<tr>']
for c in self.columns:
tab.append(f'\t<th>{c.title}')
tab.append('<tbody>')
for r in self.rows:
r_attr = [f' {key}="{val}"' for (key, val) in r.html_attr.items()]
tab.append(f'<tr{"".join(r_attr)}>')
for c in self.columns:
val = r.get(c.key)
if isinstance(val, Cell):
tab.append(val.to_html())
else:
tab.append(f'\t<td>{escape(str(val))}')
tab.append('</table>')
if self.show_downlink:
tab.append("<p>Stáhnout jako <a href='?format=en_csv'>CSV s čárkami</a>, <a href='?format=cs_csv'>CSV se středníky</a> nebo <a href='?format=tsv'>TSV</a>.")
return Markup("\n".join(tab))
def to_csv(self, fmt: FileFormat) -> bytes:
out = io.StringIO()
writer = csv.writer(out, dialect=fmt.get_dialect())
header = [c.name for c in self.columns]
writer.writerow(header)
for row in self.rows:
r = [row.get(c.key) for c in self.columns]
writer.writerow(r)
return out.getvalue().encode(fmt.get_charset())
def to_csv_stream(self, fmt: FileFormat) -> Iterable[bytes]:
out = io.StringIO()
writer = csv.writer(out, dialect=fmt.get_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().encode(fmt.get_charset())
out.seek(0)
out.truncate()
nrows = 0
yield out.getvalue().encode(fmt.get_charset())
def send_as(self, format: Union[FileFormat, str], streaming: bool = False) -> Response:
try:
fmt = FileFormat.coerce(format)
except ValueError:
raise werkzeug.exceptions.NotFound()
if streaming:
resp = Response(self.to_csv_stream(fmt))
else:
out = self.to_csv(fmt)
resp = app.make_response(out)
resp.content_type = fmt.get_content_type()
filename = self.filename + '.' + fmt.get_extension()
resp.headers.add('Content-Disposition', f'attachment; filename={filename}')
return resp
# Pomocné funkce na generování různých typů odkazů
def cell_email_link(user: db.User) -> CellLink:
return CellLink(user.email, 'mailto:' + urllib.parse.quote(user.email, safe='@'))
def cell_user_link(user: db.User, text: str) -> CellLink:
return CellLink(text, url_for('org_user', id=user.user_id))
def cell_place_link(place: db.Place, text: str) -> CellLink:
return CellLink(text, url_for('org_place', id=place.place_id), hint=place.name)