PyWebIO: interaktivní webové dialogy a formuláře v čistém Pythonu (dokončení)

7. 4. 2022
Doba čtení: 25 minut

Sdílet

 Autor: převzato s dovolením od PyWebIO
Popíšeme si tvorbu složitějších formulářů skládajících se z většího množství ovládacích prvků. Zabývat se budeme i kontrolou údajů zapisovaných do formulářů s využitím vlastních validátorů, změnou stylu vykreslení formulářů atd.

Obsah

1. PyWebIO: interaktivní webové dialogy a formuláře v čistém Pythonu (dokončení)

2. Postupné zadávání údajů v na sebe navazujících vstupních prvcích

3. Seskupení ovládacích prvků do jediného formuláře

4. Specifikace typů vstupních dat ve vstupních formulářích

5. Tvorba a využití vlastních validátorů ve vstupních formulářích

6. Zobrazení vyskakovacího okna

7. Alternativní způsob zobrazení vyskakovacího okna

8. Umístění prvků do sloupců, řádků a mřížky

9. Změna stylu vybraných prvků ve formulářích

10. Sady prvků, které je možné skrýt

11. Zobrazení rastrového obrázku přečteného ze specifikovaného zdroje

12. Zobrazení vypočteného rastrového obrázku: výpočet a zobrazení Mandelbrotovy množiny

13. Interaktivní změna parametrů výpočtu

14. Spuštění serveru s větším množstvím aplikací

15. Repositář s demonstračními příklady

16. Odkazy na Internetu

1. PyWebIO: interaktivní webové dialogy a formuláře v čistém Pythonu (dokončení)

Na úvodní článek o knihovně PyWebIO dnes navážeme. Nejprve si popíšeme způsob tvorby složitějších formulářů, které se skládají z většího množství ovládacích prvků. Zabývat se budeme i kontrolou údajů zapisovaných do formulářů, a to jak specifikací datových typů (celé číslo, řetězec atd.), tak i s využitím vlastních validátorů naprogramovaných přímo v Pythonu a volaných automaticky při vyplňování formulářů.

Obrázek 1: Výběrová tlačítka zobrazená ve webové aplikaci vytvořené s využitím knihovny PyWebIO.

Nezapomeneme ale ani na další vlastnosti; například na použití různých forem výstupů. Knihovnu PyWebIO je totiž možné v případě potřeby použít společně s dalšími knihovnami. Například lze relativně jednoduše realizovat vykreslení grafů do dynamicky generované webové stránky. Pro tento účel lze využít například knihovnu Matplotlib, ale taktéž knihovny Bokeh, pyecharts, plotly, pyg2plot či cutecharts.py.

Obrázek 2: Výběrové boxy zobrazené ve webové aplikaci vytvořené s využitím knihovny PyWebIO.

2. Postupné zadávání údajů v na sebe navazujících vstupních prvcích

V prvním demonstračním příkladu, který si v dnešním článku ukážeme, je realizováno postupné zadávání jednotlivých údajů v na sebe navazujících vstupních prvcích. Tento přístup nejvíce odpovídá klasickým skriptům s příkazy input a print, což je z vývojářského hlediska sice zcela nejjednodušší řešení, ale pro uživatele již nemusí být toto řešení příliš praktické, protože se nedá (jednoduše) vrátit k již zadaným údajům:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.input as inp
import pywebio.output as out
 
 
# vstupní údaje
out.put_text("Jméno")
name = inp.input()
 
out.put_text("Příjmení")
surname = inp.input()
 
out.put_text("Ulice")
street = inp.input()
 
out.put_text("ČP")
conscription_number = inp.input()
 
out.put_text("Město")
city = inp.input()
 
out.put_text("PSČ")
postal_code = inp.input()
 
 
# výpis výsledků
out.put_info("Zakázka bude poslána na adresu")
 
out.put_info(f"{name} {surname}\n{street} {conscription_number}\n{postal_code} {city}")

Z pohledu uživatele, který tuto aplikaci spustí, se postupně zobrazují jednotlivé vstupní prvky a po zadání příslušného údaje se přejde k dalšímu prvku:

Obrázek 3: Postupné zobrazování jednotlivých vstupních prvků na webové stránce.

Obrázek 4: Postupné zobrazování jednotlivých vstupních prvků na webové stránce.

3. Seskupení ovládacích prvků do jediného formuláře

Mnohem užitečnější by bylo zobrazení celého formuláře, do něhož by byly všechny prvky umístěny a uživatel by s nimi mohl pracovat na jediném místě. I tuto funkcionalitu knihovna PyWebIO podporuje – postačuje použít prvek typu input_group, v jehož konstruktoru se předává seznam dalších vstupních prvků. Každý z těchto prvků musí být pojmenovaný, aby bylo možné po vyplnění formuláře načíst jednotlivé položky:

