django ORM模型-一对多、多对多操作

一.外键使用

在 MySQL 中,如果使用InnoDB引擎,则支持外键约束。(另一种常用的MyIsam引擎不支持外键)

定义外键的语法为fieldname=models.ForeignKey(to_class,on_delete=' ',options),第一个参数表示引用哪个模型,第二个参数表示如果外键引用的模型删除,该字段对应的的值应该怎么处理,第三个语法为其他字段参数。

django ORM模型常用的on_delete的值

models.CASCADE:级联删除,即外键对应的那条数据删除了,这条数据也会被删除

models.SET_NULL:设置为空,即外键对应的那条数据删除了,这条数据对应的字段设置为null,前提是这个字段可以设置为空

models.SET_DEFAULT:设为默认值,即外键对应的那条数据删除了,这条数据对应的字段设置为默认值,前提是这个字段要设置一个默认值

models.PRODECT:受保护,即不允许删除外键对应的那条数据

创建两个模型,学生和班级,一个学生只能属于一个班级,一个班级可以有多个学生,学生与班级为多对一的关系。

class Students(models.Model):#学生模型
    sname=models.CharField(max_length=20)
    age=models.IntegerField()
    gender=models.BooleanField()
    cls=models.ForeignKey('Classes',on_delete=models.CASCADE)#通过cls创建学生模型与班级模型的外键关系
    def __str__(self):#自定义返回样式
        return '%s,%s,%s,%s'%(self.sname,self.age,self.gender,self.cls)
    class Meta:#自定义映射到数据库的表名
        db_table='students'

class Classes(models.Model):#班级模型
    cname=models.CharField(max_length=50)
    headmaster=models.CharField(max_length=20)
    def __str__(self):
        return '%s,%s'%(self.cname,self.headmaster)
    class Meta:
        db_table='classes'
创建学生模型和班级模型

上述cls字段的参数'Classes'表示引用Classes表的主键id作为外键,完整写法为cls=models.ForeignKey(to=''Classes,to_field='id',on_delete=models.CASCADE),to表示表,to_field表示字段。

需要注意的是,在Students模型定义时,外键属性名称为cls,但是在映射到数据库时django会将字段名称加上_id,即Student模型的cls属性在数据内对应的字段名称为cls_id。 

如果引用的模型在另外一个app中,那么需要在各自的urls.py的文件中先定义命名空间app_name='appname',再在to_class中加上app的名字,以上如果Classes是在另外一个名叫app01的app中,那么Students的cls的属性定义为cls = models.ForeignKey("app01.Classes",on_delete=models.CASCADE)。

如果要引用自己作为外键,to_class可以写为self,或者app的名字。

二.一对多(多对一)操作

如果模型A被模型B引用用作外键,django会自动给A模型添加一个属性,属性名称为B模型的小写_set,即b_set属性,表示并且该属性同样可以调用all()、objects.get()、objects.filter()、objects.first()等方法进行查询。

例如上述例子,班级会有一个students_set属性,要想获取某个班级c下的所有学生,可通过c.students_set.all()获取。

如果在B模型创建外键字段时自定义一个related_name=‘’,则会覆盖系统自动创建的属性名称,例如上述Students的cls的属性如果定义为cls = models.ForeignKey("Classes",on_delete=models.CASCADE,related_name=‘allstudents’),那么再要获取班级c下的所有学生,则应该通过c.allstudent.all()获取,并且班级不再有students_set属性。通常建议建议自定义related_name。

①增加记录

插入一个班级c和一个学生s记录

c=Classes.objects.create(cname='高三一班',headmaster='王老师')#由于学生模型会引用班级模型,因此需要先创建班级,否则后面创建学生的时候会报错 
#方法一:直接对底层数据库进行赋值 
s=Students.objects.create(sname='张三',age=19,gender='1',cls_id=1)
#方法二:设置Students模型的cls属性等于要引用的模型实例 
s=Students.objects.create(sname='张三',age=19,gender='1',cls=c)

②查询记录

对于Students模型来说,对象s的cls属性即为对应的班级对象,可通过type(s.cls)查看,结果为<class 'app01.models.Classes'>,因此可通过s.cls.属性再获取班级相关信息

例如要获取学生s所在的班级名称:

cname1=s.cls.cname #方法一
cname2=Students.objects.filter(sname='张三').values('cls__cname') #方法二
cname3=Classes.objects.filter(students__sname='张三').values('cname') #方法三

再例如要获取班级c下的学生姓名:

sname1=c.students_set.all().values('sname') #方法一
sname2=Classes.objects.filter(cname='高三一班').values('students__sname') #方法二
sname3=Students.objects.filter(cls__cname='高三一班').values('sname') #方法三

③万能的双下划线查询

