⚗️ Flask#

Flask logo

Flask est un microframework pour Python basé sur Werkzeug, Jinja 2 et de bonnes intentions.

Et avant de demander: c’est sous licence BSD !

Micro framework ?

Micro ne signifie pas que l’ensemble de votre application doit tenir au sein d’un fichier de code source unique ou encore que Flask est pauvre en fonctionnalités.

Le terme micro framework signifie que le coeur de Flask est et restera simple mais extensible.

Flask n’inclut pas de niveau d’abstraction pour les base de données, de validation de formulaire ou n’importe quels librairies existant déjà et faisant parfaitement le job.

A la place celui-ci permet l’ajout d’extension et donc de fonctionnalités. Il ne contient que le support des requêtes HTTP, le routing et le templating, tout le reste est à votre appréciation.

Indication

Python et Flask se basent sur la convention plutôt que la configuration, ainsi les templates et les fichiers statiques sont dans des sous-répertoires ayant respectivement pour noms templates et static.

Avant propos sur le développement Web#

Quelques généralités qu’il est toujours utile de rappeler, il est important de :

  • Savoir ce que vous devez faire,

  • Savoir à quoi le framework va vous servir,

  • Connaître les grands principes, les bonnes pratiques et principaux designs patterns,

  • Savoir tester un programme ou une application.

Bien sûr cela comporte quelques contraintes :

  • La scalabilité,

  • Les besoins en performances pour le requêtage,

  • La vitesse d’affichage des page

On ne bidouille plus!

L’époque où l’on codait des sites à la mode de Grand-Papa dans tout un bric à brac de fichiers comprenant tous du HTML, des scripts PHP (ou autre), des requêtes BDD, etc. est révolue.

Allez, souvenons-nous :

