To-Do List

Co je cílem tohoto cvičení?

Na tomto příkladu si vyzkoušíme použít knihovnu SQLAlchemy na práci s databází. Napíšeme si jednoduchý program pro evidenci úkolů. Také si procvičíme práci s knihovnou Click.

Předpoklady

Předpokládáme základní znalost Pythonu. Měli byste mít počítač s nainstalovaným interpretem jazyka Python ve verzi aspoň 3.6. Pro začátek si také vytvořte nové virtuální prostředí.

Do tohoto prostředí si nainstalujte knihovny sqlalchemy a click.

Krok 1 – připojení k databázi

Pro zjednodušení začneme čtením dat z databáze. Můžete si stáhnout připravená data. Stáhněte si ho do stejného adresáře, ve kterém budete mít samotný program.

Do souboru ukoly.py si stáhněte tuto základní kostru.

# ukoly.py
from sqlalchemy import create_engine


db = create_engine("sqlite:///ukoly.sqlite")

Funkce create_engine vytváří spojení s databází ukoly.sqlite, která je uložená v aktuálním adresáři. Knihovna sqlalchemy umí pracovat i s jinými typy databází než je SQLite. Ta je ale nejjednodušší, a velice vhodná na uložení dat, se kterými budeme pracovat.

Momentálně program nic nedělá. Nejprve musíme nadefinovat, jak vlastně naše data vypadají.

Krok 2 – první dotaz

Samotnou databází si můžeme přestavit jako několik tabulek, které mají nějak pojmenované sloupce. V našem příkladu budeme potřebovat jedinou tabulku, ale klidně by jich mohlo být víc.

Pro každou tabulku budeme potřebovat třídu (class), jejíž instance budou reprezentovat jednotlivé řádky v ní.

Metodu __repr__ používá Python, když potřebuje zobrazit instanci této třídy. Není určená na výpis pro uživatele, ale pro ladění programu.

from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.exc.declarative import declarative_base
from sqlalchemy.orm import sessionmaker


db = create_engine("sqlite:///ukoly.sqlite")
Base = declarative_base()


class Ukol(Base):
    # Název tabulky v databází.
    __tablename__ = "ukoly"

    # Číselný identifikátor úkolu, toto číslo bude jedinečné.
    id = Column(Integer, primary_key=True)
    # Text úkolu.
    text = Column(String)
    # Datum a čas zadání úkolu.
    zadano = Column(DateTime)
    # Datum a čas vyřešení úkolu. Prázdná hodnota znamená nehotový úkol.
    vyreseno = Column(DateTime)

    def __repr__(self):
        return f"<Ukol(text='{self.text}', zadano={self.zadano}, vyreseno={self.vyreseno})>"

Přidejte jeden import a na konec souboru ještě několik řádků. Pokud chceme z databáze vytahovat data, nebo je ukládat, potřebujeme vytvořit ještě jeden objekt. Takzvané sezení (anglicky session) využije dříve vytvořené spojení a umožňuje nám použít všechny nadefinované třídy.

from sqlalchemy.orm import sessionmaker

...

Session = sessionmaker(bind=db)
sezeni = Session()

dotaz = sezeni.query(Ukol)
print(dotaz.all())

Metoda query vytvoří dotaz, který bude vracet instance třídy Ukol. Nezadáme žádné omezení, takže chceme všechny úkoly. Metoda all na tomto dotazu potom vrací seznam všech řádků v tabulce, které odpovídají dotazu.

Tento program už půjde spustit a bude vypisovat všechny úkoly. Tento výpis ale není úplně pěkný. Upravte program tak, aby každý úkol byl na samostatném řádku, a v hezky čitelném formátu. Jednotlivé objekty mají atributy id a text, které můžeme použít.

Řešení

Krok 3 – uživatelské rozhraní

Teď program upravíme tak, abychom ho mohli postupně rozšiřovat dalšími příkazy.

Ve finále chceme, aby program fungoval takto:

$ python ukoly.py vypis
[x] 1. dej si čaj
[x] 2. bež do kina
$ python ukoly.py pridej
Nový úkol: Udělej si úkoly
Zadán úkol 3
$ python ukoly.py vyres 3
$ python ukoly.py vypis
[x] 1. dej si čaj
[x] 2. bež do kina
[x] 3. Udělej si úkoly

Nejprve musíme naimportovat knihovnu click.

Následně označíme hlavní funkci dekorátorem @click.group(). Tím řekneme, že to vlastně není příkaz sám o sobě, ale bude to skupina dalších příkazů. Hned si jeden vytvoříme a necháme ho vypisovat úkoly.

Session = sessionmaker(bind=db)
sezeni = Session()


@click.group()
def ukolnik():
    pass


@ukolnik.command()
def vypis():
    dotaz = sezeni.query(Ukol)
    ukoly = dotaz.all()
    for ukol in ukoly:
        symbol = "[x]" if ukol.vyreseno else "[ ]"
        print(f"{symbol} {ukol.id}. {ukol.text}")


if __name__ == "__main__":
    ukolnik()

Výpis by měl pořád vypadat stejně, akorát ho budeme volat trochu jinak.

Krok 4 – přidávání úkolů

Přidejte do programu další příkaz. Bude se jmenovat pridej, a vždy od uživatele dostane text úkolu, který hned vypíše.

Řešení

Teď můžeme metodu upravit tak, aby úkol opravdu vytvořila a uložila.

Nejprve musíme vytvořit instanci třídy Ukol. Tu potom přidáme do našeho sezení a řekneme databázi, že ji chceme uložit. Aktuální čas dostaneme ze standardní knihovny, takže nezapomeňte na začátek programu přidat from datetime import datetime.

Řešení

Pokud bychom vytvářeli několik úkolů, můžeme je všechny přidat a teprve potom jednou zavolat commit. Pokud bychom na toto poslední volání zapomněli, záznamy budou časem uloženy taky, ale nebude úplně přímočaré poznat, kde k tomu dojde. Je lepší najít vhodné místo a commit zavolat.

Krok 5 – vytvoření databáze

Momentálně program funguje celkem dobře, ale vždycky potřebuje, aby na disku existoval soubor s databází. Bylo by hezké, kdyby si dokázal vytvořit prázdnou databázi.

Nejprve přesuneme vytváření sezení do samostatné funkce, kterou zavoláme v každém příkazu. Toto nebude mít vliv na výsledné chování, ale program bude trošku čitelnější a jednodušší na orientaci.