双下划线可实现跨表查询。学生模型引用班级模型做外键,通过学生查询班级时,通过外键属性__进行跨表,通过班级查询学生时,直接通过表名__进行跨表。

例如上述的cname2,'cls__cname'表示通过外键跨到班级表,并且获取班级表的cname属性

而对上述的cname3,'students__sname'表示通过表名跨到学生表,并且获取学生表的sname属性。

上述例子,如果要将一篇文章加到某个作者下面,除了上面的方法外,还可以使用如下方法

user=User.objects.first()
article=Article(title='abc',content='abcdefghijklmn')
article.save()
user.articles.add(article)#将article加入user用户下
user.save()

上面这种写法,在article加入user作者前必须先保存article,这个前提是article的author字段可以为空,如果不能为空则这种写法会报错。而用下面这种方法则不会存在该问题。

user=User.objects.first()
article=Article(title='abc',content='abcdefghijklmn')
user.articles.add(article,bulk=False)

三.多对多操作

新创建两个模型,老师和班级,一个老师可以教多个班级,一个班级也会有多个老师。

class Teachers(models.Model):
    tname=models.CharField(max_length=20)
    age=models.IntegerField()
    gender=models.BooleanField()
    cls=models.ManyToManyField('Classes')
    def __str__(self):
        return '%s,%s,%s,%s'%(self.tname,self.age,self.gender,self.cls)
    class Meta:
        db_table='teachers'

class Classes(models.Model):
    cname=models.CharField(max_length=50)
    def __str__(self):
        return self.cname
    class Meta:
        db_table='classes'
创建老师和班级模型

将上述两个模型映射到数据库,会生成三张表,teachers表、classes表和teachers_cls表。

其中teachers表只有四个字段,id、tname、age和gender,并没有cls字段。

teachers_cls为django为多对多关系自动创建的一张表,命名规则为创建多对多关系的模型名称小写_多对多字段小写,只包含三个字段,id、teachers_id和classes_id。

如果不想使用django自动创建的第三张表,可以自己创建,如下,并使用该表来维护模型的多对多关系。自己创建的第三张表还可以增加另外的字段。

class Teachers(models.Model):
    tname=models.CharField(max_length=20)
    age=models.IntegerField()
    gender=models.BooleanField()
    #cls=models.ManyToManyField('Classes')
    def __str__(self):
        return '%s,%s,%s,%s'%(self.tname,self.age,self.gender,self.cls)
    class Meta:
        db_table='teachers'

class Classes(models.Model):
    cname=models.CharField(max_length=50)
    def __str__(self):
        return self.cname
    class Meta:
        db_table='classes'

class Teachers_Classes(models.Model)
    t = models.ForeignKey('Teachers')
    c = models.ForeignKey('Classes')
    ctime = models.DateField()
    class Meta:
        db_table='teachers_classes'
        unique_together=[('t','c'),]    #表示对t和c字段创建联合唯一索引
手动创建多对多的第三张表

上述方法手动创建第三张表,原本自动生成的第三张表还是会生成。如果要手动创建第三张表并且不生成django自动创建的表,需要使用ManyToManyField并且添加参数m = models.ManyToManyField( to='Classes',through='Teachers_Classes',through_fields=['t','c'])。这种方法不推荐。

①增加记录并绑定关系

c1=Classes.objects.filter(cname='一年级').first()
c2 = Classes.objects.filter(cname='二年级').first()
t1=Teachers.objects.filter(tname='李老师').first()
t2 = Teachers.objects.filter(tname='王老师').first()
c1.teachers_set.add(t1)#将t1老师绑定到c1班级,此处参数可以为老师对象,也可为老师id
t2.cls.add(c2) #将c2班级绑定到t2老师,此处参数可以为班级对象,也可以为班级id

上述,c1.teachers_set即外键中的用法,t2.cls为t2对应的班级集合

②查询记录

c=Teachers.objects.filter(tname='李老师').first().cls.all().values('cname') #李老师所带班级的名称
t=Classes.objects.filter(cname='一年级').first().teachers_set.all().values('tname') #一年级老师的名字

③删除绑定关系

Teachers.objects.filter(tname='李老师').first().cls.remove(5)#解除李老师与id为5班级的绑定关系,此处参数也可以为班级对象
Classes.objects.filter(cname='一年级').first().teachers_set.remove(5)#解除一年级与id为5老师的绑定关系,此处参数也可以为老师对象

④重置绑定关系

Teachers.objects.filter(tname='李老师').first().cls.set([4,5]) #将id为4和5的班级与李老师绑定,此处参数也可以为班级对象
Classes.objects.filter(cname='一年级').first().teachers_set.set([5,6]) #将id为5和6的老师与一年级绑定,此处参数也可以为老师对象
原文地址:https://www.cnblogs.com/Forever77/p/10154070.html