Côté serveur
Les données envoyées par le formulaire arrivent au serveur. Bottle fournit
l'objet request, qui contient toutes les informations sur la requête, et en
particulier les données du formulaire.
Classe Request
Référence de la classe Request :
https://bottlepy.org/docs/dev/api.html#bottle.BaseRequest
Il faut importer request depuis bottle :
from bottle import request
Les données de formulaire sont accessible via :
request.queryourequest.GETpour les données envoyées par la méthodeGETrequest.formsetrequest.files, ourequest.POSTqui combine les deux pour les données envoyées par la méthodePOST
Ces données sont rendues disponibles sous forme de dictionnaire (plus
précisément un
MultiDict, qui est
un type de données permettant de gérer plusieurs valeurs pour une même clé. Il
est utile pour traiter les formulaires où plusieurs valeurs peuvent être
envoyées pour un même champ).
Accéder aux données
Pour accéder aux données, on peut utiliser la syntaxe request.POST['nom'] ou
request.forms.get('nom'). La méthode get permet de spécifier une valeur par
défaut si la clé n'existe pas, par exemple
request.forms.get('nom', default=None).
Il est aussi possible d'utiliser directement un accès par attribut, par exemple
request.forms.nom.
Si plusieurs valeurs sont envoyées pour un même champ (exemple avec une requète
GET /formulaire?choix=1&choix=2&choix=3), on peut les récupérer avec
request.GET.getall('choix') ou request.GET.getlist('choix'), qui renvoient
une liste de toutes les valeurs associées à la clé choix.
.keys(), .values() et .items() fonctionnent aussi sur les objets
request.GET et request.POST, ce qui permet de les parcourir comme un
dictionnaire classique.
Lorsqu'on envoie des fichiers via un formulaire
(voir ci-dessous), on peut les récupérer avec
request.files, qui est un dictionnaire associant le nom du champ à un objet
FileUpload. Cet objet contient des méthodes pour accéder au contenu du
fichier, à son nom, à son type MIME, etc. Par exemple, pour récupérer le nom du
fichier téléchargé, on peut utiliser request.files['fichier'].filename.
La méthode save de l'objet FileUpload permet d'enregistrer le fichier sur le
serveur. Elle prend en paramètre le chemin où le fichier doit être enregistré.
Par exemple, pour enregistrer le fichier dans le dossier /tmp, on peut
utiliser (sauf sous Windows) :
request.files['fichier'].save('/tmp/' + request.files['fichier'].filename)
Routage
Le traitement des données du formulaire se fait dans une route; deux options sont possibles :
-
utiliser la même route que celle qui affiche le formulaire, en spécifiant la méthode
POSTdans le décorateur de la route. C'est souvent la méthode la plus simple pour pouvoir gérer les erreurs de saisie et afficher le formulaire avec les données déjà saisies pour correction.from bottle import route, run, template, request @route('/formulaire', method=['GET', 'POST']) def formulaire(): if request.method == 'POST': # Traitement des données du formulaire nom = request.forms.get('nom', None) message = request.forms.get('message', None) # ... traitement des données if erreur_dans_le_formulaire: return template("formulaire.html", erreur=erreur, nom=nom, message=message) # Vérification des données OK # ... traitement des données (envoi de mail, etc..) return template("merci_message.html") else: return template("formulaire.html") -
utiliser une route différente pour traiter les données du formulaire, en spécifiant l'URL de la route dans l'attribut
actiondu formulairefrom bottle import route, run, template, request @route('/formulaire', method='GET') def formulaire(): return template("formulaire.html") @route('/traitement', method='POST') def traitement(): # Traitement des données du formulaire nom = request.forms.get('nom') message = request.forms.get('message') # ... return template("merci_message.html")
Exemples
Formulaire de contact basique
<body>
<form action="/contact" method="POST">
<label for="nom">Nom :</label>
<input type="text" id="nom" name="nom" />
<label for="message">Message :</label>
<textarea id="message" name="message"></textarea>
<button type="submit">Envoyer</button>
</form>
</body>
from bottle import route, request, template, run
def envoyer_email(nom, message):
# Simuler l'envoi d'un email
print(f"Envoi d'un email à {nom} avec le message : {message}")
@route('/contact', method=['GET', 'POST'])
def contact():
if request.method == 'GET':
return template("contact_formulaire.html")
# Récupération des données du formulaire
nom = request.forms.get('nom', default=None)
message = request.forms.get('message', default=None)
# Traitement des données (par exemple, envoi d'un email)
envoyer_email(nom, message)
return template("merci.html")
Formulaire de contact avec vérification des erreurs
% setdefault("erreur", None)
% setdefault("nom", "")
% setdefault("message", "")
<!-- ... -->
<body>
<form action="/contact" method="POST">
% if erreur is not None:
<p class="erreur">{{ erreur }}</p>
% end
<label for="nom">Nom :</label>
<input type="text" id="nom" name="nom" value="{{ nom }}" />
<label for="message">Message :</label>
<textarea id="message" name="message">{{ message }}</textarea>
<button type="submit">Envoyer</button>
</form>
</body>
from bottle import route, request, template, run
def envoyer_email(nom, message):
# Simuler l'envoi d'un email
print(f"Envoi d'un email à {nom} avec le message : {message}")
@route('/contact', method=['GET', 'POST'])
def contact():
if request.method == 'POST':
# Récupération des données du formulaire
nom = request.forms.get('nom', default=None)
message = request.forms.get('message', default=None)
# Vérification des erreurs
if not nom or not message:
erreur = "Tous les champs sont obligatoires."
return template("contact_formulaire_check.html",
erreur=erreur, nom=nom, message=message)
if len(message) < 10:
erreur = "Le message doit contenir au moins 10 caractères."
return template("contact_formulaire_check.html",
erreur=erreur, nom=nom, message=message)
if "spam" in message:
erreur = "Le message contient un mot interdit."
return template("contact_formulaire_check.html",
erreur=erreur, nom=nom, message=message)
# Traitement des données (par exemple, envoi d'un email)
envoyer_email(nom, message)
return template("merci.html")
else:
return template("contact_formulaire_check.html")
Choix d'options multiples
Télécharger le code : choix_formulaire.html (avec les 2 versions et l'affichage des choix)
% setdefault("choix", list())
<!DOCTYPE html>
<html lang="fr">
Avec un `select[multiple]` :
```html
% setdefault("choix", list())
<form action="/choix" method="POST">
<label for="choix">Choisissez une ou plusieurs options :</label>
<select id="choix" name="choix" multiple>
<option value="choix1" {{"selected" if "choix1" in choix else ""}}>Option 1</option>
<option value="choix2" {{"selected" if "choix2" in choix else ""}}>Option 2</option>
<option value="choix3" {{"selected" if "choix3" in choix else ""}}>Option 3</option>
</select>
<button type="submit">Envoyer</button>
</form>
ou bien avec des input[type=checkbox] :
% setdefault("choix", list())
<form action="/choix" method="POST">
<input type="checkbox" id="choix1" name="choix"
value="choix1" {{"checked" if "choix1" in choix else ""}}>
<label for="choix1">Option 1</label><br>
<input type="checkbox" id="choix2" name="choix"
value="choix2" {{"checked" if "choix2" in choix else ""}}>
<label for="choix2">Option 2</label><br>
<input type="checkbox" id="choix3" name="choix"
value="choix3" {{"checked" if "choix3" in choix else ""}}>
<label for="choix3">Option 3</label><br>
<button type="submit">Envoyer</button>
</form>
Dans les deux cas, affichage des options choisies :
% if len(choix) > 0:
<hr>
<h2>Vous avez choisi :</h2>
<ul>
% for c in choix:
<li>{{ c }}</li>
% end
</ul>
% end
Télécharger le code du serveur
@route('/choix', method=['GET', 'POST'])
def choix():
if request.method == 'POST':
# Récupération des choix
choix = request.POST.getall('choix')
print(choix)
return template("choix_formulaire.html", choix=choix)
else:
return template("choix_formulaire.html")
Upload de fichiers
Pour permettre à l'utilisateur d'uploader un fichier, il faut utiliser
l'attribut enctype="multipart/form-data" dans la balise <form>. Cela permet
d'envoyer des fichiers binaires au serveur. request.files contiendra alors
le fichier uploadé.
Télécharger le code : upload.html
<form action="/upload" method="POST" enctype="multipart/form-data">
<label for="fichier">Choisissez un fichier :</label>
<input type="file" id="fichier" name="fichier" />
<button type="submit">Envoyer</button>
</form>
Télécharger le code du serveur
from bottle import route, request, run, template
from os.path import join, dirname
@route('/upload', method=['GET', 'POST'])
def upload():
if request.method == 'GET':
return template("upload.html")
# Récupération du fichier
fichier = request.files.get('fichier')
# Traitement du fichier (par exemple, enregistrement sur le serveur
# dans un dossier "uploads" situé dans le même répertoire que le script)
if fichier:
fichier.save(join(dirname(__file__), "uploads", fichier.filename))
return template("merci.html")
Référence : Documentation de la balise input type="file" en HTML et Documentation de la méthode save() de bottle
En pratique
Calculatrice
Créer un formulaire de calcul qui permet de saisir deux nombres et de choisir
(avec une liste déroulante) une opération (addition, soustraction,
multiplication, division), et qui affiche le résultat. La route du formulaire
et du résultat est la même (/calculatrice), la méthode est GET, et la
page doit afficher
- un message d'erreur si au moins un des deux nombres n'est pas un nombre ou si l'opération n'est pas reconnue
- le résultat du calcul juste après le formulaire (forme "[a] [op] [b] = [resultat]")


Correction
Télécharger le code : calculatrice.html
% setdefault("a", "")
% setdefault("b", "")
% setdefault("op", "")
% setdefault("resultat", None)
% setdefault("erreur", None)
<body>
<h1>Calculatrice</h1>
<form action="/calculatrice" method="GET">
% if erreur is not None:
<p class="erreur">{{erreur }}</p>
% end
<input type="text" id="a" name="a" value="{{ a }}" placeholder="Nombre 1" />
<select id="op" name="op">
<option value="" {{ "selected" if op is None else "" }}>??</option>
<option value="add" {{ "selected" if op == "add" else "" }}>+</option>
<option value="sub" {{ "selected" if op == "sub" else "" }}>-</option>
<option value="mul" {{ "selected" if op == "mul" else "" }}>*</option>
<option value="div" {{ "selected" if op == "div" else "" }}>/</option>
</select>
<input type="text" id="b" name="b" value="{{ b }}" placeholder="Nombre 2"/>
<button type="submit">=</button>
% if resultat is not None:
<span class="resultat">= {{ resultat }}</span>
% end
</form>
% end
</body>
Télécharger le code du serveur
from bottle import route, run, template, static_file, get, request
# Calculatrice
@get('/calculatrice')
def calculatrice():
a = request.query.get('a', default=None)
b = request.query.get('b', default=None)
op = request.query.get('op', default=None)
if a is None and b is None and op is None:
# Aucune valeur saisie, on affiche le formulaire vide
return template("calculatrice.html")
try:
a = float(a)
b = float(b)
except ValueError:
# Erreur de conversion, on affiche le formulaire avec un message d'erreur
return template("calculatrice.html",
erreur="Les valeurs saisies ne sont pas des nombres.",
a=a, b=b, op=op)
match op:
case "add":
resultat = a + b
case "sub":
resultat = a - b
case "mul":
resultat = a * b
case "div":
if b == 0:
return template("calculatrice.html", erreur="Division par zéro.",
a=a, b=b, op=op)
resultat = a / b
case _:
return template("calculatrice.html", erreur="Opération inconnue.",
a=a, b=b, op=op)
# Affichage du résultat
return template("calculatrice.html", a=a, b=b, op=op, resultat=resultat)
Options de voiture
Créer un formulaire à l'adresse /mavoiture, permettant de choisir des
options pour une voiture, et qui affiche un résumé des choix ainsi que le
prix de la voiture (prix de base sans options = 18000 euros) lorsque le
formulaire est soumis (méthode POST). Les options sont disponibles dans le dictionnaire
suivant :
{
"pmetal": {
"titre": "Peinture métallisée",
"prix": 500
},
"gps": {
"titre": "GPS",
"prix": 1000
},
"sieges": {
"titre": "Sièges cuir",
"prix": 1500
},
"toit": {
"titre": "Toit ouvrant panoramique",
"prix": 2000
},
"jantes": {
"titre": "Jantes alliage",
"prix": 400
}
}
Vous pouvez choisir d'afficher les options dans une liste déroulante
<select> (avec l'attribut multiple) ou avec des cases à cocher
<input type="checkbox">. Dans les deux cas, le prix total doit être affiché
sous le formulaire.

Correction
En version select[multiple] (Télécharger le code) :
% setdefault("options", {})
% setdefault("selected", [])
% setdefault("prix", 18000)
<body>
<form action="/mavoiture" method="POST">
<label for="options">Choisissez vos options :</label>
<select id="options" name="options" multiple>
% for key, value in options.items():
<option value="{{ key }}"
{{ "selected" if key in selected else "" }}>
{{ value['titre'] }} ({{ value['prix'] }} €)
</option>
% end
</select>
<button type="submit">Calculer le prix</button>
</form>
<p>Prix de base : 18000 €</p>
% if prix > 18000:
<p>Prix total : {{ prix }} €</p>
% end
</body>
Ou en version input[type=checkbox] (Télécharger le code) :
% setdefault("options", {})
% setdefault("selected", [])
% setdefault("prix", 18000)
<body>
<form action="/mavoiture" method="POST">
<label for="options">Choisissez vos options :</label>
% for key, value in options.items():
<div>
<input type="checkbox" id="{{ key }}"
name="options" value="{{ key }}"
{{ "checked" if key in selected else "" }}>
<label for="{{ key }}">
{{ value['titre'] }} ({{ value['prix'] }} €)
</label>
</div>
% end
<button type="submit">Calculer le prix</button>
</form>
<p>Prix de base : 18000 €</p>
% if prix > 18000:
<p>Prix total : {{ prix }} €</p>
% end
Télécharger le code du serveur
from bottle import route, run, template, static_file, get, request
@route('/mavoiture', method=['GET', 'POST'])
def mavoiture():
if request.method == 'POST':
# Récupération des options
options = request.POST.getall('options')
prix = 18000
for option in options:
prix += OPTIONS[option]['prix']
return template("mavoiture.html", options=OPTIONS, selected=options, prix=prix)
else:
return template("mavoiture.html", options=OPTIONS, selected=[], prix=18000)