📝 Configuration d’une application avec templates HTML, Django.

Dans cette partie nous allons voir l’implémentation d’applications web, avec rendu de pages côté serveur.

Interface web et sites web - Contexte

Avant d'être rendu comme une belle page côté utilisateur, un site web utilise différents fichiers.

Pour l’exemple du cours on utilise la page : https://example.org :

HTML

HTML (HyperText Markup Language) est le language utilisé pour structurer une page web et son contenu. Par exemple, le contenu peut être organisé sous forme de paragraphes, d’une liste à puces ou encore à l’aide d’images et de tableaux de données. C’est donc le contenu de base pour tout site web.

  • A retenir Les fichiers HTML : Ils décrivent la structure logique d’une page web, définissent le contenu des pages. Ils s’écrivent a l’aide de balises Markup de manière plutôt verbeuse.
<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is for use in illustrative examples in documents. You may use this
        domain in literature without prior coordination or asking for permission.</p>
    <p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
Pour aller plus loin

Vous pouvez par exemple consulter ce code pen simple :

Pour aller plus loin

Introduction au HTML, avec explication des balises canon.

https://www.w3schools.com/html/html_intro.asp

CSS

CSS (Cascading Style Sheets) est le language qui permet de styliser le contenu web. Sans CSS, un site n’a qu’une structure, mais pas de rendu visuel stylisé. Il est donc nécessaire de passer par la configuration de ces feuilles de style pour aboutir a un projet abouti côté Web.

  • A retenir : Les fichiers CSS : fichiers de style, ils gèrent l’apparence des balises présentées.
body {
    background-color: #f0f0f2;
    margin: 0;
    padding: 0;
    font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
    width: 600px;
    margin: 5em auto;
    padding: 2em;
    background-color: #fdfdff;
    border-radius: 0.5em;
    box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
    color: #38488f;
    text-decoration: none;
}
@media (max-width: 700px) {
    div {
        margin: 0 auto;
        width: auto;
    }
}
1. Pour aller plus loin

Javascript

JavaScript est un langage de programmation qui ajoute de l’interactivité aux sites web. Cela se produit dans les jeux, dans le comportement des réponses lorsque des boutons sont pressés, lors de la saisie de données dans des formulaires, avec des styles dynamiques, des animations, etc. Il permet de modifier le DOM, qui est le contexte rendu objet d’un site.

  • A retenir : Les fichiers javascript : éléments de scripts du site, permettent de réaliser des actions sur des éléments Html ou css.

Exemple avec un bouton qui affiche un nombre aléatoire entre 0 et 1 :

<button id="button"type="button">Affichage d'un nombre aléatoire</button>
<div id="affichage"></div>
document.getElementById("button").onclick = function() {
    document.getElementById("affichage").innerHTML = Math.random();
};

1. Pour aller plus loin

Un codepen simple : https://codepen.io/rcyou/pen/QEObEZ

2. Pour aller plus loin

Un codepen bien plus fourni : Une petite application statique qui va chercher des informations sur un pokemon avec une requête http en javascript : https://codepen.io/mikemilio/pen/poXOywy

3. Pour aller plus loin

Le DOM : https://www.w3schools.com/js/js_htmldom.asp

Templating

Ce qui va nous intéresser aujourd’hui c’est d’effectuer un rendu des pages côté serveur et donc de limiter la logique au maximum à des traitements internes a notre application : on parle alors de Server Side Rendering.

Cela a différents avantages / inconvénients : notamment un meilleur contrôle des requêtes ou également la rapidité dans le cas d’un cache applicatif.

Nous allons donc ici nous intéresser au contenu des pages rendues et donc aux fichiers html des sites, qu’on va vouloir templatiser et renseigner en fonction de divers informations qu’on aura a l’intérieur de nos programmes.

Un petit historique : La plupart des applications web “anciennes” sont en PHP + JS + HTML + CSS pour utiliser la flexibilité du php pour la construction de pages templatisées.

En python des applications du même type peuvent être construites avec le framework Django mais également a l’aide de FASTAPI en faisant renvoyer des pages HTML templatisées.

Templating Engine : Présentation de Jinja2

Un templating engine (ou moteur de templating) est un outil utilisé pour générer dynamiquement du contenu, généralement du HTML, en combinant des données et un modèle de mise en page (template). On s’en sert en général pour la génération de pages HTML côté serveur, en injectant les résultats de traitements applicatifs dans des pages.

NB: Cela permet de séparer la logique “service” de la logique vue (CF cours 1)

Jinja2 est un moteur de template pour Python, largement utilisé dans les frameworks web comme Flask. Il permet de générer du HTML dynamique en utilisant des variables, des boucles et des conditions. Il est également utilisé dans une myriade d’outils d’où la présentation .

