Image de l'article

Les requêtes Ajax avec Django

Dans cet article, je vous montre la façon moderne de réaliser une requête vers une vue Django sans avoir besoin de recharger la page (requête asynchrone).

Publié le 17 mars 2021 par ThibH

Si vous avez déjà effectué des requêtes Ajax avec Django, vous avez probablement trouvé sur internet des dizaines d'articles vous indiquant différentes façons de procéder, notamment avec JQuery.

Dans cet article, je vais vous montrer la façon moderne de réaliser une requête vers une vue Django sans avoir besoin de recharger la page (requête asynchrone).

Fetch VS Ajax

Je vous ai parlé en introduction des requêtes Ajax (Asynchronous Javascript And XML), une technique qui permet de demander des informations à votre serveur et de les récupérer sans nécessiter le rafraichissement de votre page.

C'est une technique très populaire qui est cependant assez compliquée à mettre en place.

Si vous cherchez des tutoriels sur la façon d'effectuer une requête Ajax avec Django, vous tomberez donc plus que probablement sur du code utilisant la bibliothèque JQuery, celle-ci ayant grandement facilité par le passé l'écriture de requêtes Ajax.

Mais on est en 2021, JavaScript a beaucoup évolué et il est maintenant possible de se passer de JQuery sans problème.

Dans cet article, nous allons utiliser l'API Fetch de JavaScript qui nous permet d'arriver au même résultat, sans JQuery et avec un code très épuré. Le titre de l'article est donc un peu trompeur, mais les requêtes Ajax ont tellement été utilisées par le passé que pour le référencement de cet article, je me suis permis ce petit écart sémantique 😋

Cette api est encore récente et n'est malheureusement pas encore supporté par tous les navigateurs (notamment notre très cher Internet Explorer).

Si vous souhaitez vous assurer d'une compatibilité maximale, il vous faudra donc repasser par l'objet XMLHttpRequest.

Vous pouvez voir les navigateurs supportant l'API Fetch ici (heureusement, les 3/4 des versions récentes des navigateurs modernes le supportent).
Le projet Django

Pour cet article, je vais utiliser un projet Django très basique qui me permet d'effectuer une addition entre deux nombres à l'aide de deux input HTML.

Vous pouvez retrouver les sources du projet sur Github.

Ce projet contient les fichiers suivants :

# urls.py
from django.urls import path
from ajax.views import home, compute

urlpatterns = [
    path('', home, name="home"),
    path('compute/', compute, name="compute"),
]

☝️ Le fichier de routage d'URL avec deux chemins : un pour afficher la page d'accueil du site, un autre pour effectuer le calcul de l'opération.

# views.py
from django.http import JsonResponse
from django.shortcuts import render


def home(request):
    return render(request, "ajax/index.html")


def compute(request):
    # Calcul de l'opération
    return JsonResponse({})

☝️ Le fichier des vues qui contient la vue pour la page d'accueil et la vue dans laquelle nous allons effectuer le calcul de l'opération.

# index.html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Django Ajax</title>
</head>
<body>
    <h1>Django Ajax Example</h1>
    
    <!-- Le formulaire -->
    <label>Opération</label>
    <input type="text" id="a"> + <input type="text" id="b">
    <input id="ajax-call" type="submit" value="Calculer">
    
    <!-- Nous afficherons le résultat de l'opération ici -->
    <p id="ajax"></p>
</body>
</html>

☝️ La page HTML qui contient le formulaire.

Voici ce que nous retourne la page index.html :

Vous remarquez que nous n'avons même pas mis de balise <form> pour notre formulaire, juste deux input et un bouton.

Nous verrons à la fin de cet article comment gérer le cas d'un formulaire contenant un jeton csrf.

Récupérer les données du formulaire

La première chose à faire est de récupérer les données de notre formulaire.

Dans cet exemple, j'ai donné un id de a et b aux deux inputs de mon formulaire, je vais donc pouvoir récupérer les valeurs grâce à ces identifiants et la fonction querySelector.

J'en profite également pour relier l'événement 'click' du bouton à une fonction (ici, une fonction fléchée) :

document.querySelector("#ajax-call").addEventListener("click", event => {
    let formData = new FormData();
    formData.append('a', document.querySelector("#a").value);
    formData.append('b', document.querySelector("#b").value);
})
Vous pouvez inclure ce script directement dans le fichier HTML à l'intérieur de balises <script>.

