11. Framework Flask
Obsah
Virtuální prostředí pythonu
Virtuální prostředí v Pythonu umožňuje izolovat balíčky a závislosti mezi různými projekty, takže každá aplikace může používat specifické verze knihoven bez konfliktu s jinými projekty. Základní motivace, proč používat virtuální prostředí:
- Vytvoření odděleného prostoru: virtuální prostředí je složka obsahující vlastní instalaci Pythonu a správce balíčků
pip. Tím se zabrání konfliktům mezi globálně nainstalovanými balíčky a těmi, které potřebuje daný projekt. - Použití izolovaných balíčků: když aktivujete virtuální prostředí, příkazy
pythonapipodkazují na místní kopie uvnitř prostředí, nikoli na globální systémovou instalaci. - Replikovatelnost projektu: pomocí souboru
requirements.txt(nebopyproject.toml) lze snadno sdílet projekt s ostatními, kteří mohou instalovat přesně stejné verze knihoven.
Jak vytvořit a používat virtuální prostředí?
- Vytvoření virtuálního prostředí
Tento příkaz vytvoří složku
venv, která obsahuje izolované prostředí.python -m venv venv - Aktivace virtuálního prostředí
Aktivace na Windows probíhá pomocí souboru
venv\Scripts\Activate:venv\Scripts\ActivateAktivace na Linux/macOS probíhá pomocí souboru
venv/bin/activate:source venv/bin/activate - Instalace balíčků
Jakmile je prostředí aktivní, můžete do něj instalovat balíčky standardně pomocí příkazu
pip install ....pip install numpy - Deaktivace virtuálního prostředí
Po skončení práce na projektu stačí spustit:
deactivate - Odstranění virtuálního prostředí
Stačí smazat složku
venv:rm -rf venv # Linux/macOS rd /s /q venv # Windows - Uložení nainstalovaných balíčků
Pro budoucí opětovnou instalaci si můžeme vytvořit soubor se seznamem závislostí:
pip freeze > requirements.txtTento soubor může mít například podobný obsah (kde
==označuje přesně tuto verzi a>=minimální verzi):numpy==1.21.2 pandas==1.3.3 requests>=2.26.0Když chcete nainstalovat všechny závislosti z tohoto souboru (například v jiném prostředí) stačí spustit:
pip install -r requirements.txt
Základy Flasku
Flask je jednoduchý a flexibilní webový framework napsaný v Pythonu. Je určen pro rychlou a snadnou tvorbu webových aplikací a API. Více najdete zde.
Flask je často označován za tzv. mikroframework (narozdíl například od frameworku Django), protože neobsahuje žádné vestavěné nástroje pro správu databází nebo autentizaci uživatelů, což vývojářům umožňuje přizpůsobit aplikaci přesně podle svých potřeb pomocí rozšíření a balíčků.
První aplikace
Nejprve vytvoříme jednoduchou aplikaci, co vypíše jen neformátovaný řetězec Hello world!. Instance třídy Flask (hlavní třída frameworku) představuje samotnou webovou aplikaci.
from flask import Flask
app = Flask(__name__) # __name__ je název modulu obsaující tento kód
@app.route('/') # vytvoříme odpověď pro hlavní stránku aplikace
def home():
return 'Hello world!'
if __name__ == '__main__':
app.run(debug=True, port=4444) # spuštění aplikace pro testování na specifickém portu
Dekorátor @app.route()
Dekorátor @app.route() (pro jednoduchost předpokládejme, že v proměnné app máme instanci hlavní třídy Flask) je ve Flasku používán k definování koncových bodů aplikace, tedy URL, na které aplikace reaguje. Výsledek takto dekorované funkce je uživateli vrácen, po zaslání HTTP requestu na danou URL.
@app.route('/') # route pro hlavní stránku
def home():
return "Hello world!"
@app.route('/about') # route pro stránku /about
def about():
return "Toto je stránka O nás."
@app.route('/user/<username>') # route s parametrem v url
def user_profile(username):
return f"Ahoj, {username}!"
@app.route('/post/<int:post_id>') # route s parametrem v url typu int
def post(post_id):
return f"Zobrazuji příspěvek s ID {post_id}."
Výchozí nastavení route je obsluha GET reqeustů, což můžeme upravit pomocí nepovinného parametru methods.
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/submit', methods=['POST']) # definujeme obsluhu pro POST request
def submit():
# Přijmeme data z požadavku
data = request.json # Očekáváme data ve formátu JSON
if not data:
return jsonify({"error": "No data provided"}), 400 # vracíme kód 400 - bad request
# Zpracování dat
name = data.get('name', 'Unknown')
age = data.get('age', 0)
return jsonify({
"message": "Data received!",
"name": name,
"age": age
})
if __name__ == '__main__':
app.run(debug=True, port=5555)
Test lze provést například pomocí příkazu curl.
curl -X POST http://localhost:5555/submit \
-H "Content-Type: application/json" \
-d '{"name": "Petr", "age": 25}'
Pro práci s těmito koncovými body lze použít funkce url_for (dynamické generování URL na základě názvu funkce) a redirect pro přesměrování na jinou existující URL.
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/')
def home():
return "Vítejte na hlavní stránce!"
@app.route('/login')
def login():
return "Toto je přihlašovací stránka."
@app.route('/admin')
def admin():
# přesměrování na přihlašovací stránku
return redirect(url_for('login'))
@app.route('/user/')
def user_profile(username):
return f"Profil uživatele: {username}"
@app.route('/go_to_profile/')
def go_to_profile(username):
# dynamicky vygeneruje URL pro uživatelský profil a přesměruje na něj
return redirect(url_for('user_profile', username=username))
if __name__ == '__main__':
app.run(debug=True)
Statické soubory
Při vytváření webové aplikace se nevyhneme používání statických souborů, ať už obrázků, html šablon, kaskádových stylů nebo Javascriptových skriptů. Pro přehlednost je dobré pro tyto typy souborů vytvořit samostané složky, viz. struktura projektu níže.
# typická struktura projektu ve Flasku
static/
css/
main.css
...
img/
img.png
...
js/
script.js
...
templates/
base.html
index.html
about.html
...
app.py
module.py
...
Při vytvoření instance aplikace je potřeba specifikovat, kde tyto soubory hledat.
from flask import Flask
app = Flask(__name__, template_folder="templates", static_folder="static", static_url_path="/")
Perzistence dat
Takto vytvořená aplikace si samozřejmě uchová hodnoty svých proměnných jen po dobu svého vykonávání. Pokud chceme nějaká data uchovat i po ukončení naší webové aplikace, musíme data někam uložit. Nabízí se jednoduché řešení: uložit data do souborů (csv, json, xml atd.), což může být postačující řešení pro jednoduché aplikace.
V případě komplexnějších aplikací je vhodnější použít nějaký databázový systém. V pythonu je k dispozici například balíček SQLAlchemy, který poskytuje ORM (Object-Relational Mapper), což umožňuje pracovat s databázemi pomocí Python objektů namísto přímých SQL dotazů. Kromě toho umožňuje psát SQL dotazy přímo v Pythonu a funguje s bežnými databázovými systémy, jako jsou PostgreSQL, MySQL, SQLite, Oracle atd.
HTML šablony
Pro vytváření HTML šablon se ve Flasku používá šablonovací engine Jinja kombinující html a kód, který je podobný Pythonu. Více zde.
Jednoduchý příklad
Zde je kód aplikace obsahující dvě propojené HTML stránky. Obsah souboru index.html na adrese /:
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
</head>
<body>
<h1>{{ message }}</h1>
<p>Toto je dynamický obsah vložený pomocí Flasku.</p>
<a href="/about">O nás</a>
</body>
</html>
Obsah souboru about.html na adrese /about:
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
</head>
<body>
<h1>{{ title }}</h1>
<p>{{ content }}</p>
<a href="/">Zpět na hlavní stránku</a>
</body>
</html>
Samotný kód aplikace, který používá funkci render_template(), která načte HTML šablonu ze složky templates a vloží do ní data předaná jako klíčové parametry (důležité je stejné pojmenování klíče a proměnné v šabloně).
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
# Předáme data šabloně
return render_template('index.html', title="Hlavní stránka", message="Vítejte na naší stránce!")
@app.route('/about')
def about():
return render_template('about.html', title="O nás", content="Toto je stránka o nás.")
if __name__ == '__main__':
app.run(debug=True)
Dědičnost šablon
V předchozím příkladu v obou stránkách opakujeme hlavičku HTML, což lze zjednodušit pomocí dědičnosti. Vytvoříme základní šablonu base.html:
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
</head>
<body>
<!-- navigace -->
<nav>
<a href="/">Domů</a>
<a href="/about">O nás</a>
</nav>
<hr>
<!-- hlavní obsah -->
<div>
{% block content %}
<!-- Obsah jednotlivých stránek se vloží sem -->
{% endblock %}
</div>
<hr>
<!-- patička -->
<footer>
<p>2024 Moje Aplikace</p>
</footer>
</body>
</html>
Předchozí šablonu index.html nyní můžeme zjednodušit, tím že zdědíme šablonu base.html:
{% extends "base.html" %}
{% block content %}
<h1>Vítejte na hlavní stránce!</h1>
<p>Toto je úvodní stránka naší aplikace.</p>
{% endblock %}
Cykly
Pokud proměnná, kterou vkládáme do šablony, je seznam (v příkladu je to proměnná products), můžeme přes nej v šabloně přímo iterovat. V cyklu jsou k dizpicici tyto užitečné proměnné:
loop.revindex0:Zbývající počet iterací (od nuly).loop.revindex:Zbývající počet iterací (od jedničky).loop.first:True, pokud je aktuální iterace první.loop.last:True, pokud je aktuální iterace poslední.loop.length:Celkový počet iterací v cyklu.loop.index0:Index iterace, začíná od 0.loop.index:Index iterace, začíná od 1.
<ul>
{% for product in products %}
<li>
<a href="{{ url_for('product_detail', product_id=loop.index0) }}">
{{ products[loop.index0] }}
</a>
</li>
{% endfor %}
</ul>
V této šabloně jsou odkazy v seznamu parametrizované indexem položky, čehož se využívá níže ve funkci product_detail().
from flask import Flask, render_template, url_for
app = Flask(__name__)
# seznam produktů
products = ["Produkt A", "Produkt B", "Produkt C"]
@app.route('/')
def home():
return render_template('index.html', title="Seznam produktů", products=products)
@app.route('/product/<int:product_id>')
def product_detail(product_id):
return f"Detail produktu {products[product_id]} s ID: {product_id}"
if __name__ == '__main__':
app.run(debug=True)
Podmínky
V šablonách můžeme použít i operátor if-elif-else pro podmíněné vyhodnocení. Podmínky lze navíc umístit v podstatě kamkoliv, takže nemusí generovat celé HTML tagy, ale například jenom hodnotu atributu.
<!-- příklad podmíněného výpisu seznamu v proměnné items -->
<h2>Seznam položek:</h2>
<ul>
<!-- pokud jsou items neprázdný seznam -->
{% if items %}
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
<!-- pokud jsou items prázdné -->
{% else %}
<p>Seznam je prázdný.</p>
{% endif %}
</ul>
Složené podmínky lze vytvářet pomocí logických operátorů and, or a not.
<!--
příklad podmíněného vyhodnocení pro user = {"name": "Petr", "logged_in": True, "admin": True}
k hodnotám slovníku lze v šabloně přistupovat přes tečkovou notaci
-->
{% if user.logged_in and user.admin %}
<p>Máte administrátorský přístup.</p>
{% elif user.logged_in %}
<p>Jste přihlášený, ale nemáte administrátorský přístup.</p>
{% else %}
<p>Jste nepřihlášený. <a href="/login">Přihlásit se</a></p>
{% endif %}
Filtry
Filtry slouží k úpravě nebo formátování dat přímo v šablonách. Představují jednoduchý způsob, jak manipulovat s hodnotami, aniž by bylo nutné měnit samotný Python kód. Filtry se aplikují pomocí operátoru |.
V Jinja2 je k dispozici celá řada předdefivaných základních filtrů. Celý seznam naleznete zde.
<!-- pro string = 'hello world!' se zobrazí: 'Hello World!' -->
<p>Jméno: {{ data.name|title }}</p>
<!-- pro string = 'Hello world!' se zobrazí: 'Hello' -->
<p>Popis: {{ string|truncate(5) }}</p>
<!-- pro price = 1234.5678 se zobrazí: 1234.56 -->
<p>Cena: {{ price|round(2) }} Kč</p>
<!-- pro tags = ['python', 'flask', 'jinja'] se zobrazí: 'python, flask, jinja' -->
<p>Tagy: {{ tags|join(", ") }}</p>
Vlastní filtr můžeme definovat pomocí dekorátoru @app.template_filter().
@app.template_filter('reverse')
def reverse_string(value):
return value[::-1]
@app.template_filter('currency')
def format_currency(value):
return f"{value:,.2f} Kč"
Způsob aplikace vlastního filtru je stejný jako u těch předdefinovaných.
<!-- pro string = 'Hello world!' se zobrazí: '!dlrow olleH' -->
<p>Pozdrav: {{ string|reverse }}</p>
<!-- pro price = 12345.678 se zobrazí: 'Cena: 12,345.68 Kč' -->
<p>Cena: {{ price|currency }}</p>