info = inp.input_group("Adresa",[
  inp.input("Jméno", name="name"),
  inp.input("Příjmení", name="surname"),
  inp.input("Ulice", name="street"),
  inp.input("ČP", name="conscription_number"),
  inp.input("Město", name="city"),
  inp.input("PSČ", name="postal_code")
])

Obrázek 5: Formulář s větším množstvím vstupních prvků.

Obrázek 6: Vyplněný formulář před odesláním.

Po vyplnění formuláře je v proměnné info uložen slovník, jehož klíči jsou jména ovládacích prvků a hodnotami vstupní údaje zapsané uživatelem:

Obrázek 7: Využití dat ve vyplněném formuláři (shrnutí + potvrzení).

Úplný skript založený na sdružení několika vstupních prvků do formuláře může vypadat následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.input as inp
import pywebio.output as out
 
 
# vstupní údaje
info = inp.input_group("Adresa",[
  inp.input("Jméno", name="name"),
  inp.input("Příjmení", name="surname"),
  inp.input("Ulice", name="street"),
  inp.input("ČP", name="conscription_number"),
  inp.input("Město", name="city"),
  inp.input("PSČ", name="postal_code")
])
 
# výpis výsledků
out.put_info("Zakázka bude poslána na adresu")
 
out.put_info(f"{info['name']} {info['surname']}\n{info['street']} {info['conscription_number']}\n{info['postal_code']} {info['city']}")

4. Specifikace typů vstupních dat ve vstupních formulářích

Kromě jmen jednotlivých vstupních prvků je možné specifikovat i jejich typ, například „text (řetězec)“, „celé číslo“ atd. Typ je přitom kontrolován před odesláním formuláře:

Obrázek 8: Kontrola typu zadávaných údajů před odesláním formuláře.

Způsob specifikace typu vstupu je ukázán v dalším demonstračním příkladu, jehož úplný zdrojový kód vypadá následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.input as inp
import pywebio.output as out
 
 
# vstupní údaje
info = inp.input_group("Adresa",[
  inp.input("Jméno", name="name", type=inp.TEXT),
  inp.input("Příjmení", name="surname", type=inp.TEXT),
  inp.input("Ulice", name="street", type=inp.TEXT),
  inp.input("ČP", name="conscription_number", type=inp.NUMBER),
  inp.input("Město", name="city", type=inp.TEXT),
  inp.input("PSČ", name="postal_code", type=inp.NUMBER)
])
 
# výpis výsledků
out.put_info("Zakázka bude poslána na adresu")
 
out.put_info(f"{info['name']} {info['surname']}\n{info['street']} {info['conscription_number']}\n{info['postal_code']} {info['city']}")

5. Tvorba a využití vlastních validátorů ve vstupních formulářích

Kontrola zadávaných údajů pouze na základě jejich datového typu není v praxi dostatečná, protože je například nutné zajistit vstup číselných údajů v předem známém rozsahu, zadání textových dat z nějakého slovníku atd. K dodatečné kontrole slouží takzvané validátory, což jsou funkce, které v případě korektního vstupu vrací hodnotu None a v opačném případě vrací informaci o chybě (typicky ve formě řetězce). Nejprve si ukažme, jak se validátory specifikují při tvorbě formuláře:

# vstupní údaje
info = inp.input_group("Adresa",[
  inp.input("Jméno", name="name", type=inp.TEXT),
  inp.input("Příjmení", name="surname", type=inp.TEXT),
  inp.input("Ulice", name="street", type=inp.TEXT),
  inp.input("ČP", name="conscription_number", type=inp.NUMBER),
  inp.input("Město", name="city", type=inp.TEXT, validate=check_city),
  inp.input("PSČ", name="postal_code", type=inp.NUMBER, validate=check_postal_code)
])

Velmi primitivní validátor pro kontrolu PSČ by mohl vypadat následovně (v praxi by se použila databáze známých PSČ):

def check_postal_code(value):
    if value <= 10000:
        return "Neplatné PSČ - příliš malá hodnota"
    elif value > 99999:
        return "Neplatné PSČ - příliš velká hodnota"

Kontrola, zda je zadáno správné jméno města, je řešena dalším validátorem:

def check_city(name):
    if name not in cities:
        return "Neznámé město"

Přičemž (opět pro jednoduchost) obsahuje databáze pouze deset nejlidnatějších měst:

cities = (
        "Praha",
        "Brno",
        "Ostrava",
        "Plzeň",
        "Liberec",
        "Olomouc",
        "České Budějovice",
        "Hradec Králové",
        "Ústí nad Labem",
        "Pardubice"
        )

Obrázek 9: Kontrola typu zadávaných údajů před odesláním formuláře provedená na základě validátorů.

Úplný skript s takto definovaným formulářem by mohl vypadat následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.input as inp
import pywebio.output as out
 
 
cities = (
        "Praha",
        "Brno",
        "Ostrava",
        "Plzeň",
        "Liberec",
        "Olomouc",
        "České Budějovice",
        "Hradec Králové",
        "Ústí nad Labem",
        "Pardubice"
        )
 
 