Ici, je crée un nouvel objet FormData qui me permet d'envoyer des données de formulaire à ma vue Django avec l'API Fetch.

Il existe différentes façons d'envoyer des données. On peut envoyer un objet JavaScript (que l'on convertira en chaîne de caractères avec JSON.stringify), un objet URLSearchParams ou encore, comme ici, un objet FormData).

Je vais ensuite créer un objet Request auquel je vais passer l'URL à appeler ainsi que les données à envoyer :

const request = new Request('{% url "compute" %}', {method: 'POST', body: formData});

Vous remarquerez que j'ai utilisé le langage de gabarit de Django avec la balise url afin de récupérer automatiquement l'URL associée au nom compute.

J'aurais très bien pu mettre directement une URL sous forme de chaîne de caractères comme ceci :

const request = new Request('/compute/', {method: 'POST', body: formData});

Le premier argument envoyé à Request est donc l'URL à laquelle on souhaite accéder.

Je précise ensuite que je souhaite exécuter une requête de type POST et j'envoie les données de mon formulaire dans le corps de ma requête (body).

Je peux ensuite utiliser l'API Fetch pour exécuter cette requête et récupérer la réponse (et par la suite la traiter pour afficher le résultat de l'opération sur notre page) :

fetch(request)
    .then(response => response.json())
    .then(result => {
        console.log(result);
    })

L'API Fetch nous retourne une promesse (promise en anglais), que nous pouvons traiter avec la méthode then. Là encore, nous utilisons des fonctions fléchées pour alléger le code.

Et voilà ! Vous venez d'envoyer les données de votre formulaire à l'URL indiquée.

Il nous suffit maintenant de récupérer ces données dans notre vue Django, les traiter et renvoyer l'information attendue à notre fichier HTML.

Traiter les données dans la vue

À l'intérieur de ma fonction compute, je peux récupérer les données grâce à request.POST :

def compute(request):
    a = request.POST.get("a")
    b = request.POST.get("b")
    result = int(a) + int(b)
    return JsonResponse({"operation_result": result})

request.POST est un dictionnaire, je peux donc accéder aux valeurs associées aux clés 'a' et 'b' grâce à la méthode get.

Le nom des clés contenues dans ce dictionnaire découlent directement de l'objet FormData que nous avons créé en JavaScript :

let formData = new FormData();
formData.append('a', document.querySelector("#a").value);
formData.append('b', document.querySelector("#b").value);

On aurait pu utiliser d'autres noms pour les clés, comme patrick et pascal

let formData = new FormData();
formData.append('patrick', document.querySelector("#a").value);
formData.append('pascal', document.querySelector("#b").value);

Et dans notre vue, nous aurions récupéré les valeurs associées aux clés comme ceci :

def compute(request):
    a = request.POST.get("patrick")
    b = request.POST.get("pascal")
    result = int(a) + int(b)
    return JsonResponse({"operation_result": result})

Dans notre vue, nous récupérons donc les données envoyées grâce à l'API Fetch, puis nous réalisons une simple addition (en prenant soin de convertir les données envoyées en nombre grâce à la fonction int).

Pour finir, nous retournons un objet de type JsonResponse avec un dictionnaire Python contenant le résultat de l'opération.

Afficher le résultat dans notre fichier HTML

De retour dans notre fichier HTML, à l'intérieur de nos balises <script>, nous récupérons le résultat envoyé par la vue Django à l'intérieur de la méthode then :

fetch(request)
    .then(response => response.json())
    .then(result => {
        const resultElement = document.querySelector("#ajax");
        resultElement.innerHTML = result["operation_result"];
    })

result correspond au dictionnaire que nous avons renvoyé dans la vue compute (dans le fichier views.py), nous pouvons donc accéder à la valeur associée à la clé "operation_result" en utilisant la syntaxe result["operation_result"].

On utilise ensuite querySelector pour récupérer le paragraphe dans lequel on souhaite afficher le résultat en modifiant le innerHTML.

Si vous essayez de lancer un calcul, vous obtiendrez maintenant... une belle erreur :

Forbidden (CSRF token missing or incorrect.): /compute/

Pour éviter cette erreur, on peut spécifier à la vue compute que l'on ne souhaite pas utiliser la vérification du jeton CSRF.

