我们都知道对于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. 多对多中间表详解 -- Django从入门到精通系列教程

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

  2. oracle中的dual表详解

    oracle中的dual表详解 1.DUAL表的用途 Dual 是 Oracle中的一个实际存在的表,任何用户均可读取,常用在没有目标表的Select语句块中 --查看当前连接用户 SQL> s ...

  3. [转]PostgreSQL教程:系统表详解

    这篇文章主要介绍了PostgreSQL教程(十五):系统表详解,本文讲解了pg_class.pg_attribute.pg_attrdef.pg_authid.pg_auth_members.pg_c ...

  4. Oracle外部表详解(转载)

    (外部表创建主要注意创建目录访问权限问题.目录路径格式无空格等不相关字符,即必须是当前表访问用户可以访问:关于表中行数的限制问题,如果不加限制注意添加reject limit unlimited:表中 ...

  5. Oracle外部表详解

    外部表概述 外部表只能在Oracle 9i之后来使用.简单地说,外部表,是指不存在于数据库中的表.通过向Oracle提供描述外部表的元数据,我们可以把一个操作系统文件当成一个只读的数据库表,就像这些数 ...

  6. Django路由配置之正则表达式详解

    正则表达式详解 urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles ...

  7. Django中url的生成过程详解

    在前面我们知道,Django启动之前会执行admin.py中的autodiscover()方法. def autodiscover(): autodiscover_modules('admin', r ...

  8. Django模型类Meta元数据详解

    转自:https://my.oschina.net/liuyuantao/blog/751337 简介 使用内部的class Meta 定义模型的元数据,例如: from django.db impo ...

  9. Django模型层Meta内部类详解

    Django 模型类的Meta是一个内部类,它用于定义一些Django模型类的行为特性.以下对此作一总结: abstract      这个属性是定义当前的模型类是不是一个抽象类.所谓抽象类是不会对应 ...

随机推荐

  1. SpringBoot与Docker1

    1:docker是一个开源的应用容器引擎: docker支持将软件编译成一个镜像:然后再镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像: 运行中的这个镜像称为容器,容器启动是非 ...

  2. vs调试程序时发现变量、类等程序找不到混乱问题

    vs中不能同时打开两个解决方案名称相同的程序,否则会运行时出现混乱,比如变量监测不到,类的属性不全等问题

  3. VS2010/MFC编程入门之四十二(MFC常用类:CString类)

    上一节鸡啄米讲了分割窗口的有关知识,本节开始讲解MFC的一些常用类,先来说说CString类. CString类简介 CString类作为MFC的常用类,当之无愧.可以这样说,只要是从事MFC开发,基 ...

  4. DIV内容垂直居中

    css垂直居中属性设置vertical-align: middle对div不起作用,例如: <!DOCTYPE html> <html lang="zh-CN"& ...

  5. ES6之前模拟Map数据结构的写法

    在ES6之前JavaScript 里面本身没有map对象,但是用JavaScript的Array.Object来模拟实现Map的数据结构. 现在已经有Map对象了,这里记录一下之前的写法 Array方 ...

  6. JDBC批量插入数据优化,使用addBatch和executeBatch

    JDBC批量插入数据优化,使用addBatch和executeBatch SQL的批量插入的问题,如果来个for循环,执行上万次,肯定会很慢,那么,如何去优化呢? 解决方案:用 preparedSta ...

  7. RabbitMQ-C 客户端接口使用说明

    rabbitmq-c是一个用于C语言的,与AMQP server进行交互的client库.AMQP协议为版本0-9-1.rabbitmq-c与server进行交互前需要首先进行login操作,在操作后 ...

  8. 简单的Django实现图片上传,并存储进MySQL数据库 案例——小白

    目标:通过网页上传一张图片到Django后台,后台接收并存储进数据库 真是不容易!!这个案例的代码网上太乱,不适合我,自己摸索着写,终于成功了,记录一下,仅供自己参考,有的解释可能不对,自己明白就好, ...

  9. kafka调试遇到的问题

    在三台机器上以不同的端口部署了三个kafka和zookeeper实例,对应三套环境. 如: zk1:2181 zk2:2182 zk3:2183 kafka1:9092 kafka2:9093 kaf ...

  10. 用GDB调试Segmentation 段错误【转】

    本文转载自:http://blog.csdn.net/learnhard/article/details/4879834 调试Linux程序的时候,出现Segmentation Fault是最郁闷的事 ...