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.
Le tp ci-dessous est à finir avant de commencer le projet
Une fois l’application fonctionnelle, presenter votre projet à M. MILLET lors d’un tp de base de données, vous aurez ensuite un numéro de sujet pour le mini projet qui suit.
Exemple d’application minimum : http://amillet1.pythonanywhere.com/
Jinja est un moteur de template. Il permet d’écrire du code Python dans un template (en plus du HTML)
Pour indiquer à Jinja qu’on utilise des objets Python, il faut
utiliser les syntaxes {{ }}
, {% %}
ou
{# #}
selon le code écrit
Pour insérer des objets Python dans un template, il faut les
passer en paramètres de la fonction render_template
au
niveau de la vue
Jinja permet d’étendre des templates grâce à l’héritage : Pour éviter de dupliquer du code, vous pouvez importer des templates HTML au sein d’un autre template
{{ 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))
2 structures de contrôles
{% for ... in ... %}
...
{% endfor %}
{% if ... %}
...
{% else %}
...
{% endif %}
{# commentaires #}
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() }}
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.
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
= Flask(__name__)
app "TEMPLATES_AUTO_RELOAD"] = True
app.config[= 'une cle(token) : grain de sel(any random string)'
app.secret_key
= [
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>
,tr {
table 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
[paramètres][confidentialité][vider le cache]
[préférences][vie privée et sécurité][Effacer les données ...][contenu web en cache]
Le plus simple est d’installer une extension sur chrome ou sur mozilla pour supprimer le cache du navigateur
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>
<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)
{% 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')
@app.route('/type-article/add', methods=['POST'])
def valid_add_type_article():
= request.form.get('libelle', '')
libelle print(u'type ajouté , libellé :', libelle)
= u'type ajouté , libellé :'+libelle
message 'alert-success')
flash(message, 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 }}">
<strong>{{ message }}</strong>
Information : </div>
{% endfor %}
{% endif %} {% endwith %}
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
@app.route('/type-article/delete', methods=['GET'])
def delete_type_article():
id = request.args.get('id', '')
print ("un type d'article supprimé, id :",id)
=u'un type d\'article supprimé, id : ' + id
message'alert-warning')
flash(message, return redirect('/type-article/show')
PS : pour modifier ajouter ou supprimer, il faudrait toujours utiliser un formulaire pour des raisons de sécurité (faille CSRF)
@app.route('/type-article/edit', methods=['GET'])
def edit_type_article():
id = request.args.get('id', '')
id=int(id)
= types_articles[id-1]
type_article 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():
= request.form.get('libelle', '')
libelle id = request.form.get('id', '')
print(u'type article modifié, id: ',id, " libelle :", libelle)
=u'type article modifié, id: ' + id + " libelle : " + libelle
message'alert-success')
flash(message, 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 %}
améliorer le style de l’application
Valider votre page sur le site http://validator.w3.org/
@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():
= request.form.get('nom', '')
nom = request.form.get('type_article_id', '')
type_article_id = request.form.get('prix', '')
prix = request.form.get('stock', '')
stock = request.form.get('description', '')
description = request.form.get('image', '')
image = u'article ajouté , nom:'+nom + '---- type_article_id :' + type_article_id + ' ---- prix:' + prix + ' - stock:'+ stock + ' - description:' + description + ' - image:' + image
message print(message)
'alert-success')
flash(message, return redirect('/article/show')
@app.route('/article/delete', methods=['GET'])
def delete_article():
id = request.args.get('id', '')
=u'un article supprimé, id : ' + id
message'alert-warning')
flash(message, return redirect('/article/show')
@app.route('/article/edit', methods=['GET'])
def edit_article():
id = request.args.get('id', '')
id=int(id)
= articles[id-1]
article 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', '')
= request.form.get('nom', '')
nom = request.form.get('type_article_id', '')
type_article_id = request.form.get('prix', '')
prix = request.form.get('stock', '')
stock = request.form.get('description', '')
description = request.form.get('image', '')
image = 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
message print(message)
'alert-success')
flash(message, return redirect('/article/show')
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>
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 %}
Pour aller plus loin, voici quelques exercices avec bootle