什么是Django中的OneToOne,ManyToMany和ForeignKey字段之间的区别?

use*_*903 43 python django many-to-many one-to-one foreign-key-relationship

我在Django模型中解决关系时遇到了一些困难.

有人可以解释OneToOne,ManyToMany和ForeignKey之间的区别吗?

Joh*_*dt6 114

嗯,这里基本上有两个问题:

  1. 一对一,多对多和外键关系之间的区别(一般而言)
  2. 他们与Django有什么不同.

通过简单的谷歌搜索很容易回答这两个问题,但由于我无法在SO上找到这个问题的确切欺骗,我会继续回答.

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


ForeignKey的

外键关系通常被称为多对一关系.请注意,这种关系的反向是一对多(Django提供了访问工具).顾名思义,许多对象可能与一个对象有关.

Person >--| Birthplace
   ^           ^
   |           |
  Many        One 
Run Code Online (Sandbox Code Playgroud)

在这个例子中,一个人可能只有一个出生地,但出生地可能与许多人有关.让我们看一下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
Run Code Online (Sandbox Code Playgroud)

您可以看到模型中没有定义任何关系Birthplace,并且在模型中定义了ForeignKey关系Person.假设我们创建了以下模型实例(显然不是Python语法):

  • 出生地:德克萨斯州达拉斯市
  • 出生地:纽约,纽约
  • 人:约翰史密斯,出生地:(德克萨斯州达拉斯)
  • 人:Maria Lee,出生地:(德克萨斯州达拉斯)
  • 人: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>]
Run Code Online (Sandbox Code Playgroud)

您可以看到我们创建的模型实例.现在让我们看看某人的出生地:

>> person = Person.object.get(name="John Smith")
>> person.birthplace
<Birthplace: Dallas, Texas>
>> person.birthplace.city
Dallas
Run Code Online (Sandbox Code Playgroud)

假设您希望看到所有具有特定出生地的人.正如我之前所说,Django允许您访问反向关系.默认情况下,Django RelatedManager在您的模型上创建一个manager()来处理它,名为<model>_set,其中<model>您的模型名称为小写.

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

请注意,我们可以通过related_name在模型关系中设置关键字参数来更改此管理器的名称.因此,我们将模型中的birthplace字段更改Person为:

birthplace = models.ForeignKey(Birthplace, related_name="people")
Run Code Online (Sandbox Code Playgroud)

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

>> place.people.all()
[<Person: John Smith>, <Person: Maria Lee>]
Run Code Online (Sandbox Code Playgroud)

一比一

一对一关系非常类似于多对一关系,除了它将两个对象限制为具有唯一关系.一个例子是用户和配置文件(存储有关用户的信息).没有两个用户共享相同的个人资料.

User |--| Profile
  ^          ^
  |          |
 One        One
Run Code Online (Sandbox Code Playgroud)

我们来看看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)
Run Code Online (Sandbox Code Playgroud)

我们需要的只是一个具有配置文件的用户在shell中测试它:

  • 用户: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>
Run Code Online (Sandbox Code Playgroud)

当然,您可以使用related_name上面的参数自定义反向关系的名称.


许多一对多

多对多关系可能有点棘手.首先让我说多对多领域是混乱的,应该尽可能避免.鉴于此,有很多情况下多对多关系是有道理的.

两个模型之间的多对多关系定义第一模型的零个,一个或多个对象可以与第二模型的零个,一个或多个对象相关.例如,让我们设想一个通过项目定义其工作流程的公司.项目可能与没有订单,只有一个订单或许多订单有关.订单可能与任何项目,一个项目或许多项目无关.

Order >--< Project
  ^           ^
  |           |
 Many        Many
Run Code Online (Sandbox Code Playgroud)

让我们定义我们的模型如下:

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))
Run Code Online (Sandbox Code Playgroud)

请注意,Django将RelatedManager为该orders字段创建一个访问多对多关系.

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

  • 订购:"宇宙飞船","美国宇航局"
  • 订购:"潜艇","美国海军"
  • 订购:"赛车","NASCAR"
  • 项目:订单:[]
  • 项目:订单:[(订单:"太空船","美国宇航局")]
  • 项目:订单:[(订单:"太空船","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>]
Run Code Online (Sandbox Code Playgroud)

请注意,NASA订单与2个项目有关,而美国海军订单则与之无关.另请注意,一个项目没有订单,一个项目有多个订单.

我们也可以像以前一样反过来访问这种关系:

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

ASCII基数指南

在我的ASCII图有点令人困惑的情况下,以下解释可能会有所帮助:

  • >或者<意味着"对很多人"
  • | 意思是"一个人"

所以... A --| B意味着A的实例只能与B的一个实例相关.

并且A --< B意味着A的实例可以与B的多个实例相关.

A >--< B 相当于....

A --< B
A >-- B
Run Code Online (Sandbox Code Playgroud)

因此,可以分别读取关系的每个"侧面"或方向.把它们挤在一起很方便.

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

               +---- John Smith
               |
 Dallas|-------+---- Jane Doe
               |
               +---- Joe Smoe
Run Code Online (Sandbox Code Playgroud)

资源

@MarcB提供的数据库关系的良好解释

关于基数的维基百科页面

Django文档:

models.ForeignKey

models.OneToOneField

models.ManyToManyField

一对一的关系

多对多关系

  • +1绝对美妙的底漆.将来肯定会将人们联系到这个答案. (8认同)
  • 加入书签以供将来参考:)非常感谢Johndt6 (2认同)
  • 很好的答案,谢谢。ManyToMany一直让我感到困惑 (2认同)
  • 我想补充一点,使用 `ManyToManyField` 在数据库中创建一个额外的表,其中包含 3 个字段:pk 和 2 个对连接表的引用(table1_id、table2_id)。`ManyToManyField` 是标签的绝佳选择(例如,如果您想用标签标记特定的项目/产品/位置)。 (2认同)