def check_postal_code(value):
    if value <= 10000:
        return "Neplatné PSČ - příliš malá hodnota"
    elif value > 99999:
        return "Neplatné PSČ - příliš velká hodnota"
 
 
def check_city(name):
    if name not in cities:
        return "Neznámé město"
 
 
# vstupní údaje
info = inp.input_group("Adresa",[
  inp.input("Jméno", name="name", type=inp.TEXT),
  inp.input("Příjmení", name="surname", type=inp.TEXT),
  inp.input("Ulice", name="street", type=inp.TEXT),
  inp.input("ČP", name="conscription_number", type=inp.NUMBER),
  inp.input("Město", name="city", type=inp.TEXT, validate=check_city),
  inp.input("PSČ", name="postal_code", type=inp.NUMBER, validate=check_postal_code)
])
 
# výpis výsledků
out.put_info("Zakázka bude poslána na adresu")
 
out.put_info(f"{info['name']} {info['surname']}\n{info['street']} {info['conscription_number']}\n{info['postal_code']} {info['city']}")

6. Zobrazení vyskakovacího okna

V knihovně PyWebIO nalezneme i podporu pro zobrazení jednoduchých vyskakovacích oken. Postačuje použít výstupní prvek nazvaný popup, kterému se předá jak titulek vyskakovacího okna, tak i výstupní prvky, které se v okně mají zobrazit. Volání konstruktoru tohoto ovládacího prvku tedy může vypadat následovně:

out.popup('Odeslání zakázky', [
    out.put_html('Odeslání zakázky'),
    out.put_info("Zakázka bude poslána na adresu"),
    out.put_info(f"{info['name']} {info['surname']}\n{info['street']} {info['conscription_number']}\n{info['postal_code']} {info['city']}"),
    out.put_buttons(['OK'], onclick=lambda _: close_popup())
])

S tímto výsledkem:

Obrázek 10: Vyskakovací okno zobrazené po vyplnění a odeslání formuláře.

Úplný skript s takto definovaným formulářem a s následným zobrazením vyskakovacího okna by mohl vypadat následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.input as inp
import pywebio.output as out
 
 
cities = (
        "Praha",
        "Brno",
        "Ostrava",
        "Plzeň",
        "Liberec",
        "Olomouc",
        "České Budějovice",
        "Hradec Králové",
        "Ústí nad Labem",
        "Pardubice"
        )
 
def check_postal_code(value):
    if value <= 10000:
        return "Neplatné PSČ - příliš malá hodnota"
    elif value > 99999:
        return "Neplatné PSČ - příliš velká hodnota"
 
 
def check_city(name):
    if name not in cities:
        return "Neznámé město"
 
 
# vstupní údaje
info = inp.input_group("Adresa",[
  inp.input("Jméno", name="name", type=inp.TEXT),
  inp.input("Příjmení", name="surname", type=inp.TEXT),
  inp.input("Ulice", name="street", type=inp.TEXT),
  inp.input("ČP", name="conscription_number", type=inp.NUMBER),
  inp.input("Město", name="city", type=inp.TEXT, validate=check_city),
  inp.input("PSČ", name="postal_code", type=inp.NUMBER, validate=check_postal_code)
])
 
# výpis výsledků
out.popup('Odeslání zakázky', [
    out.put_html('Odeslání zakázky'),
    out.put_info("Zakázka bude poslána na adresu"),
    out.put_info(f"{info['name']} {info['surname']}\n{info['street']} {info['conscription_number']}\n{info['postal_code']} {info['city']}"),
    out.put_buttons(['OK'], onclick=lambda _: close_popup())
])

7. Alternativní způsob zobrazení vyskakovacího okna

Ve zdrojových kódech, v nichž se knihovna PyWebIO využívá, se velmi často setkáme s využitím kontextu/kontextů. Předchozí část skriptu, která sloužila pro zobrazení vyskakovacího okna, je možné přepsat následujícím způsobem, který je (alespoň podle mého názoru) přehlednější, než předání seznamu ovládacích prvků do konstruktoru popup. Tento způsob je založen na struktuře with:

with out.popup('Odeslání zakázky') as s:
    out.put_html('Odeslání zakázky'),
    out.put_info("Zakázka bude poslána na adresu"),
    out.put_info(f"{info['name']} {info['surname']}\n{info['street']} {info['conscription_number']}\n{info['postal_code']} {info['city']}"),
    out.put_buttons(['OK'], onclick=lambda _: close_popup())

Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.input as inp
import pywebio.output as out
 
 
cities = (
        "Praha",
        "Brno",
        "Ostrava",
        "Plzeň",
        "Liberec",
        "Olomouc",
        "České Budějovice",
        "Hradec Králové",
        "Ústí nad Labem",
        "Pardubice"
        )
 