Vytvořte funkci pripoj_se, která nebude mít žádné argumenty a bude vracet nové sezení.

Řešení

K vytvoření prázdné databáze stačí do funkce pripoj_se přidat jeden řádek:

def pripoj_se():
    Base.metadata.create_all(db)
    Session = sessionmaker(bind=db)
    return Session()

Třída Base je společný předek všech našich tříd reprezentujících data. My máme pouze jednu, ale to není na závadu. Nově přidané volání se podívá, jestli pro každou třídu existuje odpovídající tabulka, a případně ji vytvoří.

Tato funkce není úplně všemocná. Pokud například budeme měnit existující tabulku, s největší pravděpodobností dostaneme chybovou hlášku. Na obecné migrace dat je lepší použít něco sofistikovanějšího, jako třeba knihovnu alembic.

Krok 6 – řešení úkolů

Pojďme přidat poslední chybějící část: označování úkolů za vyřešené. Začneme zase přidáním kostry příkazu, která dostane číslo úkolu a vypíše ho na výstup.

Řešení

Postup pro vyřešení úkolu bude následovný: najdeme úkol podle čísla, nastavíme mu čas vyřešení a uložíme ho.

Metodu query pro vytvoření dotazu už známe. Tentokrát ovšem místo všech úkolů chceme najít jeden konkrétní. K tomu použijeme filter_by, která přes pojmenované argumenty umí vyfiltrovat pouze některé řádky.

Pro vykonání dotazu existuje kromě nám už známé all() několik metod:

  • all vrací všechny výsledky jako seznam
  • first vrací první výsledek, další ignoruje
  • one zkontroluje, že máme právě jeden výsledek, a vrátí ho. Pokud by jich byl jiný počet, vyhodí výjimku.
  • one_or_none se chová podobně, ale místo výjimky vrací None
  • scalar očekává ve výsledku jeden řádek s jediným sloupcem, a vrací přímo hodnotu z tohoto jediného pole
@ukolnik.command()
@click.argument("cislo_ukolu", type=click.INT)
def vyres(cislo_ukolu):
    sezeni = pripoj_se()
    dotaz = sezeni.query(Ukol)
    ukol = dotaz.filter_by(id=cislo_ukolu).one()
    ukol.vyreseno = datetime.now()
    sezeni.add(ukol)
    sezeni.commit()

Mohli bychom použít metodu get(cislo_ukolu), která najde úkol podle klíče. To bychom si ale neprocvičili filtrování výsledků dotazu.

Krok 7 – výpis jen nedokončených úkolů

Filtrování můžeme aplikovat i pro výpis úkolů. Například bychom mohli vypisovat jenom úkoly, které ještě nejsou dokončené.

Na to se nám může hodit metoda filter, která umožňuje více porovnání než známá filter_by.

@ukolnik.command()
@click.option("--jen-nehotove", default=False, is_flag=True)
def vypis(jen_nehotove):
    sezeni = pripoj_se()
    dotaz = sezeni.query(Ukol)

    if jen_nehotove:
        dotaz = dotaz.filter(Ukol.vyreseno == None)

    ukoly = dotaz.all()
    for ukol in ukoly:
        symbol = "[x]" if ukol.vyreseno else "[ ]"
        print(f"{symbol} {ukol.id}. {ukol.text}")

Další vylepšení