<?
if($reqlivr<>0 && $reqlivr<>1)  //cas où le paramètre est erronné
$reqlivr=0;
if($reqlivr==0)
{
require ("../common/connexion.php");
$sql = mysql_query("SELECT * FROM livre ORDER BY Date DESC");
?>
<div align="left"><font face="Verdana" size="2"><br>Bienvenue dans notre livre d'or !<br>
N'hésitez pas à laisser ici vos impressions concernant le site et l'entreprise !

Pour développer un site robuste, maintenable il faut des pratiques éprouvées, génériques et partagées. Les design patterns, notamment (mais pas exclusivement), nous aident en ce sens. Un exemple très répandu aujourd’hui est le MVC: Model View Controller.

Les frameworks web implémentent pour la majorité ce genre de design pattern et bonnes pratiques, et favorisent leur bonne utilisation.

Les applications et sites web doivent être rapides pour les visiteurs qui les utilisent d’abord (UX: User eXperience) puis pour les robots indexeurs.

Par exemple, les requêtes au SGBD trop nombreuses. La gestion de la charge, et le paramétrage du SGBDR est subtil, généralement l’oeuvre d’ingénieurs spécialisés.

Une gestion de cache efficace peut en partir résoudre ce problème. Les frameworks web permettent de gérer efficacement le cache d’un site ou d’une application.

De plus un framework fournit des briques génériques: Authentification, Gestion des logs, ORM, Routing, Formulaire etc.

Plutôt que de recoder ces éléments à chaque nouveau projet, les frameworks fournissent des solutions génériques et adaptables.

Nous avons besoin d’écrire du code « qui marche », et surtout qui continue de marcher. Les mises à jours fréquentes des apps web font que le risque de la « casser » augmente au rythme des maj.

Framework

Un framework est un cadre de travail, une boite à outils, les informaticiens emploient fréquemment le mot framework, pour dénoter des réalités parfois bien différentes.

Il s’agit d’un ensemble cohérent de composants éprouvés et réutilisables (bibliothèques, classes, helpers, etc.) et d’un ensemble de préconisations pour la conception et le développement d’applications.

Typologies de frameworks

Tous les frameworks ne répondent pas aux même besoins, plusieurs frameworks peuvent être utilisés conjointement dans certaines situations.

Applicatifs Web

Applicatifs Client lourd

ORM

Présentation de contenu web

Django, Flask, Ruby on rails, Symfony, etc.

Tkinter, Cocoa, Qt

Peewee (Python), Hibernate (Java), Idiorm (PHP), etc.

Bootstrap, Foundation, PureCSS, etc.

Pourquoi utiliser un framework ?

Les avantages sont nombreux:

  • Pourquoi réécrire ce que d’autres ont déjà écrit (et testé) ?

  • Industrialisation, rationalisation, design patterns dots

  • Profitez des évolutions !

Mais il y a quelques inconvénients:

  • Courbe d’apprentissage

  • Connaissances préalables:
    • design patterns

    • bonnes pratiques d’ingénierie informatique

  • Couplage et dépendance

Installation de Flask#

Flask recommande désormais d’utiliser la dernière version de Python 3, il convient au minimum d’avoir la version 3.4 de Python.

Note

Lors de l’installation de Flask les dépendances suivantes seront automatiquement installées:

Environnement virtuel#

Nous l’avons déjà vu, il est conseillé d’utiliser un environnement virtuel Python pour gérer nos projets et leurs dépendances.

Créer l’environnement virtuel de notre projet#
mkdir meeple_street
cd meeple_street
pipenv --python 3
Installer Flask#
pipenv shell
pipenv install Flask

Quickstart with Flask#

Hello World avec Flask#
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

Mais que fait ce code ?

  1. D’abord on importe la classe Flask , une instance de cette classe sera notre application WSGI.

  2. Puis nous créeons une instance de cette classe en lui passant en argument le nom du module.

  3. Ensuite nous utilisons le décorateur route() pour informer Flask que telle url doit invoquer telle fonction.

  4. Enfin nous créeons la fonction hello_world qui correspondera automatiquement à la route nommée hello_world.

  5. Sauvegardez le code précédent dans un fichier app.py à la racine de votre projet,

  6. Lancez le serveur de développement via la commande suivante: flask run

  7. Rendez vous sur 127.0.0.1:5000

Félicitations ! Vous venez de créer votre première application Flask !

Routing#

Indication

Les applications Web modernes utilisent des URL significatives pour faciliter la vie des utilisateurs. Les utilisateurs sont plus susceptibles d’aimer une page et de revenir si la page utilise une URL explicite dont ils peuvent se souvenir.

Utilisez le décorateur route() pour lier une fonction à une URL.

@app.route('/')
def index():
    return 'Index Page'

@app.route('/hello')
def hello():
    return 'Hello, World'

Règles variables#

Vous pouvez ajouter des sections variables à une URL en les marquant avec <nom_variable>. Votre fonction reçoit alors la variable <nom_variable> en tant qu’argument.

Indication

Vous pouvez éventuellement utiliser un convertisseur pour spécifier le type de l’argument tel que <convertisseur: nom_variable>.

Voici les types de convertisseurs (extrait de la documentation de flask):

string

(default) accepts any text without a slash

int

accepts positive integers

float

accepts positive floating point values

path

like string but also accepts slashes

uuid

accepts UUID strings

Exemple de route avec des convertisseurs#
@app.route('/say_hello/<name>')
def say_hello(name):
    return "Hello %s" % name

@app.route('/multiply/<int:facteur_gauche>/<int:facteur_droite>')
def multiply(facteur_gauche, facteur_droite):
    return facteur_gauche * facteur_droite

À faire

  1. Créer une route / (index) qui affiche “Jurassic Park index”,

  2. Créer une route /say_hello/Ben qui affiche “Hello Ben” (Ben étant un paramétre passé via l’url),

  3. Créer une route pour effectuer les 4 opérations arithmétique simple (division, soustraction, multiplication, addition).

Test unitaire & d’intégration avec Flask#

Avertissement

A partir de maintenant nous nous servirons toujours du même projet pour la suite.

Indication

C’est bien beau de parler de TDD mais encore faut il le mettre en place et savoir en faire.

Plutôt que de tout tester à la main de manière répétitif et fastidieuse, nous allons apprendre à les automatiser pour notre plus grand bonheur 😀.

Pytest#

pytest logo

Pour réaliser les tests unitaires et d’intégrations nous aurons besoin de la librairie pytest.

pipenv install pytest

En reprenant la structure de notre projet vous devriez avoir:

-- app.py
-- Pipfile
-- Pipfile.lock

Nous allons donc créer un répertoire tests à la racine de notre projet qui contiendra nos fichier de tests:

-- tests/
   +-- __init__.py
   +-- test_functions.py
   +-- test_integrations.py
-- app.py
-- Pipfile
-- Pipfile.lock

Premier test unitaire avec pytest#

L’exemple le plus simple pour comprendre le principe des tests unitaires est la fonction de multiplication.

Note

Pour rappel une multiplication est l’opération mathématique permettant d’obtenir le produit de deux facteur (nommés facteur gauche et facteur droite).

Dans le fichier tests/test_functions.py ajouter le code suivant:

import pytest

def test_multiplication():
    assert 4 == multiplication(2, 2)

Lancez les tests via la commande

pytest --disable-warnings

Normalement vous devriez avoir l’erreur suivante :

$ pytest tests/ --disable-warnings
============================================= test session starts ======================================
tests/test_functions.py F                                                                         [100%]

================================================== FAILURES ============================================
_____________________________________________ test_multiplication ______________________________________

    def test_multiplication():
>       assert 4 == multiplication(2, 2)
E       NameError: name 'multiplication' is not defined

tests/test_functions.py:5: NameError
========================================== 1 failed in 0.03 seconds ====================================

C’est normal ! Nous respectons la philosophie TDD à savoir écrire nos tests d’abord puis vérifier que ceux-ci échouent !

Il convient de créer maintenant notre fonction de multiplication, pour plus de simplicité nous allons créer un module python maths qui contiendra un sous module algebra:

-- tests/
   +-- __init__.py
   +-- test_functions.py
   +-- test_integrations.py
-- maths/
   +-- __init__.py
   +-- algebra.py
-- app.py
-- Pipfile
-- Pipfile.lock

Le code du module math étant au combien complexe (l’erreur de code est volontaire ne la corriger pas):

def multiplication(left_factor, right_factor):
    return left_factor + right_factor

Si on relance notre test (pensez à ajouter un import de la fonction multiplication), celui-ci passe ! Mais notre jeu de données n’est pas complet ! Il convient donc d’ajouter une assertion supplémentaire à notre test.

À faire

  1. Ajoutez l’assertion dans notre test que le produit de 3 par 7 est 21

  2. Relancez votre test que constatez vous ?

  3. Corrigez le code de votre fonction pour régler ce souci.

Bravo vous venez de faire votre première suite de test unitaire 🎉.

Remarque

Le répertoire tests/ ainsi que le fait de préfixer nos fichier par test_* est une convention qui permet à pytest de charger de manière automatique l’ensemble des tests du répertoire.

À faire

Créer la suite de tests unitaires pour les opérations arithmétique de base (division, soustraction, addition).

Test d’intégration & Flask#

Tester nos fonctions c’est bien, utile et indispensable. Toutefois il est tout aussi indispensable de tester le retour de nos pages, nous allons donc voir comment mettre en place des tests d’intégrations.

Dans le fichier test_integrations.py du répertoire tests/

import os
import pytest
from app import app
from flask import url_for


@pytest.fixture
def client():
    app.config['TESTING'] = True
    app.config['SERVER_NAME'] = 'TEST'
    client = app.test_client()
    with app.app_context():
        pass
    app.app_context().push()
    yield client

Ajouter le test d’intégration suivant dans le fichier:

def test_index(client):
    rv = client.get('/')
    assert rv.status_code == 200
    assert b'Hello, World!' in rv.data

Le test d’intégration test_index permet de:

  • Tester si le retour de la requête sur / en méthode GET retourne le code de statut 200,

  • Et que le contenu de la réponse est bien Hello, World!.

  • Lancez la suite de tests,

  • Que constatez vous ?

Vous savez désormais tester en intégration une page de votre site web.

À faire

Comment mettre en place un test d’intégration pour la route hello de votre site ? Pensez à l’esprit TDD et comment tester cela.

Jinja#

Flask logo

Jinja <http://jinja.pocoo.org/docs/templates> est un moteur de template écrit en Python par l’équipe de Flask. Sa simplicité, son élégance et sa puissance en font l’un des meilleurs moteur de template existant. Le moteur de template Twig utiliser massivement dans le monde PHP en est une pâle traduction.

Pour rendre un template, vous pouvez utiliser la méthode render_template() lien vers la documentation.

Tout ce que vous avez à faire est de fournir le nom du template et les variables que vous souhaitez transmettre au moteur de template sous forme d’arguments.

Exemple simple de rendu d’un template#
from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)

