Flask

De Banane Atomic
Version datée du 22 août 2017 à 09:57 par Nicolas (discussion | contributions) (→‎Flask-WTF Exemple)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigationAller à la recherche

Liens

Exemple

mywebsite.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html', title='Hello', text='Some text to display')

if __name__ == '__main__':
    app.run(debug=True)
templates/index.html
<head>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
</head>

<body>
    <h1>{{ title }}</h1>
    <p>
    {{ text }}
    </p>

Jinja2

Html.svg
{{ my_variable }}
{{ my_object.my_attribute }}
{{ my_object.my_method() }}

{% if my_variable > 0 %}
{% elif my_variable < 10 %}
{% else %}
{% endif %}

<a href="{{ url_for('my_page') }}">My Page</a>
Python.svg
@app.route('/my-page-1')  # nom de l'url libre, mais @app.route doit décorer la méthode
def my_page():            # le nom de la méthode doit correspondre au nom utilisé dans url_for
    return 'My Page !!!'

Héritage de templates

base.html
<!-- définit un block vide ou non qui sera rempli, remplacé ou complété dans les templates enfants -->
{% block content %}
{% endblock %}
my_page.html
<!-- hérite du template parent -->
{% extends "base.html" %}

<!-- écrase le contenu du block content -->
{% block content %}
<p>Some text to display.</p>
<!-- récupère le contenu du block du parent -->
{{super()}}
{% endblock %}

Macros

my_macro.html
{% macro render_field(field) %}
<tr {% if field.errors %} class="error" {% endif %}>
  <td>{{ field.label }}</td>
  <td>{{ field(**kwargs)|safe }}</td>
</tr>
{% if field.errors %}
<tr>
  <td></td>
  <td>
      <ul class="error">
          {% for error in field.errors %}
              <li>{{ error }}</li>
          {% endfor %}
      </ul>
  </td>
</tr>
{% endif %}
{% endmacro %}
form.html
{% from "my_macro.html" import render_field %}

<form action="" method="post">
  {{ form.hidden_tag() }}
  <table>
      {{ render_field(form.name, size=50) }}
      <tr>
          <td></td>
          <td><input type="submit" value="Ok"></td>
      </tr>
  </table>
</form>

Custom Filters

Python.svg
@app.template_filter('dateformat')
def dateformat(value, format='%d %B %Y'):
    return value.strftime(format)
Html.svg
{{ my_object.date|dateformat }}

Route

Python.svg
@app.route('/')
@app.route('/index')
def index():
    return render_template('index.html')

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

# POST
@app.route('/formulaire', methods=('GET', 'POST'))
def formulaire():
    if request.method == 'POST':
        data = request.form['key']  # récupère l'élément key du formulaire
        return redirect(url_for('index'))  # retour à index une fois le formulaire validé
    return render_template('formulaire.html')  # affichage du formulaire

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

Flash messages

Python.svg
from flask import flash

# configurer la clé secrète pour permettre l'utilisation de session
app.config['SECRET_KEY'] = '...'
# générer une clé de 24 octets: os.urandom(24)

# enregistre un message dans la session
flash('Some message.')
Html.svg
<!-- Au chargement de la page, on récupère et affiche tous les messages -->
<article>
    {% with messages = get_flashed_messages() %}
        {% if messages %}
            <ul>
                {% for message in messages %}
                    <li>{{ message }}</li>
                {% endfor %}
            </ul>
        {% endif %}
    {% endwith %}
</article>

Flask-WTF 0.14.2

Liens

Flask-WTF Exemple

Penser à utiliser la macro render_field
main.py
from flask_wtf import FlaskForm
from wtforms.fields import StringField, DateField, SelectField
from wtforms.validators import DataRequired

class MyForm(FlaskForm):
    name = StringField('The name:', validators=[DataRequired()], size=50)
    date = DateField('Date:', validators=[DataRequired()], format='%d %B %Y', size=50)
    country = SelectField('Country', coerce=int, validators=[DataRequired()], style='width:465px;')
    # coerc pour convertir la value str → int
    # pour la balise select, size définit le nombre d'éléments sélectionnable et non la longueur

    def __init__(self, *args, **kwargs):  # permet de définir la liste de choix pour country
        super(MyForm, self).__init__(**kwargs)
        self.country.choices = [(k, v.name) for k, v in all_countries.items()]  # (value, label) pairs

    def validate(self):  # custom validation
        if self.name.data and self.name.data[0].isupper():  # test si le nom commence par une majuscule
            return super().validate()  # appel les autres validateurs
        else:
            return False


@app.route('/edit/<id>', methods=('GET', 'POST'))
def edit(id):
    my_object = ...  # récupère l'objet à éditer
    if request.method == 'GET':
        my_form = MyForm(obj=my_object)
        my_form.country.data = my_object.country.id_country
    else:
        my_form = MyForm()

    if my_form.validate_on_submit():
        my_form.populate_obj(my_object)
        my_object.country = all_countries[my_object.country]  # ici my_object.country est un int
        return redirect('index')  # une fois le formulaire validé, redirection vers index

    return render_template('edit.html', id=id, form=my_form)
edit.html
<form action="" method="post">
  {{ form.hidden_tag() }}
  <table class="table">
      <tr>
          <td>{{ form.name.label }}</td>
          <td>{{ form.name(size = 50) }}</td>
      </tr>
      {% if field.errors %}
      <tr>
          <td></td>
          <td>
              <ul>
                  {% for error in form.name.errors %}
                      <li>{{ error }}</li>
                  {% endfor %}
              </ul>
          </td>
      </tr>
      {% endif %}
      <tr>
          <td></td>
          <td>
              <a class="btn btn-default" href="{{ url_for('index') }}">Cancel</a>
              <input type="submit" class="btn btn-primary" value="Save changes">
          </td>
      </tr>
  </table>
</form>

Flask-SQLAlchemy

Python.svg
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

    def __repr__(self):
        return '<User %r>' % self.username

Flask-Bootstrap

  • Flask-Bootstrap inclue et utilise Boostrap 3.3.7
  • Flask-Bootstrap a été conçu pour Flask-WTF version 0.9.2, alors que la version 0.14.2 est disponible.
Python.svg
from flask_bootstrap import Bootstrap

app = Flask(__name__)
Bootstrap(app)
Html.svg
{% extends "bootstrap/base.html" %}

{% block styles %}
{{ super() }}
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
{% endblock %}

{% block title %}This is an example page{% endblock %}

{% block content %}
  <h1>Hello, Bootstrap</h1>
{% endblock %}

{% block scripts %}
{{ super() }}
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
{% endblock %}

Arborescence de fichiers

  • mywebsite.py
  • static
    • css
    • img
    • js
  • templates
    • index.html
    • 404.html

Model, Template, View

Nom Équivalent MVC Description
Model Model Classes business, stockage des données (SQLAlchemy)
Template View Génération du HTML (Jinja2)
View Controller Gestion des requêtes HTTP et envoie des réponses HTTP
Routage et mapping des URL

Flask-DebugToolbar

Bash.svg
sudo pip install flask-debugtoolbar
Python.svg
from flask_debugtoolbar import DebugToolbarExtension

app = Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = b'...'
app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False

toolbar = DebugToolbarExtension(app)

Installation

Bash.svg
pacman -S python-flask python-flask-wtf python-flask-sqlalchemy