Faire des sondages avec flask et ethercalc

Voir les slides

NOTE PRÉLIMINAIRE Les commandes de ce tutorial sont données pour une machine Linux récente. Suivant votre plateforme, vous devrez y apporter quelques adaptations. Notamment, vous devrez peut-être remplacer la commande python par python3, ou py, ou py -3 ou…

Par contre le code python lui-même devrait bien entendu rester inchangé.

Installer uv

Dans ce tutoriel nous allons utiliser uv pour gérer les dépendances.

Suivre les instructions d’installation du site officiel de uv.

Installer flask

~$ mkdir polls
~$ cd polls
~/polls$ uv init

Cela va créer un fichier pyproject.toml, un .python-version, un README.md ainsi qu’un main.py (que nous n’utiliserons pas, vous pouvez le supprimer). Ajoutons maintenant une dépendance à flask:

~/polls$ uv add flask

Le fichier pyproject.toml devrait contenir une description de votre projet avec ses dépendances:

~/polls$ cat pyroject.toml
[project]
name = "polls"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "flask>=3.1.3",
]

uv a aussi créé un fichier uv.lock qui contient une description précise de toutes les dépendances installées avec leur version. Versionnez ce fichier avec le reste, cela vous permettra de reproduire l’environnement de manière fiable!

Hello World avec Flask

Créez un fichier polls.py avec un programme Flask minimal:

from flask import Flask
app = Flask(__name__)

@app.get('/')
def hello():
    return 'Hello World!'

C’est le moment de tester:

~/polls$ uv run flask --app polls run --debug

S’il n’y a pas d’erreur, rendez-vous sur http://localhost:5000/ où vous devriez être accueilli par un sympathique Hello World!

Remarque Dans de nombreux tutos flask un peu anciens, vous verrez @app.route() au lieu du @app.get() ci-dessus. app.get(<path>) est au fait un alias vers app.route(<path>, methods=['GET']) (ajouté dans Flask 2.0). Cela permet de rendre les choses plus concises quand on veut utiliser d’autres méthodes http (voir ci-dessous pour POST).

Créer une feuille ethercalc…

Évidemment le mieux serait d’héberger Ethercalc nous-même, mais pour ce rapide tuto nous utiliserons une instance hébergée ailleurs. L’application est disponible sur différents serveurs, mais ils ne sont pas tous disponibles tout le temps, et certains ne tiennent pas bien la charge. Choisissez l’un des serveurs ci-dessous (pas tous le même!) et changez-en si vous rencontrez des problèmes.

Sur l’un de ces serveurs, créez une nouvelle feuille et:

  1. Prenez note de l’ID généré car nous en aurons besoin par la suite (et c’est la seule manière de retrouver votre feuille…).
  2. Dans la cellule A1, entrez le titre de votre sondage.
  3. Dans les cellules B2, C2, E2, … entrez les options de votre sondage. Pour plus de clareté, vous pouvez noter “Nom” en A2 (le nom des personnes ayant voté s’inscrira dans cette colonne).

Exemple de google doc

Nous allons maintenant changer la page d’accueil: remplacez la méthode hello() par

@app.get('/')
def poll_list():
    # Adaptez l'ID à la feuille que vous avez créée ci-dessus!
    return render_template('home.html', poll_id='zxya1mcjhwkv')

Pour que cela fonctionne, il faudra

from flask import Flask, render_template
<h1>Application de sondage</h1>

<p>Voir le <a href="/{{poll_id}}">sondage</a></p>

Testez: http://localhost:5000/ devrait maintenant vous donner un lien vers votre sondage… mais naturellement un lien brisé!

On va donc corriger ça. D’abord on se donne un moyen de récupérer les données du sondage. Vers le haut du fichier, on ajoute une définition de “constante”:

# Bien entendu, adaptez l'URL au serveur choisi!
ETHERCALC_URL = 'https://ethercalc.net'

puis on définit la fonction suivante:

def get_poll_as_csv(id):

    url = f'{ETHERCALC_URL}/_/{id}/csv'
    answer = urlopen(url)
    content =  answer.read().decode(answer.headers.get_content_charset()).splitlines()
    reader = csv.reader(content)
    return list(reader)

… Sans oublier d’ajouter les imports correspondants en haut du fichier:

from urllib.request import urlopen
import csv

Ensuite on définit la route:

@app.get("/<id>")
def display_poll(id):

    sheet = get_poll_as_csv(id)

    # First line has title
    title = sheet.pop(0)[0]

    # Second line is dummy value, then options:
    options = sheet.pop(0)[1:]

    return render_template("poll.html", **locals())

… et enfin le template poll.html

<h1>{{title}}</h1>

<ul>
    {% for o in options %}
        <li>{{o}}</li>
    {% endfor %}
</ul>

Vous devriez maintenant pouvoir aller sur la page du sondage et voir les options possibles.

Vers une mise en place du vote…

Nous avons réussi à aller chercher des informations dans un tableur, il nous reste à les modifier…

Juste sous le <h1> de poll.html, ajoutez

<form action="" method="post">
    <label for="id_who">Your Name</label>
    <input type="text" name="who" id="id_who" />
    <input type="submit" class="button" value="Vote!"/>
</form>

On se donne le moyen d’ajouter des données à notre feuille via l’API d’ethercalc:

def add_row_to_poll(id, data):

    url = f'{ETHERCALC_URL}/_/{id}'
    headers = {
        'Content-Type': 'text/csv'
    }
    new_row = ','.join(f'"{s}"' for s in data)+','
    request = Request(url,
                      headers=headers,
                      data=new_row.encode('utf-8'))
    urlopen(request)

On complète l’import d’urllib:

from urllib.request import urlopen, Request

Et enfin, on ajoute

@app.post('/<id>')
def vote(id):

    voter = request.form['who']
    add_row_to_poll(id, [voter])

    return redirect(url_for('display_poll', id=id))

(sans oublier d’importer request, redirect et url_for du module flask!)

On devrait maintenant pouvoir “voter”: le nom du participant est ajouté au tableur… mais le participant n’a pas encore l’occasion de donner son avis!

À vous de jouer!

En vous aidant de la documentation de flask, de celle de Jinja, et au besoin de la doc de l’API d’ethercalc effectuez les tâches suivantes:

Et si vous avez le temps…

Ajoutez quelques améliorations parmi les idées ci-dessous:

Pour aller plus loin…