22 KiB
title, author
title | author |
---|---|
URLs dans Django | 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 fonctionpath
; - 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.
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 de la fonction
django.urls.path
- Types d'emplacements dynamiques
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 :
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 :
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.
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 :
from django.urls import path, include
urlpatterns = [
path("application/", include("application.urls", namespace="nom"))
]
Documentation officielle de la fonction 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 :
from django.urls import path, include
urlpatterns = [
path("dossier/", include("application.urls", namespace="application"))
]
# Module `application.urls`
app_name = "application" # À définir pour l'espace de noms
urlpatterns = ["…"]
É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 :
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)
str
: toute chaîne de caractères non vide, sans le slashint
: toute chaîne représentant un entier positifslug
: uniquement des lettres latines non accentuées, chiffres,-
,_
et aucun espaceuuid
: 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 :
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 :
# 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.
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 :
from django.urls import path
from somewhere import view_parametrized
urlpatterns = [
path("view/<uuid:uuid_val>/", view_parametrized, name="view-name")
]
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.
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'objetsDetailView
: 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 :
from django.views.generic import TemplateView
class MyView(TemplateView):
template_name = "nom du template"
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
.
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.
# 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
.
# 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.
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
- Requête entrante: Le middleware peut modifier ou rejeter la requête.
- Processus de la vue: La requête est traitée par la vue.
- Réponse sortante: Le middleware peut modifier la réponse avant qu'elle ne soit renvoyée.
- 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
# 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
# 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
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".
# settings.py
ALLOWED_HOSTS = ("127.0.0.1", "machinea", "192.168.1.15")