【Django】model模型—字段关联关系:多对多

多对多

例子:如果 Pizza 含有多种 Topping (配料) -- 也就是一种 Topping 可能存在于多个 Pizza 中,并且每个 Pizza 含有多种 Topping。

python 复制代码
from django.db import models

# 配料
class Topping(models.Model):
    # ...
    pass


class Pizza(models.Model):
    # ...
    # Topping为关联模型
    toppings = models.ManyToManyField(Topping)

ManyToManyField字段名最好是关联的模型名的复数形式,以表示所关联模型的集合,需要添加一个位置参数,即关联的模型类名。
ManyToManyField字段可以在任何一个模型中添加,但不能同时在两模型中添加该字段。通常添加到更符合直观印象的模型当中。如上例中披萨中添加多种配料。

如果只是为了存储两个模型之间的多对多关系,上述方式就足够使用,但是如果需要在多对多的关系中存入其他数据,则需要一个中间模型发挥作用,在使用ManyToManyField字段的时候使用through参数指定中间模型。

例子,考虑一个需要跟踪音乐人属于哪个音乐组的应用程序:

python 复制代码
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)

中间模型的限制条件:

  • 中间模型M与两个多对多关系的模型A和B之间有且仅有一个指向A和指向B的外键,或者在ManyToManyField字段中指定through_fields参数。
  • 多对多关系中如果是自己指向自己,则中间模型中可以有指向同一个模型的两个外键,代表关系的两端(对比第一点中的AB),但是如果外键数量超过2个,则必须指定through_fields参数。

模型实例的应用:

python 复制代码
>>> 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()set() 来创建关系,只要为中间任何必需的字段指定through_defaults

python 复制代码
>>> beatles.members.add(john, through_defaults={"date_joined": date(1960, 8, 1)})
>>> beatles.members.create(
...     name="George Harrison", through_defaults={"date_joined": date(1960, 8, 1)}
... )
>>> beatles.members.set(
...     [john, paul, ringo, george], through_defaults={"date_joined": date(1960, 8, 1)}
... )

如果由中介模型定义的自定义中介表不对 (model1, model2) 对进行唯一性强制,允许多个值,则 remove() 调用将删除所有中介模型实例:

python 复制代码
>>> Membership.objects.create(
...     person=ringo,
...     group=beatles,
...     date_joined=date(1968, 9, 4),
...     invite_reason="You've been gone for a month and we miss you.",
... )
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # remove()删除所有关联ringo的实例
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>

clear() 方法可以用来删除一个实例的所有多对多关系:

python 复制代码
>>> # Beatles解散了
>>> beatles.members.clear()
>>> # 删除所有相关关系
>>> Membership.objects.all()
<QuerySet []>

可以通过中间模型的属性进行查询:

python 复制代码
# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
...     group__name="The Beatles", membership__date_joined__gt=date(1961, 1, 1)
... )
<QuerySet [<Person: Ringo Starr]>

多对多反向关系查询:

python 复制代码
>>> 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.'
相关推荐
marsh02064 分钟前
23 openclaw防止SQL注入:参数化查询与ORM安全使用
数据库·sql·安全·ai·编程·技术
源码之家5 分钟前
计算机毕业设计:基于Python的美食菜谱数据分析可视化系统 Django框架 爬虫 机器学习 数据分析 可视化 食物 食品 菜谱(建议收藏)✅
爬虫·python·数据分析·django·flask·课程设计·美食
原来是猿7 分钟前
为什么要配置环境变量?
linux·数据库·python
星辰_mya9 分钟前
MVCC 与事务隔离:MySQL 如何实现“读不阻塞写”?
java·数据库·mysql·面试·架构
m0_7381207212 分钟前
渗透测试——Ripper靶机详细横向渗透过程(rips扫描文件,水平横向越权,Webmin直接获取root权限)
linux·网络·数据库·安全·web安全·php
大能嘚吧嘚13 分钟前
Redis客户端框架-Redisson
数据库·redis·缓存
源码之家15 分钟前
计算机毕业设计:基于Python的汽车数据可视化分析系统 Django框架 Scrapy爬虫 可视化 车辆 懂车帝大数据 数据分析 机器学习(建议收藏)✅
python·信息可视化·django·flask·汽车·课程设计·美食
神龙斗士24017 分钟前
MySQL在Navicat中 库的操作 表的操作
数据库·mysql
攒了一袋星辰18 分钟前
10万级用户数据日更与定向推送系统的可靠性设计
java·数据库·算法
弘毅 失败的 mian18 分钟前
嵌入式系统观
数据库·经验分享·笔记·物联网·嵌入式