---
title: URLs dans Django
author: Steve Kossouho
---

# Afficher des pages avec Django

----

## Routage d'URLs

Tous les serveurs d'application web ont besoin de savoir comment afficher des pages à des URLs distinctes. 

Par exemple, si vous demandez au serveur à l'adresse `https://mon.adresse.com` l'URL dont le nom est `https://mon.adresse.com/resource`, le serveur doit pouvoir définir ce qu'il va faire de la ressource dont le chemin est `/resource`. (Et en général, il doit vous renvoyer un contenu avec le code de statut HTTP 404 s'il ne le peut pas)

Pour ce faire, la majorité des systèmes de serveurs web mettent en place un système de routage d'URLs où le développeur configure quelles URLs seront acceptées par le serveur et ce qu'elles fournissent comme ressource.

----

### Exemple de routage

Si du côté du développeur, vous configurez votre site ainsi :

- Le développeur configure le routage d'URL pour accepter l'URL `/about`;
- Le développeur indique que l'URL `/about` pointera vers une fonction qui renvoie le contenu à afficher au navigateur;

Ce qui se produira lorsqu'une requête HTTP sera envoyée par votre navigateur web vers votre serveur web sera le suivant :

- Vous demandez une ressource via votre navigateur, `/about`
- Le serveur vérifie dans ses configurations d'URL si l'URL demandée correspond à une route configurée
- Si **oui**, la fonction associée à l'URL est exécutée et une réponse peut être renvoyée au navigateur
- Si **non**, le serveur renverra si possible un contenu de page avec un code HTTP 404 (ressource non trouvée)

----

## Exemple de routage d'URL dans un projet

Lorsque nous avons un nouveau projet Django dans lequel travailler, nous pouvons déclarer des URLs et rédiger du code afin que lesdites URLs nous affichent un document (HTML ou non).

La méthode imposée par le framework consiste à définir une variable possédant un nom spécifique, dans un module de votre projet qui par défaut est `urls.py` :

- La variable qui recense les URLs principales de votre projet est nommée `urlpatterns`, et est une liste de valeurs configurées via des appels d'une fonction `path`;
- Le nom du module contenant les URLs principales est configurable via la variable `settings.ROOT_URLCONF`{.python}. 

----

L'exemple de code suivant est un exemple général de routes principales qu'il est possible de configurer dans Django.

```python {.numberLines}
from django.urls import path, include

urlpatterns = [
    # Les URLs de l'application `application` seront accessibles dans l'URL appli/
    path("appli/", include("application.urls")),
    # L'URL par défaut du site reçoit un nom user-friendly (voir Cool URLs)
    path("", view_function, name="<url-friendly-name>"),
    # Une URL peut indiquer des parties dynamiques acceptées par le routage 
    path("url/<int:dynamique>", view_dynamic, name="<url-friendly-name-2>"),  # URL dynamique
]
```

