一对多及多对多关系

  • 一对多关系
    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()

以上创建了两条老师数据

由于我们设置外键关联可以为空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
<Headmaster: 渔夫>
>>> c1 = Class.objects.all().first()
>>> c1
<Class: 一班>
>>> c1.teacher
<Headmaster: 伙夫>
>>> t1.delete()
(1, {'modelsapp.Headmaster': 1})
>>> c1 = Class.objects.all().first()
>>> c1
<Class: 一班>
>>> c1.teacher
>>> #这里什么都没有,因为此时C1的老师已经是个None了

注意:
1、要记得删除之后要重新获取一次数据,否则查看到的结果中还是之前获取到的有老师的班级数据
2、c1 = Class.objects.all().first()或是c1=Class.objects.filter(id=2).first(),都需要加.first(),否则会报错!!!


  • 多对多关系
    多对多关系在模型中使用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函数进行多值增加,在ManyToManyField所在模型的记录使用add方法

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

B1乐队含有A1,A2两名成员,B2乐队含有A1成员

B1.artist.all()
[<Artist: Bob>, <Artist: Jack>]
B2.artist.all()
<QuerySet [<Artist: Bob>]>

可以在音乐家表中查找某个音乐家属于哪些乐队

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来进行关系的断开,而不是直接使用delete,remove只会断开数据之间的联系,但是不会将数据删除
在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我们就可以关联到对应的士兵表,并且对应返回结果可以执行我们常用的filter,exclude等查询操作

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初始化为+,来取消反向查询

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容