def check_postal_code(value):
    if value <= 10000:
        return "Neplatné PSČ - příliš malá hodnota"
    elif value > 99999:
        return "Neplatné PSČ - příliš velká hodnota"
 
 
def check_city(name):
    if name not in cities:
        return "Neznámé město"
 
 
# vstupní údaje
info = inp.input_group("Adresa",[
  inp.input("Jméno", name="name", type=inp.TEXT),
  inp.input("Příjmení", name="surname", type=inp.TEXT),
  inp.input("Ulice", name="street", type=inp.TEXT),
  inp.input("ČP", name="conscription_number", type=inp.NUMBER),
  inp.input("Město", name="city", type=inp.TEXT, validate=check_city),
  inp.input("PSČ", name="postal_code", type=inp.NUMBER, validate=check_postal_code)
])
 
# výpis výsledků
with out.popup('Odeslání zakázky') as s:
    out.put_html('Odeslání zakázky'),
    out.put_info("Zakázka bude poslána na adresu"),
    out.put_info(f"{info['name']} {info['surname']}\n{info['street']} {info['conscription_number']}\n{info['postal_code']} {info['city']}"),
    out.put_buttons(['OK'], onclick=lambda _: close_popup())

8. Umístění prvků do sloupců, řádků a mřížky

Vstupní, popř. výstupní prvky ve formuláři lze umístit do sloupců (put_col), řádků (put_row) nebo do mřížky (put_grid). Podívejme se nyní na (triviální) způsob, jak se vždy dvojice výstupních prvků umístí do jediného řádku:

# výpis výsledků
out.put_info("Zakázka bude poslána na adresu")
 
out.put_row([
    out.put_text(info['name']),
    out.put_text(info['surname']),
], size="30% 30%")
 
out.put_row([
    out.put_text(info['street']),
    out.put_text(info['conscription_number']),
], size="30% 10%")
 
out.put_row([
    out.put_text(info['postal_code']),
    out.put_text(info['city']),
], size="10% 20%")

Obrázek 11: Zobrazení informací, z nichž některé jsou umístěny na stejném řádku.

Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.input as inp
import pywebio.output as out
 
 
cities = (
        "Praha",
        "Brno",
        "Ostrava",
        "Plzeň",
        "Liberec",
        "Olomouc",
        "České Budějovice",
        "Hradec Králové",
        "Ústí nad Labem",
        "Pardubice"
        )
 
def check_postal_code(value):
    if value <= 10000:
        return "Neplatné PSČ - příliš malá hodnota"
    elif value > 99999:
        return "Neplatné PSČ - příliš velká hodnota"
 
 
def check_city(name):
    if name not in cities:
        return "Neznámé město"
 
 
# vstupní údaje
info = inp.input_group("Adresa",[
  inp.input("Jméno", name="name", type=inp.TEXT),
  inp.input("Příjmení", name="surname", type=inp.TEXT),
  inp.input("Ulice", name="street", type=inp.TEXT),
  inp.input("ČP", name="conscription_number", type=inp.NUMBER),
  inp.input("Město", name="city", type=inp.TEXT, validate=check_city),
  inp.input("PSČ", name="postal_code", type=inp.NUMBER, validate=check_postal_code)
])
 
# výpis výsledků
out.put_info("Zakázka bude poslána na adresu")
 
out.put_row([
    out.put_text(info['name']),
    out.put_text(info['surname']),
], size="30% 30%")
 
out.put_row([
    out.put_text(info['street']),
    out.put_text(info['conscription_number']),
], size="30% 10%")
 
out.put_row([
    out.put_text(info['postal_code']),
    out.put_text(info['city']),
], size="10% 20%")

9. Změna stylu vybraných prvků ve formulářích

S využitím metody style lze modifikovat styl vykreslení některých prvků ve formulářích. Styl, který se zapisuje v CSS (a může používat prakticky všechny triky tohoto jazyka), může být nastaven pro jednotlivé prvky:

out.put_row([
    out.put_text(info['name']).style('color:red'),
    out.put_text(info['surname']).style('color:red'),
], size="30% 30%")

Popř:

 
out.put_row([
    out.put_text(info['street']).style('font-size:75%'),
    out.put_text(info['conscription_number']).style('font-size:75%'),
], size="30% 10%")

Nebo je možné nastavit styl pro celou skupinu ovládacích prvků, například pro prvky sdružené a umístěné na jednom řádku:

out.put_row([
    out.put_text(info['postal_code']),
    out.put_text(info['city']),
], size="10% 20%").style('background-color:#ccffcc')

Obrázek 12: Ukázka změny stylu vybraných prvků ve formulářích.

Opět si ukažme, jak by mohl vypadat ucelený demonstrační příklad:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.input as inp
import pywebio.output as out
 
 
cities = (
        "Praha",
        "Brno",
        "Ostrava",
        "Plzeň",
        "Liberec",
        "Olomouc",
        "České Budějovice",
        "Hradec Králové",
        "Ústí nad Labem",
        "Pardubice"
        )
 
