Django中OneToOne、ManyToMany和ForeignKey字段有什么区别?
我在理解Django模型中的关系时遇到了一些困难。
有人能解释一下OneToOne、ManyToMany和ForeignKey之间有什么区别吗?
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提供