Outils pour utilisateurs

Outils du site


cours:informatique:dev:python:concevez_site_avec_flask:320_ajouter_comportement

Notes et transcriptions du cours “Concevez un site avec Flask” disponible sur la plateforme Openclassrooms

Ajoutez un peu d'intelligence !

Vous avez vu, dans les chapitres précédents, comment créer une application Flask, la configurer et intégrer des fonctionnalités intéressantes telles que l'authentification par Facebook.

Dans ce chapitre vous ajouterez la dernière fonctionnalité manquante : la génération d'une image à partager sur Facebook.

Cette image contiendra le prénom de la personne suivi de la description trouvée préalablement.

Par exemple :

Générez une image de fond

Dans sa documentation, Facebook conseille que les images à partager aient les dimensions suivantes : 1 200 x 630 pixels.

L'image basique sera donc un rectangle de 1200 x 630 pixels coloré en bleu turquoise (une des couleurs du template).

Nous allons utiliser la librairie Pillow (Python Imaging Library) pour générer l'image.

Commencez par l'installer en utilisant PIP :

pip3 install Pillow

Créez une nouvelle classe dans utils.py.

utils.py
from random import choice as random_choice
from fbapp.models import Content, Gender
from PIL import Image
 
...
 
class OpenGraphImage:
 
    def __init__(self, first_name, description):
        background = self.base()
 
    def base(self):
        img = Image.new('RGB', (1200, 630), '#18BC9C')
        return img

Pour tester le code de génération de l'image, on va utiliser la méthode show() dans la méthode d'initialisation, commenter la partie du script qui concerne les modèles puis créer une instance de la classe OpenGraphImage():

utils.py
from PIL import Image
 
 
#def find_content(aGender):
#    # get all contents for a specific gender aGender giving by parameter
#    contents_for_a_gender = Content.query.filter(Content.gender == Gender[aGender]).all()
#    
#    return random_choice(contents_for_a_gender)
 
class OpenGraphImage:
 
    def __init__(self, first_name, description):
        background = self.base()
        background.show()
 
    def base(self):
        img = Image.new('RGB', (1200, 630), '#18BC9C')
        return img
 
 
aDesc = """
   Toi, tu sais comment utiliser la console ! Jamais à court d'idées pour réaliser ton objectif, tu es déterminé-e et persévérant-e. Tes amis disent d'ailleurs volontiers que tu as du caractère et que tu ne te laisses pas marcher sur les pieds. Un peu hacker sur les bords, tu aimes trouver des solutions à tout problème. N'aurais-tu pas un petit problème d'autorité ? ;-)
"""
 
OpenGraphImage('Toby', aDesc)

Exécuter le script :

$ python3 ./fbapp/utils.py

Écrivez sur une image

La seconde étape consiste à écrire deux éléments sur l'image de base : le prénom et la description.

Écrivez le prénom

Créez une nouvelle méthode : print_on_image() qui prend plusieurs arguments : l'image de base sur laquelle écrire, le texte, la taille de police et la position dans l'image de base.

Créez une instance, pour le prénom, dont la taille de police est égale à 70px.

La hauteur représente la coordonnée, en ordonnée, à partir de laquelle le logiciel commence à tracer des lettres. Indiquez “50” afin de commencer à écrire à 50px du bord supérieur.

utils.py
#from random import choice as random_choice
#from fbapp.models import Content, Gender
import os
from PIL import Image, ImageFont, ImageDraw
 
 
#def find_content(aGender):
#    # get all contents for a specific gender aGender giving by parameter
#    contents_for_a_gender = Content.query.filter(Content.gender == Gender[aGender]).all()
#    
#    return random_choice(contents_for_a_gender)
 
class OpenGraphImage:
 
    def __init__(self, first_name, description):
        background = self.base()
        self.print_on_img(background, first_name.capitalize(), 70, 50)
        background.show()
 
    def base(self):
        img = Image.new('RGB', (1200, 630), '#18BC9C')
        return img
 
    def print_on_img(self, img, text, size, height):
        # On commence par charger la police utilisée.
        font = ImageFont.truetype(os.path.join('fbapp', 'static', 'fonts', 'Arcon-Regular.otf'), size)
 
        # Création d'une nouvelle instance
        draw = ImageDraw.Draw(img)
 
        # textsize renvoie la largeur et la hauteur en pixel
        # d'une chaine de caractères donnée.
        w = font.size
        h = draw.textlength(text, font)
 
        # Calcul de la position pour que le texte soit centré
        # et non pas aligné à gauche.  
        position = ((img.width - w) / 2, height)
 
        # Ajout du texte à l'image.
        draw.text(position, text, (255, 255, 255), font=font)
 
