Remarques importantes sur l'ORM
- Le champ
id(AutoField) est ajouté automatiquement ; pour un nom personnalisé, déclarez-le explicitement. - Pour une clé étrangère, Django ajoute suffixe
_iddans la base de données. null=Truesur une ForeignKey permet de stockerNULL.
Paramètres de ForeignKey
to: modèle lié ;'self'pour auto-référence (relations hiérarchiques).to_field: champ cible dans le modèle lié.related_name: nom pour l'accès inverse depuis le modèle lié.related_query_name: préfixe de requête inverse (remplace le nom de table dans les filtres).db_constraint: siFalse, supprime la contrainte d'intégrité référentielle en base (la cohérence est à la charge de l'application).
Exemple d'utilisation de related_name et related_query_name
class Auteur(models.Model):
nom = models.CharField(max_length=100)
class Livre(models.Model):
titre = models.CharField(max_length=100)
auteur = models.ForeignKey(Auteur, on_delete=models.CASCADE, related_name='livres')
# Accès inverse via related_name
auteur = Auteur.objects.get(id=1)
livres = auteur.livres.all() # équivaut à Livre.objects.filter(auteur=auteur)
# Filtre avec related_query_name
auteurs = Auteur.objects.filter(livre__titre='Django débutant') # 'livre' est le related_query_name par défaut
Relations multi-tables
Un-à-un (OneToOneField)
Crée une clé étrangère avec unique=True.
class ProfilAuteur(models.Model):
adresse = models.CharField(max_length=100)
telephone = models.CharField(max_length=20)
auteur = models.OneToOneField('Auteur', on_delete=models.CASCADE)
Un-à-plusieurs (ForeignKey)
La clé étrangère est placée du côté « plusieurs ».
class MaisonEdition(models.Model):
nom = models.CharField(max_length=100)
class Ouvrage(models.Model):
titre = models.CharField(max_length=100)
editeur = models.ForeignKey(MaisonEdition, on_delete=models.CASCADE)
Plusieurs-à-plusieurs (ManyToManyField)
Django crée automatiquement une table de liaison.
class Etudiant(models.Model):
nom = models.CharField(max_length=100)
cours = models.ManyToManyField('Cours')
Opérations d'ajout et de modification
Ajout en relation un-à-plusieurs
# Méthode 1 : affectation directe de id
Livre.objects.create(titre='Python avancé', editeur_id=2)
# Méthode 2 : affectation d'objet
editeur = MaisonEdition.objects.get(nom='O\'Reilly')
Livre.objects.create(titre='Flask pour débutants', editeur=editeur)
Ajout en relation plusieurs-à-plusieurs
etudiant = Etudiant.objects.get(nom='Alice')
c1 = Cours.objects.get(titre='Maths')
c2 = Cours.objects.get(titre='Physique')
etudiant.cours.add(c1, c2) # avec objets
etudiant.cours.add(3, 4) # avec ids
etudiant.cours.set([5, 6]) # remplace toutes les relations
etudiant.cours.remove(c1) # supprime une relation
etudiant.cours.clear() # vide toutes les relations
Requêtes inter-tables (objets et champs)
Requête directe (du côté où se trouve la clé étrangère)
# Objet
livre = Livre.objects.get(titre='Python')
print(livre.editeur.nom)
# Champ (double underscore)
livres = Livre.objects.filter(editeur__nom='O\'Reilly')
Requête inverse (depuis le modèle non porteur de la clé)
# Objet : nom_manager_set (ou related_name)
editeur = MaisonEdition.objects.get(nom='O\'Reilly')
livres = editeur.livre_set.all() # 'livre' est le nom du modèle en minuscule
# Champ : nom_modele__champ
editeurs = MaisonEdition.objects.filter(livre__titre='Python')
Cas particulier un-à-un : pas de _set, on utilise directement le nom du modèle (p. ex. profil.auteur.nom).
Requêtes chaînées sur plusieurs tables
# Obtenir les titres des livres et les noms des éditeurs
# dont l'auteur habite une ville commençant par 'Lyon'
resultats = Livre.objects.filter(
auteur__profil__ville__startswith='Lyon'
).values('titre', 'editeur__nom')
Modèle intermédiaire (through)
Lorsque la table de liaison doit contenir des champs supplémentaires, on définit un modèle explicite et on l'indique avec through.
class Membre(models.Model):
nom = models.CharField(max_length=100)
class Club(models.Model):
nom = models.CharField(max_length=100)
membres = models.ManyToManyField(Membre, through='Adhesion')
class Adhesion(models.Model):
membre = models.ForeignKey(Membre, on_delete=models.CASCADE)
club = models.ForeignKey(Club, on_delete=models.CASCADE)
date_inscription = models.DateField()
motivation = models.CharField(max_length=200)
Avec un modèle intermédiaire, les méthodes add(), create(), set() sont interdites. On doit créer directement des instances du modèle intermédiaire.
# Création
Adhesion.objects.create(membre=m1, club=c1, date_inscription=date.today(), motivation='Passion')
# Nettoyage
club.membres.clear() # supprime toutes les Adhesion liées
Opérations en masse (bulk_create / bulk_update)
# Insertion en masse
nouvelles_adhesions = [
Adhesion(membre=m, club=c, date_inscription=date.today(), motivation='Découverte')
for m in membres
]
Adhesion.objects.bulk_create(nouvelles_adhesions)
# Mise à jour en masse : supprimer puis insérer
with transaction.atomic():
Adhesion.objects.filter(club=c).delete()
Adhesion.objects.bulk_create(nouvelles_adhesions)
Génération inverse de modèles (inspectdb)
Django peut générer des classes de modèles à partir d'une base de données existante.
python manage.py inspectdb > models.py
La commande inspectdb produit un fichier models.py contenant les définitions crorespondant aux tables de la base. On peut ensuite l'importer dans une application Django.