Flask va chercher automatiquement le template dans le dossier templates qui se trouve à la racine du projet.

Indication

Pensez à lire la documentation de Jinja pour découvrir sa puissance.

Exemple de template#
<!DOCTYPE html>
<title>Hello from Flask</title>
{% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello, World!</h1>
{% endif %}

À faire

  1. Écrivez le test d’intégrations pour la route hello,

  2. Modifiez la route hello pour rendre le template hello.html,

  3. Créez un template pour les routes index, say_hello et pour les opérations arithmétiques (il est possible d’utiliser la même pour ces routes-ci).

Jinja et l’héritage de template#

La partie la plus puissante de Jinja est l’héritage des templates. L’héritage de templates vous permet de créer un « squelette » de base contenant tous les éléments communs de votre site et définir des blocs que les modèles enfants peuvent remplacer.

Cela semble compliqué mais reste très basique. Il est plus facile de comprendre cela en commençant par un exemple.

Ce template, que nous appellerons base.html, définit un simple squelette de document HTML. C’est le travail des templates « enfants » de remplir les blocs vides de contenu:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}{% endblock %} - My Webpage</title>
</head>
<body>
    <main>{% block content %}{% endblock %}</main>
    <footer>
        {% block footer %}
        &copy; Copyright 2008 by <a href="http://domain.invalid/">you</a>.
        {% endblock %}
    </footer>
</body>
</html>

Dans cet exemple, les balises {% block %} définissent trois blocs que les modèles enfants peuvent remplir.

La balise de bloc indique simplement au moteur de template qu’un modèle enfant peut remplacer les espaces réservés qu’il contient.

Un modèle enfant pourrait ressembler à ceci:

{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block content %}
    <h1>Index</h1>
    <p class="important">
      Welcome to my awesome homepage.
    </p>
{% endblock %}

La balise {% extend %} est la clé ici. Il indique au moteur de modèle que ce modèle « étend » un autre modèle. Lorsque le système de gabarit évalue ce gabarit, il localise d’abord le parent. La balise extend devrait être la première balise du modèle.

À faire

  1. Créer un template de base,

  2. Faites en hériter chacun de vos templates.

URL et redirection#

Pour créer une URL vers une fonction spécifique, utilisez la fonction url_for() (documentation ici).

