Django模型层操作之多对一、多对多、一对一表关系详解

前言

这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题

于是乎,我自己开发了一款面试宝典,希望能帮到大家,也希望有更多的Python新人真正加入从事到这个行业里,让python火不只是停留在广告上。

微信小程序搜索:Python面试宝典

或可关注原创个人博客:https://lienze.tech

也可关注微信公众号,不定时发送各类有趣猎奇的技术文章:Python编程学习

多对一关系

Django使用django.db.models.ForeignKey定义多对一关系。

ForeignKey需要一个位置参数:与该模型关联的类

class Info(models.Model):
  	user = models.ForeignKey(other_model,on_delete=models.SET_NULL)

生活中的多对一关系:班主任,班级关系。一个班主任可以带很多班级,但是每个班级只能有一个班主任

class Headmaster(models.Model):
    name = models.CharField(max_length=50)
    def __str__(self):
        return self.name
class Class(models.Model):
    class_name = models.CharField(max_length=50)
    teacher = models.ForeignKey(Headmaster,null=True,on_delete=models.SET_NULL)
    def __str__(self):
        return self.class_name
>>> H1 = Headmaster(name='渔夫')
>>> H1.save()
>>> H1
<Headmaster: 渔夫>
>>> H2 = Headmaster(name='农夫')
>>> H2.save()
>>> Headmaster.objects.all()
[<Headmaster: 渔夫>, <Headmaster: 农夫>]

以上创建了两条老师数据

由于我们设置外键关联可以为空null=True,所以此时在班级表创建时,可以直接保存,不需要提供老师数据

>>> C1 = Class(class_name='一班')
>>> C2 = Class(class_name='二班')
#如果外键设置不为空时,保存会引发以下错误
# IntegrityError: NOT NULL constraint failed: bbs_class.teacher_id
>>> C1.teacher = H1
>>> C2.teacher = H2
>>> C1.save()
>>> C2.save()

将老师分配个班级之后,由于班级表关联了老师字段,我们可以通过班级找到对应老师

虽然老师表中没有关联班级字段,但是也可以通过老师找到他所带的班级,这种查询方式也叫作关联查询

通过模型类名称后追加一个_set,来实现反向查询

>>> H1.class_set.all()
<QuerySet [<Class: 一班>]>

由于我们这是一个多对一的关系,也就说明我们的老师可以对应多个班级

  • 我们可以继续给H1老师分配新的班级
>>> C3 = Class(class_name='三班')
>>> C3.teacher = H1
>>> C3.save()
>>> H1.class_set.all()
[<Class: 一班>, <Class: 三班>]

一个班级只能对应一个老师,外键是唯一的,那么你在继续给C1班级分配一个新的老师时,会覆盖之前的老师信息,并不会保存一个新的老师

>>> H3 = Headmaster(name='伙夫')
>>> H3.save()
>>> C1.teacher
<Headmaster: 渔夫>
>>> C1.teacher=H3
>>> C1.save()
>>> C1.teacher
<Headmaster: 伙夫>
  • 把这个班级的老师删除,由于设置了外键字段可以为null,此时班级的老师选项为null
>>> t1 = Headmaster.objects.all().first()
>>> t1
>>> c1 = Class.objects.all().first()
<Headmaster: 渔夫>
>>> c1
<Class: 一班>
>>> c1.teacher
<Headmaster: 渔夫>
>>> t1.delete()
(1, {'modelsapp.Headmaster': 1})
>>> c1 = Class.objects.all().first()
>>> c1
<Class: 一班>
>>> c1.teacher
>>> #这里什么都没有,因为此时C1的老师已经是个None了
  • 注意:要记得删除之后要重新获取一次数据,否则查看到的结果中还是之前获取到的有老师的班级数据
多对多关系

多对多关系在模型中使用ManyToManyField字段定义

多对多关系可以是具有关联,也可以是没有关联,所以不需要明确指定on_delete属性

生活中,多对多关系:一个音乐家可以隶属于多个乐队,一个乐队可以有多个音乐家

class Artist(models.Model):
    artist_name = models.CharField(max_length=50)
    def __str__(self):
        return self.artist_name
class Band(models.Model):
    band_name = models.CharField(max_length=50)
    artist = models.ManyToManyField(Artist)
    def __str__(self):
        return self.band_name
  • 创建音乐家以及乐队
>>> from bbs.models import Artist,Band
>>> A1 = Artist.objects.create(artist_name='Jack')
>>> A2 = Artist.objects.create(artist_name='Bob')
>>> B1 = Band.objects.create(band_name='FiveMonthDay')
>>> B2 = Band.objects.create(band_name='SHE')