Ce n'est pas très sécuritaire, mais c'est la façon la plus simple à mettre en place, avec le décorateur csrf_exempt :

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def compute(request):
    a = request.POST.get("patrick")
    b = request.POST.get("pascal")
    result = int(a) + int(b)
    return JsonResponse({"operation_result": result})

Après ça, l'affichage devrait fonctionner : 

Récupérer le jeton CSRF

Afin de voir comment récupérer le jeton CSRF, nous allons légèrement modifier notre formulaire pour l'entourer d'une balise <form> et y ajouter la balise {% csrf_token %} :

<form method="POST">
    {% csrf_token %}
    <label>Opération</label>
    <input type="text" id="a"> + <input type="text" id="b">
    <input id="ajax-call" type="submit" value="Calculer">
</form>

Si vous allez inspecter le code HTML de la page, vous verrez que la balise csrf_token de Django a été remplacée par un input caché :

<input type="hidden" name="csrfmiddlewaretoken" value="1ZPZEVBlqvC1atIqYk6ZDF7N5d8Tyw6BXiFBCkCrWesggOSVR8zHMyw6cyYQFE04">

Nous pouvons récupérer la valeur du jeton grâce à querySelector, en ciblant l'attribut name de cet input :

let csrfTokenValue = document.querySelector('[name=csrfmiddlewaretoken]').value;

Une fois la valeur du jeton récupérée, nous allons l'ajouter à l'en-tête de notre objet Request :

const request = new Request('{% url "compute" %}', {
        method: 'POST',
        body: formData,
        headers: {'X-CSRFToken': csrfTokenValue}  // On ajoute le token dans l'en-tête
});

Voilà à quoi ressemble le JavaScript avec tous les éléments : 

document.querySelector("#ajax-call").addEventListener("click", event => {
    event.preventDefault();
    let formData = new FormData();
    formData.append('a', document.querySelector("#a").value);
    formData.append('b', document.querySelector("#b").value);

    // On récupère la valeur du jeton CSRF
    let csrfTokenValue = document.querySelector('[name=csrfmiddlewaretoken]').value;
    const request = new Request('{% url "compute" %}', {
        method: 'POST',
        body: formData,
        headers: {'X-CSRFToken': csrfTokenValue}  // On ajoute le token dans l'en-tête
    });

    // On exécute la requête
    fetch(request)
        .then(response => response.json())
        .then(result => {
            const resultElement = document.querySelector("#ajax");
            resultElement.innerHTML = result["operation_result"];
        })
})
Vous remarquerez que nous avons ajouté la ligne event.preventDefault(); au début de notre fonction.

Cette ligne permet d'éviter la soumission automatique du formulaire, qui provoquerait un rafraîchissement de la page suite à un envoi classique du formulaire.

Et voilà, on peut maintenant enlever le décorateur csrf_exempt de notre vue et notre page affichera le résultat correctement.

Voici l'entièreté de notre fichier HTML :

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Django Ajax</title>
</head>
<body>
<h1>Django Ajax Example</h1>


<!-- Le formulaire -->
<form method="POST">
    {% csrf_token %}
    <label>Opération</label>
    <input type="text" id="a"> + <input type="text" id="b">
    <input id="ajax-call" type="submit" value="Calculer">
</form>

<!-- Nous afficherons le résultat de l'opération ici -->
<p id="ajax"></p>


<script>
document.querySelector("#ajax-call").addEventListener("click", event => {
    event.preventDefault();
    let formData = new FormData();
    formData.append('a', document.querySelector("#a").value);
    formData.append('b', document.querySelector("#b").value);
    let csrfTokenValue = document.querySelector('[name=csrfmiddlewaretoken]').value;
    const request = new Request('{% url "compute" %}', {
        method: 'POST',
        body: formData,
        headers: {'X-CSRFToken': csrfTokenValue}
    });
    fetch(request)
        .then(response => response.json())
        .then(result => {
            const resultElement = document.querySelector("#ajax");
            resultElement.innerHTML = result["operation_result"];
        })
})
</script>

</body>
</html>

Ça peut paraître compliqué la première fois qu'on fait toutes ces étapes, mais le cœur de la requête asynchrone tient en quelques lignes. Le code est également assez explicite (même si ça reste du JavaScript).