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

104 投票
2 回答
41787 浏览
提问于 2025-04-18 17:48

我在理解Django模型中的关系时遇到了一些困难。

有人能解释一下OneToOne、ManyToMany和ForeignKey之间有什么区别吗?

2 个回答

0

在我看来,一对一和一对多的区别是这样的:
一对一:意思是一个人只能拥有一个护照。
一对多:意思是一个人可以有多个地址,比如(常住地址、办公室地址、备用地址)。
如果你调用父类模型,它会自动调用多个子类。

288

这里其实有两个问题:

  1. 一对一、多对多和外键关系之间的区别是什么?
  2. 它们在Django中的具体区别是什么?

这两个问题通过简单的谷歌搜索就能轻松找到答案,不过因为我在StackOverflow上找不到完全相同的问题,所以我就来回答一下。

需要注意的是,在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语法):

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

现在我们可以看到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>]

一对一(One-to-one)

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

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


多对多(Many-to-many)

多对多关系可能有点复杂。让我先说,多对多字段比较麻烦,尽量避免使用。不过在很多情况下,多对多关系是合理的。

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

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来访问多对多关系。

让我们创建以下模型实例(用我不一致的语法!):

  • 订单:“宇宙飞船”,“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的订单与两个项目相关联,而美国海军的订单没有相关联的项目。还要注意,一个项目没有订单,而一个项目有多个订单。

我们也可以像之前一样以反向方式访问关系:

>> 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提供

关于基数的维基百科页面

Django文档:

models.ForeignKey

models.OneToOneField

models.ManyToManyField

一对一关系

多对多关系

撰写回答