Objectif

Réaliser les interfaces pour afficher le contenu de 2 tableaux (table) de dictionnaires (python) ; Réaliser les interfaces minimum pour ajouter/modifier/supprimer les données d’une ligne des 2 tableaux.

utilisation d’un moteur de templates

Instructions dans les Gabarits (templates)

afficher des variables

{{ variable }}

{{ MyDictionnary.key }}

Les résultats des requêtes SQL sont le plus souvent un dictionnaire (une seule colonne) ou une liste de dictionnaires (une liste de tuples (occurrences, enregistrements))

conditions et boucles

2 structures de contrôles

{% for ... in ... %} 
     ... 
{% endfor %}


{% if ... %} 
     ... 
{% else %}
     ... 
{% endif %}

commentaire

{# commentaires #}

heritage

Pour éviter des répétitions entre templates, il est possible de recourir à un mécanisme d’héritage. Pour hériter de la structure d’un template base.html, il faut indiquer à la première ligne du template hérité :

{% extends 'base.html' %}

Lors de l’héritage, certains blocs de codes peuvent être surchargés. Ces blocs sont définis via le tag block et endblock

{% block mon_block %} ... {% endblock %}
  {{ super() }}

moteurs de templates similaires

application 1

exemple d’héritage : la première vue

créer un nouveau projet flask sur pycharm


ouvrir un terminal et créer les dossiers et les fichiers pour les vues

touch templates/layout.html  templates/_nav.html
mkdir templates/article templates/type_article
touch templates/article/show_article.html  templates/article/add_article.html templates/article/edit_article.html
touch templates/type_article/show_type_article.html  templates/type_article/add_type_article.html templates/type_article/edit_type_article.html
touch static/mes_styles.css
touch launcher.sh
chmod u+x launcher.sh

créer un lanceur comme dans tp précedent launcher.sh

flask --debug  --app app  run  --host 0.0.0.0


Copier les fichiers ressources dans le dossier static

Pour ouvrir rapidement le dossier static et extraire l’archive avec pycharm : ouvrir le dossier static [clic droit sur le dossier static][open in][files] ou [directory path] et glisser le contenu de l’archive dans le dossier static ouvert avec l’explorateur de fichiers.


heritage dans les vues

remplacer le code du fichier app.py

#! /usr/bin/python
# -*- coding:utf-8 -*-
from flask import Flask, request, render_template, redirect, url_for, abort, flash

app = Flask(__name__)
app.config["TEMPLATES_AUTO_RELOAD"] = True
app.secret_key = 'une cle(token) : grain de sel(any random string)'

articles = [
    {'id': 1, 'nom': 'Enveloppes (50p)', 'type_article_id': 1, 'prix': 2, 'stock': 1, 'description': None, 'image': None},
    {'id': 2, 'nom': 'Stylo noir', 'type_article_id': 1, 'prix': 1, 'stock': 10, 'description': None, 'image': 'stylo.jpeg'},
    {'id': 3, 'nom': 'Boite de rangement', 'type_article_id': 1, 'prix': 3, 'stock': 7, 'description': None, 'image': 'boites.jpeg'},
    {'id': 4, 'nom': 'Chaise', 'type_article_id': 2, 'prix': 40, 'stock': 2, 'description': None, 'image': 'chaise.jpeg'},
    {'id': 5, 'nom': 'Tables', 'type_article_id': 2, 'prix': 200, 'stock': 3, 'description': None, 'image': 'table.jpeg'},
    {'id': 6, 'nom': 'Salon de Jardin alu', 'type_article_id': 3, 'prix': 149, 'stock': 3, 'description': None, 'image': 'salonJardin2.jpg'},
    {'id': 7, 'nom': 'Table+6 fauteuilles de Jardin', 'type_article_id': 3, 'prix': 790, 'stock': 3, 'description': None, 'image': 'tableFauteuilsJardin1.jpg'},
    {'id': 8, 'nom': 'Set Table + 4 bancs', 'type_article_id': 3, 'prix': 229, 'stock': 2, 'description': None, 'image': 'setTableChaises.jpg'},
    {'id': 9, 'nom': 'arrosoir bleu', 'type_article_id': 4, 'prix': 14, 'stock': 2, 'description': None, 'image': 'arrosoir1.jpg'},
    {'id': 10, 'nom': 'arrosoir griotte', 'type_article_id': 4, 'prix': 10, 'stock': 2, 'description': None, 'image': 'arrosoir2.jpg'}, 
    {'id': 11, 'nom': 'tuyau arrosage', 'type_article_id': 4, 'prix': 32, 'stock': 3, 'description': None, 'image': 'tuyauArrosage1.jpg'}, 
    {'id': 12, 'nom': 'tournevis', 'type_article_id': 5, 'prix': 24, 'stock': 3, 'description': None, 'image': 'lotTourneVis.jpg'},
    {'id': 13, 'nom': 'marteau menuisier', 'type_article_id': 5, 'prix': 8, 'stock': 3, 'description': None, 'image': 'marteau.jpg'},
    {'id': 14, 'nom': 'pince multiprise', 'type_article_id': 5, 'prix': 22, 'stock': 3, 'description': None, 'image': 'pinceMultiprise.jpg'}, 
    {'id': 15, 'nom': 'perceuse', 'type_article_id': 5, 'prix': 150, 'stock': 3, 'description': None, 'image': 'perceuse.jpg'}    
]

types_articles = [
    {'id': 1, 'libelle': 'Fourniture de bureau'},
    {'id': 2, 'libelle': 'Mobilier'},
    {'id': 3, 'libelle': 'Mobilier Jardin'},
    {'id': 4, 'libelle': 'Arrosage'},
    {'id': 5, 'libelle': 'Outils'},
    {'id': 6, 'libelle': 'Divers'}
]

@app.route('/')
def show_accueil():
    return render_template('layout.html')

if __name__ == '__main__':
    app.run()

ajouter au fichier templates/layout.html le contenu ci-dessous :

<!doctype html>
<html lang="fr">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    {% block title %}
        <title>layout</title>
    {% endblock %}
    <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" >
    {% block stylesheets %}
        <link rel="stylesheet" href="{{ url_for('static', filename='mes_styles.css') }}" >
        {#<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.css') }}" >#}
    {% endblock %}
</head>
<body>
{% include('_nav.html') %}

{% block body %}
    <h2 style="color:blue">layout.html</h2>  
{% endblock %}
{% block javascripts %}
    {#<script src="{{ url_for('static', filename='bootstrap/js/bootstrap.js') }}"></script>#}
{% endblock %}
</body>
</html>

ajouter au fichier templates/_nav.html le contenu ci-dessous :

<nav class="menu">
    <a href="/article/show"> article </a>  -
    <a href="/type-article/show"> type d'article </a>  -
</nav>
<hr>


table ,tr {
  table-layout: fixed;
  width: 80%;
    height: 40px;
  border-collapse: collapse;
  border: 1px solid purple;
  background-color: yellow;
}

a {
    width:150px;
    height: 30px;
    background-color:gold;
    border: 1px green groove ;
    text-align:center;
    vertical-align: middle;
    padding: 5px;
    margin : 5px;
    border-radius: 15px;
}


.flashes {
    padding: 5px;
    margin : 5px; 
}

.alert-warning {
    background-color: rgba(255, 197, 197, 1);
}
.alert-success {
    background-color: #bdfdbd;
}

⚠️ => Si les nouveaux styles ne sont pas prise en compte : ATTENTION au cache du navigateur, il faut parfois effacer ce cache

Le plus simple est d’installer une extension sur chrome ou sur mozilla pour supprimer le cache du navigateur

affichage du premier tableau de données : les types d’articles


ajouter au fichier templates/type_article/show_type_article.html le contenu ci-dessous :

{% extends 'layout.html' %}

{% block title %}
    <title>afficher les types d'article</title>
{% endblock %}

{% block body %}
<h3>Types d'article</h3>
<a href="/type-article/add">ajouter un type d'article</a>
<br><br>

<table class="table">
    <thead class="thead-dark">
        <tr>
            <th>id</th><th>designation</th><th>opération</th>
        </tr>
    </thead>
    <tbody>
    {% if types_articles | length >= 1 %}
        {% for ligne in types_articles %}
           <tr>
                  <td>{{ ligne.id }}</td>
                  <td>{{ ligne.libelle }}</td>
                  <td>
                      <a href="/type-article/edit?id={{ ligne.id }}">editer</a>&nbsp;
                      <a href="/type-article/delete?id={{ ligne.id }}" >supprimer</a>
                  </td>
           </tr>
        {% endfor %}
    {% else %}
            <tr>
                  <td>
                      pas de type d'article
                  </td>
           </tr>
    {% endif %}
    </tbody>
</table>
{% endblock %}
@app.route('/type-article/show')
def show_type_article():
    #print(types_articles)
    return render_template('type_article/show_type_article.html', types_articles=types_articles)

formulaire pour ajouter un type d’article

{% extends 'layout.html' %}

{% block title %}
    <title>ajouter un type d'article</title>
{% endblock %}

{% block body %}
  <h2>ajout d'un type d'article</h2>
  <form method="post" action="/type-article/add" >
    <fieldset>
        <legend> Créer un type d'article</legend>
        <label for="libelle">libelle</label><br>
        <input id="libelle" type="text" name="libelle" size="18" value="" placeholder="Saisir le libelle du type de l'article"><br>
        <input type="submit" value="Creer" name="Valider" >
    </fieldset>
  </form>
{% endblock %}
@app.route('/type-article/add', methods=['GET'])
def add_type_article():
    return render_template('type_article/add_type_article.html')

passage de paramètres d’un formulaire à l’application : Méthode POST : ajouter des données

@app.route('/type-article/add', methods=['POST'])
def valid_add_type_article():
    libelle = request.form.get('libelle', '')
    print(u'type ajouté , libellé :', libelle)
    message = u'type ajouté , libellé :'+libelle
    flash(message, 'alert-success')
    return redirect('/type-article/show')

ajouter après l’instruction {% include('_nav.html') %} dans le fichier templates/layout.html les instructions suivantes pour afficher les messages “flash”

{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
    {% for categorie, message in messages %}
    <div role="alert" class="alert {{ categorie }}">
           Information : <strong>{{ message }}</strong>
    </div>
    {% endfor %}
{% endif %}
{% endwith %}
passage de paramètres d’un formulaire à l’application

l’application flask récupère un objet request, cet objet possède tous les paramètres INPUT [NAME avec pour valeur VALUE] passés dans la requête HTTP


passage de paramètres d’un lien à l’application : Méthode GET : supprimer des données

@app.route('/type-article/delete', methods=['GET'])
def delete_type_article():
    id = request.args.get('id', '')
    print ("un type d'article supprimé, id :",id)
    message=u'un type d\'article supprimé, id : ' + id
    flash(message, 'alert-warning')
    return redirect('/type-article/show')
passage de paramètres d’un lien(url) à l’application

PS : pour modifier ajouter ou supprimer, il faudrait toujours utiliser un formulaire pour des raisons de sécurité (faille CSRF)

modifier des données : Passage de paramètres d’un lien à l’application : Méthode GET. Passage de paramètres d’un formulaire à l’application : Méthode POST

@app.route('/type-article/edit', methods=['GET'])
def edit_type_article():
    id = request.args.get('id', '')
    libelle = request.args.get('libelle', '')     # comment passé plusieurs paramètres (clé primaire composés)
    id=int(id)
    type_article = types_articles[id-1]
    return render_template('type_article/edit_type_article.html', type_article=type_article)

@app.route('/type-article/edit', methods=['POST'])
def valid_edit_type_article():
    libelle = request.form['libelle']
    id = request.form.get('id', '')
    print(u'type article modifié, id: ',id, " libelle :", libelle)
    message=u'type article modifié, id: ' + id + " libelle : " + libelle
    flash(message, 'alert-success')
    return redirect('/type-article/show')
{% extends 'layout.html' %}

{% block title %}
    <title>modifier un type d'article</title>
{% endblock %}

{% block body %}
  <h2>modifier un type d'article</h2>

  <form method="post" action="/type-article/edit">
    <fieldset>
        <legend> Modifier un type d'article</legend>
        <input type="hidden" name="id" size="18" value="{{ type_article.id }}" id="id">
        <label for="libelle">libelle</label><br>
        <input type="text" name="libelle" size="18" value="{{ type_article.libelle }}" id="libelle"><br>
        <input type="submit" value="Modifier" name="Modifier" >
    </fieldset>
  </form>
{% endblock %}
modifier les données

mettre en forme les formulaires le menu et le tableau

améliorer le style de l’application


Valider votre page sur le site http://validator.w3.org/

interface du CRUD sur la table “article”

@app.route('/article/show')
def show_article():
    # print(articles)
    return render_template('article/show_article.html', articles=articles)

@app.route('/article/add', methods=['GET'])
def add_article():
    return render_template('article/add_article.html', types_articles=types_articles)

@app.route('/article/add', methods=['POST'])
def valid_add_article():
    nom = request.form.get('nom', '')
    type_article_id = request.form.get('type_article_id', '')
    prix = request.form.get('prix', '')
    stock = request.form.get('stock', '')
    description = request.form.get('description', '')
    image = request.form.get('image', '')
    message = u'article ajouté , nom:'+nom + '---- type_article_id :' + type_article_id + ' ---- prix:' + prix + ' - stock:'+  stock + ' - description:' + description + ' - image:' + image
    print(message)
    flash(message, 'alert-success')
    return redirect('/article/show')

@app.route('/article/delete', methods=['GET'])
def delete_article():
    id = request.args.get('id', '')
    message=u'un article supprimé, id : ' + id
    flash(message, 'alert-warning')
    return redirect('/article/show')

@app.route('/article/edit', methods=['GET'])
def edit_article():
    id = request.args.get('id', '')
    id=int(id)
    article = articles[id-1]
    return render_template('article/edit_article.html', article=article, types_articles=types_articles)

@app.route('/article/edit', methods=['POST'])
def valid_edit_article():
    id = request.form.get('id', '')
    nom = request.form.get('nom', '')
    type_article_id = request.form.get('type_article_id')
    prix = request.form.get('prix', '')
    stock = request.form.get('stock', '')
    description = request.form.get('description', '')
    image = request.form.get('image', '')
    message = u'article modifié , nom:'+nom + '---- type_article_id :' + type_article_id + ' ---- prix:' + prix + ' - stock:'+  stock + ' - description:' + description + ' - image:' + image + u' ------ pour l article d identifiant :' + id
    print(message)  
    flash(message, 'alert-success')
    return redirect('/article/show')

amélioration : la liste déroulante

voici un exemple possible de code pour réaliser une liste déroulante à partir d’un tableau de données


<select name="type_article_id" required>
    <option value="">Sélectionner un type d'article</option>
    {% for type in types_articles %}
         <option value="{{ type.id }}"> {{ type.libelle }} </option>
    {% endfor %}
</select>
<select name="type_article_id" required>
    {% for type in types_articles %}
    <option value="{{ type.id }}"
            {% if article.type_article_id is defined and type.id ==  article.type_article_id %}selected{% endif %}
    > {{ type.libelle }} </option>
    {% endfor %}
</select>

amélioration : afficher une image

Les images sont dans le dossier static

<img style="width:40px;height:40px" src="/static/images/{{ ligne.image }}" alt="image  de {{ ligne.nom }}">

Afficher une image seulement si le nom existe

{%  if ligne.image is not none and ligne.image != "" %}
    <img style="width:40px;height:40px"  src="{{ url_for('static', filename = 'images/' + ligne['image']) }}"  alt="image  de {{ ligne.nom }}" >
{% else %}
    <img style="width:40px;height:40px"   src="{{ url_for('static', filename='images/no_photo.jpeg')}}"  alt="no image" >
{% endif %} 

annexes ressources


Pour aller plus loin, voici quelques exercices avec bootle




openclassroom