web-dev-qa-db-ja.com

DjangoのOneToOne、ManyToMany、ForeignKeyフィールドの違いは何ですか?

Djangoモデルで関係を回避するのに少し苦労しています。

誰かがOneToOne、ManyToMany、ForeignKeyの違いを説明できますか?

42
user3915903

さて、ここには本質的に2つの質問があります。

  1. 1対1、多対多、および外部キー関係の違い(一般的に)は何ですか
  2. Django固有の違いは何ですか。

これらの質問はどちらも簡単なGoogle検索で簡単に回答できますが、SOでこの質問の正確な重複を見つけることができないので、先に進んで回答します。

Djangoでは、関係は関係の一方の側でのみ定義されることに注意してください。


外部キー

外部キー関係は、一般に多対1の関係として知られています。この関係の逆は1対多です(Djangoはアクセスするツールを提供します)。名前が示すように、多くのオブジェクトは1つに関連している可能性があります。

_Person >--| Birthplace
   ^           ^
   |           |
  Many        One 
_

この例では、人の出生地は1つだけですが、出生地は多くの人に関連している場合があります。 Djangoでこの例を見てみましょう。これらが私たちのモデルだとしましょう:

_class Birthplace(models.Model):
    city = models.CharField(max_length=75)
    state = models.CharField(max_length=25)

    def __unicode__(self):
        return "".join(self.city, ", ", self.state)

class Person(models.Model):
    name = models.CharField(max_length=50)
    birthplace = models.ForeignKey(Birthplace)

    def __unicode__(self):
        return self.name
_

Birthplaceモデル内ではリレーションが定義されておらず、ForeignKeyモデル内ではPersonリレーションが定義されていることがわかります。モデルの次のインスタンスを作成するとします(明らかにPython構文ではありません):

  • 出身地:テキサス州ダラス
  • 出身地:ニューヨーク、ニューヨーク
  • 人:ジョン・スミス、生家:(テキサス州ダラス)
  • 人:マリア・リー、出身地:(テキサス州ダラス)
  • 人物:ダニエル・リー、出身地:(ニューヨーク、ニューヨーク)

これで、Djangoがこれらのリレーションを使用する方法を確認できます(_./manage.py Shell_はあなたの友達です!):

_>> from somewhere.models import Birthplace, Person
>> Person.objects.all()
[<Person: John Smith>, <Person: Maria Lee>, <Person: Daniel Lee>]
>> Birthplace.objects.all()
[<Birthplace: Dallas, Texas>, <Birthplace: New York City, New York>]
_

作成したモデルインスタンスを確認できます。それでは、誰かの生家をチェックしてみましょう。

_>> person = Person.object.get(name="John Smith")
>> person.birthplace
<Birthplace: Dallas, Texas>
>> person.birthplace.city
Dallas
_

特定の出生地を持つすべての人に会いたいとしましょう。前に言ったように、Djangoは逆の関係にアクセスできます。デフォルトでは、Djangoはマネージャーを作成します( RelatedManager )これを処理するモデルでは、_<model>_set_という名前で、_<model>_は小文字のモデル名です。

_>> place = Birthplace.objects.get(city="Dallas")
>> place.person_set.all()
[<Person: John Smith>, <Person: Maria Lee>]
_

モデル関係で_related_name_キーワード引数を設定することにより、このマネージャーの名前を変更できることに注意してください。したがって、birthplaceモデルのPersonフィールドを次のように変更します。

_birthplace = models.ForeignKey(Birthplace, related_name="people")
_

これで、きれいな名前でその逆の関係にアクセスできます。

_>> place.people.all()
[<Person: John Smith>, <Person: Maria Lee>]
_

1対1

1対1の関係は、2つのオブジェクトを一意の関係に制限することを除いて、多対1の関係に非常に似ています。この例は、ユーザーとプロファイル(ユーザーに関する情報を保存する)です。 2人のユーザーが同じプロファイルを共有することはありません。

_User |--| Profile
  ^          ^
  |          |
 One        One
_

Djangoでこれを見てみましょう。 Djangoで定義されているため、ユーザーモデルを定義する必要はありません。ただし、DjangoはDjango.contrib.auth.get_user_model()を使用してユーザーをインポートするため、これが実行されます。プロファイルモデルは次のように定義できます。

_class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL) # Note that Django suggests getting the User from the settings for relationship definitions
    fruit = models.CharField(max_length=50, help_text="Favorite Fruit")
    facebook = models.CharField(max_length=100, help_text="Facebook Username")

    def __unicode__(self):
        return "".join(self.fruit, " ", self.facebook)
