该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程

所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址。


我们都知道对于ManyToMany字段,Django采用的是第三张中间表的方式。通过这第三张表,来关联ManyToMany的双方。下面我们根据一个具体的例子,详细解说中间表的使用。

一、默认中间表

首先,模型是这样的:

class Person(models.Model):
name = models.CharField(max_length=128) def __str__(self):
return self.name class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person) def __str__(self):
return self.name

在Group模型中,通过members字段,以ManyToMany方式与Person模型建立了关系。

让我们到数据库内看一下实际的内容,Django为我们创建了三张数据表,其中的app1是应用名。

然后我在数据库中添加了下面的Person对象:

再添加下面的Group对象:

让我们来看看,中间表是个什么样子的:

首先有一列id,这是Django默认添加的,没什么好说的。然后是Group和Person的id列,这是默认情况下,Django关联两张表的方式。如果你要设置关联的列,可以使用to_field参数。

可见在中间表中,并不是将两张表的数据都保存在一起,而是通过id的关联进行映射。

二、自定义中间表

一般情况,普通的多对多已经够用,无需自己创建第三张关系表。但是某些情况可能更复杂一点,比如如果你想保存某个人加入某个分组的时间呢?想保存进组的原因呢?

Django提供了一个through参数,用于指定中间模型,你可以将类似进组时间,邀请原因等其他字段放在这个中间模型内。例子如下:

from django.db import models

class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField() # 进组时间
invite_reason = models.CharField(max_length=64) # 邀请原因

在中间表中,我们至少要编写两个外键字段,分别指向关联的两个模型。在本例中就是‘Person’和‘group’。

这里,我们额外增加了‘date_joined’字段,用于保存人员进组的时间,‘invite_reason’字段用于保存邀请进组的原因。

下面我们依然在数据库中实际查看一下(应用名为app2):

注意中间表的名字已经变成“app2_membership”了。

Person和Group没有变化。

但是中间表就截然不同了!它完美的保存了我们需要的内容。

三、使用中间表

针对上面的中间表,下面是一些使用例子(以欧洲著名的甲壳虫乐队成员为例):

>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
... date_joined=date(1962, 8, 16),
... invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>

与普通的多对多不一样,使用自定义中间表的多对多不能使用add(), create(),remove(),和set()方法来创建、删除关系,看下面:

>>> # 无效
>>> beatles.members.add(john)
>>> # 无效
>>> beatles.members.create(name="George Harrison")
>>> # 无效
>>> beatles.members.set([john, paul, ringo, george])

为什么?因为上面的方法无法提供加入时间、邀请原因等中间模型需要的字段内容。唯一的办法只能是通过创建中间模型的实例来创建这种类型的多对多关联。但是,clear()方法是有效的,它能清空所有的多对多关系。

>>> # 甲壳虫乐队解散了
>>> beatles.members.clear()
>>> # 删除了中间模型的对象
>>> Membership.objects.all()
<QuerySet []>

一旦你通过创建中间模型实例的方法建立了多对多的关联,你立刻就可以像普通的多对多那样进行查询操作:

# 查找组内有Paul这个人的所有的组(以Paul开头的名字)
>>> Group.objects.filter(members__name__startswith='Paul')
<QuerySet [<Group: The Beatles>]>

可以使用中间模型的属性进行查询:

# 查找甲壳虫乐队中加入日期在1961年1月1日之后的成员
>>> Person.objects.filter(
... group__name='The Beatles',
... membership__date_joined__gt=date(1961,1,1))
<QuerySet [<Person: Ringo Starr]>

可以像普通模型一样使用中间模型:

>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'

这一部分内容,需要结合后面的模型query,如果暂时看不懂,没有关系。


对于中间表,有一点要注意(在前面章节已经介绍过,再次重申一下),默认情况下,中间模型只能包含一个指向源模型的外键关系,上面例子中,也就是在Membership中只能有Person和Group外键关系各一个,不能多。否则,你必须显式的通过ManyToManyField.through_fields参数指定关联的对象。参考下面的例子:

from django.db import models

class Person(models.Model):
name = models.CharField(max_length=50) class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership',
through_fields=('group', 'person'),
) class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(
Person,
on_delete=models.CASCADE,
related_name="membership_invites",
)
invite_reason = models.CharField(max_length=64)

多对多中间表详解 -- Django从入门到精通系列教程的更多相关文章

  1. Part 4:表单和类视图--Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  2. Part 2:模型与后台管理admin站点--Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  3. 关系类型字段 -- Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  4. 字段的参数 -- Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  5. 模型的元数据Meta -- Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  6. 模型的继承 -- Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  7. 查询操作 -- Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  8. Django中不返回QuerySets的API -- Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  9. Django简介--Django从入门到精通系列教程

    该系列教程系个人原创,并同步发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

随机推荐

  1. Swagger文档转Word 文档

    GitHub 地址:https://github.com/JMCuixy/SwaggerToWord/tree/developer 原创作品,转载请注明出处:http://www.cnblogs.co ...

  2. Sql的基础知识(一)

    一.基础 1.说明:创建数据库 CREATE DATABASE database-name 2.说明:删除数据库 drop database dbname 3.说明:备份 sql server --- ...

  3. codeforces 767A Snacktower(模拟)

    A. Snacktower time limit per test:2 seconds memory limit per test:256 megabytes input:standard input ...

  4. android仿微信红包动画、Kotlin综合应用、Xposed模块、炫酷下拉视觉、UC浏览器滑动动画等源码

    Android精选源码 仿微信打开红包旋转动画 使用Kotlin编写的Android应用,内容你想象不到 Android手机上的免Root Android系统日志Viewer 一个能让微信 Mater ...

  5. EMC题

    [面试题]EMC易安信面试题解 1. 除以59的余数是多少. 来自wiki:费马小定理是数论中的一个定理:假如a是一个整数,p是一个質数,那么 如果a不是p的倍数,这个定理也可以写成 这个书写方式更加 ...

  6. 面试(2)-java-se-HashSet和TreeSet12

    Set是java中一个不包含重复元素的collection.更正式地说,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素.正如其名称所暗示的, ...

  7. BLE空中升级 谈(二)

    BLE 空中升级谈 -- CC2541 的产品开发中OAD注意事项(续) TI CC2541支持多个硬件,多个软件对它进行空中升级,可以有不同的组合,硬件有 编号 名称 Hex 用法 1 Cc2540 ...

  8. leak finder

    介绍 leak finder 是google开源团队发布了一个新的可以帮助web应用程序开发者在他们的JavaScript程序中找出内存泄露问题的工具: http://feedproxy.google ...

  9. 基于VUE选择上传图片并在页面显示(图片可删除)

    demo例子: 依赖文件 : http://files.cnblogs.com/files/zhengweijie/jquery.form.rar HTML文本内容: <template> ...

  10. 手把手教你搭建 vue 环境

    第一步 node环境安装 1.1 如果本机没有安装node运行环境,请下载node 安装包进行安装1.2 如果本机已经安装node的运行换,请更新至最新的node 版本下载地址:https://nod ...