Exploitez la puissance du Django ORM : relations, N+1 queries, select_related(), prefetch_related(), indexation et bonnes pratiques de performance.
QuerySet : les bases
Les QuerySets sont les couches d'abstraction de Django ORM. Elles sont paresseuses (lazy) : elles n'exécutent la requête que lorsque nécessaire :
# Pas d'exécution - QuerySet seulement
users = User.objects.filter(age__gte=18)
# Exécution - itération sur les résultats
for user in users:
print(user.name)
# Ou conversion en liste
users_list = list(users)
Les filtres QuerySet les plus courants :
| Filtre | Syntaxe | SQL équivalent |
|---|---|---|
| Égal | filter(age=25) | WHERE age = 25 |
| Supérieur | filter(age__gte=18) | WHERE age >= 18 |
| Contient | filter(name__icontains='john') | LIKE |
| In | filter(status__in=['active', 'pending']) | IN |
| Null | filter(deleted_at__isnull=True) | IS NULL |
Relations entre modèles
Django ORM supporte les relations One-to-Many, Many-to-Many et One-to-One :
# models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
publication_date = models.DateField()
tags = models.ManyToManyField('Tag', related_name='books')
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
Accédez aux relations inversées via le related_name :
# Accès direct
author = Author.objects.get(id=1)
books = author.books.all() # Utilise related_name
# Ou sans related_name
books = Book.objects.filter(author__id=1)
Éviter le problème N+1
Le problème N+1 génère une requête pour chaque enregistrement. Django fournit select_related() et prefetch_related() :
# ❌ MAUVAIS - 1 + N requêtes (N+1 problem)
books = Book.objects.all()
for book in books:
print(book.author.name) # Nouvelle requête pour chaque livre!
# ✅ BON - Une seule requête (JOIN)
books = Book.objects.select_related('author')
for book in books:
print(book.author.name) # Aucune nouvelle requête
# ✅ POUR les relations Many-to-Many
books = Book.objects.prefetch_related('tags')
for book in books:
for tag in book.tags.all():
print(tag.name) # Cache utilisé, pas de nouvelle requête
Chaînez les optimisations :
books = Book.objects.select_related('author') \
.prefetch_related('tags') \
.filter(publication_date__year=2024) \
.values('title', 'author__name')
Agrégation et statistiques
Utilisez agrégations pour calculer directement en base de données :
from django.db.models import Count, Sum, Avg, Max, Min
# Nombre total de livres par auteur
authors_books = Author.objects.annotate(
total_books=Count('books')
).values('name', 'total_books')
# Prix moyen des livres
avg_price = Book.objects.aggregate(avg_price=Avg('price'))
# Résultat: {'avg_price': 29.99}
# Statistiques multiples
stats = Book.objects.aggregate(
total=Count('id'),
avg_price=Avg('price'),
max_price=Max('price'),
min_price=Min('price')
)
# {'total': 150, 'avg_price': 29.99, 'max_price': 99.99, 'min_price': 9.99}
Indexation et performance
Optimisez les performances en créant des index :
class Book(models.Model):
title = models.CharField(max_length=200, db_index=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
publication_date = models.DateField(db_index=True)
is_published = models.BooleanField(default=False, db_index=True)
class Meta:
indexes = [
models.Index(fields=['publication_date', '-created_at']),
models.Index(fields=['author', 'is_published']),
]
Pour analyser les performances, utilisez django.db.connection.queries :
from django.db import connection
from django.test.utils import override_settings
@override_settings(DEBUG=True)
def check_queries():
books = Book.objects.select_related('author').all()
list(books)
print(f"Nombre de requêtes: {len(connection.queries)}")
for query in connection.queries:
print(f"Temps: {query['time']}, SQL: {query['sql'][:100]}")