Elle accepte le nom de la fonction en tant que premier argument et un nombre quelconque d’arguments, chacun correspondant à une partie variable de la règle d’URL. Des parties variables inconnues sont ajoutées à l’URL en tant que paramètres de requête.

Important

Pourquoi créer des URL en utilisant la fonction d’inversion d’URL url_for() au lieu de les coder en dur dans vos templates ?

  • L’inversion est souvent plus descriptive que le codage en dur des URL.

  • Vous pouvez modifier vos URL en une seule fois au lieu de vous rappeler de modifier manuellement les URL codées en dur.

  • La création d’URL gère l’échappement des caractères spéciaux et des données Unicode de manière transparente.

  • Les chemins générés sont toujours absolus, évitant un comportement inattendu des chemins relatifs dans les navigateurs.

Exemple de génération d’url#
<a href="{{ url_for('say_hello', name='Ben') }}">Hello Ben ?</a>

Fichier statique#

Un site web sans feuille de style est assez pauvre, il convient donc d’utiliser des fichiers statiques pour le mettre en forme.

Même si il s’agit du boulot du serveur HTTP, Flask s’occupe de générer l’url vers les fichiers statiques.

Il faut tout d’abord créer un répertoire static à la racine du projet. Pour générer les URL des fichier statiques, il faut utiliser la fonction url_for() avec le endpoint static. Par exemple:

<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />

À faire

  1. Créer un menu de navigation avec un lien pour la page d’accueil et la page hello,

  2. Au sein de la page d’accueil créer des liens vers chaque page que nous avons créer (say_hello, opérations arithmétiques, etc.)

  3. Ajouter normalize.css et un fichier de style CSS à votre projet.

Félications ! Nous venons de découvrir les bases du microframework Flask ensemble.

Welcome to Jurassic Park#

La société InGen ; spécialisée dans la création de créatures préhistoriques génétiquement modifiées; souhaite créer un site pour son dernier projet.

IngenLogo

La majeure partie de son activité consiste à faire naître des dinosaures et à les présenter au sein de Jurassic Park. John Hammond, le PDG de l’entreprise InGen, travaille à la création d’un parc d’attractions sur Isla Nublar, une île qu’il possède au large du Costa Rica. Le parc comporte des animaux disparus tels que les dinosaures, comme attractions. Les créatures ont été clonés dans des installations spéciales à proximité sur Isla Sorna et amenés au parc.

Énoncé

Le but de ce projet est de contrôler votre acquisition de connaissances par un simple exercice. Il convient de mettre en application les notions vues.

En appliquant les notions vues sur Python, pipenv, Flask, Jinja et requests, vous réaliserez un site web basique présentant les différents dinosaures du parc. Les données vous sont pour cela fournit via l’API REST suivante API Dinosaurs.

À faire

  • Création de deux pages différentes (accueil et détails sur un dinosaure),

  • Mise en oeuvre du TDD (Test Driven Development),

  • Versionning sous Git (pensez à versionner votre projet au fur et à mesure de votre avancé),

  • Les données sont disponibles via l’API Dinosaurs ,

  • Les fichiers css, d’images, etc. sont disponibles ici ,

  • Les templates HTML vous sont fournis dans la suite de cet énoncé,

  • Quelques conseils également.

Attendu du projet

  1. Page d’accueil listant l’ensemble des dinosaures

  1. Page de détails sur un dinosaure

  • L’url de cette page devra être sous la forme /dinosaur/<slug du dinosaure>/ (où le slug correspond au slug retourné par l’API Dinosaurs);

  • Les données d’un seul dinosaure peuvent être récupérées via l’API Dinosaurs, par exemple ingen.ugaritic.fr/api/dinosaurs/brachiosaurus permet de récupérer les informations du brachiosaure;

  • Les « Top Rated Dinosaurs » sont une récupération aléatoire de trois dinosaures parmis les septs retournées par l’API Dinosaurs ingen.ugaritic.fr/api/dinosaurs , l’utilisation de la méthode sample de la classe random vous est vivement conseillé.

Indication

  • Créer d’abord votre projet sous Gitlab et pensez directement à le cloner;

  • Récupérer les fichiers static et placer les dans un répertoire static à la racine de votre projet;

  • Penser à faire un pipenv install de toutes les libs;

  • L’approche TDD vous aidera grandement, quelles sont les fonctions que je vais devoir développer et les T.U associés, les pages et les T.I associées;

  • Avancer par étape, d’abord la récupération de tous les dinosaures (test ?) puis la page d’accueil, puis la page de détail et enfin les « Top Rated Dinosaurs »;

  • Respirer c’est important;

Quelques templates ?

base.html#
<!DOCTYPE html>
<html>
   <head>
      <title>{%block title %}{% endblock %}Jurassic Park</title>
      <meta charset="utf8" />
      <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/reset.css') }}" />
      <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}" />
      <link href="https://fonts.googleapis.com/css?family=Montserrat|Staatliches" rel="stylesheet">
   </head>
   <body>
      <header>
            <a href="/">
               Jurassic Park
            </a>
            <nav>
               <a href="/">Home</a>
            </nav>
      </header>
      <main>
            {% block main %}{% endblock %}
      </main>
      <footer>
            <img src="{{ url_for('static', filename='img/logojp.png') }}" alt="Jurassic Park" />
      </footer>
   </body>
