Skip to content
Snippets Groups Projects
Select Git revision
  • c7153d34c9da1e351a117a5db2c6715fac725cad
  • master default
2 results

Program.cs

Blame
  • table.py 8.58 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, List, 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
    from mo.web.util import user_html_flags
    
    
    @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
        in_html: bool           # Jestli sloupec zobrazit na webu
        in_export: Optional[bool] # Jestli sloupec defaultně exportovat, None = ani nenabízet
    
        def __init__(self, key: str, name: Optional[str] = None, title: Optional[str] = None, in_html: bool = True, in_export: Optional[bool] = True):
            self.key = key
            self.name = name or key
            self.title = title or self.name
            self.in_html = in_html
            self.in_export = in_export
    
    
    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]
        html_suffix: Markup
    
        def __init__(self, text: str, url: str, hint: Optional[str] = None, html_suffix: Markup = Markup("")):
            Cell.__init__(self, text)
            self.url = url
            self.hint = hint
            self.html_suffix = html_suffix
    
        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>' + str(self.html_suffix)
    
    
    class CellInput(Cell):
        name: str
        value: str
        type: str
        attrs: Dict[str, Optional[str]]
    
        def __init__(self, name: str, value: str, type: str = "text", attrs: Dict[str, Optional[str]] = {}):
            Cell.__init__(self, "")
            self.name = name
            self.value = value
            self.type = type
            self.attrs = attrs
    
        def to_html(self) -> str:
            out = f'<td><input type="{self.type}" name="{self.name}" value="{self.value}"'
            for (attr, value) in self.attrs.items():
                out += f' {attr}'
                if value is not None:
                    out += '=' + escape(str(value))
            return out + '>'
    
    
    class CellCheckbox(CellInput):
        def __init__(self, name: str, value: str, checked: bool = False):
            attrs = {}
            if checked:
                attrs['checked'] = None
            CellInput.__init__(self, name, value, "checkbox", attrs)
    
    
    class CellMarkup(Cell):
        text: str
        html: str
    
        def __init__(self, text: str, html: Union[str, Markup]):
            self.text = text
            self.html = str(html)
    
        def __str__(self) -> str:
            return self.text
    
        def to_html(self) -> str:
            return self.html
    
    
    class OrderCell(Cell):
        place: int
        span: int
        continuation: bool
    
        def __init__(self, place: int, span: int = 1, continuation: bool = False):
            self.place = place
            self.span = span
            self.continuation = continuation
    
        def __str__(self) -> str:
            if self.span == 1:
                return f"{self.place}."
            else:
                return f"{self.place}.–{self.place + self.span - 1}."
    
        def to_html(self) -> str:
            if self.continuation:
                return ""  # covered by rowspan cell above this one
            elif self.span == 1:
                return f"<td>{self.__str__()}"
            else:
                return f"<td rowspan='{self.span}'>{self.__str__()}"
    
    
    class Table:
        id: str  # validní jako HTML id a unikátní pro tabulky zobrazené na stejné stránce
        columns: Sequence[Column]
        rows: Iterable[Row]
        filename: str
        show_downlink: bool
        table_class: str
    
        def __init__(
            self, id: str, columns: Sequence[Column], rows: Iterable[Row],
            filename: str, show_downlink: bool = True,
            table_class: str = "data"
        ):
            self.id = id
            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}" id="{self.id}">', '<thead>', '<tr>']
    
            for c in self.columns:
                if not c.in_html:
                    continue
                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:
                    if not c.in_html:
                        continue
                    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, export_columns: List[Column]) -> bytes:
            out = io.StringIO()
            writer = csv.writer(out, dialect=fmt.get_dialect())
    
            header = [c.name for c in export_columns]
            writer.writerow(header)
    
            for row in self.rows:
                r = [row.get(c.key) for c in export_columns]
                writer.writerow(r)
    
            return out.getvalue().encode(fmt.get_charset())
    
        def to_csv_stream(self, fmt: FileFormat, export_columns: List[Column]) -> Iterable[bytes]:
            out = io.StringIO()
            writer = csv.writer(out, dialect=fmt.get_dialect())
    
            header = [c.name for c in export_columns]
            writer.writerow(header)
    
            nrows = 0
            for row in self.rows:
                r = [row.get(c.key) for c in export_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()
    
            export_columns = [c for c in self.columns if c.in_export]
    
            if streaming:
                resp = Response(self.to_csv_stream(fmt, export_columns))
            else:
                out = self.to_csv(fmt, export_columns)
                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_email_link_flags(user: db.User) -> CellLink:
        return CellLink(user.email, 'mailto:' + urllib.parse.quote(user.email, safe='@'), html_suffix=user_html_flags(user))
    
    
    def cell_user_link(user: db.User, text: str) -> CellLink:
        return CellLink(text, url_for('org_user', id=user.user_id))
    
    
    def cell_pion_link(user: db.User, contest_id: int, text: str) -> CellLink:
        return CellLink(text, url_for('org_contest_user', ct_id=contest_id, user_id=user.user_id))
    
    
    def cell_place_link(place: db.Place, text: Optional[str] = None) -> CellLink:
        return CellLink(text or place.get_code(), url_for('org_place', id=place.place_id), hint=place.name)
    
    
    def cell_contest_link(contest: db.Contest, site: Optional[db.Place] = None, text: Optional[str] = None) -> CellLink:
        if site:
            return CellLink(text or site.get_code(), url_for('org_contest', ct_id=contest.contest_id, site_id=site.place_id), hint=site.name)
        else:
            return CellLink(text or contest.place.get_code(), url_for('org_contest', ct_id=contest.contest_id), hint=contest.place.name)