Tady je několik tipů, co by se v tomto programu dalo vylepšit:

  • Ošetření chyb: momentálně program spadne, pokud se pokusíme vyřešit neexistující úkol.
  • Řazení výpisu: teď jsou úkoly vypsané od nejstaršího. Možná bychom je chtěli řadit v opačném pořadí. Dotaz má metodu order_by(), které můžeme zadat sloupec, podle kterého se bude řadit. Také můžeme řadit v opačném pořadí, třeba pomocí Ukol.zadano.desc().
  • Mohli bychom přidat další příkaz, který smaže některé úkoly (třeba ty, které jsou vyřešené, nebo starší než nějaký limit). Dotaz s aplikovanými filtry má metodu delete(), která smaže všechny odpovídající záznamy.
{
  "data": {
    "sessionMaterial": {
      "id": "session-material:2019/brno-jaro-knihovny:sqlalchemy:0",
      "title": "To-Do List",
      "html": "\n          \n    \n\n    <h1>To-Do List</h1>\n<h2>Co je c&#xED;lem tohoto cvi&#x10D;en&#xED;?</h2>\n<p>Na tomto p&#x159;&#xED;kladu si vyzkou&#x161;&#xED;me pou&#x17E;&#xED;t knihovnu SQLAlchemy na pr&#xE1;ci s datab&#xE1;z&#xED;.\nNap&#xED;&#x161;eme si jednoduch&#xFD; program pro evidenci &#xFA;kol&#x16F;. Tak&#xE9; si procvi&#x10D;&#xED;me pr&#xE1;ci s\nknihovnou Click.</p>\n<h2>P&#x159;edpoklady</h2>\n<p>P&#x159;edpokl&#xE1;d&#xE1;me z&#xE1;kladn&#xED; znalost Pythonu. M&#x11B;li byste m&#xED;t po&#x10D;&#xED;ta&#x10D; s nainstalovan&#xFD;m\ninterpretem jazyka Python ve verzi aspo&#x148; 3.6. Pro za&#x10D;&#xE1;tek si tak&#xE9; vytvo&#x159;te nov&#xE9;\nvirtu&#xE1;ln&#xED; prost&#x159;ed&#xED;.</p>\n<p>Do tohoto prost&#x159;ed&#xED; si nainstalujte knihovny <code>sqlalchemy</code> a <code>click</code>.</p>\n<h2>Krok 1 &#x2013; p&#x159;ipojen&#xED; k datab&#xE1;zi</h2>\n<p>Pro zjednodu&#x161;en&#xED; za&#x10D;neme &#x10D;ten&#xED;m dat z datab&#xE1;ze. M&#x16F;&#x17E;ete si st&#xE1;hnout\n<a href=\"/2019/brno-jaro-knihovny/beginners/todo-list/static/ukoly.sqlite\">p&#x159;ipraven&#xE1; data</a>. St&#xE1;hn&#x11B;te si ho do\nstejn&#xE9;ho adres&#xE1;&#x159;e, ve kter&#xE9;m budete m&#xED;t samotn&#xFD; program.</p>\n<p>Do souboru <code>ukoly.py</code> si st&#xE1;hn&#x11B;te tuto z&#xE1;kladn&#xED; kostru.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"c1\"># ukoly.py</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sqlalchemy</span> <span class=\"kn\">import</span> <span class=\"n\">create_engine</span>\n\n\n<span class=\"n\">db</span> <span class=\"o\">=</span> <span class=\"n\">create_engine</span><span class=\"p\">(</span><span class=\"s2\">&quot;sqlite:///ukoly.sqlite&quot;</span><span class=\"p\">)</span>\n</pre></div><p>Funkce <code>create_engine</code> vytv&#xE1;&#x159;&#xED; spojen&#xED; s datab&#xE1;z&#xED; <code>ukoly.sqlite</code>, kter&#xE1; je\nulo&#x17E;en&#xE1; v aktu&#xE1;ln&#xED;m adres&#xE1;&#x159;i. Knihovna <code>sqlalchemy</code> um&#xED; pracovat i s jin&#xFD;mi\ntypy datab&#xE1;z&#xED; ne&#x17E; je <code>SQLite</code>. Ta je ale nejjednodu&#x161;&#x161;&#xED;, a velice vhodn&#xE1; na\nulo&#x17E;en&#xED; dat, se kter&#xFD;mi budeme pracovat.</p>\n<p>Moment&#xE1;ln&#x11B; program nic ned&#x11B;l&#xE1;. Nejprve mus&#xED;me nadefinovat, jak vlastn&#x11B; na&#x161;e\ndata vypadaj&#xED;.</p>\n<h2>Krok 2 &#x2013; prvn&#xED; dotaz</h2>\n<p>Samotnou datab&#xE1;z&#xED; si m&#x16F;&#x17E;eme p&#x159;estavit jako n&#x11B;kolik tabulek, kter&#xE9; maj&#xED; n&#x11B;jak\npojmenovan&#xE9; sloupce. V na&#x161;em p&#x159;&#xED;kladu budeme pot&#x159;ebovat jedinou tabulku, ale\nklidn&#x11B; by jich mohlo b&#xFD;t v&#xED;c.</p>\n<p>Pro ka&#x17E;dou tabulku budeme pot&#x159;ebovat t&#x159;&#xED;du (<code>class</code>), jej&#xED;&#x17E; instance budou\nreprezentovat jednotliv&#xE9; &#x159;&#xE1;dky v n&#xED;.</p>\n<p>Metodu <code>__repr__</code> pou&#x17E;&#xED;v&#xE1; Python, kdy&#x17E; pot&#x159;ebuje zobrazit instanci t&#xE9;to t&#x159;&#xED;dy.\nNen&#xED; ur&#x10D;en&#xE1; na v&#xFD;pis pro u&#x17E;ivatele, ale pro lad&#x11B;n&#xED; programu.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">sqlalchemy</span> <span class=\"kn\">import</span> <span class=\"n\">create_engine</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sqlalchemy</span> <span class=\"kn\">import</span> <span class=\"n\">Column</span><span class=\"p\">,</span> <span class=\"n\">Integer</span><span class=\"p\">,</span> <span class=\"n\">String</span><span class=\"p\">,</span> <span class=\"n\">DateTime</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sqlalchemy.exc.declarative</span> <span class=\"kn\">import</span> <span class=\"n\">declarative_base</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sqlalchemy.orm</span> <span class=\"kn\">import</span> <span class=\"n\">sessionmaker</span>\n\n\n<span class=\"n\">db</span> <span class=\"o\">=</span> <span class=\"n\">create_engine</span><span class=\"p\">(</span><span class=\"s2\">&quot;sqlite:///ukoly.sqlite&quot;</span><span class=\"p\">)</span>\n<span class=\"n\">Base</span> <span class=\"o\">=</span> <span class=\"n\">declarative_base</span><span class=\"p\">()</span>\n\n\n<span class=\"k\">class</span> <span class=\"nc\">Ukol</span><span class=\"p\">(</span><span class=\"n\">Base</span><span class=\"p\">):</span>\n    <span class=\"c1\"># N&#xE1;zev tabulky v datab&#xE1;z&#xED;.</span>\n    <span class=\"n\">__tablename__</span> <span class=\"o\">=</span> <span class=\"s2\">&quot;ukoly&quot;</span>\n\n    <span class=\"c1\"># &#x10C;&#xED;seln&#xFD; identifik&#xE1;tor &#xFA;kolu, toto &#x10D;&#xED;slo bude jedine&#x10D;n&#xE9;.</span>\n    <span class=\"nb\">id</span> <span class=\"o\">=</span> <span class=\"n\">Column</span><span class=\"p\">(</span><span class=\"n\">Integer</span><span class=\"p\">,</span> <span class=\"n\">primary_key</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n    <span class=\"c1\"># Text &#xFA;kolu.</span>\n    <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">Column</span><span class=\"p\">(</span><span class=\"n\">String</span><span class=\"p\">)</span>\n    <span class=\"c1\"># Datum a &#x10D;as zad&#xE1;n&#xED; &#xFA;kolu.</span>\n    <span class=\"n\">zadano</span> <span class=\"o\">=</span> <span class=\"n\">Column</span><span class=\"p\">(</span><span class=\"n\">DateTime</span><span class=\"p\">)</span>\n    <span class=\"c1\"># Datum a &#x10D;as vy&#x159;e&#x161;en&#xED; &#xFA;kolu. Pr&#xE1;zdn&#xE1; hodnota znamen&#xE1; nehotov&#xFD; &#xFA;kol.</span>\n    <span class=\"n\">vyreseno</span> <span class=\"o\">=</span> <span class=\"n\">Column</span><span class=\"p\">(</span><span class=\"n\">DateTime</span><span class=\"p\">)</span>\n\n    <span class=\"k\">def</span> <span class=\"fm\">__repr__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n        <span class=\"k\">return</span> <span class=\"n\">f</span><span class=\"s2\">&quot;&lt;Ukol(text=&apos;{self.text}&apos;, zadano={self.zadano}, vyreseno={self.vyreseno})&gt;&quot;</span>\n</pre></div><p>P&#x159;idejte jeden import a na konec souboru je&#x161;t&#x11B; n&#x11B;kolik &#x159;&#xE1;dk&#x16F;. Pokud chceme z\ndatab&#xE1;ze vytahovat data, nebo je ukl&#xE1;dat, pot&#x159;ebujeme vytvo&#x159;it je&#x161;t&#x11B; jeden\nobjekt. Takzvan&#xE9; sezen&#xED; (anglicky <em>session</em>) vyu&#x17E;ije d&#x159;&#xED;ve vytvo&#x159;en&#xE9; spojen&#xED; a\numo&#x17E;&#x148;uje n&#xE1;m pou&#x17E;&#xED;t v&#x161;echny nadefinovan&#xE9; t&#x159;&#xED;dy.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">sqlalchemy.orm</span> <span class=\"kn\">import</span> <span class=\"n\">sessionmaker</span>\n\n<span class=\"o\">...</span>\n\n<span class=\"n\">Session</span> <span class=\"o\">=</span> <span class=\"n\">sessionmaker</span><span class=\"p\">(</span><span class=\"n\">bind</span><span class=\"o\">=</span><span class=\"n\">db</span><span class=\"p\">)</span>\n<span class=\"n\">sezeni</span> <span class=\"o\">=</span> <span class=\"n\">Session</span><span class=\"p\">()</span>\n\n<span class=\"n\">dotaz</span> <span class=\"o\">=</span> <span class=\"n\">sezeni</span><span class=\"o\">.</span><span class=\"n\">query</span><span class=\"p\">(</span><span class=\"n\">Ukol</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">dotaz</span><span class=\"o\">.</span><span class=\"n\">all</span><span class=\"p\">())</span>\n</pre></div><p>Metoda <code>query</code> vytvo&#x159;&#xED; dotaz, kter&#xFD; bude vracet instance t&#x159;&#xED;dy <code>Ukol</code>. Nezad&#xE1;me\n&#x17E;&#xE1;dn&#xE9; omezen&#xED;, tak&#x17E;e chceme v&#x161;echny &#xFA;koly. Metoda <code>all</code> na tomto dotazu potom\nvrac&#xED; seznam v&#x161;ech &#x159;&#xE1;dk&#x16F; v tabulce, kter&#xE9; odpov&#xED;daj&#xED; dotazu.</p>\n<p>Tento program u&#x17E; p&#x16F;jde spustit a bude vypisovat v&#x161;echny &#xFA;koly. Tento v&#xFD;pis ale\nnen&#xED; &#xFA;pln&#x11B; p&#x11B;kn&#xFD;. Upravte program tak, aby ka&#x17E;d&#xFD; &#xFA;kol byl na samostatn&#xE9;m &#x159;&#xE1;dku,\na v hezky &#x10D;iteln&#xE9;m form&#xE1;tu. Jednotliv&#xE9; objekty maj&#xED; atributy <code>id</code> a <code>text</code>,\nkter&#xE9; m&#x16F;&#x17E;eme pou&#x17E;&#xED;t.</p>\n<div class=\"solution\" id=\"solution-0\">\n    <h3>&#x158;e&#x161;en&#xED;</h3>\n    <div class=\"solution-cover\">\n        <a href=\"/2019/brno-jaro-knihovny/beginners/todo-list/index/solutions/0/\"><span class=\"link-text\">Uk&#xE1;zat &#x159;e&#x161;en&#xED;</span></a>\n    </div>\n    <div class=\"solution-body\" aria-hidden=\"true\">\n        <div class=\"highlight\"><pre><span></span><span class=\"o\">...</span>\n\n<span class=\"n\">ukoly</span> <span class=\"o\">=</span> <span class=\"n\">dotaz</span><span class=\"o\">.</span><span class=\"n\">all</span><span class=\"p\">()</span>\n<span class=\"k\">for</span> <span class=\"n\">ukol</span> <span class=\"ow\">in</span> <span class=\"n\">ukoly</span><span class=\"p\">:</span>\n    <span class=\"n\">symbol</span> <span class=\"o\">=</span> <span class=\"s2\">&quot;[x]&quot;</span> <span class=\"k\">if</span> <span class=\"n\">ukol</span><span class=\"o\">.</span><span class=\"n\">vyreseno</span> <span class=\"k\">else</span> <span class=\"s2\">&quot;[ ]&quot;</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"s2\">&quot;{symbol} {ukol.id}. {ukol.text}&quot;</span><span class=\"p\">)</span>\n</pre></div>\n    </div>\n</div><h2>Krok 3 &#x2013; u&#x17E;ivatelsk&#xE9; rozhran&#xED;</h2>\n<p>Te&#x10F; program uprav&#xED;me tak, abychom ho mohli postupn&#x11B; roz&#x161;i&#x159;ovat dal&#x161;&#xED;mi p&#x159;&#xED;kazy.</p>\n<p>Ve fin&#xE1;le chceme, aby program fungoval takto:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>python ukoly.py vypis\n<span class=\"go\">[x] 1. dej si &#x10D;aj</span>\n<span class=\"go\">[x] 2. be&#x17E; do kina</span>\n<span class=\"gp\">$ </span>python ukoly.py pridej\n<span class=\"go\">Nov&#xFD; &#xFA;kol: Ud&#x11B;lej si &#xFA;koly</span>\n<span class=\"go\">Zad&#xE1;n &#xFA;kol 3</span>\n<span class=\"gp\">$ </span>python ukoly.py vyres <span class=\"m\">3</span>\n<span class=\"gp\">$ </span>python ukoly.py vypis\n<span class=\"go\">[x] 1. dej si &#x10D;aj</span>\n<span class=\"go\">[x] 2. be&#x17E; do kina</span>\n<span class=\"go\">[x] 3. Ud&#x11B;lej si &#xFA;koly</span>\n</pre></div><p>Nejprve mus&#xED;me naimportovat knihovnu <code>click</code>.</p>\n<p>N&#xE1;sledn&#x11B; ozna&#x10D;&#xED;me hlavn&#xED; funkci dekor&#xE1;torem <code>@click.group()</code>. T&#xED;m &#x159;ekneme, &#x17E;e\nto vlastn&#x11B; nen&#xED; p&#x159;&#xED;kaz s&#xE1;m o sob&#x11B;, ale bude to skupina dal&#x161;&#xED;ch p&#x159;&#xED;kaz&#x16F;. Hned si\njeden vytvo&#x159;&#xED;me a nech&#xE1;me ho vypisovat &#xFA;koly.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">Session</span> <span class=\"o\">=</span> <span class=\"n\">sessionmaker</span><span class=\"p\">(</span><span class=\"n\">bind</span><span class=\"o\">=</span><span class=\"n\">db</span><span class=\"p\">)</span>\n<span class=\"n\">sezeni</span> <span class=\"o\">=</span> <span class=\"n\">Session</span><span class=\"p\">()</span>\n\n\n<span class=\"nd\">@click.group</span><span class=\"p\">()</span>\n<span class=\"k\">def</span> <span class=\"nf\">ukolnik</span><span class=\"p\">():</span>\n    <span class=\"k\">pass</span>\n\n\n<span class=\"nd\">@ukolnik.command</span><span class=\"p\">()</span>\n<span class=\"k\">def</span> <span class=\"nf\">vypis</span><span class=\"p\">():</span>\n    <span class=\"n\">dotaz</span> <span class=\"o\">=</span> <span class=\"n\">sezeni</span><span class=\"o\">.</span><span class=\"n\">query</span><span class=\"p\">(</span><span class=\"n\">Ukol</span><span class=\"p\">)</span>\n    <span class=\"n\">ukoly</span> <span class=\"o\">=</span> <span class=\"n\">dotaz</span><span class=\"o\">.</span><span class=\"n\">all</span><span class=\"p\">()</span>\n    <span class=\"k\">for</span> <span class=\"n\">ukol</span> <span class=\"ow\">in</span> <span class=\"n\">ukoly</span><span class=\"p\">:</span>\n        <span class=\"n\">symbol</span> <span class=\"o\">=</span> <span class=\"s2\">&quot;[x]&quot;</span> <span class=\"k\">if</span> <span class=\"n\">ukol</span><span class=\"o\">.</span><span class=\"n\">vyreseno</span> <span class=\"k\">else</span> <span class=\"s2\">&quot;[ ]&quot;</span>\n        <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"s2\">&quot;{symbol} {ukol.id}. {ukol.text}&quot;</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">if</span> <span class=\"vm\">__name__</span> <span class=\"o\">==</span> <span class=\"s2\">&quot;__main__&quot;</span><span class=\"p\">:</span>\n    <span class=\"n\">ukolnik</span><span class=\"p\">()</span>\n</pre></div><p>V&#xFD;pis by m&#x11B;l po&#x159;&#xE1;d vypadat stejn&#x11B;, akor&#xE1;t ho budeme volat trochu jinak.</p>\n<h2>Krok 4 &#x2013; p&#x159;id&#xE1;v&#xE1;n&#xED; &#xFA;kol&#x16F;</h2>\n<p>P&#x159;idejte do programu dal&#x161;&#xED; p&#x159;&#xED;kaz. Bude se jmenovat <code>pridej</code>, a v&#x17E;dy od\nu&#x17E;ivatele dostane text &#xFA;kolu, kter&#xFD; hned vyp&#xED;&#x161;e.</p>\n<div class=\"solution\" id=\"solution-1\">\n    <h3>&#x158;e&#x161;en&#xED;</h3>\n    <div class=\"solution-cover\">\n        <a href=\"/2019/brno-jaro-knihovny/beginners/todo-list/index/solutions/1/\"><span class=\"link-text\">Uk&#xE1;zat &#x159;e&#x161;en&#xED;</span></a>\n    </div>\n    <div class=\"solution-body\" aria-hidden=\"true\">\n        <div class=\"highlight\"><pre><span></span><span class=\"nd\">@ukolnik.command</span><span class=\"p\">()</span>\n<span class=\"nd\">@click.option</span><span class=\"p\">(</span><span class=\"s2\">&quot;--zadani&quot;</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s2\">&quot;Nov&#xFD; &#xFA;kol&quot;</span><span class=\"p\">)</span>\n<span class=\"k\">def</span> <span class=\"nf\">pridej</span><span class=\"p\">(</span><span class=\"n\">zadani</span><span class=\"p\">):</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"s2\">&quot;OK: {zadani}&quot;</span><span class=\"p\">)</span>\n</pre></div>\n    </div>\n</div><p>Te&#x10F; m&#x16F;&#x17E;eme metodu upravit tak, aby &#xFA;kol opravdu vytvo&#x159;ila a ulo&#x17E;ila.</p>\n<p>Nejprve mus&#xED;me vytvo&#x159;it instanci t&#x159;&#xED;dy <code>Ukol</code>. Tu potom p&#x159;id&#xE1;me do na&#x161;eho\nsezen&#xED; a &#x159;ekneme datab&#xE1;zi, &#x17E;e ji chceme ulo&#x17E;it. Aktu&#xE1;ln&#xED; &#x10D;as dostaneme ze\nstandardn&#xED; knihovny, tak&#x17E;e nezapome&#x148;te na za&#x10D;&#xE1;tek programu p&#x159;idat <code>from\ndatetime import datetime</code>.</p>\n<div class=\"solution\" id=\"solution-2\">\n    <h3>&#x158;e&#x161;en&#xED;</h3>\n    <div class=\"solution-cover\">\n        <a href=\"/2019/brno-jaro-knihovny/beginners/todo-list/index/solutions/2/\"><span class=\"link-text\">Uk&#xE1;zat &#x159;e&#x161;en&#xED;</span></a>\n    </div>\n    <div class=\"solution-body\" aria-hidden=\"true\">\n        <div class=\"highlight\"><pre><span></span><span class=\"nd\">@ukolnik.command</span><span class=\"p\">()</span>\n<span class=\"nd\">@click.option</span><span class=\"p\">(</span><span class=\"s2\">&quot;--zadani&quot;</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s2\">&quot;Nov&#xFD; &#xFA;kol&quot;</span><span class=\"p\">)</span>\n<span class=\"k\">def</span> <span class=\"nf\">pridej</span><span class=\"p\">(</span><span class=\"n\">zadani</span><span class=\"p\">):</span>\n    <span class=\"n\">ukol</span> <span class=\"o\">=</span> <span class=\"n\">Ukol</span><span class=\"p\">(</span><span class=\"n\">text</span><span class=\"o\">=</span><span class=\"n\">zadani</span><span class=\"p\">,</span> <span class=\"n\">zadano</span><span class=\"o\">=</span><span class=\"n\">datetime</span><span class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">())</span>\n    <span class=\"n\">sezeni</span><span class=\"o\">.</span><span class=\"n\">add</span><span class=\"p\">(</span><span class=\"n\">ukol</span><span class=\"p\">)</span>\n    <span class=\"n\">sezeni</span><span class=\"o\">.</span><span class=\"n\">commit</span><span class=\"p\">()</span>\n</pre></div>\n    </div>\n</div><p>Pokud bychom vytv&#xE1;&#x159;eli n&#x11B;kolik &#xFA;kol&#x16F;, m&#x16F;&#x17E;eme je v&#x161;echny p&#x159;idat a teprve potom\njednou zavolat <code>commit</code>. Pokud bychom na toto posledn&#xED; vol&#xE1;n&#xED; zapomn&#x11B;li,\nz&#xE1;znamy budou &#x10D;asem ulo&#x17E;eny taky, ale nebude &#xFA;pln&#x11B; p&#x159;&#xED;mo&#x10D;ar&#xE9; poznat, kde k tomu\ndojde. Je lep&#x161;&#xED; naj&#xED;t vhodn&#xE9; m&#xED;sto a <code>commit</code> zavolat.</p>\n<h2>Krok 5 &#x2013; vytvo&#x159;en&#xED; datab&#xE1;ze</h2>\n<p>Moment&#xE1;ln&#x11B; program funguje celkem dob&#x159;e, ale v&#x17E;dycky pot&#x159;ebuje, aby na disku\nexistoval soubor s datab&#xE1;z&#xED;. Bylo by hezk&#xE9;, kdyby si dok&#xE1;zal vytvo&#x159;it pr&#xE1;zdnou\ndatab&#xE1;zi.</p>\n<p>Nejprve p&#x159;esuneme vytv&#xE1;&#x159;en&#xED; sezen&#xED; do samostatn&#xE9; funkce, kterou zavol&#xE1;me v\nka&#x17E;d&#xE9;m p&#x159;&#xED;kazu. Toto nebude m&#xED;t vliv na v&#xFD;sledn&#xE9; chov&#xE1;n&#xED;, ale program bude\ntro&#x161;ku &#x10D;iteln&#x11B;j&#x161;&#xED; a jednodu&#x161;&#x161;&#xED; na orientaci.</p>\n<p>Vytvo&#x159;te funkci <code>pripoj_se</code>, kter&#xE1; nebude m&#xED;t &#x17E;&#xE1;dn&#xE9; argumenty a bude vracet\nnov&#xE9; sezen&#xED;.</p>\n<div class=\"solution\" id=\"solution-3\">\n    <h3>&#x158;e&#x161;en&#xED;</h3>\n    <div class=\"solution-cover\">\n        <a href=\"/2019/brno-jaro-knihovny/beginners/todo-list/index/solutions/3/\"><span class=\"link-text\">Uk&#xE1;zat &#x159;e&#x161;en&#xED;</span></a>\n    </div>\n    <div class=\"solution-body\" aria-hidden=\"true\">\n        <div class=\"highlight\"><pre><span></span><span class=\"o\">...</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">pripoj_se</span><span class=\"p\">():</span>\n    <span class=\"n\">Session</span> <span class=\"o\">=</span> <span class=\"n\">sessionmaker</span><span class=\"p\">(</span><span class=\"n\">bind</span><span class=\"o\">=</span><span class=\"n\">db</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">Session</span><span class=\"p\">()</span>\n\n\n<span class=\"nd\">@click.group</span><span class=\"p\">()</span>\n<span class=\"k\">def</span> <span class=\"nf\">ukolnik</span><span class=\"p\">():</span>\n    <span class=\"k\">pass</span>\n\n\n<span class=\"nd\">@ukolnik.command</span><span class=\"p\">()</span>\n<span class=\"k\">def</span> <span class=\"nf\">vypis</span><span class=\"p\">():</span>\n    <span class=\"n\">sezeni</span> <span class=\"o\">=</span> <span class=\"n\">pripoj_se</span><span class=\"p\">()</span>\n    <span class=\"o\">...</span>\n\n\n<span class=\"nd\">@ukolnik.command</span><span class=\"p\">()</span>\n<span class=\"nd\">@click.option</span><span class=\"p\">(</span><span class=\"s2\">&quot;--zadani&quot;</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s2\">&quot;Nov&#xFD; &#xFA;kol&quot;</span><span class=\"p\">)</span>\n<span class=\"k\">def</span> <span class=\"nf\">pridej</span><span class=\"p\">(</span><span class=\"n\">zadani</span><span class=\"p\">):</span>\n    <span class=\"n\">sezeni</span> <span class=\"o\">=</span> <span class=\"n\">pripoj_se</span><span class=\"p\">()</span>\n    <span class=\"o\">...</span>\n</pre></div>\n    </div>\n</div><p>K vytvo&#x159;en&#xED; pr&#xE1;zdn&#xE9; datab&#xE1;ze sta&#x10D;&#xED; do funkce <code>pripoj_se</code> p&#x159;idat jeden &#x159;&#xE1;dek:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">pripoj_se</span><span class=\"p\">():</span>\n    <span class=\"n\">Base</span><span class=\"o\">.</span><span class=\"n\">metadata</span><span class=\"o\">.</span><span class=\"n\">create_all</span><span class=\"p\">(</span><span class=\"n\">db</span><span class=\"p\">)</span>\n    <span class=\"n\">Session</span> <span class=\"o\">=</span> <span class=\"n\">sessionmaker</span><span class=\"p\">(</span><span class=\"n\">bind</span><span class=\"o\">=</span><span class=\"n\">db</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">Session</span><span class=\"p\">()</span>\n</pre></div><p>T&#x159;&#xED;da <code>Base</code> je spole&#x10D;n&#xFD; p&#x159;edek v&#x161;ech na&#x161;ich t&#x159;&#xED;d reprezentuj&#xED;c&#xED;ch data. My\nm&#xE1;me pouze jednu, ale to nen&#xED; na z&#xE1;vadu. Nov&#x11B; p&#x159;idan&#xE9; vol&#xE1;n&#xED; se pod&#xED;v&#xE1;, jestli\npro ka&#x17E;dou t&#x159;&#xED;du existuje odpov&#xED;daj&#xED;c&#xED; tabulka, a p&#x159;&#xED;padn&#x11B; ji vytvo&#x159;&#xED;.</p>\n<p>Tato funkce nen&#xED; &#xFA;pln&#x11B; v&#x161;emocn&#xE1;. Pokud nap&#x159;&#xED;klad budeme m&#x11B;nit existuj&#xED;c&#xED;\ntabulku, s nejv&#x11B;t&#x161;&#xED; pravd&#x11B;podobnost&#xED; dostaneme chybovou hl&#xE1;&#x161;ku. Na obecn&#xE9;\nmigrace dat je lep&#x161;&#xED; pou&#x17E;&#xED;t n&#x11B;co sofistikovan&#x11B;j&#x161;&#xED;ho, jako t&#x159;eba knihovnu\n<a href=\"https://pypi.org/project/alembic/\">alembic</a>.</p>\n<h2>Krok 6 &#x2013; &#x159;e&#x161;en&#xED; &#xFA;kol&#x16F;</h2>\n<p>Poj&#x10F;me p&#x159;idat posledn&#xED; chyb&#x11B;j&#xED;c&#xED; &#x10D;&#xE1;st: ozna&#x10D;ov&#xE1;n&#xED; &#xFA;kol&#x16F; za vy&#x159;e&#x161;en&#xE9;. Za&#x10D;neme\nzase p&#x159;id&#xE1;n&#xED;m kostry p&#x159;&#xED;kazu, kter&#xE1; dostane &#x10D;&#xED;slo &#xFA;kolu a vyp&#xED;&#x161;e ho na v&#xFD;stup.</p>\n<div class=\"solution\" id=\"solution-4\">\n    <h3>&#x158;e&#x161;en&#xED;</h3>\n    <div class=\"solution-cover\">\n        <a href=\"/2019/brno-jaro-knihovny/beginners/todo-list/index/solutions/4/\"><span class=\"link-text\">Uk&#xE1;zat &#x159;e&#x161;en&#xED;</span></a>\n    </div>\n    <div class=\"solution-body\" aria-hidden=\"true\">\n        <div class=\"highlight\"><pre><span></span><span class=\"nd\">@ukolnik.command</span><span class=\"p\">()</span>\n<span class=\"nd\">@click.argument</span><span class=\"p\">(</span><span class=\"s2\">&quot;cislo_ukolu&quot;</span><span class=\"p\">,</span> <span class=\"nb\">type</span><span class=\"o\">=</span><span class=\"n\">click</span><span class=\"o\">.</span><span class=\"n\">INT</span><span class=\"p\">)</span>\n<span class=\"k\">def</span> <span class=\"nf\">vyres</span><span class=\"p\">(</span><span class=\"n\">cislo_ukolu</span><span class=\"p\">):</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"s2\">&quot;Zna&#x10D;&#xED;m {cislo_ukolu} jako vy&#x159;e&#x161;en&#xE9;&quot;</span><span class=\"p\">)</span>\n</pre></div>\n    </div>\n</div><p>Postup pro vy&#x159;e&#x161;en&#xED; &#xFA;kolu bude n&#xE1;sledovn&#xFD;: najdeme &#xFA;kol podle &#x10D;&#xED;sla, nastav&#xED;me\nmu &#x10D;as vy&#x159;e&#x161;en&#xED; a ulo&#x17E;&#xED;me ho.</p>\n<p>Metodu <code>query</code> pro vytvo&#x159;en&#xED; dotazu u&#x17E; zn&#xE1;me. Tentokr&#xE1;t ov&#x161;em m&#xED;sto v&#x161;ech &#xFA;kol&#x16F;\nchceme naj&#xED;t jeden konkr&#xE9;tn&#xED;. K tomu pou&#x17E;ijeme <code>filter_by</code>, kter&#xE1; p&#x159;es\npojmenovan&#xE9; argumenty um&#xED; vyfiltrovat pouze n&#x11B;kter&#xE9; &#x159;&#xE1;dky.</p>\n<p>Pro vykon&#xE1;n&#xED; dotazu existuje krom&#x11B; n&#xE1;m u&#x17E; zn&#xE1;m&#xE9; <code>all()</code> n&#x11B;kolik metod:</p>\n<ul>\n<li><code>all</code> vrac&#xED; v&#x161;echny v&#xFD;sledky jako seznam</li>\n<li><code>first</code> vrac&#xED; prvn&#xED; v&#xFD;sledek, dal&#x161;&#xED; ignoruje</li>\n<li><code>one</code> zkontroluje, &#x17E;e m&#xE1;me pr&#xE1;v&#x11B; jeden v&#xFD;sledek, a vr&#xE1;t&#xED; ho. Pokud by jich\nbyl jin&#xFD; po&#x10D;et, vyhod&#xED; v&#xFD;jimku.</li>\n<li><code>one_or_none</code> se chov&#xE1; podobn&#x11B;, ale m&#xED;sto v&#xFD;jimky vrac&#xED; <code>None</code></li>\n<li><code>scalar</code> o&#x10D;ek&#xE1;v&#xE1; ve v&#xFD;sledku jeden &#x159;&#xE1;dek s jedin&#xFD;m sloupcem, a vrac&#xED; p&#x159;&#xED;mo\nhodnotu z tohoto jedin&#xE9;ho pole</li>\n</ul>\n<div class=\"highlight\"><pre><span></span><span class=\"nd\">@ukolnik.command</span><span class=\"p\">()</span>\n<span class=\"nd\">@click.argument</span><span class=\"p\">(</span><span class=\"s2\">&quot;cislo_ukolu&quot;</span><span class=\"p\">,</span> <span class=\"nb\">type</span><span class=\"o\">=</span><span class=\"n\">click</span><span class=\"o\">.</span><span class=\"n\">INT</span><span class=\"p\">)</span>\n<span class=\"k\">def</span> <span class=\"nf\">vyres</span><span class=\"p\">(</span><span class=\"n\">cislo_ukolu</span><span class=\"p\">):</span>\n    <span class=\"n\">sezeni</span> <span class=\"o\">=</span> <span class=\"n\">pripoj_se</span><span class=\"p\">()</span>\n    <span class=\"n\">dotaz</span> <span class=\"o\">=</span> <span class=\"n\">sezeni</span><span class=\"o\">.</span><span class=\"n\">query</span><span class=\"p\">(</span><span class=\"n\">Ukol</span><span class=\"p\">)</span>\n    <span class=\"n\">ukol</span> <span class=\"o\">=</span> <span class=\"n\">dotaz</span><span class=\"o\">.</span><span class=\"n\">filter_by</span><span class=\"p\">(</span><span class=\"nb\">id</span><span class=\"o\">=</span><span class=\"n\">cislo_ukolu</span><span class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">one</span><span class=\"p\">()</span>\n    <span class=\"n\">ukol</span><span class=\"o\">.</span><span class=\"n\">vyreseno</span> <span class=\"o\">=</span> <span class=\"n\">datetime</span><span class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">()</span>\n    <span class=\"n\">sezeni</span><span class=\"o\">.</span><span class=\"n\">add</span><span class=\"p\">(</span><span class=\"n\">ukol</span><span class=\"p\">)</span>\n    <span class=\"n\">sezeni</span><span class=\"o\">.</span><span class=\"n\">commit</span><span class=\"p\">()</span>\n</pre></div><div class=\"admonition note\"><p>Mohli bychom pou&#x17E;&#xED;t metodu <code>get(cislo_ukolu)</code>, kter&#xE1; najde &#xFA;kol podle kl&#xED;&#x10D;e.\nTo bychom si ale neprocvi&#x10D;ili filtrov&#xE1;n&#xED; v&#xFD;sledk&#x16F; dotazu.</p>\n</div><h2>Krok 7 &#x2013; v&#xFD;pis jen nedokon&#x10D;en&#xFD;ch &#xFA;kol&#x16F;</h2>\n<p>Filtrov&#xE1;n&#xED; m&#x16F;&#x17E;eme aplikovat i pro v&#xFD;pis &#xFA;kol&#x16F;. Nap&#x159;&#xED;klad bychom mohli vypisovat\njenom &#xFA;koly, kter&#xE9; je&#x161;t&#x11B; nejsou dokon&#x10D;en&#xE9;.</p>\n<p>Na to se n&#xE1;m m&#x16F;&#x17E;e hodit metoda <code>filter</code>, kter&#xE1; umo&#x17E;&#x148;uje v&#xED;ce porovn&#xE1;n&#xED; ne&#x17E;\nzn&#xE1;m&#xE1; <code>filter_by</code>.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"nd\">@ukolnik.command</span><span class=\"p\">()</span>\n<span class=\"nd\">@click.option</span><span class=\"p\">(</span><span class=\"s2\">&quot;--jen-nehotove&quot;</span><span class=\"p\">,</span> <span class=\"n\">default</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">is_flag</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n<span class=\"k\">def</span> <span class=\"nf\">vypis</span><span class=\"p\">(</span><span class=\"n\">jen_nehotove</span><span class=\"p\">):</span>\n    <span class=\"n\">sezeni</span> <span class=\"o\">=</span> <span class=\"n\">pripoj_se</span><span class=\"p\">()</span>\n    <span class=\"n\">dotaz</span> <span class=\"o\">=</span> <span class=\"n\">sezeni</span><span class=\"o\">.</span><span class=\"n\">query</span><span class=\"p\">(</span><span class=\"n\">Ukol</span><span class=\"p\">)</span>\n\n    <span class=\"k\">if</span> <span class=\"n\">jen_nehotove</span><span class=\"p\">:</span>\n        <span class=\"n\">dotaz</span> <span class=\"o\">=</span> <span class=\"n\">dotaz</span><span class=\"o\">.</span><span class=\"n\">filter</span><span class=\"p\">(</span><span class=\"n\">Ukol</span><span class=\"o\">.</span><span class=\"n\">vyreseno</span> <span class=\"o\">==</span> <span class=\"bp\">None</span><span class=\"p\">)</span>\n\n    <span class=\"n\">ukoly</span> <span class=\"o\">=</span> <span class=\"n\">dotaz</span><span class=\"o\">.</span><span class=\"n\">all</span><span class=\"p\">()</span>\n    <span class=\"k\">for</span> <span class=\"n\">ukol</span> <span class=\"ow\">in</span> <span class=\"n\">ukoly</span><span class=\"p\">:</span>\n        <span class=\"n\">symbol</span> <span class=\"o\">=</span> <span class=\"s2\">&quot;[x]&quot;</span> <span class=\"k\">if</span> <span class=\"n\">ukol</span><span class=\"o\">.</span><span class=\"n\">vyreseno</span> <span class=\"k\">else</span> <span class=\"s2\">&quot;[ ]&quot;</span>\n        <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"s2\">&quot;{symbol} {ukol.id}. {ukol.text}&quot;</span><span class=\"p\">)</span>\n</pre></div><h2>Dal&#x161;&#xED; vylep&#x161;en&#xED;</h2>\n<p>Tady je n&#x11B;kolik tip&#x16F;, co by se v tomto programu dalo vylep&#x161;it:</p>\n<ul>\n<li>O&#x161;et&#x159;en&#xED; chyb: moment&#xE1;ln&#x11B; program spadne, pokud se pokus&#xED;me vy&#x159;e&#x161;it\nneexistuj&#xED;c&#xED; &#xFA;kol.</li>\n<li>&#x158;azen&#xED; v&#xFD;pisu: te&#x10F; jsou &#xFA;koly vypsan&#xE9; od nejstar&#x161;&#xED;ho. Mo&#x17E;n&#xE1; bychom je cht&#x11B;li\n&#x159;adit v opa&#x10D;n&#xE9;m po&#x159;ad&#xED;. Dotaz m&#xE1; metodu <code>order_by()</code>, kter&#xE9; m&#x16F;&#x17E;eme zadat\nsloupec, podle kter&#xE9;ho se bude &#x159;adit. Tak&#xE9; m&#x16F;&#x17E;eme &#x159;adit v opa&#x10D;n&#xE9;m po&#x159;ad&#xED;,\nt&#x159;eba pomoc&#xED; <code>Ukol.zadano.desc()</code>.</li>\n<li>Mohli bychom p&#x159;idat dal&#x161;&#xED; p&#x159;&#xED;kaz, kter&#xFD; sma&#x17E;e n&#x11B;kter&#xE9; &#xFA;koly (t&#x159;eba ty, kter&#xE9;\njsou vy&#x159;e&#x161;en&#xE9;, nebo star&#x161;&#xED; ne&#x17E; n&#x11B;jak&#xFD; limit). Dotaz s aplikovan&#xFD;mi filtry m&#xE1;\nmetodu <code>delete()</code>, kter&#xE1; sma&#x17E;e v&#x161;echny odpov&#xED;daj&#xED;c&#xED; z&#xE1;znamy.</li>\n</ul>\n\n\n        "
    }
  }
}