我们都知道对于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. js之数组排序

    数组,大家都不陌生,只要是学编程的人都知道这个入门的数据结构,在js中也是有数组这个概念的,跟普通的数组一样只是定义的形式不同罢了.下面是一个数组的排序代码: <html> <hea ...

  2. PHP获取http头信息

    PHP手册提供了现成的函数: getallheaders (PHP 4, PHP 5) getallheaders — Fetch all HTTP request headers 说明 array ...

  3. 3:2 OGNL 简介

    OGNL : (对象图导航语言) 从一个对象到另一个对象 OGNL来源于Xwork: OGNL的作用: OGNL在数据进出值栈的时候进行类型转换

  4. Linux基础命令---gzexe

    gzexe 压缩可执行文件,在执行程序的时候可以自动实现解压.此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.SUSE.openSUSE.Fedora. 1.语法       g ...

  5. Linux服务器配置---ftp配置

    FTP其他配置 在配置文件中,还有一些关于vsftpd的其他设置,这里列出来 # Example config file /etc/vsftpd/vsftpd.conf # Allow anonymo ...

  6. HexDump.java解析,android 16进制转换

    HexDump.java解析android 16进制转换 package com.android.internal.util; public class HexDump { private final ...

  7. SVN版本服务器搭建

    windows:        https://blog.csdn.net/lu1024188315/article/details/74082227 SVN 的下载地址如下 http://torto ...

  8. web前端----jQuery动画效果

    动画效果 // 基本 show([s,[e],[fn]]) hide([s,[e],[fn]]) toggle([s],[e],[fn]) // 滑动 slideDown([s],[e],[fn]) ...

  9. P3868 [TJOI2009]猜数字

    [TJOI2009]猜数字 中国剩余定理 求解i=1 to n : x≡a[i] (mod b[i])的同余方程组 设 t= ∏i=1 to n b[i] 我们先求出 i=1 to n : x≡1 ( ...

  10. 使用node连接MongoDB数据 综本地及linux服务器记

    gitee地址 启动mongo D:\MongoDB> ./bin/mongod --dbpath ./data/db MongoDB 提供了简单的 HTTP 用户界面. 如果你想启用该功能,需 ...