Voyez plutôt ce template, qui sert pour configurer un fichier a envoyer par mail.

<html>
<body>
    <h1>Bonjour {{ user_name }},</h1>
    <p>Merci de vous être inscrit sur notre plateforme.</p>
    <p>Votre email : {{ user_email }}</p>
    {% if is_admin %}
    <p><strong>Vous avez des privilèges administrateurs.</strong></p>
    {% endif %}
    <p>Cordialement,</p>
    <p>L'équipe du support du site XXXX</p>
</body>
</html>
  • Jinja2 utilise des doubles accolades {{ }} pour insérer des variables dynamiques dans un template. Ces variables sont remplacées lors du rendu du template.
  • Jinja2 prend en charge les structures de contrôle comme les conditions (if), les boucles (for) et les filtres (upper, lower, length etc.). Ces structures permettent d’adapter le contenu rendu en fonction des données.

Un peu de code

    with open(os.path.join(chemin_script,"template.html"),"r",encoding="utf-8") as fichier_template:
        contenu_fichier_template = fichier_template.read()
        template_rempli_non_admin=template.render(
            user_name=USER_NAME,user_email=USER_EMAIL,is_admin=False
        )

Avec les valeurs user_name = "antoine", user_email = "antoinebrunetti@gmail.com" et is_admin = True

<html>
<body>
    <h1>Bonjour antoine,</h1>
    <p>Merci de vous être inscrit sur notre plateforme.</p>
    <p>Votre email : antoinebrunetti@gmail.com</p>
    <p><strong>Vous avez des privilèges administrateurs.</strong></p>
    <p>Cordialement,</p>
    <p>L'équipe de support</p>
</body>
</html>
Pour aller plus loin 1

Documentation officielle https://jinja.palletsprojects.com/en/stable/intro/

Pour aller plus loin 2

Accédez a l’exemple conçu pour le cours et executez le : https://github.com/conception-logicielle-ensai/exemples-cours/tree/main/cours-4/web-templating

Server Side Rendering (SSR) : Exemple avec FastAPI

Le rendu côté serveur (Server Side Rendering, SSR) est une architecture où les pages HTML sont générées sur le serveur avant d’être envoyées au navigateur. Cela permet d’améliorer le temps de chargement initial.

Remarque on utilise aussi souvent cela pour le référencement (SEO), en particulier pour les moteurs de recherche qui ont du mal à indexer le contenu rendu côté client.

Principe

Comme vu précédemment, FastAPI est un framework web rapide pour Python, il permet de mettre a disposition du code via HTTP.

En s’appuyant sur du templating Jinja de pages HTML on peut donc dynamiquement répondre a des requêtes utilisateurs en fabriquant les pages HTML côté serveur.

Mise en oeuvre

Nativement, après l’installation de jinja2, fastapi permet l’usage des templates précédemment présentés :

  • from fastapi.templating import Jinja2Templates.

Il suffit d’organiser le code projet ainsi :

projet/
│-- templates/
│   │-- index.html
│-- main.py # ou un autre module main

Et d’importer les templates ainsi :

from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")

Exemple de projet simple : lien vers l’exemple-minimal

Il existe beaucoup d’éléments de configuration, inspirez vous de la documentation et des exemples de code.

Pour aller plus loin 1

La documentation est ici https://fastapi.tiangolo.com/advanced/templates/

Pour aller plus loin 2

Un exemple plus concret pour vous inspirer pour vos projets : https://github.com/conception-logicielle-ensai/exemples-cours/tree/main/cours-4/fastapi-web-templating/app

Django : Le framework “tout en un”

Django est le framework historique de développement d’applications avec rendu de template html côté serveur. Il propose une myriade de fonctionnalités qui ont la particularité d’être dans une approche opt-in, on peut activer ou desactiver une fonctionnalité par de la configuration applicative.

Il propose une architecture interne implémentant le pattern MTV (Model-Template-View) :

  • Modèle (Model) : DAO (Data Access Object) - des objets mappés en base de données accessibles via l’ORM interne a django.

Pour info un ORM, Object Relational Mapping, est un outil créant un couplage entre un modèle de classes et un modèle de données en base de données, il implémente différentes méthodes pour récupérer les objets

  • Vue (View) : Définition de la logique métier et permet de renvoyer une réponse HTTP adaptée a une requête.

Il implémente également un Middleware : composant interceptant les requêtes et leur appliquant des propriétés => Authentification, session, redirection..

Configuration

Dans un projet Django, la configuration globale se fait a l’interieur même du projet, ou par variables d’environnements :

https://docs.djangoproject.com/fr/5.1/topics/settings/#designating-the-settings

Documentation externe:

Pour démarrer si vous partez sur cette solution :

Exemple simple

https://github.com/conception-logicielle-ensai/exemples-cours/tree/main/cours-4/django/exemple_simple

