# Documentation Django 6.0.7

# Traitement conditionnel de vue

Les clients HTTP peuvent envoyer plusieurs en-têtes pour signaler au serveur des copies de ressources qu’ils ont déjà rencontrées. C’est fréquemment employé lors de l’obtention d’une page Web (par une requête HTTP `GET`) pour éviter d’envoyer toutes les données quand le client en a déjà récupéré une partie. Cependant, les mêmes en-têtes peuvent être utilisés par toutes les méthodes HTTP (`POST`, `PUT`, `DELETE`, etc.).

Pour chaque page (réponse) que Django renvoie depuis une vue, il est susceptible de définir deux en-têtes HTTP : l’en-tête `ETag` et l’en-tête `Last-Modified`. Ces en-têtes sont facultatifs dans les réponses HTTP. Ils peuvent être définis par la fonction de vue ou il est possible de compter sur l’intergiciel `ConditionalGetMiddleware` pour définir l’en-tête `ETag`.

Lorsqu’un client demande une nouvelle fois la même ressource, il peut envoyer avec la requête un en-tête tel que If-Modified-Since ou If-Unmodified-Since contenant la date de dernière modification qu’il a reçue, ou If-Match ou If-None-Match contenant la dernière valeur `ETag` qu’il a reçue. Si la version actuelle de la page correspond à la valeur `ETag` envoyée par le client ou que la ressource n’a pas été modifiée entre temps, le serveur peut renvoyer un code de statut 304 au lieu d’une réponse complète, indiquant par là au client que rien n’a changé. En fonction de l’en-tête, si la page a été modifiée ou ne correspond pas au contenu `ETag` envoyé par le client, un code de statut 412 (Precondition Failed) peut être renvoyé.

Dans les cas où vous avez besoin d’un contrôle plus fin, vous pouvez utiliser les fonctions de traitement conditionnel par vue.

## Le décorateur `condition`

Parfois (et assez souvent en réalité), vous pouvez créer des fonctions pour calculer rapidement la valeur ETag ou la date de dernière modification d’une ressource, sans devoir procéder à toutes les opérations nécessaires à la construction complète de la vue. Django peut ensuite utiliser ces fonctions pour fournir une option de « court-circuitage rapide » du traitement de la vue. Par exemple pour indiquer au client que le contenu n’a pas été modifié depuis la requête précédente.

Ces deux fonctions sont transmises comme paramètres au décorateur `django.views.decorators.http.condition`. Ce décorateur les exploite (vous pouvez n’en fournir qu’une s’il n’est pas possible de calculer facilement et rapidement les deux éléments) pour savoir si les en-têtes de la requête HTTP correspondent à ceux de la ressource. Lorsque ce n’est pas le cas, une nouvelle copie de la ressource doit être calculée et donc la vue normale est appelée.

La signature du décorateur `condition` correspond à ceci :

    condition(etag_func=None, last_modified_func=None)

Les deux fonctions, celle pour calculer la valeur `ETag` et celle pour la date de dernière modification, reçoivent l’objet `request` entrant ainsi que les mêmes paramètres, dans le même ordre, que la fonction de vue qu’elles enveloppent. La fonction de rappel `last_modified_func` doit renvoyer une valeur date/heure standard indiquant la date de dernière modification de la ressource ou `None` si la ressource n’existe pas. La fonction de rappel du décorateur `etag` doit renvoyer une chaîne représentant la valeur ETag de la ressource, ou `None` si la ressource n’existe pas.

Le décorateur définit les en-têtes `ETag` et `Last-Modified` de la réponse s’ils ne sont pas déjà définis par la vue et si la méthode de la requête est sûre (`GET` ou `HEAD`).

Il est probablement plus adéquat d’expliquer la façon d’utiliser cette fonctionnalité par un exemple. Admettons que vous ayez ces deux modèles représentant un petit système de blog :

    import datetime
    from django.db import models

    class Blog(models.Model): ...

    class Entry(models.Model):
        blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
        published = models.DateTimeField(default=datetime.datetime.now)
        ...

Si la page d’accueil affichant les derniers articles de blog ne change que lorsque vous ajoutez un nouvel article, vous pouvez calculer très rapidement la date de dernière modification. Vous avez besoin de la date `published` la plus récente de chaque article associé à ce blog. Une façon de le faire serait :

    def latest_entry(request, blog_id):
        return Entry.objects.filter(blog=blog_id).latest("published").published

Vous pouvez dès lors utiliser cette fonction pour fournir une détection rapide d’une page d’accueil inchangée :

    from django.views.decorators.http import condition

    @condition(last_modified_func=latest_entry)
    def front_page(request, blog_id): ...

Attention à l’ordre des décorateurs

Lorsque `condition()` renvoie une réponse conditionnelle, tout décorateur qui le suit sera ignoré et ne s’appliquera pas à la réponse. Ainsi, tout décorateur devant s’appliquer à la fois à la réponse de vue normale et à la réponse conditionnelle doit se trouver au-dessus de `condition()`. En particulier, `vary_on_cookie()`, `vary_on_headers()` et `cache_control()` doivent figurer en premier car la RFC 9110 exige que les en-têtes qu’ils définissent soient présents dans les réponses 304.