def check_postal_code(value):
    if value <= 10000:
        return "Neplatné PSČ - příliš malá hodnota"
    elif value > 99999:
        return "Neplatné PSČ - příliš velká hodnota"
 
 
def check_city(name):
    if name not in cities:
        return "Neznámé město"
 
 
# vstupní údaje
info = inp.input_group("Adresa",[
  inp.input("Jméno", name="name", type=inp.TEXT),
  inp.input("Příjmení", name="surname", type=inp.TEXT),
  inp.input("Ulice", name="street", type=inp.TEXT),
  inp.input("ČP", name="conscription_number", type=inp.NUMBER),
  inp.input("Město", name="city", type=inp.TEXT, validate=check_city),
  inp.input("PSČ", name="postal_code", type=inp.NUMBER, validate=check_postal_code)
])
 
# výpis výsledků
out.put_info("Zakázka bude poslána na adresu")
 
out.put_row([
    out.put_text(info['name']).style('color:red'),
    out.put_text(info['surname']).style('color:red'),
], size="30% 30%")
 
out.put_row([
    out.put_text(info['street']).style('font-size:75%'),
    out.put_text(info['conscription_number']).style('font-size:75%'),
], size="30% 10%")
 
out.put_row([
    out.put_text(info['postal_code']),
    out.put_text(info['city']),
], size="10% 20%").style('background-color:#ccffcc')

10. Sady prvků, které je možné skrýt

U rozsáhlejších formulářů může být užitečné, když se některé prvky (na základě akce uživatele) skryjí, resp. přesněji řečeno „složí“ (fold, collapse). K tomuto účelu slouží funkce nazvaná put_collapse, které je možné předat libovolné množství ovládacích prvků. Typicky se ovšem používá alternativní způsob založený na využití kontextu a tudíž i konstrukce with (ta je čitelnější):

with out.put_collapse("Jméno a příjmení"):
    out.put_row([
        out.put_text(info['name']).style('color:red'),
        out.put_text(info['surname']).style('color:red'),
    ], size="30% 30%")

Takto může vypadat výsledek v případě, že je použito několik ovládacích prvků typu „collapse“, do nichž jsou vloženy další ovládací prvky:

Obrázek 13: Skrývání ovládacích prvků uživatelem.

Obrázek 14: Skrývání ovládacích prvků uživatelem.

Opět si ukažme způsob použití „collapse“ v reálnějším příkladu:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.input as inp
import pywebio.output as out
 
 
cities = (
        "Praha",
        "Brno",
        "Ostrava",
        "Plzeň",
        "Liberec",
        "Olomouc",
        "České Budějovice",
        "Hradec Králové",
        "Ústí nad Labem",
        "Pardubice"
        )
 
def check_postal_code(value):
    if value <= 10000:
        return "Neplatné PSČ - příliš malá hodnota"
    elif value > 99999:
        return "Neplatné PSČ - příliš velká hodnota"
 
 
def check_city(name):
    if name not in cities:
        return "Neznámé město"
 
 
# vstupní údaje
info = inp.input_group("Adresa",[
  inp.input("Jméno", name="name", type=inp.TEXT),
  inp.input("Příjmení", name="surname", type=inp.TEXT),
  inp.input("Ulice", name="street", type=inp.TEXT),
  inp.input("ČP", name="conscription_number", type=inp.NUMBER),
  inp.input("Město", name="city", type=inp.TEXT, validate=check_city),
  inp.input("PSČ", name="postal_code", type=inp.NUMBER, validate=check_postal_code)
])
 
# výpis výsledků
out.put_info("Zakázka bude poslána na adresu")
 
with out.put_collapse("Jméno a příjmení"):
    out.put_row([
        out.put_text(info['name']).style('color:red'),
        out.put_text(info['surname']).style('color:red'),
    ], size="30% 30%")
 
with out.put_collapse("Adresa"):
    out.put_row([
        out.put_text(info['street']).style('font-size:75%'),
        out.put_text(info['conscription_number']).style('font-size:75%'),
    ], size="30% 10%")
 
    out.put_row([
        out.put_text(info['postal_code']),
        out.put_text(info['city']),
    ], size="10% 20%").style('background-color:#ccffcc')

11. Zobrazení rastrového obrázku přečteného ze specifikovaného zdroje

Velmi jednoduchým způsobem je možné ve formuláři zobrazit rastrový obrázek (typicky uložený ve formátech JPEG, PNG, GIF či WebP). Takový obrázek lze načíst ze specifikovaného zdroje, tedy typicky ze zadané URL nebo ze souboru. To ovšem není vše, protože zobrazit je možné i obrázek reprezentovaný objektem knihovny PIL/Pillow. To mj. znamená, že obrázek je možné vypočítat, například na základě dat zadaných uživatelem.