Il s’agit d’un projet mis en place assez simplement, inspirez vous du README Suivez un des tutoriels

https://openclassrooms.com/fr/courses/7172076-debutez-avec-le-framework-django

https://docs.djangoproject.com/en/5.1/intro/

Pour aller plus loin 1

Documentation overview: https://docs.djangoproject.com/en/5.1/intro/overview/

Pour aller plus loin 2

Exemple “avancé” : https://github.com/conception-logicielle-ensai/exemples-cours/tree/main/cours-4/django/app le README est également constitué a façon pour vous permettre de démarrer.

Pour aller plus loin 3

Django design philosophies : voir https://docs.djangoproject.com/fr/5.1/misc/design-philosophies/

Websockets

Qu’est-ce qu’un WebSocket ?

Un WebSocket est un protocole de communication bidirectionnel qui permet une interaction en temps réel entre un client et un serveur.

=> Contrairement au protocole HTTP, qui fonctionne sur un modèle requête-réponse, WebSocket établit une connexion persistante où les deux parties peuvent envoyer et recevoir des messages à tout moment.

Ce protocole est particulièrement utile pour les applications nécessitant des mises à jour en temps réel, comme les chats en ligne, les jeux multijoueurs ou les notifications instantanées.

Un peu de TCP

WebSocket fonctionne au-dessus de TCP (Transmission Control Protocol), qui est un protocole de transport garantissant la livraison fiable des données. Lorsqu’une connexion WebSocket est établie, elle commence par une requête HTTP spéciale appelée “handshake”. Une fois le handshake réussi, la connexion passe en mode WebSocket et permet l’échange de données en temps réel.

Les acteurs dans une architecture WebSocket

Il existe deux types d’acteurs principaux dans une architecture WebSocket : les clients et les serveurs.

Le client est l’entité qui initie la connexion WebSocket en envoyant une requête au serveur. Une fois la connexion établie, il peut envoyer et recevoir des messages en temps réel.

Le serveur écoute les connexions entrantes des clients, gère les sessions et transmet les messages aux destinataires appropriés. Il est responsable de maintenir la connexion active et de relayer les messages entre les différents clients.

Ces deux composants travaillent ensemble pour offrir une communication fluide et en temps réel, essentielle pour de nombreuses applications modernes.

Exemple d’implémentation

L’exemple ici présenté correspond a l’exemple du cours : https://github.com/conception-logicielle-ensai/exemples-cours/tree/main/cours-4/websocket

On repose sur la librairie légère websockets et la librairie asyncio par practicité.

  • Côté serveur
import asyncio
import websockets

# Ensemble pour stocker les connexions actives
connected_clients = set()

async def broadcast(message):
    for client in connected_clients:
        await client.send(message)

async def handle_client(websocket):
    # Ajoute le client connecté à l'ensemble
    connected_clients.add(websocket)
    print(f"Client connecté. Total clients : {len(connected_clients)}")

    try:
        async for message in websocket:
            print(f"Message reçu : {message}")
            await broadcast(f"Message d'un client : {message}")
    except websockets.exceptions.ConnectionClosed:
        print("Client déconnecté.")
    finally:
        # Retire le client de l'ensemble à sa déconnexion
        connected_clients.remove(websocket)
        print(f"Client déconnecté. Total clients : {len(connected_clients)}")

async def main():
    async with websockets.serve(handle_client, "localhost", 8765):
        print("Serveur WebSocket démarré sur ws://localhost:8765")
        await asyncio.Future()  # Garde le serveur actif

if __name__ == "__main__":
    asyncio.run(main())

On démarre un serveur accesible sur le port 8765

  • Côté client
import asyncio
import websockets

async def communicate():
    uri = "ws://localhost:8765"
    async with websockets.connect(uri) as websocket:
        message = "Hello, WebSocket Server!"
        print(f"Envoi : {message}")
        await websocket.send(message)
        response = await websocket.recv()
        print(f"Réponse du serveur : {response}")

if __name__ == "__main__":
    asyncio.run(communicate())

On s’y connecte directement puis on broadcast des messages

  • Il est également possible de gérer une session directement dans le navigateur via l’usage du Javascript.
  const socket = new WebSocket("ws://localhost:8765");

        socket.onopen = function(event) {
            console.log("Connexion établie avec le serveur WebSocket");
        };

        socket.onmessage = function(event) {
            console.log("Message reçu du serveur :", event.data);
            document.getElementById("messages").innerHTML += `<p>${event.data}</p>`;
        };

        socket.onclose = function(event) {
            console.log("Connexion fermée");
        };

        function sendMessage() {
            const message = "Hello depuis le client javascript !";
            console.log("Envoi :", message);
            socket.send(message);
        }

Travaux pratiques

Mettez en place une première architecture dans vos projets :