</html>
Template HTML pour la liste des dinosaures#
<!-- Héritage de template ? Block(s) ? -->
<section>
   <h2>The world of Jurassic Park</h2>

   <!-- iterate here ? -->
      <a href="">
            <figure>
               <img src="" alt="" />
               <figcaption>Discover the Brachiosaurus</figcaption>
            </figure>
      </a>
</section>
Template d’un dinosaure#
<!-- Héritage de template ? Block(s) ? -->
<div class="grid">
   <article>
      <h2>Discover the Brachiosaurus</h2>

      <div class="description">
            Lorem ispum
      </div>

      <div class="card">
            <img src="" alt="" />
            <dl>
               <dt>Name</dt>
               <dd>Brachiosaurus</dd>
               <dt>Name meaning</dt>
               <dd>Brachiosaurus</dd>
               <dt>Diet</dt>
               <dd>Brachiosaurus</dd>
               <dt>Height</dt>
               <dd>Brachiosaurus m.</dd>
               <dt>Length</dt>
               <dd>Brachiosaurus m.</dd>
               <dt>Weight</dt>
               <dd>Brachiosaurus kg.</dd>
            </dl>
      </div>
   </article>

   <aside>
      <h3>Top rated dinosaurs</h3>
      <!-- iterate here ? -->
            <a href="">
               <figure>
                  <img src="" alt="" />
                  <figcaption>Discover the Brachiosaurus</figcaption>
               </figure>
            </a>
   </aside>
</div>

ORM et Flask#

Note

Un ORM est une technique de programmation informatique qui crée l’illusion d’une base de données orientée objet à partir d’une base de données relationnelle en définissant des correspondances entre cette base de données et les objets du langage utilisé. On pourrait le désigner par « correspondance entre monde objet et monde relationnel ».

Attention

Flask ne fournit pas d’ORM par défault, il laisse la faculté à l’utilisateur de choisir la librairie qui lui convient.

Découverte de Peewee#

Note

Peewee est un (petit) ORM simple. Il a peu de concepts (mais expressifs), ce qui le rend facile à apprendre et intuitif à utiliser. Celui-ci est open source et disponible ici pewee-orm.com.

Installation de peewee#
pipenv install peewee

La base de données pour peewee

Nous allons créer la base de données SQLite suivante:

https://www.plantuml.com/plantuml/svg/hL6zIWH13Exp565AuGI5QxOw54GRNq7Y93ixtEpCaMIBuxXtTnRntCBI9f3l9_b23kQalim5B58HVBpVWnrpFP7IZ9yKZqdRKZcsqXHTgLgdfFf6nJBIKlobLN-dMV2rkw38wEWDDQV9aRDAzDmg0CH2PlZKI3cjyWiWucNCKlYAcGVCrIM9Rk7QXGEQQwvfYrjPqb-uIX4o-M3oxblhbcMngFcqvhcPhp_1dhiuDJt_Nwu-6dxF3NS1TpiC3-6sQi20RuPj301JPi5PV6fitqtC6el0GIhtFtq1

Boardgames Database Scheme#

Après avoir installé le paquet peewee il suffit de configurer une connexion à notre base de données.

A la racine de votre projet créer un fichier models.py avec le contenu suivant:

from peewee import *

database = SqliteDatabase("boardgames.sqlite3")

Notre premier Model avec Peewee#

Nous définissons d’abord une classe BaseModel qui permet de spécifier la connexion à la BDD à tous nos modéles.

class BaseModel(Model):

    class Meta:
        database = database

Puis de créer directement notre classe Boardgame :

class Boardgame(BaseModel):
    name = CharField()
    slug = CharField()
    description = TextField()
    release_date = DateField()
    created_at = DateTimeField(default=datetime.datetime.now)

Voilà ! Notre model est créé, à nous de l’utiliser !

Création de la base#

Avant de commencer à requêter sur notre base, il convient de la créer !

Pour ce faire nous allons créer deux fonctions dans notre fichier models.py, l’une pour créer les tables et l’autre pour les détruire:

def create_tables():
    with database:
        database.create_tables([Boardgame, ])


def drop_tables():
    with database:
        database.drop_tables([Boardgame, ])

Enfin nous allons créer les commandes python associer pour créer/détruire les tables au sein du fichier app.py à la fin du fichier ajouter les lignes:

@app.cli.command()
def initdb():
    """Create database"""
    create_tables()
    click.echo('Initialized the database')

@app.cli.command()
def dropdb():
    """Drop database tables"""
    drop_tables()
    click.echo('Dropped tables from database')

Il suffit d’appeller ces commandes dans notre shell:

$ flask initdb
$ flask dropdb

Création de données#

Nous pourrions écrire des données à la main pour remplir notre base de données mais cela serait long et ennuyeux, nous allons donc utiliser la puissance de l’ORM et une librairie de création de fausses données pour créer différents enregistrements.

