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
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ě:
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:
16. Odkazy na Internetu
- Low code Python web framework
https://www.pyweb.io/ - Repositář projektu
https://github.com/pywebio/PyWebIO/ - Getting Started
https://www.pyweb.io/tutorial.html - Dokumentace
https://pywebio.readthedocs.io/en/latest/ - Why PyWebIO?
https://github.com/pywebio/PyWebIO/wiki/Why-PyWebIO%3F - PyWebIO demos
https://pywebio-demos.pywebio.online/ - PyWebIO Chart Gallery
https://pywebio-charts.pywebio.online/ - Awesome Python
https://awesome-python.com/ - A complete guide to web development in Python
https://www.educative.io/blog/web-development-in-python - Python Web Development Tutorials
https://realpython.com/tutorials/web-dev/ - What is Flask Python
https://pythonbasics.org/what-is-flask-python/ - CherryPy
https://cherrypy.dev/ - Projekt Zenity
https://wiki.gnome.org/Projects/Zenity - Nástroj Dialog
http://invisible-island.net/dialog/ - Plotly
https://plotly.com/ - Bokeh
https://bokeh.org/ - pyecharts
https://github.com/pyecharts/pyecharts/blob/master/README.en.md - 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/ - Alternatives to PyWebIO
https://stackshare.io/pywebio/alternatives - The fastest way to build and share data apps – Streamlit
https://streamlit.io/ - Dash Enterprise
https://plotly.com/dash/ - pglet
https://pglet.io/