# Documentation Django 5.2.8

# Relations plusieurs-à-plusieurs

Pour définir une relation plusieurs-à-plusieurs, utilisez un champ `ManyToManyField`.

Dans cet exemple, un `Article` peut être publié dans plusieurs objets `Publication` et une `Publication` possède plusieurs objets `Article`:

    from django.db import models

    class Publication(models.Model):
        title = models.CharField(max_length=30)

        class Meta:
            ordering = ["title"]

        def __str__(self):
            return self.title

    class Article(models.Model):
        headline = models.CharField(max_length=100)
        publications = models.ManyToManyField(Publication)

        class Meta:
            ordering = ["headline"]

        def __str__(self):
            return self.headline

Dans ce qui suit, vous trouverez des exemples d’opérations pouvant être effectuées en utilisant les possibilités de l’API Python.

Créez quelques instances `Publication`:

    >>> p1 = Publication(title="The Python Journal")
    >>> p1.save()
    >>> p2 = Publication(title="Science News")
    >>> p2.save()
    >>> p3 = Publication(title="Science Weekly")
    >>> p3.save()

Créez un `Article`:

    >>> a1 = Article(headline="Django lets you build web apps easily")

Vous ne pouvez pas le lier à une `Publication` avant qu’il ait été enregistré :

    >>> a1.publications.add(p1)
    Traceback (most recent call last):
    ...
    ValueError: "<Article: Django lets you build web apps easily>" needs to have a value for field "id" before this many-to-many relationship can be used.

Enregistrez-le !

    >>> a1.save()

Associez l”`Article` à une `Publication`:

    >>> a1.publications.add(p1)

Créez un autre `Article` et faites-le paraître dans ses publications :

    >>> a2 = Article(headline="NASA uses Python")
    >>> a2.save()
    >>> a2.publications.add(p1, p2)
    >>> a2.publications.add(p3)

Ajouter une seconde fois ne pose pas problème, la relation ne sera pas dupliquée :

    >>> a2.publications.add(p3)

L’ajout d’un objet du mauvais type génère une exception `TypeError`:

    >>> a2.publications.add(a1)
    Traceback (most recent call last):
    ...
    TypeError: 'Publication' instance expected

Créez et ajoutez une `Publication` à un `Article` en une seule étape en utilisant `create()`:

    >>> new_publication = a2.publications.create(title="Highlights for Children")

Les objets Article ont accès à leurs objets `Publication` liés :

    >>> a1.publications.all()
    <QuerySet [<Publication: The Python Journal>]>
    >>> a2.publications.all()
    <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>

Les objets `Publication` ont accès à leurs objets `Article` liés :

    >>> p2.article_set.all()
    <QuerySet [<Article: NASA uses Python>]>
    >>> p1.article_set.all()
    <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
    >>> Publication.objects.get(id=4).article_set.all()
    <QuerySet [<Article: NASA uses Python>]>

Il est possible d’interroger les relations plusieurs-à-plusieurs en utilisant des recherches traversant les relations:

    >>> Article.objects.filter(publications__id=1)
    <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
    >>> Article.objects.filter(publications__pk=1)
    <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
    >>> Article.objects.filter(publications=1)
    <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
    >>> Article.objects.filter(publications=p1)
    <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>

    >>> Article.objects.filter(publications__title__startswith="Science")
    <QuerySet [<Article: NASA uses Python>, <Article: NASA uses Python>]>

    >>> Article.objects.filter(publications__title__startswith="Science").distinct()
    <QuerySet [<Article: NASA uses Python>]>

La fonction `count()` respecte aussi `distinct()`:

    >>> Article.objects.filter(publications__title__startswith="Science").count()
    2

    >>> Article.objects.filter(publications__title__startswith="Science").distinct().count()
    1

    >>> Article.objects.filter(publications__in=[1, 2]).distinct()
    <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
    >>> Article.objects.filter(publications__in=[p1, p2]).distinct()
    <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>

Les requêtes plusieurs-à-plusieurs inverses sont possibles (c’est-à-dire des requêtes commençant par la table qui ne possède pas le champ `ManyToManyField`) :

    >>> Publication.objects.filter(id=1)
    <QuerySet [<Publication: The Python Journal>]>
    >>> Publication.objects.filter(pk=1)
    <QuerySet [<Publication: The Python Journal>]>

    >>> Publication.objects.filter(article__headline__startswith="NASA")
    <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>

    >>> Publication.objects.filter(article__id=1)
    <QuerySet [<Publication: The Python Journal>]>
    >>> Publication.objects.filter(article__pk=1)
    <QuerySet [<Publication: The Python Journal>]>
    >>> Publication.objects.filter(article=1)
    <QuerySet [<Publication: The Python Journal>]>
    >>> Publication.objects.filter(article=a1)
    <QuerySet [<Publication: The Python Journal>]>

    >>> Publication.objects.filter(article__in=[1, 2]).distinct()
    <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>
    >>> Publication.objects.filter(article__in=[a1, a2]).distinct()
    <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>