Nejprve se ovšem podívejme na první zmíněnou možnost, tj. na zobrazení obrázku načteného ze specifikovaného zdroje, zde konkrétně z URL:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.output as out
 
out.put_image('https://www.python.org/static/img/python-logo.png')

Výsledná webová stránka s formulářem by mohla vypadat takto:

Obrázek 15: Rastrový obrázek načtený z externího zdroje.

12. Zobrazení vypočteného rastrového obrázku: výpočet a zobrazení Mandelbrotovy množiny

Ve skutečnosti však nemusí být obrázek pouze načten ze souboru (nebo z jiného podobného statického zdroje), protože ho je možné libovolným způsobem vygenerovat a reprezentovat ve formátu kompatibilním s knihovnami PIL (Python Imaging Library), resp. Pillow. Podívejme se nyní na způsob vygenerování a zobrazení takového obrázku. Nejprve musíme provést import příslušné knihovny pro manipulaci s rastrovými obrázky:

from PIL import Image

Nastavíme rozměry obrázku:

IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256

Následujícím příkazem se vytvoří prázdný obrázek o rozměrech 256×256 pixelů, který je uložen v operační paměti:

image = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT))

A konečně do obrázku vykreslíme nějaká data (například přímou manipulací s pixely):

recalc_fractal(image, palette_blues.palette, -2.0, -1.5, 1.0, 1.5, 1000)

Následně již jen postačuje takový obrázek vložit do formuláře, a to bez jeho nutnosti uložení na disk:

out.put_image(image)

Výsledek získaný tímto postupem může vypadat například následovně – jedná se o vizualizaci slavné Mandelbrotovy množiny:

Obrázek 16: Rastrový obrázek s vypočtenou Mandelbrotovou množinou.

Celý skript, který byl použit pro výpočet a následné vykreslení Mandelbrotovy množiny, vypadá takto:

#!/usr/bin/env python
 
import pywebio.output as out
 
from PIL import Image
import palette_blues
 
# textura by mela byt ctvercova a jeji sirka i vyska by mela byt
# mocninou cisla 2
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
 
 
def mandelbrot(cx, cy, maxiter):
    """Calculate number of iterations for given complex number to escape from set."""
    c = complex(cx, cy)
    z = 0
    for i in range(0, maxiter):
        if abs(z) > 2:
            return i
        z = z * z + c
    return 0
 
 
def recalc_fractal(image, palette, xmin, ymin, xmax, ymax, maxiter=1000):
    """Recalculate the whole fractal and render the set into given image."""
    width, height = image.size  # rozmery obrazku
    stepx = (xmax - xmin) / width
    stepy = (ymax - ymin) / height
 
    y1 = ymin
    for y in range(0, height):
        x1 = xmin
        for x in range(0, width):
            i = mandelbrot(x1, y1, maxiter)
            i = 3 * i % 256
            color = (palette[i][0], palette[i][1], palette[i][2])
            image.putpixel((x, y), color)
            x1 += stepx
        y1 += stepy
 
 
image = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT))
 
recalc_fractal(image, palette_blues.palette, -2.0, -1.5, 1.0, 1.5, 1000)
 
out.put_image(image)

13. Interaktivní změna parametrů výpočtu

Vzhledem k tomu, že jednotlivé ovládací prvky jsou na formulář vkládány ze skriptu napsaného v Pythonu, mají vývojáři plnou kontrolu nad tím, kdy a jakým způsobem se bude formulář (programově) měnit. Například je možné formulář neustále zobrazovat v (nekonečné) smyčce, ptát se uživatele na parametry nějakého výpočtu, zopakovat výpočet (resp. jeho výsledek) a následně zobrazit jeho výsledky. Ostatně si to můžeme snadno vyzkoušet.

Budeme zobrazovat výsledek nějakého výpočtu prezentovaný ve formě rastrového obrázku. Tento obrázek je vykreslen na základě parametrů a, b a c ve funkci fm:

a = 40
b = 20
c = 50
 
image = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT))
 
fm(image, palette_blues.palette, -1.0, -1.0, 1.0, 1.0, a, b, c)
out.put_image(image)

Příklad zobrazeného výsledku:

Obrázek 17: Obrázek s vypočtenou texturou – výchozí zobrazení s formulářem.

Ve formuláři je následně možné modifikovat parametry a, b a c. Celý formulář vznikne sloučením tří ovládacích prvků, což je technologie, kterou již dobře známe:

# vstupní údaje
info = inp.input_group("FM synthesis",[
   inp.slider(label="a", name="a", value=a, min_value=1, max_value=100),
   inp.slider(label="b", name="b", value=b, min_value=1, max_value=50),
   inp.slider(label="c", name="c", value=c, min_value=1, max_value=100),
])
 
a = info["a"]
b = info["b"]
c = info["c"]

Po změně parametrů je možné výpočet provést znovu a vykreslit nový obrázek:

Obrázek 18: Postupná změna parametrů vypočtené a vykreslované textury.

Celý tento postup je možné opakovat v (nekonečné) smyčce, popř. ve smyčce řízené nějakým dalším ovládacím prvkem (typu tlačítko „Ukončení výpočtu“):

while True:
    image = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT))
 
    fm(image, palette_blues.palette, -1.0, -1.0, 1.0, 1.0, a, b, c)
    out.put_image(image)
 
    # vstupní údaje
    info = inp.input_group("FM synthesis",[
      inp.slider(label="a", name="a", value=a, min_value=1, max_value=100),
      inp.slider(label="b", name="b", value=b, min_value=1, max_value=50),
      inp.slider(label="c", name="c", value=c, min_value=1, max_value=100),
    ])
 
    a = info["a"]
    b = info["b"]
    c = info["c"]

Obrázek 19: Postupná změna parametrů vypočtené a vykreslované textury.

Úplný zdrojový kód dnešního posledního demonstračního příkladu vypadá následovně:

#!/usr/bin/env python
 
"""Texture rendering based on FM synthesis."""
 
import pywebio.input as inp
import pywebio.output as out
 
from PIL import Image
import palette_blues
from math import *
 
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
 
 
def fm(image, palette, xmin, ymin, xmax, ymax, a, b, c):
    """Generate texture based on FM synthesis algorithm."""
    width, height = image.size  # rozmery obrazku
    stepx = (xmax - xmin) / width
    stepy = (ymax - ymin) / height
 
    a /= 10.0
 
    y1 = ymin
    for y in range(0, height):
        x1 = xmin
        for x in range(0, width):
            x1 += stepx
            val = 100 + 100.0 * sin(x / a + 2 * sin(x / b + y / c))
            i = int(val) & 255
            color = (palette[i][0], palette[i][1], palette[i][2])
            image.putpixel((x, y), color)
        y1 += stepy
 
 
def main():
    a = 40
    b = 20
    c = 50
 
    while True:
        image = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT))
 
        fm(image, palette_blues.palette, -1.0, -1.0, 1.0, 1.0, a, b, c)
        out.put_image(image)
 
        # vstupní údaje
        info = inp.input_group("FM synthesis",[
          inp.slider(label="a", name="a", value=a, min_value=1, max_value=100),
          inp.slider(label="b", name="b", value=b, min_value=1, max_value=50),
          inp.slider(label="c", name="c", value=c, min_value=1, max_value=100),
        ])
 
        a = info["a"]
        b = info["b"]
        c = info["c"]
 
 
main()

14. Spuštění serveru s větším množstvím aplikací

Poslední funkcí nabízenou knihovnou PyWebIO, kterou si v dnešním článku popíšeme, je možnost spustit několik aplikací s webovými formuláři v rámci jediného webového serveru. V tom nejjednodušším případě by měla struktura adresáře s aplikacemi vypadat takto:

.
├── basic_output.py
├── popup.py
├── radio.py
└── server.py

Kód, který se bude spouštět, je uložen ve skriptu server.py (jméno je volitelné), jehož obsah je následující:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.platform
 
pywebio.platform.path_deploy(".", port=12345)

Ostatní soubory obsahují jednotlivé webové aplikace. Každý z takových souborů musí obsahovat funkci main, která bude automaticky spuštěna ve chvíli, kdy si uživatel danou webovou aplikaci spustí:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
import pywebio.output as out
 
 
def main():
    out.put_text("Hello world!")

Následně jsou jednotlivé aplikace nabídnuty na úvodní stránce serveru (tuto stránku lze realizovat jako samostatnou aplikaci se styly atd.):

Obrázek 20: Nabídka jednotlivých webových aplikací nabízených webovým serverem.

Pokud jsou jednotlivé webové aplikace složitější (více formulářů, komunikace s databází atd.), lze takové aplikace uložit do samostatných adresářů, kde se může nacházet libovolné množství souborů. Vždy jeden z nich však musí obsahovat funkci main, která je spuštěna po výběru této aplikace:

├── basic
│   └── basic_output.py
├── popup
│   └── popup.py
├── radio
│   └── radio.py
└── server.py

Výsledná stránka s nabídkou aplikací bude vypadat nepatrně odlišně:

bitcoin školení listopad 24

Obrázek 21: Nabídka jednotlivých webových aplikací nabízených webovým serverem.

15. Repositář s demonstračními příklady