Faker https://faker.readthedocs.io

Faker est un paquet Python qui génère de fausses données. Que vous deviez amorcer votre base de données, créer de beaux documents XML, compléter votre persistance pour la soumettre à un test de résistance, ou anonymiser des données provenant d’un service en production, Faker est fait pour vous.

Comme d’habitude on installe le paquet faker via pipenv install faker. Et le paquet awesome-slugify également.

Puis au sein de notre fichier app.py nous allons créer la fonction suivante:

@app.cli.command()
def fakedata():
    from faker import Faker
    from slugify import slugify
    fake = Faker()
    for pk in range(0, 42):
        name = fake.company()
        Boardgame.create(name=name,
                         slug=slugify(name, to_lower=True)
                         description=fake.catch_phrase(),
                         release_date=fake.date())

Puis pour lancer la commande:

$ flask fakedata

Requête via un peewee#

Pour plus d’informations sur la manière d’écrire des requêtes, je vous invite à vous rendre à cette page de la documentation.

Dans le fichier app.py nous allons créer une route pour afficher l’ensemble des jeux de plateaux au sein d’un fichier de templating boardgames.html.

Voici le code permettant de récupérer l’ensemble des jeux présent en base:

Boardgame.select()

Cette ligne permet de récupérer tout les jeux présent en bases. A vous maintenant d’itérer sur la liste retourner afin d’obtenir une liste des jeux au sein de votre template.

Associations entre models#

Nous allons créer la classe python qui correspond à notre table Category puis définir la clé étrangère entre Boardgame et Category :

class Category(BaseModel):
    name = CharField()
    slug = CharField()
    created_at = DateTimeField(default=datetime.datetime.now)


class Boardgame(BaseModel):
    name = CharField()
    slug = CharField()
    description = TextField()
    release_date = DateField()
    created_at = DateTimeField(default=datetime.datetime.now)
    category = ForeignKeyField(Category, backref="boardgames")

N’oubliez pas d’ajouter dans les fonctions de création et de suppression des tables la classe Category.

Puis nous allons mettre à jour notre générateur de données:

@app.cli.command()
def fakedata():
    from faker import Faker
    fake = Faker()

    for pk in range(0, 5):
        name = fake.company()
        Category.create(name=name,
                        slug=slugify(name, to_lower=True))

    for category in Category.select():
        for pk in range(0, 4):
        name = fake.company()
        Boardgame.create(name=name,
                         slug=slugify(name, to_lower=True)
                         description=fake.catch_phrase(),
                         release_date=fake.date(),
                         category=category)

Ainsi nous pouvons obtenir le nom de la catégorie d’un jeux via:

boardgame.category.name

Ou encore la liste des jeux d’une catégorie via:

category.boardgames

À faire

  1. Créer une route pour afficher l’ensemble des jeux avec leur catégorie;

  2. Créer une route pour afficher l’ensemble des catégorie et les jeux associés;

  3. Créer une vue pour afficher uniquement les informations d’un seul jeux en prenant en paramètre (slug ) le slug du jeux cf la partie sur le requêtage de la documentation.

Note

Nous venons de voir que manipuler une base de données est extrèmement simple avec les bons outils, je vous invite donc à lire la documentation de Peewee notamment sur la partie Querying.

Formulaire#

Note

Nous avons vu comment créer notre schéma de base de données et des entités de manière automatique. Toutefois toute application web a à un moment ou un autre besoin d’utiliser des formulaires. Nous allons donc découvrir gérer des saisies utilisateurs.

Attention

Flask ne fournit pas de librairie de gestion de formulaire par défault, il laisse la faculté à l’utilisateur de choisir la librairie qui lui convient.

Découverte de WTForms#

Note

WTForms est la librairie de gestion de formulaire. Celle-ci est open source et disponible à cette adresse et pour une rapide introduction.

Nous allons utiliser Flask WTForms qui est une simple intégration de WTForms au sein de Flask.

Comme toujours un simple pipenv install flask-wtf.

Notre premier formulaire#

Important

Nous allons réutiliser la base de données que nous avons créé pour permettre l’ajout et l’édition de formulaire.

Nous définissons dans un premier temps notre formulaire au sein du fichier forms.py :

from flask_wtf import FlaskForm
from wtforms import StringField, SelectField
from wtforms.fields.html5 import DateField
from wtforms.validators import DataRequired, Length

class BoardgameForm(FlaskForm):
    pass

Une fois cela fait nous pouvons définir les champs un à un :

class BoardgameForm(FlaskForm):
    name = StringField('Name', validators=[
                       DataRequired(), Length(min=3, max=20)])

Ainsi nous indiquons que le champ name:

  • a pour label Name ;

  • doit être renseigné et avoir une longueur comprise entre 3 et 20 caractères (via les validators).

class BoardgameForm(FlaskForm):
    name = StringField('Name', validators=[
                       DataRequired(), Length(min=3, max=20)])
    release_date = DateField('Release date', validators=[DataRequired()])

Nous définissons également notre champ date de sortie puis enfin le champ model qui lui est un peu plus complexe:

class BoardgameForm(FlaskForm):
    name = StringField('Name', validators=[
                       DataRequired(), Length(min=3, max=20)])
    release_date = DateField('Release date', validators=[DataRequired()])
    category = SelectField('Category')

Il s’agit d’un champ de type Select qui possédent comme choix les couples id/version issus de la table Category.

Nous avons défini notre formulaire nous pouvons désormais passé à notre vue.

@app.route('/boardgame/create')
def boardgame_create():
    form = BoardgameForm()
    form.category.choices = [(m.id, m.name) for m in Category.select()]
    return render_template('boardgame/form.html', form=form)

Et nous allons également créer le template correspondant.

Créer le fichier boardgames/form.html dans le répertoire template avec le contenu suivant:

{% extends 'base.html' %}

{% block body %}

<h2>Create a boardgame</h2>

<form method="POST">
    {% for field in form %}
        {% if field.widget.input_type != 'hidden' %}
            {{ field.label() }}
            {% if field.errors %}
                {% for error in field.errors %}
                <div class="alert alert-error">{{ error }}</div>
                {% endfor %}
            {% endif %}
        {% endif %}
        {{ field() }}

    {% endfor %}
    <button type="submit">Save</button>
</form>
{% endblock %}

Nous affichons un à un les champs de notre formulaire en n’affichant que si nécessaire les labels. Les messages d’erreurs par champs sont également gérés.

Essayez maintenant de soumettre votre formulaire que se passe t’il ?

Méthodes HTTP#

Pour informer Flask qu’il doit accepter d’autres méthodes HTTP que GET il suffit de l’indiquer dans la signature de la route.

@app.route('/boardgame/create', methods=['GET', 'POST', ])
def boardgame_create():
    pass

Important

Vous aurez besoin au préalable d’indiquer une clé secrête à votre application Flask pour que celle-ci puisse gérer la validation csrf.

app.secret_key = 'HelloWorld' #Don't use it .. !

Essayez maintenant de soumettre votre formulaire que se passe t’il ?

Gestion du formulaire#

La méthode validate_on_submit permet de gérer automatiquement la récupération des données depuis le corps de la requête POST et de valider celles-ci.

@app.route('/boardgame/create', methods=['GET', 'POST', ])
def boardgame_create():
    form = BoardgameForm()
    if form.validate_on_submit():
        pass
    return render_template('boardgame/form.html', form=form)

Essayez maintenant de soumettre votre formulaire que se passe t’il ?

Gestion des données en base#

Nous allons créer une instance de boardgame (vide) qui sera ensuite « peupler » depuis notre formulaire via la méthode populate_obj

@app.route('/boardgame/create', methods=['GET', 'POST', ])
def boardgame_create():
    boardgame = Boardgame()
    form = BoardgameForm()
    if form.validate_on_submit():
        form.populate_obj(boardgame)
        boardgame.save()
        flash('Hooray ! Boardgame created !')
        return redirect(url_for('boardgames'))
    return render_template('boardgames/form.html', form=form)

La fonction flash permet de lancer des messages « flash » au sein de notre application. Pour en savoir plus je vous invite à lire encore une fois la documentation.

Vous aurez besoin de mettre à jour votre fichier jinja (base.html ?) avec le contenu suivant:

{% with messages = get_flashed_messages() %}
  {% if messages %}
    <ul class=flashes>
    {% for message in messages %}
      <li>{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

Essayez maintenant de soumettre votre formulaire que se passe t’il ?

Typage des données#

Les données reçues depuis le corps d’une requête POST sont toutes de types string. Il convient donc d’indiquer à notre SelectField que le type de données reçu doit être forcer en int.

class BoardgameForm(FlaskForm):
    name = StringField('Name', validators=[
                       DataRequired(), Length(min=3, max=20)])
    release_date = DateField('Release date', validators=[DataRequired()])
    category = SelectField('Category', coerce=int)

Indication

Il existe une librairie qui permet de faire le lien entre un model peewee et un formulaire wtf, celle-ci se nomme wtf-peewee. Vous savez désormais comment l’installer dans votre environnement virtuel.

Au sein de notre fichier forms.py :

from wtfpeewee.orm import model_form


SimpleBoardgameForm = model_form(Boardgame)

Voici un exemple de vue pour l’édition d’un boardgame:

@app.route('/baordgame/edit/<int:boardgame_id>/', methods=['GET', 'POST'])
def boardgame_edit(boardgame_id):
   try:
      boardgame = Boardgame.get(id=boardgame_id)
   except Entry.DoesNotExist:
      abort(404)

   if request.method == 'POST':
      form = SimpleBoardgameForm(request.form, obj=boardgame)
      if form.validate():
            form.populate_obj(boardgame)
            boardgame.save()
            flash('Your entry has been saved')
   else:
      form = SimpleBoardgameForm(obj=boardgame)

   return render_template('boardgames/form.html', form=form, boardgame=boardgame)

À faire

  1. Créer une route éditer un jeu;

  2. Créer une route pour ajouter/modifier une catégorie;

  3. Créer une route pour supprimer un jeu.

Note

Nous venons de voir que gérer les formulaires et leurs validations est extrèmement simple avec les bons outils.

Flask-RESTful#

Nous avons découvert comment requêter une API REST, il est donc temps de découvrir comment en créer une.

Flask-RESTful est une extension pour Flask qui prend en charge la création rapide d’API REST. C’est une abstraction légère qui fonctionne avec les ORM existants.

pipenv install flask-restful

Quickstart avec Flask-RESTful#

Il est temps d’écrire notre première API REST.

Une API Flask-RESTful minimale ressemble à ceci:

from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}

