# Django 5.0.9.dev20240806133106 ドキュメント

# 多対多 (many-to-many) 関係

多対多の関係を定義するには、`ManyToManyField` を使用します。

この例では、一つの `Article` は複数の `Publication` オブジェクトに掲載することができ、一つの `Publication` は複数の `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

以下は、Python API の機能を使って実行できる操作の例です。

いくつかの `出版物` を作成する:

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

一つの `Article` を作成する:

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

保存されるまでは `Publication` と関連付けることはできません:

    >>> 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.

保存しましょう！

    >>> a1.save()

`Article` と `Publication` を関連付ける:

    >>> a1.publications.add(p1)

別の `Article` を作成し、それを `Publications` に表示するようにセットする:

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

2回の追加はOKで、関係が重複することはありません:

    >>> a2.publications.add(p3)

間違った型のオブジェクトを追加すると `TypeError` が発生します:

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

`Article` に `Publication` をワンステップで作成・追加するには `create()` を使います:

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

`Article` オブジェクトはリレーション先の `Publication` オブジェクトにアクセスできます:

    >>> 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>]>

`Publication` オブジェクトはリレーション先の `Article` オブジェクトにアクセスできます:

    >>> 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>]>

多対多の関係は リレーションを横断するルックアップ を使ってクエリできます:

    >>> 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>]>

`count()` 関数は `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>]>

逆の多対多クエリもサポートされています (つまり、 `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>]>

リレーション先項目の除外も期待通りに動作します（SQLは少し複雑ですが）:

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

もし `Publication` を削除すると、その `Articles` にはアクセスできなくなります:

    >>> 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 []>

もし `Article` を削除すると、その `Publications` にはアクセスできなくなります:

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

多対多のリレーションシップの「反対側の」端を経由した追加:

    >>> 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>]>

キーワードを使った反対側の端からの追加:

    >>> 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>]>

`Article` から `Publication` を削除する:

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

反対側からも:

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

リレーションのセットも指定できます:

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

リレーションのセットを削除することもできます:

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

反対側からも削除できます:

    >>> 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>]>

削除した `Article` と `Publication` を再作成する:

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

複数の `Publications` を一括削除 - 削除された出版物への参照は削除されます:

    >>> 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>]>

複数の記事を一括削除 - 削除したオブジェクトへの参照は削除されます:

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

`delete()` の後は、 `QuerySet` のキャッシュをクリアする必要があります。参照されたオブジェクトは削除されます:

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