logo oujood
🔍

Les relations entre modèles Django

La plupart des applications web gèrent des données liées entre elles : un article appartient à un auteur, un produit a plusieurs catégories, un utilisateur a un profil. Django modélise ces liens avec trois types de relations. Ce chapitre les explique avec des exemples concrets.

OUJOOD.COM

Pourquoi les relations entre modèles

Une base de données relationnelle est conçue pour éviter la duplication : plutôt que de répéter les informations d'un auteur dans chaque article qu'il écrit, on stocke l'auteur une seule fois et on établit un lien entre les deux tables. Django modélise ces liens directement dans les classes Python avec trois types de champs de relation.

ForeignKey — relation plusieurs-à-un

C'est la relation la plus courante. Un article appartient à un auteur, mais un auteur peut écrire plusieurs articles. La clé étrangère se place du côté "plusieurs" — ici, dans le modèle Article :

  📋 Copier le code

from django.db import models

class Auteur(models.Model):
    nom   = models.CharField(max_length=255)
    email = models.EmailField()

    def __str__(self):
        return self.nom

class Article(models.Model):
    titre   = models.CharField(max_length=255)
    contenu = models.TextField()
    # Un article appartient à un auteur
    # Si l'auteur est supprimé, ses articles le sont aussi (CASCADE)
    auteur  = models.ForeignKey(Auteur, on_delete=models.CASCADE)

    def __str__(self):
        return self.titre

Le paramètre on_delete est obligatoire. Il définit ce qui se passe quand l'objet référencé est supprimé :

CASCADE — supprime aussi les objets liés. L'option la plus courante.

SET_NULL — met la clé étrangère à NULL (nécessite null=True).

PROTECT — empêche la suppression si des objets liés existent.

SET_DEFAULT — assigne la valeur par défaut définie sur le champ.

Interroger une ForeignKey

  📋 Copier le code

# Accéder à l'auteur d'un article
article = Article.objects.get(id=1)
print(article.auteur.nom)

# Tous les articles d'un auteur (relation inverse)
auteur = Auteur.objects.get(id=1)
articles = auteur.article_set.all()

# Filtrer les articles par attribut de l'auteur (double underscore)
Article.objects.filter(auteur__nom='Dubois')

Django crée automatiquement une relation inverse : depuis un Auteur, vous accédez à ses articles via auteur.article_set.all(). Le nom article_set est généré automatiquement — vous pouvez le changer avec related_name :

  📋 Copier le code

# related_name personnalise le nom de la relation inverse
auteur = models.ForeignKey(Auteur, on_delete=models.CASCADE, related_name='articles')

# Maintenant on peut écrire :
auteur.articles.all()  # Plus lisible que auteur.article_set.all()

ManyToManyField — relation plusieurs-à-plusieurs

Un article peut avoir plusieurs tags, et un tag peut apparaître dans plusieurs articles. Django gère cette relation avec ManyToManyField et crée automatiquement une table intermédiaire en base de données.

  📋 Copier le code

class Tag(models.Model):
    nom = models.CharField(max_length=100)

    def __str__(self):
        return self.nom

class Article(models.Model):
    titre  = models.CharField(max_length=255)
    auteur = models.ForeignKey(Auteur, on_delete=models.CASCADE)
    # Un article peut avoir plusieurs tags, un tag plusieurs articles
    tags   = models.ManyToManyField(Tag, blank=True)

Interroger une ManyToMany

  📋 Copier le code

# Ajouter des tags à un article
article = Article.objects.get(id=1)
tag_python = Tag.objects.get(nom='Python')
article.tags.add(tag_python)

# Tous les tags d'un article
article.tags.all()

# Tous les articles ayant le tag 'Python'
Article.objects.filter(tags__nom='Python')

# Retirer un tag
article.tags.remove(tag_python)

OneToOneField — relation un-à-un

Chaque utilisateur a exactement un profil, et chaque profil appartient à exactement un utilisateur. OneToOneField est une ForeignKey avec une contrainte d'unicité supplémentaire.

  📋 Copier le code

from django.contrib.auth.models import User

class Profil(models.Model):
    # Chaque profil est lié à exactement un utilisateur Django
    utilisateur = models.OneToOneField(User, on_delete=models.CASCADE)
    bio         = models.TextField(blank=True)
    avatar      = models.ImageField(upload_to='avatars/', blank=True)

    def __str__(self):
        return f"Profil de {self.utilisateur.username}"

  📋 Copier le code

# Accéder au profil depuis l'utilisateur
user = User.objects.get(username='admin')
print(user.profil.bio)

# Accéder à l'utilisateur depuis le profil
profil = Profil.objects.get(id=1)
print(profil.utilisateur.email)

Optimiser les requêtes avec select_related

Par défaut, Django effectue une requête SQL séparée chaque fois que vous accédez à un objet lié. Sur une liste de 100 articles, accéder à article.auteur.nom génère 101 requêtes. select_related() charge les relations en une seule requête SQL avec un JOIN :

  📋 Copier le code

# Sans select_related : 1 + N requêtes SQL
articles = Article.objects.all()
for a in articles:
    print(a.auteur.nom)  # 1 requête par auteur !

# Avec select_related : 1 seule requête SQL (JOIN)
articles = Article.objects.select_related('auteur').all()
for a in articles:
    print(a.auteur.nom)  # Données déjà chargées

Pour les relations ManyToMany, utilisez prefetch_related() à la place — il effectue deux requêtes optimisées plutôt qu'un JOIN qui peut dupliquer les lignes.

Par carabde | Mis à jour le 05 mai 2025