## Raccourcis pour le calcul d’une seule valeur

En règle générale, si vous pouvez fournir des fonctions à la fois pour le calcul de la valeur `ETag` et la date de dernière modification, il est bien de le faire. On ne connaît pas à l’avance les en-têtes qu’un client HTTP donné va envoyer, on peut donc s’attendre à devoir gérer les deux cas de figure. Cependant, il peut arriver qu’une seule des deux valeurs soit facile à calculer et Django fournit donc des décorateurs qui ne gèrent que la valeur `ETag` ou que la date de dernière modification.

Les décorateurs `django.views.decorators.http.etag` et `django.views.decorators.http.last_modified` reçoivent le même type de fonction que le décorateur `condition`. Leur signature est :

    etag(etag_func)
    last_modified(last_modified_func)

Nous pourrions écrire l’exemple précédent qui n’utilisait que la fonction de dernière modification en choisissant l’un de ces décorateurs :

    @last_modified(latest_entry)
    def front_page(request, blog_id): ...

…ou :

    def front_page(request, blog_id): ...

    front_page = last_modified(latest_entry)(front_page)

### Test des deux conditions avec `condition`

Il pourrait paraître plus élégant pour certaines personnes d’essayer d’enchaîner les décorateurs `etag` et `last_modified` pour tester les deux pré-conditions. Cependant, l’effet produit serait faux.

    # Bad code. Don't do this!
    @etag(etag_func)
    @last_modified(last_modified_func)
    def my_view(request): ...

    # End of bad code.

Le premier décorateur n’a aucune conscience du second et pourrait répondre que la réponse n’a pas été modifiée même quand le second décorateur en déciderait autrement. Le décorateur `condition` utilise les deux fonctions de rappel de concert pour prendre la bonne décision.

## Utilisation des décorateurs avec d’autres méthodes HTTP

Le décorateur `condition` est utile au-delà des requêtes `GET` et `HEAD` (les requêtes `HEAD` sont similaires aux requêtes `GET` dans cette optique). Il peut aussi être utilisé pour faire les vérifications nécessaires pour les requêtes `POST`, `PUT` et `DELETE`. Dans ces cas de figure, l’idée n’est pas de renvoyer une réponse « non modifiée », mais d’indiquer au client que la ressource qu’ils tentent de modifier a subi des modifications dans l’intervalle.

Par exemple, considérez l’échange suivant entre un client et un serveur :

1. Le client demande `/foo/`.

2. Le serveur répond avec un contenu dont la valeur `ETag` vaut `"abcd1234"`.

3. Le client envoie une requête HTTP `PUT` vers `/foo/` pour mettre à jour la ressource. Il envoie également un en-tête `If-Match: "abcd1234"` pour indiquer la version qu’il tente de mettre à jour.

4. Le serveur vérifie si la ressource a changé en calculant la valeur `ETag` comme il l’a fait pour la requête `GET` (en utilisant la même fonction). Si la ressource a effectivement changé, il renvoie un code de statut 412 signifiant « Échec de condition préalable » (precondition failed).

5. Le client envoie une requête `GET` vers `/foo/` après la réception de la réponse 412 afin de récupérer une version mise à jour du contenu avant de le mettre à jour.

La chose importante démontrée par cet exemple est que les mêmes fonctions peuvent être utilisées pour calculer les valeurs `ETag` et date de dernière modification dans toutes les situations. En fait, vous devez utiliser les mêmes fonctions afin que les mêmes valeurs soient renvoyées à chaque fois.

En-têtes de validation avec les méthodes de requête non sûres

Le décorateur `condition` ne définit les en-têtes de validation (`ETag` et `Last-Modified`) que pour les méthodes HTTP sûres, c’est-à-dire `GET` et `HEAD`. SI vous souhaitez les avoir avec d’autres méthodes, vous devez les définir dans la vue. Voir RFC 9110 Section 9.3.4 pour connaître la distinction entre la définition d’un en-tête de validation dans les réponses aux requêtes envoyées par `PUT` en comparaison de `POST`.

## Comparaison avec l’intergiciel de traitement conditionnel

Django fournit une gestion conditionnelle de `GET` via l’intergiciel `django.middleware.http.ConditionalGetMiddleware`. Bien qu’il soit adapté à de nombreuses situations, les fonctionnalités de cet intergiciel présentent des limites quant à un usage avancé :

- Il est appliqué globalement à toutes les vues d’un projet.

- Il ne vous économise pas le travail de génération de la réponse, ce qui peut être coûteux.

- Il n’est adapté qu’aux requêtes HTTP `GET`.

Vous devriez choisir ici l’outil le plus approprié à votre problème particulier. Si vous arrivez à calculer rapidement les valeurs `ETag` et de date de dernière modification et que certaines vues prennent du temps à produire leur contenu, vous devriez envisager d’utiliser le décorateur `condition` présenté dans ce document. Par contre, si tout fonctionne déjà de manière assez fluide, restez-en à la solution des intergiciels et vous continuerez de réduire la quantité de trafic réseau en direction des clients dans les cas où les contenus des vues n’ont pas changé.