Django中的OneToOne、ManyToMany和ForeignKey字段有什么区别?

104

我对Django模型中的关系有一些困惑。

能否有人解释一下OneToOne、ManyToMany和ForeignKey之间的区别?


3
这是一个数据库的概念,不只是Django特有的:http://www.databaseprimer.com/pages/table-relationships/。外键是指定这些关系的方式。 - Marc B
2个回答

281

这里基本上有两个问题:

  1. 一对一、多对多和外键关系的区别(通常情况下)。
  2. 它们在Django中特定的区别是什么。

这两个问题都可以通过简单的Google搜索很容易地得到答案,但由于我无法在SO上找到确切的重复问题,因此我会回答这个问题。

请注意,在Django中,关系应该只定义在关系的一侧。


ForeignKey

外键关系通常称为多对一关系。请注意,这种关系的反向是一对多关系(Django提供了访问工具)。正如名称所示,多个对象可以与一个对象相关联。

Person >--| Birthplace
   ^           ^
   |           |
  Many        One 

在这个例子中,一个人可能只有一个出生地,但一个出生地可能与多个人相关联。现在我们来看一下 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模型中没有定义任何关系,在Person模型中定义了一个ForeignKey关系。假设我们创建以下模型实例(显然不是Python语法):

  • Birthplace: 达拉斯,得克萨斯州
  • Birthplace: 纽约市,纽约州
  • Person: 约翰·史密斯,出生地:(达拉斯,得克萨斯州)
  • Person: 玛丽亚·李,出生地:(达拉斯,得克萨斯州)
  • Person: 丹尼尔·李,出生地:(纽约市,纽约州)

现在我们可以看到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关键字参数来更改此管理器的名称。因此,我们将在Person模型中更改birthplace字段为:

birthplace = models.ForeignKey(Birthplace, related_name="people")

现在,我们可以使用一个漂亮的名称访问该反向关系:

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

一对一

一对一关系与多对一关系非常相似,不同之处在于它限制了两个对象之间具有唯一的关系。例如,用户和个人资料(存储用户信息)之间的关系就是一对一的。没有两个用户共享同一个个人资料。

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)

我们只需要一个有个人资料的用户在 shell 中测试一下:

  • 用户:johndt6
  • 个人资料:user: johndt6,“奇异鸟”,“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参数自定义反向关系的名称。


多对多关系

多对多关系可能有点棘手。首先我要说的是,多对多字段很麻烦,应尽可能避免使用。但是,在许多情况下,多对多关系是有意义的。

两个模型之间的多对多关系定义了第一个模型的零个、一个或多个对象与第二个模型的零个、一个或多个对象之间的关系。例如,假设一个公司通过项目来定义他们的工作流程。一个项目可能与零个、一个或多个订单相关联。一个订单可能与零个、一个或多个项目相关联。

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将为“orders”字段创建一个“RelatedManager”来访问多对多关系。
让我们创建以下模型实例(采用我不一致的语法!):
- 订单:“Spaceship”,“NASA” - 订单:“Submarine”,“US Navy” - 订单:“Race car”,“NASCAR” - 项目:订单:[] - 项目:订单:[(Order:“Spaceship”,“NASA”)] - 项目:订单:[(Order:“Spaceship”,“NASA”),(Order:“Race car”,“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个项目有关,而美国海军订单与任何项目无关。还要注意一个项目没有订单,而另一个项目有多个订单。

我们也可以以同样的方式反向访问关系:

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

ASCII 基数指南

如果我的 ASCII 图表有些令人困惑,下面的解释可能会有所帮助:

  • >< 表示 "到许多个"
  • | 表示 "到一个"

因此...A --| B 意味着 A 的一个实例只能与 B 的一个实例相关联。

A --< B 意味着 A 的一个实例可以与许多个 B 的实例相关联。

A >--< B 等同于....

A --< B
A >-- B

因此,每个关系的“方向”都可以单独阅读。将它们合并在一起只是为了方便。

扩展其中一个关系可能更有意义:

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

资源

@MarcB提供了一个关于数据库关系的好解释

Cardinality的维基百科页面

Django文档:

model.ForeignKey

models.OneToOneField

models.ManyToManyField

一对一关系

多对多关系


5
我想补充一点,使用ManyToManyField会在数据库中创建一个额外的表,其中包含3个字段:主键和2个连接表的引用(table1_id,table2_id)。ManyToManyField非常适合标签(例如,如果您想使用标签标记特定的项目/产品/职位)。 - 0leg
1
这是有关ManyToManyField中间表的文档 - https://docs.djangoproject.com/en/3.1/ref/models/fields/#id1。 - 0leg

0
在我看来,一对一和一对多的区别是: 一对一:意味着一个人只能拥有一个护照 一对多:意味着一个人可以拥有多个地址,例如(永久地址、办公地址、次要地址) 如果您调用父模型,它将自动调用许多子类。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接