_

必要なのは、シェルでこれをテストするプロファイルを持つ1人のユーザーだけです。

  • ユーザー:johndt6
  • プロフィール:ユーザー:johndt6、「Kiwi」、「blah_blah」

これで、ユーザーモデルからユーザーのプロファイルに簡単にアクセスできます。

_>> user = User.objects.all()[0]
>> user.username
johndt6
>> user.profile
<Profile: Kiwi blah_blah>
>> user.profile.fruit
Kiwi
>> profile = Profile.objects.get(user=user)
>> profile.user
<User: johndt6>
_

もちろん、上記の_related_name_引数を使用して、逆リレーションの名前をカスタマイズできます。


多対多

多対多の関係は、少し注意が必要です。多対多のフィールドは乱雑であり、可能な場合は避けるべきだと言って始めましょう。それを考えると、多対多の関係が理にかなっている状況はたくさんあります。

2つのモデル間の多対多の関係は、最初のモデルのゼロ、1つまたは複数のオブジェクトが2番目のモデルのゼロ、1つまたは複数のオブジェクトに関連付けられることを定義します。例として、プロジェクトを通じてワークフローを定義する会社を想像してみましょう。プロジェクトは、注文なし、1つの注文のみ、または多くの注文に関連している場合があります。注文は、プロジェクトなし、1つのプロジェクト、または多くのプロジェクトに関連している場合があります。

_Order >--< Project
  ^           ^
  |           |
 Many        Many
_

モデルを次のように定義しましょう。

_class Order(models.Model):
    product = models.CharField(max_length=150)  # Note that in reality, this would probably be better served by a Product model
    customer = models.CharField(max_length=150)  # The same may be said for customers

    def __unicode__(self):
        return "".join(self.product, " for ", self.customer)

class Project(models.Model):
    orders = models.ManyToManyField(Order)

    def __unicode__(self):
        return "".join("Project ", str(self.id))
_

Django=は、多対多の関係にアクセスするためにRelatedManagerフィールドにordersを作成します。

モデルの次のインスタンスを作成してみましょう(一貫性のない構文で!):

  • 順序:「宇宙船」、「NASA​​」
  • 順序:「潜水艦」、「米海軍」
  • 注文:「レースカー」、「NASCAR」
  • プロジェクト:注文:[]
  • プロジェクト:注文:[(注文: "宇宙船"、 "NASA")]
  • プロジェクト:注文:[(注文: "宇宙船"、 "NASA")、(注文: "レースカー"、 "NASCAR")]

これらの関係には、次のようにアクセスできます。

_>> Project.objects.all()
[<Project: Project 0>, <Project: Project 1>, <Project: Project 2>]
>> for proj in Project.objects.all():
..     print(proj)
..     proj.orders.all()  # Note that we must access the `orders`
..                        # field through its manager
..     print("")
Project 0
[]

Project 1
[<Order: Spaceship for NASA>]

Project 2
[<Order: Spaceship for NASA>, <Order: Race car for NASCAR>]
_

NASAの命令は2つのプロジェクトに関連しており、米海軍の命令はどれにも関連していないことに注意してください。また、1つのプロジェクトには注文がなく、1つのプロジェクトには複数の注文があることに注意してください。

以前と同じ方法で、逆に関係にアクセスすることもできます。

_>> order = Order.objects.filter(customer="NASA")[0]
>> order.project_set.all()
[<Project: Project 0>, <Project: Project 2>]
_

ASCIIカーディナリティガイド

私のASCII図が少し混乱している可能性が高い場合、以下の説明が役立つかもしれません:

  • _>_または_<_は「多」を意味します
  • _|_は「1対1」を意味します

したがって... _A --| B_は、AのインスタンスがBの1つのインスタンスにのみ関連付けられることを意味します。

_A --< B_は、AのインスタンスをBの多くのインスタンスに関連付けることができることを意味します。

_A >--< B_は次と同等です...

_A --< B
A >-- B
_

したがって、関係の各「側面」または方向は個別に読み取ることができます。それらを一緒につぶすのは便利です。

これらの関係のいずれかを拡張すると、より意味があります。

_               +---- John Smith
               |
 Dallas|-------+---- Jane Doe
               |
               +---- Joe Smoe
_

資源

データベース関係の適切な説明 @MarcBにより提供

カーディナリティに関するウィキペディアのページ

Django Docs:

_models.ForeignKey_

_models.OneToOneField_

_models.ManyToManyField_

1対1の関係

多対多の関係

107
Johndt6