Django模型层中的字段关系(一对一、多对一、多对多)

字段关系

  • 字段关系是django维护表关系的方式;其中主要有一对一,多对一以及多对多,
  • 现在的一对一及多对一关系中需要设置on_delete属性用来描述当关联数据被删除时的操作,有如下一些

models.CASCADE:删除关联数据,与之关联也删除

models.PROTECT:删除关联数据,引发错误ProtectedError

models.SET_NULL:与之关联的值设置为null(前提FK字段需要设置为可空)

models.SET_DEFAULT: 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)

models.DO_NOTHING:删除关联数据,什么也不做

一对一关系

模型类使用OneToOneField用来定义一对一关系;

比如当你拥有一个老师表时,紧接着你还需要一个教授表,那么教授表可能拥有老师表的一系列属性,那么你还不想把老师表中的字段直接复制到教授表那么可以通过OnToOneField来实现教授表继承老师表。

其实,在使用模型类继承时,也隐含有一个一对一关系

  • OneToOneField(to, on_delete, parent_link=False, options)
class Teacher(models.Model):
    name = models.CharField(max_length=50)
    age = models.CharField(max_length=50)
    def __str__(self):
        return self.name
class Professor(models.Model):
    teacher = models.OneToOneField(Teacher,primary_key=True,on_delete=models.CASCADE)
    big_project = models.CharField(max_length=50)
    def __str__(self):
        return self.teacher.name

manage.py shell下进行数据库操作

>>> t1 = Teacher.objects.create(name='Jack',age='22')
>>> t2 = Teacher.objects.create(name='Bob',age='17')
>>> p1 = Professor.objects.create(teacher=t1,big_project='雾霾净化术')
>>> p1.teacher
<Teacher: Jack>
>>> p1.teacher = t2
>>> p1.save()
>>> p1.teacher
<Teacher: Bob>

在上面的测试中,看似已经将p1对应的教授变成了Bob;

但是在数据库中之前t1老师所对应的教授信息还存在,此时的赋值操作并不会覆盖掉教授他之前的教授数据,值是重新创建了一条。

正确的做法应该是将某一条数据的一对一关系通过delete关系先删除之后再重新赋予

多对一关系

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

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

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

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乐队含有A1,A2两名成员

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

你可能感兴趣的:(Django,模型层,字段关系)