api.add_resource(HelloWorld, '/')

Vous savez désormais créer une api rest. Pour la tester vous pouvez utiliser l’extension restclient pour firefox par exemple.

Lister l’ensemble des éléments présents en base#

from flask_restful import fields, marshal_with

boardgame_fields = {
    'name': fields.String,
    'release_date': fields.String,
    'description': fields.String,
    'slug': fields.String,
}

class BoardgamesAPI(Resource):

    @marshal_with(boardgame_fields)
    def get(self):
        return [d for d in Boardgame.select()]

api.add_resource(BoardgamesAPI, '/api/boardgames/')

Lister un élément#

class BoardgameAPI(Resource):
    def get(self, boardgame_slug):
        return Dinosaur.get(boardgame_slug)

api.add_resource(BoardgameAPI, '/api/boardgames/<boardgame_slug>')

Et les CRUD dans tous cela ?#

Il va falloir modifier nos classes en ajoutant les méthodes suivantes:

class BoardgameAPI(Resource):

    def delete(self, boardgame_slug):
        pass

    def put(self, boardgame_slug):
        pass


class BoardgameAPI(Resource):

    def post(self):
        pass

En utilisant la documentation de flask-restful pouvez vous implémenter les cruds de l’api Dinosaur ?

Projet Microblogging avec Flask#

Contexte

Fraichement diplômé, vous venez de vous lancer à votre compte en tant qu’auto-entrepreneur. Vous venez d’obtenir votre premier contrat qui consiste en la réalisation d’un site de Micro Blogging pour le compte d’un client local.

Après négociation avec le dit client vous avez convenu d’une réalisation d’un site web en Python utilisant le framework Flask et le moteur de base de données SQLite pour une livraison à la fin du mois de novembre 2018.

Vous devez créer une application basic de MicroBlogging nommé Microlly.

Les fonctionnalités minimun de ce site sont les suivantes.

User Story 1

En tant que visiteur je souhaite pouvoir m’inscrire dans le but de pouvoir créer un compte sur Microlly et m’y connecter.

User Story 2

En tant que visiteur je souhaite pouvoir me connecter dans le but de publier une nouvelle entrée sur Microlly.

User Story 3

En tant qu’utilisateur je souhaite pouvoir créer une publication dans le but de partager des informations avec les utilisateurs.

User Story 4

En tant qu’utilisateur je souhaite pouvoir modifier ou supprimer une de mes publications dans le but de corriger une erreur.

User Story 5

En tant que visiteur je souhaite voir un listing des dernières publications sur la page d’accueil dans le but de m’informer sur les partages des utilisateurs de la plateforme.

User Story 6

En tant que visiteur je souhaite voir l’ensemble des publications d’un utilisateur (sur plusieurs pages au besoin) dans le but de lire plus de publications de cet utilisateur.

Description du modèle de base

Voici une rapide description des données attendues pour une publication.

https://www.plantuml.com/plantuml/svg/VP6nJiD038RtUmeh8wYHG6Bfr0aneIxCbNFd95QTkyhd6r3L1-2vU34kHKWf2epsv__NRtkN3TNA9CvbZqBms3wvVEHuGiK9ElJ7GLE9mIT92gOOSqKKRJ4LgCSYzfoYlU94y59LJgWti0JAmsWGMCaRf-YS5ymPzgKJzZYtuEm0uFxSCqcuWB49RI6RSXnkI9V2-ohmIbUdGq1RbvRJZ6xvvIVBMoRz5QBKLgoxh9hrhDaEV-Bg-G-b2LcMf6tRESho8CrT0viDD0_DDTiMFZ-yia4zjtCZ1uA9R4mXVnUUrGivjwCOwYk_00==

Diagramme UML de la base de données de Microlly<#

Conditions de livraison

Vous devez réaliser le travail précédent en vous basant sur les notions que nous avons abordé en cours notamment la programmation orientée objet en Python et le framework Flask.

Votre dépôt git devra contenir :

  • Le projet et les sources de votre site web

  • Le fichier SQLite de votre base de données

  • Un fichier README.md contenant une rapide explication de votre projet

  • Le fichier Pipfile et Pipfile.lock

Quelques Indications

Voici quelques liens utiles pour gérer quelques eléments du projet :

Indication

Pensez à commenter votre code (intelligement), au nommage de vos modules/méthode/class/variable/etc. et aux tests (TDD)! Vous êtes libre d’ajouter des fonctionnalités à votre travail cela sera bien evidemment valorisé.