aDesc = """
   Toi, tu sais comment utiliser la console ! Jamais à court d'idées pour réaliser ton objectif, tu es déterminé-e et persévérant-e. Tes amis disent d'ailleurs volontiers que tu as du caractère et que tu ne te laisses pas marcher sur les pieds. Un peu hacker sur les bords, tu aimes trouver des solutions à tout problème. N'aurais-tu pas un petit problème d'autorité ? ;-)
"""
 
OpenGraphImage('Toby', aDesc)

Dans le code ci-dessus nous utilisons deux nouveaux modules de la librairie :

  • ImageFont qui permet d'utiliser une police de caractère personnalisée ;
  • ImageDraw de dessiner sur une image.

Écrivez la description

Écrire la description demande un peu plus de réflexion. Avez-vous trouvé pourquoi ?

Si vous exécutez le script en passant en paramètres une phrase assez longue, Python renverra une erreur. En effet, vous essayez d'écrire hors de l'image !

Il faut donc :

  • découper la phrase d'origine en plusieurs sous-phrases,
  • imprimer ces dernières sur l'image.

Voici notre solution :

utils.py
from random import choice as random_choice
#from fbapp.models import Content, Gender
import os
from textwrap import wrap as twrap
 
from PIL import Image, ImageFont, ImageDraw
 
 
#def find_content(aGender):
#    # get all contents for a specific gender aGender giving by parameter
#    contents_for_a_gender = Content.query.filter(Content.gender == Gender[aGender]).all()
#    
#    return random_choice(contents_for_a_gender)
 
class OpenGraphImage:
 
    def __init__(self, first_name, description):
        background = self.base()
        self.print_on_img(background, first_name.capitalize(), 70, 50)
        # retournes plusieurs chaines sans couper au milieu des mots
        sentences = twrap(description, width=40)
 
        # current_h : hauteur à laquelle commencer à écrire
        # pad : hauteur interligne en pixels
        current_h, pad = 180, 10
 
        for sentence in sentences:
            w, h = self.print_on_img(background, sentence, 40, current_h)
            # incrementer la hauteur avant d'écrire une nouvelle ligne
            current_h += 50
 
 
        background.show()
 
    def base(self):
        img = Image.new('RGB', (1200, 630), '#18BC9C')
        return img
 
    def print_on_img(self, img, text, size, height):
        # On commence par charger la police utilisée.
        font = ImageFont.truetype(os.path.join('fbapp', 'static', 'fonts', 'Arcon-Regular.otf'), size)
 
        # Création d'une nouvelle instance
        draw = ImageDraw.Draw(img)
 
        # textsize renvoie la largeur et la hauteur en pixel
        # d'une chaine de caractères donnée.
        w = font.size
        h = draw.textlength(text, font)
 
        # Calcul de la position pour que le texte soit centré
        # et non pas aligné à gauche.  
        position = ((img.width - w) / 2, height)
 
        # Ajout du texte à l'image.
        draw.text(position, text, (255, 255, 255), font=font)
 
        # On retourne la largeur et la hauteur
        return(w, h)
 
 
aDesc = """
   Toi, tu sais comment utiliser la console ! Jamais à court d'idées pour réaliser ton objectif, tu es déterminé-e et persévérant-e. Tes amis disent d'ailleurs volontiers que tu as du caractère et que tu ne te laisses pas marcher sur les pieds. Un peu hacker sur les bords, tu aimes trouver des solutions à tout problème. N'aurais-tu pas un petit problème d'autorité ? ;-)
"""
 
OpenGraphImage('Toby', aDesc)

Renvoyer le chemin vers l'image dans la vue

L'image est créée mais comment l'enregistrer ? Utilisez la méthode save() qui prend en paramètres le chemin vers le dossier où enregistrer l'image.

Créez une nouvelle méthode protégée, _path, qui renverra ce chemin et ajoutez une nouvelle méthode qui renverra le nom de l'image uniquement _location

utils.py
from random import choice as random_choice
from fbapp.models import Content, Gender
import os
from textwrap import wrap as twrap
 
from PIL import Image, ImageFont, ImageDraw
 
 
def find_content(aGender):
    # get all contents for a specific gender aGender giving by parameter
    contents_for_a_gender = Content.query.filter(Content.gender == Gender[aGender]).all()
 
    return random_choice(contents_for_a_gender)
 