Zdrojové kódy všech minule i dnes popsaných demonstračních příkladů určených pro programovací jazyk Python 3 byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Demonstrační příklad Stručný popis příkladu Cesta
1 01_basic_output.py zobrazení běžného textu ve webovém prohlížeči https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/01_ba­sic_output.py
2 02_table.py zobrazení tabulky ve webovém prohlížeči https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/02_table.py
3 03_table.py programová příprava tabulky zobrazené ve webovém prohlížeči https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/03_table.py
4 04_table.py programová příprava tabulky zobrazené ve webovém prohlížeči https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/04_table.py
5 05_color.py podpora pro symbolické názvy barev https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/05_color.py
6 06_message.py výpis ostylovaných zpráv do webové stránky https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/06_message.py
7 07_put_html.py podpora pro výstup HTML kódu z PyWebIO https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/07_put_html­.py
8 08_put_markdown.py výpis textu (dokumentu) napsaného ve značkovacím jazyku Markdown https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/08_put_mar­kdown.py
9 09_put_code.py výpis zdrojového kódu se zvýrazněním syntaxe https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/09_put_co­de.py
10 10_progress_bar.py animované zobrazení průběhu operace https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/10_pro­gress_bar.py
11 11_loading.py informace o výpočtu či načítání stránky https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/11_loading.py
12 12_loading.py informace o výpočtu či načítání stránky https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/12_loading.py
13 13_input.py vstupní textové pole https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/13_input.py
14 14_input.py vylepšená vstupní textová pole https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/14_input.py
15 15_input_numbers.py kontrola korektnosti numerických údajů https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/15_in­put_numbers.py
16 16_input_numbers_required.py povinné vstupní parametry https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/16_in­put_numbers_required.py
17 17_radio.py přepínače https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/17_radio.py
18 18_checkbox.py výběrové boxy https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/18_chec­kbox.py
19 19_actions.py skupina akcí https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/19_actions.py
20 20_slider.py interaktivní posuvník https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/20_slider.py
       
21 21_more_inputs.py postupné zadávání údajů v na sebe navazujících vstupních prvcích https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/21_mo­re_inputs.py
22 22_more_inputs.py seskupení ovládacích prvků do jediného formuláře https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/22_mo­re_inputs.py
23 23_input_validators.py specifikace typů vstupních dat ve vstupních formulářích https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/23_in­put_validators.py
24 24_better_validators.py tvorba a využití vlastních validátorů ve vstupních formulářích https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/24_bet­ter_validators.py
25 25_popup.py zobrazení vyskakovacího okna https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/25_popup.py
26 26_popup.py zobrazení vyskakovacího okna https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/26_popup.py
27 27_rows.py zarovnání prvků do sloupců, řádků a mřížek https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/27_rows.py
28 28_style.py změna stylu zobrazení ovládacích prvků https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/28_style.py
29 29_collapse.py sady prvků, které je možné skrýt https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/29_co­llapse.py
30 30_image.py zobrazení rastrového obrázku přečteného ze specifikovaného zdroje https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/30_image.py
31 31_mandelbrot.py výpočet a zobrazení Mandelbrotovy množiny https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/31_man­delbrot.py
32 32_fm_synth.py interaktivní změna parametrů výpočtu https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/32_fm_syn­th.py
32 server1 Server s několika aplikacemi, první řešení https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/server1
33 server2 Server s několika aplikacemi, druhé řešení https://github.com/tisnik/most-popular-python-libs/blob/master/pywebio/server2

16. Odkazy na Internetu

  1. Low code Python web framework
    https://www.pyweb.io/
  2. Repositář projektu
    https://github.com/pywebio/PyWebIO/
  3. Getting Started
    https://www.pyweb.io/tutorial.html
  4. Dokumentace
    https://pywebio.readthedoc­s.io/en/latest/
  5. Why PyWebIO?
    https://github.com/pywebi­o/PyWebIO/wiki/Why-PyWebIO%3F
  6. PyWebIO demos
    https://pywebio-demos.pywebio.online/
  7. PyWebIO Chart Gallery
    https://pywebio-charts.pywebio.online/
  8. Awesome Python
    https://awesome-python.com/
  9. A complete guide to web development in Python
    https://www.educative.io/blog/web-development-in-python
  10. Python Web Development Tutorials
    https://realpython.com/tutorials/web-dev/
  11. What is Flask Python
    https://pythonbasics.org/what-is-flask-python/
  12. CherryPy
    https://cherrypy.dev/
  13. Projekt Zenity
    https://wiki.gnome.org/Pro­jects/Zenity
  14. Nástroj Dialog
    http://invisible-island.net/dialog/
  15. Plotly
    https://plotly.com/
  16. Bokeh
    https://bokeh.org/
  17. pyecharts
    https://github.com/pyechar­ts/pyecharts/blob/master/RE­ADME.en.md
  18. Tvorba grafů v Jupyter Notebooku s využitím knihovny Matplotlib
    https://www.root.cz/clanky/tvorba-grafu-v-jupyter-notebooku-s-vyuzitim-knihovny-matplotlib/
  19. Alternatives to PyWebIO
    https://stackshare.io/pywe­bio/alternatives
  20. The fastest way to build and share data apps – Streamlit
    https://streamlit.io/
  21. Dash Enterprise
    https://plotly.com/dash/
  22. pglet
    https://pglet.io/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.