Python può essere usato anche per creare siti web. Il modo migliore per farlo è utilizzare un framework dedicato che semplifica tutte le operazioni base, come
Flask.
La prima cosa da fare è installare
Flask
Il classico sito di esempio "ciao mondo", basato su
Flask, diventa:
# app.py
from flask import Flask
# creo la app Flask
app = Flask(__name__)
# definisco la prima route, home
@app.route("/")
def main():
return "<html><head><title>Test flask</title></head><body><h1>ciao mondo</h1></body></html>"
# verifica se è il programma principale
# e manda in esecuzione il web server su http://localhost:5000
# in questo caso in debug, ovvero si riavvia ad ogni cambiamento dei file
if __name__ == "__main__":
app.run(debug=True, port=5000)
dove, dopo aver importato il modulo
Flask, creo l'applicazione
app e, tramite l'attributo
@app.route(percorso), definisco con quale
url risponderà la specifica funzione (in questo caso
main()).
La funzione associata ad una
route deve restituire l'
html che rappresenta la pagina.
Può essere testato con
Ovviamente gestire una pagina web
come stringa non è sicuramente il modo migliore per creare un sito.
Flask mette a disposizione una gestione basata su file di
template html tramite il metodo
render_template.
Per gestire i template deve essere creata una cartella
templates in cui inserire tutti i template
html, la struttura del progetto diventa:
- templates
- index.html
- pagina1.html
- app.py
Il file
index.html conterrà questo
html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{title}}</title>
<!-- link rel="stylesheet" href="/css/style.css"-->
</head>
<body>
<h1>ciao mondo</h1>
<!-- script src="/app/app.js"></script-->
</body>
</html>
il file
pagina1.html sarà identica con la differenza della scritta
<h1>Pagina 1</h1>.
Ho utilizzato il placeholder racchiuso tra doppie parentesi graffe {{title}}, questo verrà sostituito in fase di render con il valore passato tramite il metodo render_template()
a questo punto posso modificare il codice ed utilizzare il template tramite il metodo
render_template() passando il valore
title:
# importo il modulo template
from flask import Flask, render_template
...
@app.route("/")
def main():
return render_template("index.html", title="Test Falsk")
...
allo stesso modo in cui ho aggiunto la pagina di root
/ posso aggiungere anche altre pagine, ad esempio la url
/abouts, che richiama la funzione
pagina1:
...
@app.route("/abouts")
def pagina1():
return render_template("pagina1.html", title="Altro titolo")
...
Posso fare un ulteriore ottimizzazione ai template, anziché riportare in ogni template le parti comuni, le sposto in un file che chiamo
base.html<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{title}}</title>
<!-- link rel="stylesheet" href="/css/style.css"-->
</head>
<body>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/abouts">Pagina 1</a></li>
</ul>
</nav>
{% block content %}{% endblock %}
<!-- script src="/app/app.js"></script-->
</body>
</html>
Per chi conosce
MVC in
C# è l'equivalente della pagina di
_Layout.cshtml.
La particolarità di questa pagina è l'uso del placeholder
{% block content %}{% endblock %} che indica la posizione dove verrà inserito il contenuto delle singole pagine in fase di rendering.
Sempre per chi conosce
MVC in
C# è simile all'uso di
@RenderBody() o
@RenderSection(nomeSection) nella pagina di
_Layout.cshtml.
Le singole pagine si semplificano e diventano:
<!-- index.html -->
{% extends "base.html" %}
{% block content %}
<h1>ciao mondo</h1>
{% endblock %}
<!-- pagina1.html -->
{% extends "base.html" %}
{% block content %}
<h1>Pagina 1</h1>
{% endblock %}
Si può notare l'uso del tag
{% extends "base.html" %} per indicare che questa pagina userà, o meglio estenderà, la pagina
base.html e il tag
{% block content %} ... html ... {% endblock %} il cui contenuto verrà inserito nel template
base nella posizione corrispondente all'omonimo tag
content.
Il tag
block ha questa sintassi, nella pagina
base metto un tag nella forma:
{% block nome_del_blocco %}{% endblock %}
che può avere un contenuto di
default, contenuto che verrà sovrascritto solo se il tag verrà usato in un template che estende
base{% block nome_del_blocco %}
... posso inserire qualunque codice html o altri tag ...
{% endblock %}
nel template, quando serve, uso la stessa sintassi, con lo stesso nome, per definire il
blocco a cui voglio dare una diversa implementazione
{% block nome_del_blocco %}
... html ...
{% endblock %}
ovviamente posso avere
più blocchi con
nomi differenti.
Come ulteriore finezza aggiungo il template
error404.html per customizzare gli errori di pagina non trovata
<html>
<body>
<h1>404 Pagina non trovata</h1>
</body>
</html>
e modifico il codice
Python per reindirizzare tutte le richieste non risolte dalle
route alla pagina di gestione errori
@app.errorhandler(404)
def page_not_found(error):
return render_template('error404.html'), 404
Con la stessa tecnica, posso aggiungere quante pagine voglio tramite l'uso di
@app.route(), lo schema è sempre questo:
@app.route(<url_percorso>)
def nome_funzione():
return render_template(<nome_template>, <eventuali_parametri>)
Un sito però si compone anche di risorse
statiche come immagini, file
JavaScript,
CSS, ecc...
Per utilizzare le risorse statiche deve essere creata l'applicazione
Flask (variabile app) passando il percorso locale dove sono presenti le risorse statiche.
import os
from flask import Flask, render_template
# costruisco il percorso alla cartella "static"
# basandomi sul path dell'applicazione corrente
static_file_dir = os.path.join(os.path.dirname(
os.path.realpath(__file__)), "static")
# creo la app passando il percorso della cartella con le risorse statiche
app = Flask(__name__, static_url_path="/static", static_folder=static_file_dir)
Al template di progetto aggiungo la cartella
static con le risorse necessarie:
- static
- logo.png
- ...altri file o cartelle...
- templates
- base.html
- index.html
- ...altri template...
- app.py
e modifico il template
base per aggiungere un immagine statica
<!DOCTYPE html>
<html>
...
<body>
<!-- immagine come risorsa statica -->
<header><img src="/static/logo.png"></header>
<nav>...
</body>
</html>
Finorà ho mostrato solo pagine
html con contenuto statico.
Un sito reale si compone di pagine
dinamiche, ovvero pochi template
html valorizzati con dati presi da un database.
Quindi imposto l'applicazione con questa nuova struttura:
- static
- logo.png
- ...altri file o cartelle...
- templates
- base.html
- error404.html
- page.html
- app.py
- mock.py
Per prima cosa, creo il modulo
mock.py che simulerà il
database:
# mock.py
# definisco 3 pagine, simulo il db con una collection
db = [
{"id": 0, "title": "Home", "body": "By <a href='https://www.sgart.it'><b>sgart.it</b></a>"},
{"id": 1, "title": "Pagina 1", "body": "corpo della <b>pagina 1</b>"},
{"id": 2, "title": "Info", "body": "Test Flask"},
]
# metodo per recuperare la pagina singola
def get(id):
for item in db:
if item["id"] == id:
print(item)
return item
return None
# metodo per recuperare il menu in base alle pagine presenti nel db
def menu():
items = []
for item in db:
items.append({"id": item["id"], "title": item["title"]})
return items
In
Python l'equivalente delle keyword
null di altri linguaggi è
None.
questo modulo sarà usato da
app.py con le seguenti modifiche:
# app.py
import os
...
# importo il modulo mock che simula il database
import mock as db
...
# definisco un metodo che si occupa di caricare la pagina dal DB
# se non la trova mostra la pagina di errore 404
def get_page(id):
page = db.get(id)
if page == None:
# se non trovo la pagina errore 404
return page_not_found(None)
# recupero il menu
menu = db.menu()
# faccio il render
return render_template("page.html", menu=menu, page=page)
# definisco la prima route, home, caricherà la pagina con id=0
@app.route("/")
def main():
return get_page(0)
# aggiungo una nuova route per caricare le altre pagine nella forma /pagina/2
@app.route("/pagina/<int:id>")
def pagina(id):
return get_page(id)
...
In questo caso uso una route con parametri nella forma @app.route("/nome_url/<tipo_dato:nome_parametro>"), che corrisponde a una url tipo: /pagina/2. Dove il valore 2 corrisponde all'id della pagina nel database.
Ho modificato il metodo
render_template per passare sia la collection con i valori per il
menu, sia il dictionary con i dati della pagina (
page), quindi diventa
return render_template("page.html", menu=menu, page=page).
Adesso non rimane che ridefinire i template per generare in automatico il
menu e la
pagina.
La pagina
base.html diventa:
...
<style>li.selected a { font-weight: bold; }</style>
</head>
<body>
...
<nav>
<ul>
<!-- genero il menu usando un ciclo for -->
{% for item in menu %}
<!-- determino se il menu corrisponde alla pagina corrente, se si lo evidenzio-->
<li class="{{ 'selected' if item['id'] == page['id'] else '' }}">
{% if item["id"] == 0 %}
<!-- se è la home page, semplifico il link -->
<a href="/">{{item['title']}}</a></li>
{% else %}
<a href="/pagina/{{item['id']}}">{{item['title']}}</a>
{% endif %}
</li>
{% endfor %}
</ul>
</nav>
<div class="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
in questo template ho introdotto alcune cose nuove:
- il ciclo for tramite la sintassi: {% for <variabile> in <collection> %} ...html... {% endfor %}
- il costrutto if tramite la sinstassi: {% if <condizione> %} ...html condizione vera... {% else %} ...html condizione falsa... {% endif %}
- il già citato {% block content %}{% endblock %} usato come placeholder per il contenuto delle singole pagine
non rimane che definire il template della singola pagina,
page.html:
<!-- page.html -->
{% extends "base.html" %}
{% block content %}
<h1>{{page["title"]}}</h1>
<div class="body">
{# commento: uso "safe" per evitare l'escape dei codici html #}
{{page["body"] | safe}}
</div>
{% endblock %}
Dato che il il
body della pagina continene anche del codice
html, non è sufficiente il tag nella forma
{{ valore }}, Questa sintassi fa un render
sicuro facendo l'escape di tutti i tag
html.
Se voglio che l'
html venga renderizzato correttamente, senza modifiche, devo usare il filtro
safe nella forma
{{ valore | safe }}Ricapitolando un progetto
Python con
Flask prevede:
- l'import dei muduli os e flask
- la creazione di un app app = Flask(__name__, static_url_path="/static", static_folder=static_file_dir) con il supporto per i file statici
- la definizione di una o più route @app.route(percorso) con eventuali parametri
- la creazione di una cartella templates con file html, solitamente un base.html e uno o più template di pagina
nei
template posso avere i seguenti tag:
- {{ valore }}: inserisce il valore di una variabile nel template ( con la variante {{ valore | safe }} )
- {{% ... %}}: istruzioni, tipo: for, if, block, extends, assegnamento variabili, ecc...
- {{# ... #}}: inserisce un commento in pagina che non verrà renderizzato nell'html
Se ho la necessità di inserire delle parentesi graffe senza che vengano interpretate come tag, posso usare
{% raw %}:
{% raw %}
<p>
Esempio di escape: con il tag {% raw %} posso usare le { parentesi graffe }
senza che vengano processate dal parser del template
</p>
{% endraw %}
L'esempio finale completo può essere trovato su
GitHub - Esempio Flask in PythonEsempio finale Questa è solo una veloce panoramica di
Flask, per approfondimenti vedi la
documentazione ufficiale.