class OpenGraphImage:
 
    def __init__(self, uid, first_name, description):
        self.location = self._location(uid)
        background = self.base()
        self.print_on_img(background, first_name.capitalize(), 70, 50)
        # retournes plusieurs chaines sans couper au milieu des mots
        sentences = twrap(description, width=40)
 
        # current_h : hauteur à laquelle commencer à écrire
        # pad : hauteur interligne en pixels
        current_h, pad = 180, 10
 
        for sentence in sentences:
            w, h = self.print_on_img(background, sentence, 40, current_h)
            # incrementer la hauteur avant d'écrire une nouvelle ligne
            current_h += 50
 
        background.save(self._path(uid))
 
    def base(self):
        img = Image.new('RGB', (1200, 630), '#18BC9C')
        return img
 
    def print_on_img(self, img, text, size, height):
        # On commence par charger la police utilisée.
        font = ImageFont.truetype(os.path.join('fbapp', 'static', 'fonts', 'Arcon-Regular.otf'), size)
 
        # Création d'une nouvelle instance
        draw = ImageDraw.Draw(img)
 
        # textsize renvoie la largeur et la hauteur en pixel
        # d'une chaine de caractères donnée.
        w = font.size
        h = draw.textlength(text, font)
 
        # Calcul de la position pour que le texte soit centré
        # et non pas aligné à gauche.  
        position = ((img.width - w) / 2, height)
 
        # Ajout du texte à l'image.
        draw.text(position, text, (255, 255, 255), font=font)
 
        # On retourne la largeur et la hauteur
        return(w, h)
 
    def _path(self, uid):
        # Le nom de l'image est l'identifiant Facebook de la personne.
        # Cela nous garantit de ne pas avoir de doublon.
        return os.path.join('fbapp', 'static', 'tmp', '{}.jpg'.format(uid))
 
 
    def _location(self, uid):
        return 'tmp/{}.jpg'.format(uid)
views.py
from flask import Flask, render_template, url_for, request
 
app = Flask(__name__)
 
# Config options - Make sure you created a 'config.py' file.
# To get one variable, tape app.config['MY_VARIABLE']
app.config.from_object('config')
 
from .utils import find_content, OpenGraphImage
 
...
 
@app.route('/result/')
def result():
    req_gender = request.args.get('gender')
    req_username = request.args.get('first_name')
    uid = request.args.get('id')
    profile_pic = 'https://graph.facebook.com/' + uid + '/picture?type=large'
    aDescription = find_content(req_gender).description
    img = OpenGraphImage(uid, req_username, aDescription).location
    og_url = url_for('index', img=img, _external=True)
 
    return render_template('result.html',
        username=req_username,
        photo=profile_pic,
        description=aDescription,
        og_url=og_url)
 
...

Lancez le serveur et allez sur l'URL suivante :

http://localhost:5000/result/?first_name=C%C3%A9line&id=1933433063608371&gender=female

Elle devrait se charger normalement.

Ouvrez l'inspecteur et regardez le contenu du head : vous devriez voir que la balise méta og:url contient désormais votre nouvelle image !

Copiez l'URL de la balise et collez-la dans la barre de navigation. Elle devrait ressembler à celle-ci :

http://localhost:5000/index/?img=tmp%2F1933433063608371.jpg

Là encore, regardez le contenu du head : la balise méta og:image devrait maintenant contenir votre toute nouvelle photo de couverture ! L'URL devrait ressembler à la suivante :

http://localhost:5000/static/tmp/1933433063608371.jpg

D'ailleurs, vous ne trouvez pas que tester tout ceci devient un peu contraignant ? Moi, si. J'ai bien envie de tester le parcours utilisateur afin de ne plus avoir à le faire à la main.

En résumé

En résumé

Dans ce chapitre, vous avez vu comment :

  • Générer et manipuler une image grâce à la librairie externe Pillow
    • Pillow est disponible depuis le gestionnaire de paquets Pypi
    • Pour travailler sur une image, Pillow vient avec les objets Image, ImageFont et ImageDraw
  • Créer le chemin d’une image et le retourner dans une vue Flask
    • Utiliser le module os de Python pour manipuler des chemins
    • Ajouter un paramètre avec l’image dans la fonction render_template() de la vue pour la passer au template
    • Tester de manière simple l’application en ouvrant l’inspecteur de code du navigateur
      • Cela permet de vérifier le contenu des balises HTML généré par le template

◁ Précédent | ⌂ Retour au sommaire | Suivant ▷

cours/informatique/dev/python/concevez_site_avec_flask/320_ajouter_comportement.txt · Dernière modification : 2023/10/17 18:52 de yoann