Les Crash-Devs d'un Passionné

Gérer les relations Many to Many dans Django

/Catégorie/django

Temps de lecture : 4 minutes

Le framework django implémente dans son ORM un système de relation Many to Many (M2M) afin de mettre en relation un objet avec un autre de façon multiple.

Un objet A peut avoir plusieurs liens avec un objet B, et vis versa.

Sous un digramme UML on le matérialise de cette manière

uml-relation-many-to-many

Et django le comprendra en créant une table d'association (Table AB) pour faire la liaison

many_to_many_django_table

Lors de l'écriture de ce modèle, nous devons ajouter un champ ManyToManyField, nous allons voir comment le configurer.

Création des modèles

Nous allons créer un objet Article

clipboard
Copier le code
1
2
3
4
5
6
7
8
from django.db import models

class Article(models.Model):

  title = models.CharField(max_length=100)

  class Meta:
    app_label = 'blog'

avec un système de tag pour classifier l'article

clipboard
Copier le code
1
2
3
class Tag(models.Model):

    name = models.CharField(max_length=40)

Pour mettre en place la relation nous allons utiliser ManyToManyField

Créer un lien Many to Many

Dans quel modèle dois je mettre la relation M2M ?

Tout dépend de votre logique applicative, dans notre exemple il parait plus judicieux de placer cette relation dans le modèle article.

En effet le tag est un élément décrivant votre article, d'ailleurs il peut être absent.

clipboard
Copier le code
1
2
3
4
class Article(models.Model):

    title = models.CharField(max_length=100)
    tags = models.ManyToManyField(Tag)

Appliquer les changements

Ces 2 modèles ont été modifiés avec succès il reste maintenant à appliquer les modifications en base de données. Pour cela il suffit de lancer

python manage.py makemigrations 

python manage.py migrate

Django va créer un dossier migrations dans votre projet et ajouter des fichiers contenant les évolutions de votre modèle.

Créer vos objets

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
a1 = Article('Configurer M2M')
a1.save()

t1 = Tag(name='django')
a1.tags.add(t1)
# Pas de duplication si on lance une 2eme fois
a1.tags.add(t1)
# Sans le all il ne récupère pas les relations
a1.tags.all()

# On également récupérer les relation Article depuis Tag
t1.article_set.all()

Il est ainsi possible de faire un reverse comme on le voit ligne 12, c'est-à-dire récupérer les objets depuis le modèle qui ne possède pas le champ M2M.

Dans notre exemple le tag n'a pas la relation mais on peut tout de même partir de cet objet pour ensuite obtenir les informations

Tag.objects.filter(article=a1)

Et dans l'interface d'administration

Un point de vigilance sur cette partie, en effet sauvegarder un modèle via le formulaire de l'admin ne va pas exécuter une transaction de manière atomique.

Une transaction atomique est un ensemble d'opérations pouvant être exécuté entièrement sans pouvoir être interrompu. Une transaction atomique exécutera l'ensemble de nos requêtes ou aucune.

Dans le CRUD de django tant que le formulaire n'est pas terminé la transaction est en cours. Or dans une transaction avec un modèle ayant un M2M il est nécessaire de valider la transaction une première fois afin d'obtenir l'id de l'objet pour l'affecter dans la table d'association.

Ainsi pour pallier à cette problématique il faut valider la transaction avant de pourvoir lié l'objet Tag à l'objet Article

clipboard
Copier le code
1
2
3
4
5
from django.db import transaction

...
with transaction.atomic():
    a1.tags.add(*all_tags)