DjangoBrothers BLOG ✍️

2018/07/29

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

related_nameの存在意義とは?

DjangoでForeignKeyを使う時は、オプションの引数としてrelated_nameを指定することができます。また、状況によっては必ず指定しなくてはいけない場面があります。

related_nameはどういう働きをしているのか、説明したいと思います。まずは、ForeignKeyを使って、FoodモデルとPersonモデルを紐づけるところからです。

models.py

from django.db import models

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

    def __str__(self):
        return self.name

class Person(models.Model):
    name = models.CharField(max_length=100)
    favorite_food = models.ForeignKey(Food, on_delete="CASCADE", null=True)

    def __str__(self):
        return self.name

上のようにfavorite_foodフィールドでForeignKeyを使うことによってFoodとPersonが結びつき、以下のように参照と逆参照ができるようになります。

参照と逆参照

# 参照
>>> person1 = Person.objects.get(name="ジョブス")
>>> person1.favorite_food
<Food: 寿司>

# 逆参照
>>> food1 = Food.objects.get(name="寿司")
>>> food1.person_set.all()
<QuerySet [<Person: ジョブス>, <Person: ローラ>, <Person: ジョン>]>

person1.favorite_foodのように、インスタンス.フィールド名とすることで参照ができ、上記ではジョブスの好きな食べ物を取得しています。

また、food1.person_setのように、インスタンス.クラス名_setとすることで逆参照でき、上記では寿司が好きな人の一覧をクエリセットとして取得しています。

複数のフィールドでForeignKeyを使う

ここまでは、単純なForeignKeyの使い方です。では、Personクラス内で、favorite_foodフィールドとは別にもう一つFoodを参照するフィールドを作成したらどうなるでしょう。嫌いな食べ物を保存するフィールドとして、hate_foodを追加してみます。

Personクラスは以下のようになります。

models.py

class Person(models.Model):
    name = models.CharField(max_length=100)
    favorite_food = models.ForeignKey(Food, on_delete="CASCADE", null=True)
    hate_food = models.ForeignKey(Food, on_delete="CASCADE", null=True)

    def __str__(self):
        return self.name

このようにすると、以下のようにエラーが出てしまいます。

エラー

ERRORS:
app.Person.favorite_food: (fields.E304) Reverse accessor for 'Person.favorite_food' clashes with reverse accessor for 'Person.hate_food'.
    HINT: Add or change a related_name argument to the definition for 'Person.favorite_food' or 'Person.hate_food'.
app.Person.hate_food: (fields.E304) Reverse accessor for 'Person.hate_food' clashes with reverse accessor for 'Person.favorite_food'.
    HINT: Add or change a related_name argument to the definition for 'Person.hate_food' or 'Person.favorite_food'.
System check identified 2 issues (0 silenced).

簡単に言うと、この記述じゃfavorate_foodとhate_foodに対して逆参照することができないよと言っています。

最初の例だと、food1.person_setとすることで逆参照することができましたが、Foodを参照しているフィールドが複数あると、person_setと記述しても、「寿司を好きな人の一覧」なのか「寿司を嫌いな人の一覧」なのか、どちらのことを指しているのかがわかりませんよね。

related_nameの使い方

これを解決するためにあるのが、related_nameです。

それぞれのフィールドにrelated_nameを指定しておき、逆参照するときはクラス名ではなくrelated_nameを使うことで逆参照が可能となります。

models.py

class Person(models.Model):
    name = models.CharField(max_length=100)
    favorite_food = models.ForeignKey(Food, on_delete="CASCADE", null=True, related_name = "favorite_people")
    hate_food = models.ForeignKey(Food, on_delete="CASCADE", null=True, related_name = "hate_people")

    def __str__(self):
        return self.name

上のようにそれぞれのフィールドにrelated_nameを与えることで、以下のように逆参照することができます。

related_nameを使った逆参照

>>> food1 = Food.objects.get(name="寿司")

# 寿司が好きな人の一覧
>>> food1.favorite_people.all()
<QuerySet [<Person: ジョブス>, <Person: ローラ>, <Person: ジョン>]>

# 寿司が嫌いな人の一覧
>>> food1.hate_people.all()
<QuerySet [<Person: ケイ>]>

以上のように、複数のフィールドで同じモデルを参照する場合は、related_nameをつけるようにしましょう。