L’exclusion d’un élément lié fonctionne aussi tel qu’on pourrait le prévoir (même si le code SQL sous-jacent est un peu complexe) :

    >>> Article.objects.exclude(publications=p2)
    <QuerySet [<Article: Django lets you build web apps easily>]>

Si nous supprimons une `Publication`, ses instances `Article` liées ne pourront plus y accéder :

    >>> p1.delete()
    >>> Publication.objects.all()
    <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>]>
    >>> a1 = Article.objects.get(pk=1)
    >>> a1.publications.all()
    <QuerySet []>

Si nous supprimons un `Article`, ses instances `Publication` liées ne pourront plus y accéder :

    >>> a2.delete()
    >>> Article.objects.all()
    <QuerySet [<Article: Django lets you build web apps easily>]>
    >>> p2.article_set.all()
    <QuerySet []>

Ajout à partir de l”« autre côté » de la relation plusieurs-à-plusieurs :

    >>> a4 = Article(headline="NASA finds intelligent life on Earth")
    >>> a4.save()
    >>> p2.article_set.add(a4)
    >>> p2.article_set.all()
    <QuerySet [<Article: NASA finds intelligent life on Earth>]>
    >>> a4.publications.all()
    <QuerySet [<Publication: Science News>]>

Ajout à partir de l’autre côté en utilisant des mots-clés :

    >>> new_article = p2.article_set.create(headline="Oxygen-free diet works wonders")
    >>> p2.article_set.all()
    <QuerySet [<Article: NASA finds intelligent life on Earth>, <Article: Oxygen-free diet works wonders>]>
    >>> a5 = p2.article_set.all()[1]
    >>> a5.publications.all()
    <QuerySet [<Publication: Science News>]>

Suppression d’une `Publication` à partir d’un `Article`:

    >>> a4.publications.remove(p2)
    >>> p2.article_set.all()
    <QuerySet [<Article: Oxygen-free diet works wonders>]>
    >>> a4.publications.all()
    <QuerySet []>

Et depuis l’autre côté :

    >>> p2.article_set.remove(a5)
    >>> p2.article_set.all()
    <QuerySet []>
    >>> a5.publications.all()
    <QuerySet []>

Les jeux de relations peuvent être définis :

    >>> a4.publications.all()
    <QuerySet [<Publication: Science News>]>
    >>> a4.publications.set([p3])
    >>> a4.publications.all()
    <QuerySet [<Publication: Science Weekly>]>

Les jeux de relations peuvent être effacés :

    >>> p2.article_set.clear()
    >>> p2.article_set.all()
    <QuerySet []>

Et l’effacement peut se faire depuis l’autre côté :

    >>> p2.article_set.add(a4, a5)
    >>> p2.article_set.all()
    <QuerySet [<Article: NASA finds intelligent life on Earth>, <Article: Oxygen-free diet works wonders>]>
    >>> a4.publications.all()
    <QuerySet [<Publication: Science News>, <Publication: Science Weekly>]>
    >>> a4.publications.clear()
    >>> a4.publications.all()
    <QuerySet []>
    >>> p2.article_set.all()
    <QuerySet [<Article: Oxygen-free diet works wonders>]>

Recréez l”`Article` et la `Publication` que nous avons supprimés :

    >>> p1 = Publication(title="The Python Journal")
    >>> p1.save()
    >>> a2 = Article(headline="NASA uses Python")
    >>> a2.save()
    >>> a2.publications.add(p1, p2, p3)

Supprimez en vrac certaines instances `Publication`, et les références aux publications supprimées ne seront plus présentes dans les éléments liés :

    >>> Publication.objects.filter(title__startswith="Science").delete()
    >>> Publication.objects.all()
    <QuerySet [<Publication: Highlights for Children>, <Publication: The Python Journal>]>
    >>> Article.objects.all()
    <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA finds intelligent life on Earth>, <Article: NASA uses Python>, <Article: Oxygen-free diet works wonders>]>
    >>> a2.publications.all()
    <QuerySet [<Publication: The Python Journal>]>

Supprimez en vrac certains articles ; les références aux objets supprimés ne sont plus présentes :

    >>> q = Article.objects.filter(headline__startswith="Django")
    >>> print(q)
    <QuerySet [<Article: Django lets you build web apps easily>]>
    >>> q.delete()

Après la suppression avec `delete()`, le cache de `QuerySet` doit être effacé et les objets référencés ne sont plus présents :

    >>> print(q)
    <QuerySet []>
    >>> p1.article_set.all()
    <QuerySet [<Article: NASA uses Python>]>