1. ホーム
  2. python

[解決済み] Django の OneToOne, ManyToMany, ForeignKey フィールドの違いは何ですか?

2023-03-21 06:25:15

質問

Django のモデルでリレーションシップについて理解するのに少し苦労しています。

誰か OneToOne, ManyToMany, ForeignKey の違いを説明してくれませんか?

どのように解決するのですか?

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

  1. 1 対 1、多対多、および外部キー リレーションの違い (一般的に) は何ですか。
  2. Django に特有のそれらの違いは何ですか。

これらの質問の両方は、単純なGoogle検索によって非常に簡単に答えることができますが、私はSOでこの質問の正確な重複を見つけることができないので、私は先に行くと、答えるでしょう。

Djangoでは、リレーションシップは片方だけで定義することに注意してください。


外部キー

外部キーリレーションは、一般に多対一のリレーションとして知られています。この関係の逆は一対多であることに注意して下さい (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の構文ではありません)。

  • 生まれ故郷。テキサス州ダラス
  • 出生地 ニューヨーク州ニューヨーク市
  • 人物 ジョン・スミス(John Smith) 出身地:(テキサス州ダラス
  • 人 マリア・リー、出生地:(テキサス州ダラス)
  • 人 ダニエル・リー(Daniel Lee)、出生地:(ニューヨーク州ニューヨーク市

さて、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の関係は、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 = 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つのモデル間の多対多の関係は、最初のモデルの0個、1個またはそれ以上のオブジェクトが、2番目のモデルの0個、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 フィールドを使用して多対多の関係にアクセスすることができます。

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

  • 順序: "Spaceship", "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の図が少しわかりにくいかもしれませんが、以下の説明が参考になるかもしれません。

  • > または < は、「多くの人に」という意味です。
  • | は「一人に」という意味です。

だから... A --| B は、AのインスタンスがBのインスタンスに1つだけ関連づけられることを意味します。

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

A >--< B は...と等価です。

A --< B
A >-- B

このように、関係の "side" または方向は、それぞれ別々に読むことができます。ただ、一緒につぶすと便利なのです。

これらの関係の一つを拡張することは、より意味があるかもしれません。

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

リソース

dbの関係性の良い説明 提供:@MarcB

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

Django のドキュメントです。

models.ForeignKey

models.OneToOneField

models.ManyToManyField

一対一の関係

多対多の関係