【Django】 ManyToManyField 获取数据时 按添加时间排列

在Django中,ManyToManyField是常用的Field,实现数据库中多对多模型。

例如,人与团体的关系(一个人可加入多个团体,一个团体有多个人):

class Person(models.Model):
    name = models.CharField(max_length=50)

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, related_name='groups')

但是,当我们通过group.members(或person.groups)获取一个组织的所有成员(或获取这个人参加的所有组织)时,拿到的数据是按照group_id (或person_id)排列的,有时这样的行为不符合预期:

比如前端展示这个团体的人,希望是按照加入顺序来展示,而非按照人的id排序。
试想,当团队人数很多,你加了一个人后,他不是出现在最后,而是钻在一群人的中央,你很可能找不到他了!甚至怀疑自己有没有添加成功。这种类似的场合很常见

解决方案(无需改变数据库结构)

Django在实现ManyToManyField时,会默默引入一张“中间表”,它默认包括3列:idmodel1_idmodel2_id。以上面的Person和Group为例,这3列就是:idperson_idgroup_id。这个id是自增索引,当然也意味着:建立的时间晚,id相对就大。我们可以通过id来实现“按添加时间排列”,而无需对数据库结构做任何改变!

为了实现它,我们获取数据的方式需要适当改变:

group1 = Group.objects.get(id=1)
# 之前的方式,数据按 person_id 排列
group1.members.all()
# 改变后,数据按添加时间排列
Group.members.through.objects.filter(group=group1).order_by('id').all()

原理是什么?

刚才提到,Django会默默引入一张“中间表”,这张“中间表”其实也是对应着一个隐藏的"Modal"的(生成顺序其实是Django先生成隐藏的Modal,再根据这个Modal生成那个数据库表)。Group.members.through这句话其实是引用了那个隐藏的"Modal"。它后面的.objects,就像对待普通Modal一样,对待它就可以了。

“隐藏的Modal”是什么?

在本文开头的例子,隐藏的"Modal"长这个样子:

class Person_Group(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)

它生成的数据库表就是3列:idperson_idgroup_id

其实,这张表你也可以自己定义出来,不让Django自动生成,只是你需要多提供一个参数throuth给ManyToManyField:

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')

class Membership(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    invite_reason = models.CharField(max_length=64)

这里我们给中间表起了名字Membership,名字随意起,through参数跟名字是对应的就行。

为什么through是写的字符串'Membership',而非直接写Membership
因为Python解析器解析类的定义时,会把类的属性、方法都定义完,才会继续解析下一个类(在定义members时,Membership尚未被解析,此时如果引用它会报错,所以Django面对这种问题的解决方案就是——先用字符串代替,这是很常见的做法)

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页