DjangoBrothers BLOG ✍️

2018/07/28

このエントリーをはてなブックマークに追加
Python Django クエリセット ManyToMany

ManyToMany フィルターなどのオブジェクト操作一覧

モデル同士を紐づける方法は、ForeinKey(1対多)やOneToOne(1対1)などがありますが、今日はManyToMany(多対多)についてです。

ManyToMany(多対多)を作る

PersonモデルとHobbyモデルの関係性で考えてみます。

Personは、サッカーやピアノなど複数のHobbyを持つことが考えられますし、逆に、サッカーというHobbyはたくさんの人の趣味となり得ます。

データベースのイメージは以下のようになります。

このテーブルを作成するモデルは以下の通りです。blank=Trueを指定することで、Personは必ずしもHobbyを持たなくてもよくなります。上の画像の例でいうと「ジョン」のような感じです。

models.py

from django.db import models

class Hobby(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Person(models.Model):
    name = models.CharField(max_length=100)
    hobbys = models.ManyToManyField(Hobby, blank=True)

    def __str__(self):
        return self.name

オブジェクト操作

画像のようにデータが保存されていると仮定して、データの扱い方をいくつか紹介します。

参照と逆参照

HobbyとPersonはManyToManyFieldによって紐付けられています。このとき、Person側からみたHobbyとの関係を参照、Hobby側からみたPersonとの関係を逆参照といいます。

それぞれ参照しあっているので、以下のようにデータを取得することができます。逆参照の場合、オブジェクト.クラス名_setとすることで、クエリセットを取得します。

参照と逆参照

#参照
>>> person1 = Person.objects.get(id=1)
>>> person1.hobbys.all()
<QuerySet [<Hobby: サッカー>, <Hobby: ピアノ>, <Hobby: プログラミング>]>

#逆参照
>>> hobby1 = Hobby.objects.get(id=1)
>>> hobby1.person_set.all()
<QuerySet [<Person: ジョブス>, <Person: ローラ>]>
>>>

作成、追加、取り除き、全削除

ManyToManyフィールドにオブジェクトを追加したり、すでに保管されているオブジェクトを削除したりする方法です。

1つ目のcreateメソッドは、「映画鑑賞」というこれまでになかった新しいHobbyオブジェクトを作成した上で、それをperson1のhobbysフィールドに保管しています。

追加・取り除き・全削除

#作成と保管
>>> person1.hobbys.create(name="映画鑑賞")
>>> person1.hobbys.all()
<QuerySet [<Hobby: サッカー>, <Hobby: ピアノ>, <Hobby: プログラミング>, <Hobby: 映画鑑賞>]>

>>> hobby3 = Hobby.objects.get(id=3)
#追加
>>> person1.hobbys.add(hobby3)
>>> person1.hobbys.all()
<QuerySet [<Hobby: サッカー>, <Hobby: ピアノ>, <Hobby: 読書>, <Hobby: プログラミング>, <Hobby: 映画鑑賞>]>

#取り除き
>>> person1.hobbys.remove(hobby3)
>>> person1.hobbys.all()
<QuerySet [<Hobby: サッカー>, <Hobby: ピアノ>, <Hobby: プログラミング>, <Hobby: 映画鑑賞>]>

#全削除
>>> person1.hobbys.clear()
>>> person1.hobbys.all()
<QuerySet []>
>>>

ManyToManyFieldでフィルターをかける

フィルター

#idが1のHobby(サッカー)を趣味に持つユーザー一覧
>>> Person.objects.filter(hobbys=1)
<QuerySet [<Person: ローラ>, <Person: ジョブス>]>

#nameが"サッカー"のHobbyを趣味に持つユーザー一覧
>>> Person.objects.filter(hobbys__name="サッカー")
<QuerySet [<Person: ローラ>, <Person: ジョブス>]>

#idが1または2のHobbyを持つユーザー一覧
>>> Person.objects.filter(hobbys__in=[1,2])
<QuerySet [<Person: ローラ>, <Person: ジョブス>, <Person: ケイ>, <Person: ジョブス>]>

ManyToManyフィールドのオブジェクトが多い順に表示

ManyToManyフィールドに保存されているオブジェクトが多い順、つまり、持つ趣味が多い順にPersonオブジェクトを並べます。

保有するオブジェクト順

>>> from django.db.models import Count
>>> Person.objects.all().annotate(Count("hobbys")).order_by('-hobbys__count')
<QuerySet [<Person: ジョブス>, <Person: ローラ>, <Person: ケイ>, <Person: ジョン>]

annotateについては、こちらの記事でも紹介しています。