创建出两个乐队之后对其进行音乐家的添加

多对多字段添加时,可以使用add函数进行多值增加

>>> B1.artist.add(A1,A2)
>>> B2.artist.add(A2)

B1乐队含有A1A2两名成员

B2乐队含有A1成员

>>> B1.artist.all()
[<Artist: Bob>, <Artist: Jack>]
>>> B2.artist.all()	
[<Artist: Jack>]
  • 可以在音乐家表中查找某个乐家属于哪些乐队
>>> Band.objects.filter(artist=A1) # 这里使用的是我们模型类来进行查找。
[<Band: SHE>, <Band: FiveMonthDay>] # A1乐家属于,SHE以及FiveMonthDay
>>> Band.objects.filter(artist=A2)
[<Band: SHE>]
  • 也可以查找这音乐家在哪个乐队
>>> A1.band_set.all() # 直接通过具体数据对象进行查找
[<Band: SHE>, <Band: FiveMonthDay>]
>>> A2.band_set.all()
[<Band: SHE>]

多对多关联字段的删除,要使用remove来进行关系的断开

而不是直接使用deleteremove只会断开数据之间的联系,但是不会将数据删除

  • 在B1乐队中删除A1乐家
>>> B1.artist.remove(A1)
>>> B1.artist.all()
<QuerySet [<Artist: Bob>]>

关联表的查询

如果想要查询的字段在关联表,则使用表名小写__字段来进行跨表查询操作

  • 创建一个多对一关系的父子表,一个父亲可能有多个儿子
class Father(models.Model):
	name = models.CharField(max_length=30)
	age = models.CharField(max_length=30)
    def __str__(self):
        return self.name
class Son(models.Model):
    father = models.ForeignKey(Father,on_delete=models.CASCADE)
    name = models.CharField(max_length=30)
    def __str__(self):
        return self.name
  • 创建父亲和👦们
>>> f1 = Father.objects.create(name='Jack',age='30')
>>> s1 = Son.objects.create(name='Json',father=f1)
>>> s2 = Son.objects.create(name='Json2',father=f1)

>>> f2 = Father.objects.create(name='Bob',age='40')
>>> s3 = Son.objects.create(name='Json3',father=f2)
  • 查询所有父亲名字是jack的孩子
>>> Son.objects.filter(father__name__exact='Jack')
[<Son: Json>, <Son: Json2>]
  • 查询所有儿子名开头为J的父亲
>>> Father.objects.filter(son__name__startswith='J')
[<Father: Jack>, <Father: Jack>, <Father: Bob>]
  • 获取到某一个父亲的所有孩子,通过某一条数据的小写表名_set反向查询
>>> f1.son_set.all()
>>> [<Son: Json>, <Son: Json2>]

数据的反向查询

默认的,当有某一条数据获取到之后,我们可以通过模型类名称加上一个 _set,来实现反向查询

  • 现在设计两个表为军队和士兵表,并且士兵多对一关联军队
class Aramy(models.Model):
	name = models.CharField(max_length=30)
    def __str__(self):
        return self.name
class Soldier(models.Model):
    aramy = models.ForeignKey(Aramy,on_delete=models.CASCADE)
    name = models.CharField(max_length=30)
    def __str__(self):
		return self.name
  • 创建一些数据
>>> a1 = Aramy(name='一军')
>>> a1.save()
>>> s1 = Soldier(name='张三',aramy=a1)
>>> s1.save()
>>> s2 = Soldier(name='李四',aramy=a1)
>>> s2.save()

通过soldier_set我们就可以关联到对应的士兵表

并且对应返回结果可以执行我们常用的filterexclude等查询操作

>>> a1.soldier_set.all()
[<Soldier: 张三>, <Soldier: 李四>]
>>> a1.soldier_set.filter(name='张三')
[<Soldier: 张三>]

也可以通过定义关联字段中的related_name值,来实现自定义的反向查询名字

  • 注意related_name的值必须唯一
class Aramy(models.Model):
    name = models.CharField(max_length=30)
    def __str__(self):
        return self.name
class Soldier(models.Model):
    aramy = models.ForeignKey(Aramy,on_delete=models.CASCADE,related_name='soldier')
    name = models.CharField(max_length=30)
    def __str__(self):
        return self.name

接下来通过某条数据反向查询

>>> a1 = Aramy.objects.all()[0]
>>> s1 = Soldier.objects.get(name='张三')
>>> a1.soldier.all()
[<Soldier: 张三>, <Soldier: 李四>]

注意related_name一定是一个唯一的值,否则反向查找时会出现二异性错误,也可以将related_name初始化为+,来取消反向查询

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李恩泽的技术博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值