【Django杂记】2、Django3.2的ManyToManyField(多对多)中的through的作用

我们使用ManyToManyField自动生成第三张表,如果我们想要对自动生成的第三张表做一些额外的字段,对于这些情况,Django允许你指定用于控制多对多关系的模型,你可以在中间模型当中添加额外的字段,在ManyToManyField的时候使用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)
  • 你需要在设置中间模型的时候,显式地为多对多关系中涉及的中间模型指定外键;这种显式声明定义了这两个模型之间是如何关联的。
  • 在中间模型当中需要有一些限制:
    • 如果你的中间模型当中,可以有两个指向同一个模型的外键,但这两个外键分别代表多对多关系(不同)的两端。如果外键的个数超过两个,那么你必须通过ManyToManyField.through_fields参数显式的指向外键名,如下:
class Author(models.Model):
   name = models.CharField(max_length=32)
   books = models.ManyToManyField('Book',through='AuthorBook',through_fields=['author','book']

class AuthorBook(models.Model):
   author = models.ForeignKey(Author,related_name='a' ,on_delete=models.CASCADE)
   book = models.ForeignKey(Book, on_delete=models.CASCADE)
   tuijian = models.ForeignKey(Author,related_name='b',on_delete=models.CASCADE)
# 参数related_name:用来区分同一个表的外键,方便之后用来反向查询
  • 现在已经通过中间模型完成你的ManyToManyField(例子中的Membership),开始创建一些多对多关系了。你可以通过实例化中间模型来创建关系
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
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()方法会删除所有中间模型的实例
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>]>
# This deletes both of the intermediate model instances for Ringo Starr
beatles.members.remove(ringo)
beatles.members.all()
--> <QuerySet [<Person: Paul McCartney>]>
  • 方法clear()用于实例的所有多对多关系
beatles.members.clear()
# Note that this deletes the intermediate model instances
Membership.objects.all()
--> <QuerySet []>
  • 一旦你建立了自定义多对多关联关系,就可以执行查询操作。和一般的多对多关联关系一样,你可以使用多对多关联模型的属性来操作
Group.objects.filter(members__name__startswith='Paul')
--> <QuerySet [<Group: The Beatles>]>
  • 当你使用中间模型的时候,你也可以查询他的属性
Person.objects.filter(group__name='The Beatles', membership__date_joined__gt=date(1961,1,1))
--> <QuerySet [<Person: Ringo Starr]>
  • 你也可以直接查询Membership模型:
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.'
  • 访问同样信息的方法也可以通过Person对象来查询多对多递归关联关系:
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.'
posted @ 2022-04-26 14:05  郭祺迦  阅读(556)  评论(0)    收藏  举报