zoukankan      html  css  js  c++  java
  • 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的老师与一年级绑定,此处参数也可以为老师对象
  • 相关阅读:
    AxWindowsMediaPlayer的详细用法
    C# 播放器控件的常用方法
    在 C# 中使用文件名启动应用程序
    备份和恢复Android手机的NAND分区
    刷Recovery的方法
    WPF RadioButton的探究,为啥选中一个其他都自动不选中了呢?
    WPF大牛Josh Smith 转投 iOS 的怀抱
    终端服务的剪贴板的缺陷,导致WPF调用Clipboard.SetText() 失败
    用df命令查看分区情况
    WPF与输入法冲突研究之二:TextInput事件的BUG?
  • 原文地址:https://www.cnblogs.com/Forever77/p/10154070.html
Copyright © 2011-2022 走看看