- [Documentation officielle](https://docs.djangoproject.com/en/dev/ref/urls/#path) de la fonction `django.urls.path`
- [Types d'emplacements dynamiques](https://docs.djangoproject.com/en/5.0/topics/http/urls/#path-converters)

----

### La fonction `django.urls.path`

Lorsque vous souhaitez définir des routes dans votre projet (ou votre application Django), vous ajoutez des résultats d'appels de la fonction `path` comme éléments de la liste contenue dans la variable `urlpatterns`.

La fonction `path` possède la signature suivante :

- `def path(route: str, view_function: Callable, kwargs: dict, name: str)`{.python}
- `route` : URL à configurer. Ne contient pas le `/` de début;
- `view_function` : référence de fonction à associer à l'URL lorsqu'on y accède;
- `name` : donner un nom à l'URL pour la référencer même si l'URL change de forme.

----

### Écrire une fonction de vue

Les fonctions de vue que vous associez à vos routes sont de simples fonctions, qui sont toujours appelées par le système de routage de Django de la même manière :

```python {.numberLines}
from django.http import HttpResponse, HttpRequest

def view_function(request: HttpRequest) -> HttpResponse:
    return HttpResponse("Page content")
```

La fonction **doit** au moins posséder un premier argument positionnel nommé `request`, qui contiendra les données de requête HTTP, et retourner un objet de la classe `HttpResponse`, qui contiendra les informations de réponse HTTP (en-têtes, etc.).

----

### Décrire une simple URL statique

Pour définir une URL simple à laquelle on renvoie un contenu (page HTML ou autre document), il nous faut 2 choses, à savoir ajouter une route, et ajouter la fonction à associer à cette route :

```python {.numberLines}
from django.urls import path

urlpatterns = [
    path("chemin", reference_fonction, name="nom-de-reference")
]
``` 

L'argument `name` de la fonction `path` permet de référencer l'URL a posteriori non pas par son chemin absolu, mais par un nom. Cet argument est recommandé, car coder en dur des URLs partout dans notre code va causer des soucis de maintenabilité dès que vous souhaiterez modifier l'URL.

```{.python}
from django.http import HttpResponse

def reference_fonction(request):
    return HttpResponse("Document content")    
```

----

### Inclure les URLs fournies par une application

Django propose des outils de routage avancés, et parmi ces outils, la réutilisation d'URLs définies dans une application Django. 

Supposons qu'une application Django propose son propre fichier `urls.py`
avec ses propres routes. Ces routes peuvent être intégrées à votre projet en les ajoutant comme sous-URLs d'une route définie dans votre projet.

Si l'application se dédie à la gestion d'utilisateurs et définit trois sous-routes aux URLs :

- `view/<int:idx>`
- `edit/<uuid:uuid>`
- `report/<int:idx>`

Vous pouvez les intégrer à une URL de votre projet, ex. `users/`, et y accéder via les URLs suivantes :

- `users/view/<int:idx>`
- `users/edit/<uuid:uuid>`
- `users/report/<int:idx>`

----

Pour définir ce type d'URL, il vous faut :

- Une application Django (à ajouter à `settings.INSTALLED_APPS`)
- Un module `urls` dans l'application avec une configuration d'URL
- Puis, dans le module `urls` de votre projet :

```python {.numberLines}
from django.urls import path, include

urlpatterns = [
    path("application/", include("application.urls", namespace="nom"))
]
```

[Documentation officielle de la fonction `include`](https://docs.djangoproject.com/en/4.1/ref/urls/#include)

----

### Espace de nom d'inclusion d'URLs

Lorsque vous utilisez la fonction `include`, vous pouvez donner un nom de "groupe" commun aux URLs incluses. 
Pour cela, vous devez passer un argument `namespace` qui contiendra le nom de groupe des URLs. Vous devrez utiliser ce nom de groupe lorsque vous souhaiterez référencer ces URLs par la suite.

**Note** : Lorsque vous précisez un `namespace` dans la fonction `include`, Django refusera votre définition, à moins que vous précisiez quel est le nom de l'application incluse. La façon la plus simple de faire est d'ajouter, dans le fichier `urls.py` de votre application, une variable nommée `app_name` contenant le nom de l'application.

--- 

### Espace de noms d'URLs

Exemple de code pour les espaces de noms :

```python {.numberLines}
from django.urls import path, include
urlpatterns = [
    path("dossier/", include("application.urls", namespace="application"))
]
```

```python {.numberLines}
# Module `application.urls`
app_name = "application"  # À définir pour l'espace de noms
urlpatterns = ["…"]
```

[Documentation de include](https://docs.djangoproject.com/fr/3.2/ref/urls/#include)

----

### Éléments dynamiques d'une URL

On peut, et il vaut mieux, pouvoir définir des URLs avec des portions dynamiques, ex. `users/view/<identifiant>`. 
Pour faire cela, la fonction `path` nous offre une méthode simple et efficace pour définir lesdits emplacements; 
il suffit de marquer la portion dynamique entre chevrons et suivre le format suivant :

```python {.numberLines}
from django.urls import path
from application.views import view_reference

urlpatterns = [
    path("view/<type:nom>/", view_reference, name="view-name")
]
```

----

Dans l'exemple précédent, la mention `type` doit être remplacée par l'un des types reconnus par le moteur de routage de Django ([Formats reconnus pour les parties dynamiques d'URL](https://docs.djangoproject.com/fr/3.2/topics/http/urls/#path-converters))

- `str` : toute chaîne de caractères non vide, sans le slash
- `int` : toute chaîne représentant un entier positif
- `slug` : uniquement des lettres latines non accentuées, chiffres, `-`, `_` et aucun espace
- `uuid` : nombre sur 128 bits représenté en hexadécimal, groupé par 8-4-4-4-12 chiffres séparés par un tiret.
- `path` : toute chaîne de caractères non vide, slash inclus

----

#### Fonction de vue et éléments dynamiques

La fonction de vue ciblée par une définition d'URL contenant une ou plusieurs portions dynamiques devra accepter un argument portant le nom défini dans la définition d'URL. Si votre définition de route est ainsi :

```python {.numberLines}
from django.urls import path
from application.views import view_reference

urlpatterns = [
    path("view/<int:value>/", view_reference, name="view-name")
]
```

Alors votre définition de fonction de vue doit suivre la signature suivante :

```python {.numberLines}
# Module application.views
from django.http import HttpResponse, HttpRequest

def view_reference(request: HttpRequest, value: int = None):
    """
    Fonction de vue.
    
    L'argument `value` est converti automatiquement depuis
    une chaîne (dans l'URL) vers le type à reconnaître.
    """
    return HttpResponse("Texte de la page.")
```

----

## Écriture de vues

Une fois les routes définies, il faut les lier à des vues. Les vues sont de simples fonctions qui prennent au moins un argument positionnel nommé `request`. L'objet `request` contient des données relatives à la requête HTTP entrante, et la vue doit toujours renvoyer un objet de type `HttpResponse`. Il s'agit d'un contenu de réponse HTTP classique renvoyé au navigateur.

```python
def view_example(request):
    return HttpResponse("Texte à renvoyer.")
```

----

Lorsqu'une vue est appelée via une URL qui accepte des portions dynamiques, la signature de la vue doit accepter des arguments du même nom que les portions dynamiques de l'URL :

```python
from django.urls import path
from somewhere import view_parametrized

urlpatterns = [
    path("view/<uuid:uuid_val>/", view_parametrized, name="view-name")
]
```

```python
def view_parametrized(request, uuid_val=None):
    # Récupérer l'objet possédant l'UUID ou renvoyer un 
    return HttpResponse("Page relative à l'UUID demandé.")
```

----

### Types de réponses disponibles

Django offre plusieurs classes de réponses pour les vues. Voici un tableau de différentes classes pour renvoyer une page 404, 403, ou une redirection avec Django :

| Type de renvoi          | Description                           |
|-------------------------|---------------------------------------|
| `HttpResponse`          | Contenu basique renvoyé au navigateur |
| `HttpResponseNotFound`  | Page 404                              |
| `HttpResponseForbidden` | Page 403                              |
| `HttpResponseRedirect`  | Redirection vers une autre URL        |

Il existe toutefois de meilleurs outils pour générer des pages 404, 403 et redirections.

----

### Renvoyer des redirections

Django offre une fonction de raccourci pour renvoyer des redirections.

```python {.numberLines}
from django.shortcuts import redirect

def view_example(request):
    """Redirection vers une autre vue."""
    return redirect("url-name", permanent=True)
```

La fonction `redirect`{.py} prend en argument le nom de la route vers laquelle rediriger, et accepte facultativement un argument `permanent` qui indique si la redirection est permanente ou non.

----

### HttpResponse, c'est bien mais léger

Devoir renvoyer manuellement une instance de `HttpResponse` dans chaque vue peut sembler fastidieux, notamment si l'on souhaite générer des pages complexes et interactives. Naturellement, Django propose des fonctions utilitaires pour renvoyer des contenus qui ne sont pas directement présents dans du code Python, mais dans des fichiers séparés (on parle de [templates]{.naming}).

Pas de panique, il existe des façons de renvoyer du contenu dans les vues sans créer d'objet `HttpResponse` de cette façon. On peut par exemple renvoyer le contenu d'un fichier de _template_ via des fonctions simples. (voir le chapitre sur les gabarits)

----

## Vues génériques basées sur les classes

Django 1.3 a introduit un système de vues qui sont basées sur un système de classes. 
L'objectif était de rendre élégante l'écriture de vues en n'écrivant que des classes où il suffirait de préciser quelques attributs,
et ainsi créer facilement des pages intelligentes.

Dans la pratique, ça n'est pas un si beau gain de temps que prévu :

- Une vue de type fonction est souvent simple à comprendre, et s'écrit simplement.
- Il existe de nombreuses classes à connaître pour écrire des vues diverses.
- Ces classes-là possèdent également des méthodes qu'il faut connaître pour aller plus loin.
- La documentation sur le sujet est dense mais incomplète (notamment sur le système complexe de mixins)

----

### Utilité des vues basées sur les classes

Même si le système est trop complexe pour démarrer (il faut se documenter tout le temps), certaines classes de vues
sont utiles car assez concises :

- `TemplateView` : permet de rendre rapidement un template (configurable)
- `ListView` : permet d'afficher une liste d'objets
- `DetailView` : permet d'afficher la page de détail d'un objet

----

### Utiliser une classe de vue

Si l'on définit une classe de vue pour afficher le contenu d'un template :

```python
from django.views.generic import TemplateView

class MyView(TemplateView):
    template_name = "nom du template"
```

```python
from django.urls import path
urlpatterns = [
    path("view/", MyView.as_view(), name="view-name")
]
```

----

### Pages spéciales : 404

Imaginez que vous avez défini une URL dynamique pour afficher une page produit. L'URL existe sous la forme `view/<uuid:reference>`. Supposons qu'un visiteur arrive à une URL correspondant au format défini, mais que vous ne trouvez dans votre base de données aucun produit correspondant à la référence passée dans l'URL.

Vous souhaiterez renvoyer une page 404 classique sur votre site web. Pour ne pas vous embêter, Django propose un mécanisme simple pour rendre automatiquement une page 404 (personnalisable); il suffit de lever manuellement une exception de type `Http404`.

```python
from django.http import Http404
def my_view(request, user_id):
    user = find_user(user_id)
    if user is None:
        raise Http404()
```

----

## Le Concept de Middleware dans Django

Un [middleware]{.naming} dans Django est une couche d'[intergiciel]{.naming} qui permet d'intercepter et de traiter les requêtes et réponses HTTP. Il s'agit d'une série de composants qui s'exécutent pendant le traitement des requêtes, offrant un moyen de modifier l'entrée ou la sortie globale de l'application.

----

### Qu'est-ce qu'un Middleware ?

Un middleware est une classe qui définit des méthodes pour intervenir à différentes étapes du cycle de traitement des requêtes et des réponses. Les méthodes les plus couramment utilisées sont :

| Méthode                        | Description                                                                                                           |
|--------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| `__init__(self, get_response)` | Initialisation du middleware.                                                                                         |
| `__call__(self, request)`      | Traitement de la requête avant qu'elle n'atteigne la vue, et de la réponse avant qu'elle ne soit renvoyée au client. |

----

### Comment Fonctionne le Middleware ?

Lorsqu'une requête est reçue, Django la passe à travers chaque middleware dans l'ordre défini dans le paramètre `MIDDLEWARE` de votre fichier `settings.py`. Chaque middleware peut :

- Modifier la requête avant qu'elle n'atteigne la vue.
- Modifier la réponse avant qu'elle ne soit renvoyée au client.
- Gérer les exceptions.
- Traiter les réponses de templates.

----

### Exemple de Middleware Intégré

Django fournit plusieurs middlewares intégrés, tels que :


| Middleware                   | Description                                                                              |
|------------------------------|------------------------------------------------------------------------------------------|
| `AuthenticationMiddleware`    | Gère l'authentification des utilisateurs (ajoute un attribut `user` à la requête, etc.). |
| `SessionMiddleware`           | Gère les sessions côté serveur.                                                          |
| `CommonMiddleware`            | Ajoute des améliorations utiles, comme la gestion des en-têtes HTTP.                     |

----

### Créer un Middleware Personnalisé

Voici comment, par exemple, créer un middleware personnalisé qui enregistre le temps pris par une vue pour s'exécuter.

```python {.numberLines}
# myapp/middleware.py

import time

class TimingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # Initialisation du middleware

    def __call__(self, request):
        # Code exécuté pour chaque requête avant la vue
        start_time = time.time()

        response = self.get_response(request)

        # Code exécuté pour chaque réponse après la vue
        duration = time.time() - start_time
        print(f"La vue a pris {duration:.2f} secondes.")

        return response
```

----

### Enregistrer le Middleware

Pour que Django utilise votre middleware, vous devez l'ajouter à la liste `MIDDLEWARE` dans `settings.py`.

```python {.numberLines}
# settings.py
MIDDLEWARE = [
    # ... autres middlewares ...
    'myapp.middleware.TimingMiddleware',
]
```

----

### Explication du Fonctionnement


| Méthode                        | Description                                                                                                                                   |
|--------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| `__init__(self, get_response)` | Django passe la fonction `get_response` au middleware. Cette fonction est appelée pour obtenir la réponse de la vue ou du middleware suivant. |
| `__call__(self, request)`      | Cette méthode est appelée pour chaque requête. Vous pouvez y ajouter du code qui s'exécute avant et après l'appel de `get_response(request)`. |

----

### Middleware avec Méthodes Additionnelles

Vous pouvez également implémenter des méthodes spécifiques pour intervenir à différentes étapes spécifiques.

```python {.numberLines}
class MyCustomMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def process_view(self, request, view_func, view_args, view_kwargs):
        # Code exécuté juste avant l'appel de la vue
        pass

    def process_exception(self, request, exception):
        # Code exécuté si une exception est levée
        pass

    def process_template_response(self, request, response):
        # Code exécuté si la réponse est un TemplateResponse
        return response
```

----

### Cycle de Traitement des Requêtes

1. **Requête entrante**: Le middleware peut modifier ou rejeter la requête.
2. **Processus de la vue**: La requête est traitée par la vue.
3. **Réponse sortante**: Le middleware peut modifier la réponse avant qu'elle ne soit renvoyée.
4. **Réponse au client**: La réponse finale est envoyée au client.

----

### Cas d'Utilisation Courants

- **Authentification et Autorisation**: Contrôle d'accès aux vues.
- **Journalisation**: Enregistrement des requêtes et réponses pour le débogage.
- **Gestion des Exceptions**: Capture et traitement des erreurs.
- **Compression**: Compression des réponses pour réduire la taille des données transférées.

----

### Exemples Pratiques

Middleware pour Bloquer des IP

```python {.numberLines}
# myapp/middleware.py

class BlockIPMiddleware:
    BLOCKED_IPS = ['192.168.1.1']

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        ip = request.META.get('REMOTE_ADDR')
        if ip in self.BLOCKED_IPS:
            return HttpResponseForbidden("Votre IP a été bloquée.")
        response = self.get_response(request)
        return response
```

----

Middleware pour Ajouter des En-têtes HTTP

```python {.numberLines}
# myapp/middleware.py

class CustomHeaderMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        response['X-Custom-Header'] = 'MaValeurPersonnalisee'
        return response
```

----

![Exemple de flux de Middleware](assets/images/views-middleware-flow.png)

----

## Divers : Débogage et accès

Lorsque dans vos paramètres, `settings.DEBUG = False` (ce qui est la seule valeur acceptable en production) Django n'autorise plus l'accès à vos vues par défaut.

Pour accéder à vos vues, vous devez définir explicitement une liste d'adresses IP qui ont accès à votre site dans `settings.ALLOWED_HOSTS`. Le paramètre accepte des adresses IPv4, IPv6 et noms d'hôte. La valeur `"*"` signifie "toutes adresses".

```python {.numberLines}
# settings.py
ALLOWED_HOSTS = ("127.0.0.1", "machinea", "192.168.1.15")
```
