我们都知道对于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. python学习笔记--Django入门四 管理站点--二

    接上一节  python学习笔记--Django入门四 管理站点 设置字段可选 编辑Book模块在email字段上加上blank=True,指定email字段为可选,代码如下: class Autho ...

  2. django第四天(路由别名,django2.x新特性和自定义转换器)

    django第四天 路由别名 1.路由别名: 给路由路径命名一个名字 url(r'^login/$',views.login,name = 'login') 2.为什么要用路由别名 ①当路由路径过长时 ...

  3. Django - Xadmin (四) Filter

    Django - Xadmin (四) Filter Filter 功能描述 与 admin 组件中 Filter 功能类似,在展示页面右侧放置一列标签,通过点击这些标签来筛选出该标签相关的数据. 比 ...

  4. day 68 Django基础四之模板系统

      Django基础四之模板系统   本节目录 一 语法 二 变量 三 过滤器 四 标签Tags 五 模板继承 六 组件 七 自定义标签和过滤器 八 静态文件相关 一 语法   模板渲染的官方文档 关 ...

  5. day 54 Django基础四之模板系统

    Django基础四之模板系统   本节目录 一 语法 二 变量 三 过滤器 四 标签Tags 五 模板继承 六 组件 七 自定义标签和过滤器 八 静态文件相关 一 语法   模板渲染的官方文档 关于模 ...

  6. Django基础四之测试环境和ORM查询

    Django基础四之测试环境和ORM查询 目录 Django基础四之测试环境和ORM查询 1. 搭建测试环境 1.1 测试环境搭建方法: 1.2 使用测试环境对数据库进行CURD 1.3 返回Quer ...

  7. Django(四)

    一.请求周期 url> 路由 > 函数或类 > 返回字符串或者模板语言? 1.Form表单提交: 提交 -> url > 函数或类中的方法 - .... HttpResp ...

  8. Django入门四之数据库相关

    1. 数据库设置 在settings.py中配置数据库 我首先使用的是sqlite3,所以配置如下 2. 数据库的数据结构定义 #blog/models.py #定义了一个表(Student),表里两 ...

  9. Django(四)框架之第三篇模板语法

    https://www.cnblogs.com/yuanchenqi/articles/6083427.htm https://www.cnblogs.com/haiyan123/p/7725568. ...

随机推荐

  1. Linux上read命令的使用

    一:read倾听是一种美德       1.倾听键盘的输入并保存到变量中              例如:#! /bin/bash            echo  "please inpu ...

  2. ElasticSearch2.3.1环境搭建哪些不为人知的坑

    首先说明一点,大家最好不要用什么尝鲜版,用比稳定版就好了,要不麻烦不断,另外出了问题,最好去官网,或者google搜索,因为这样靠谱些,要不现在好多都是低版本的,1.4的什么的,结果按照安装,多少情况 ...

  3. uint16,uint32是什么?

    记得之前在刷笔试题的时候就看见过这个问题,发现当时上网百度后又忘了. 最近在看CryEngine3引擎代码的时候又晕了,趁现在赶紧记下来~ 在查看CE3的代码时我发现了这个变量,TFlowNodeId ...

  4. 【工具】java发送GET、POST请求

    前项目使用这种HTTP的方式进行数据交互,目前已更换数据交互方式,但是作为接口提供调用来说还是比较简洁高效的: 总体流程就是: 1.发送HTTP请求 2.获取返回的JSON对象 3.JSON转换 pa ...

  5. MySQL数据库修改字段的长度

    数据库版本:5.7.22 使用DDL语句:alter table 表名 modify 字段名 字符类型(长度) 例如: alter table db2.admin modify password );

  6. J-link使用SWD下载的连线方式

    手头有两块开发板,一个是F103ZET6,另一个是C8T6.后者开发板没有JTAG口,所以只能用SWD下载和调试程序. 有如下总结: 1.有些开发板对boot的电平有要求,网上说boot0要接高电平. ...

  7. Codeforces Gym101201B:Buggy Robot(BFS + DP)

    题目链接 题意 给出一个n*m的地图,还有一个操作序列,你原本是要按照序列执行操作的,但是你可以修改操作:删除某些操作或者增加某些操作,问从'R'到'E'最少需要多少次修改操作. 思路 和上次比赛做的 ...

  8. 基于C#的机器学习--我应该接受这份工作吗-使用决策树

    决策树 要使决策树完整而有效,它必须包含所有的可能性.事件序列也必须提供,并且是互斥的,这意味着如果一个事件发生,另一个就不能发生. 决策树是监督机器学习的一种形式,因为我们必须解释输入和输出应该是什 ...

  9. web前端css(一)

    一  css的引入方式: 1)    行内样式:     <p style=”color: green”>我是一个段落</p> 2)    内接样式: <style ty ...

  10. Elasticsearch(三) 插件安装

    1.head插件 命令:  ./bin/plugin install mobz/elasticsearch-head