Champs intégrés et paramètres courants
Types de champs fréquemment utilisés
models.AutoField(primary_key=True)
// Clé primaire entière auto-incrémentée
models.IntegerField()
// Entier signé (-2147483648 à 2147483647)
models.CharField(max_length=255)
// Chaîne de caractères avec longueur maximale obligatoire
models.DateTimeField(auto_now_add=True)
// Stocke l’heure de création (une seule fois)
models.DateTimeField(auto_now=True)
// Met à jour automatiquement à chaque sauvegarde
models.DateField()
// Format YYYY-MM-DD
models.TimeField()
// Format HH:MM:SS[.uuuuuu]
models.BooleanField()
// En base : 0 ou 1 ; en Python : True/False
models.TextField()
// Pour des contenus longs (articles, descriptions, etc.)
models.FileField(upload_to='uploads/files/')
// Stocke le chemin du fichier ; le fichier est physiquement déposé dans le dossier spécifié
models.ImageField(upload_to='uploads/images/', height_field='height', width_field='width')
// Sous-classe de FileField avec validation d’image et extraction automatique des dimensions
models.DecimalField(max_digits=10, decimal_places=2)
// Nombre décimal précis (ex. : montants financiers)
models.EmailField()
// Valide le format email ; stocké comme VARCHAR en base
Correspondance ORM ↔ MySQL
| ORM | MySQL |
|---|---|
AutoField |
INT AUTO_INCREMENT PRIMARY KEY |
CharField(max_length=100) |
VARCHAR(100) |
IntegerField |
INT |
DecimalField(max_digits=8, decimal_places=2) |
DECIMAL(8,2) |
DateField |
DATE |
DateTimeField |
DATETIME |
BooleanField |
TINYINT(1) |
TextField |
TEXT |
FileField |
VARCHAR (stocke un chemin) |
Paramètres universels des champs
null=True // Autorise NULL en base (à distinguer de blank=True pour les formulaires)
blank=True // Autorise la valeur vide dans les formulaires
default=0 // Valeur par défaut lors de la création (peut être une fonction, ex: timezone.now)
unique=True // Contrainte d’unicité au niveau base
db_index=True // Crée un index B-tree sur ce champ
primary_key=True // Définit la clé primaire (remplace AutoField si utilisé avec IntegerField)
verbose_name="Nom affiché" // Libellé lisible dans l’admin et les formulaires
help_text="Indication supplémentaire" // Texte d’aide dans les formulaires
db_column="col_name" // Nom personnalisé de la colonne en base
Extensions avancées des modèles
Création de champs personnalisés
Pour imposer un type SQL spécifique non fourni nativement (ex. : CHAR(N) fixe), on étend models.Field :
from django.db import models
class FixedLengthCharField(models.Field):
def __init__(self, length, *args, **kwargs):
self.length = length
super().__init__(*args, **kwargs)
def db_type(self, connection):
return f'char({self.length})'
class Product(models.Model):
sku = models.CharField(max_length=12) # standard
code = FixedLengthCharField(length=8) # personnalisé → CHAR(8)
Gestion des valeurs énumérées avec choices
Plutôt que de stocker des chaînes répétitives (ex. : "Homme", "Femme"), on utilise des identifiants compacts :
class UserProfile(models.Model):
GENDER_CHOICES = [
(0, 'Non renseigné'),
(1, 'Masculin'),
(2, 'Féminin'),
(3, 'Autre'),
]
gender = models.SmallIntegerField(
choices=GENDER_CHOICES,
default=0
)
# Usage dans le code :
# instance.gender → retourne 1 (valeur brute)
# instance.get_gender_display() → retourne 'Masculin'
Les choix peuvent aussi utiliser des chaînes comme clés :
STATUS_CHOICES = [
('draft', 'Brouillon'),
('published', 'Publié'),
('archived', 'Archivé'),
]
status = models.CharField(max_length=12, choices=STATUS_CHOICES)
Modélisation des relations many-to-many
Approche 1 : Relation implicite (ManyToManyField)
class Article(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField('Tag')
class Tag(models.Model):
name = models.CharField(max_length=50)
✅ Avantages : API riche (add(), remove(), set(), clear()), accès bidirectionnel.
❌ Inconvénient : impossible d’ajouter des champs métiers (ex. : date de liaison, rôle, poids) à la table intermédiaire.
Approche 2 : Table intermédiaire explicite
class Article(models.Model):
title = models.CharField(max_length=200)
class Tag(models.Model):
name = models.CharField(max_length=50)
class ArticleTag(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
weight = models.PositiveSmallIntegerField(default=1)
created_at = models.DateTimeField(auto_now_add=True)
✅ Avantages : totale flexibilité (champs supplémentaires, contraintes, index).
❌ Inconvénient : aucune méthode ORM native — toutes les opérations se font via ArticleTag.objects.create(...).
Approche 3 : Hybridation (through)
class Article(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(
'Tag',
through='ArticleTag',
through_fields=('article', 'tag')
)
class Tag(models.Model):
name = models.CharField(max_length=50)
class ArticleTag(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
weight = models.PositiveSmallIntegerField(default=1)
✅ Avantages : accès bidirectionnel (article.tags.all(), tag.article_set.all()) + données enrichies.
❌ Inconvénient : les méthodes add()/remove() ne sont pas disponibles — il faut créer manuellement les instances ArticleTag.
Optimisation des écritures massives
Insertion séquentielle (inefficace)
# À éviter pour >100 objets
for i in range(10000):
Book.objects.create(title=f'Livre {i}')
# → 10 000 requêtes INSERT distinctes
Insertion groupée avec bulk_create
books = [Book(title=f'Livre {i}') for i in range(10000)]
Book.objects.bulk_create(books, batch_size=1000)
# → 10 requêtes INSERT (10 × 1000 lignes), bien plus rapide
Remarques :
- batch_size limite la taille de chaque insrtuction SQL (évite les timeouts ou dépassements mémoire).
- Les objets retournés n’ont pas encore d’id si la base ne supporte pas RETURNING (ex. : MySQL avant 8.0.21).
- Aucun signal post_save n’est déclenché — à gérer manuellement si nécessaire.
Performance des requêtes paginées
Une pagination naïve avec LIMIT offset, count devient coûteuse sur de grands jeux :
-- Première page : rapide (lit ~50 lignes)
SELECT * FROM book LIMIT 0, 50;
-- Dernière page : lent (lit 100 000 lignes, en ignore 99 950)
SELECT * FROM book LIMIT 99950, 50;
Solutions recommandées :
• Pagination basée sur la dernière valeur lue (« keyset pagination ») : WHERE id > 99950 ORDER BY id LIMIT 50.
• Utilisation de select_related() / prefetch_related() pour réduire les requêtes N+1.
• Index appropriés sur les champs utilisés dans ORDER BY et WHERE.