TOC

条件查询

    在日常的数据库开发中,条件查询用得非常之多,比如常见的SQL查询过程中用到的与(and)、或(or)、非(not),这种在where语句后面实现多条件查询的场景极其频繁,那么对于Django的ORM框架来讲,实现过滤查询的主要途径之一就是filter方法,但是我们经过测试,我们会发现,我们的filter函数,仅支持一个表达式,并不支持多个表达式联合,当然,这也有解决方法;

与条件查询

    与条件查询,即AND查询,如果我们期望在filter函数内部使用AND这种多条件并行成立的查询语句,主要有五种,其实它们本质没有任何区别,生产的SQL语句是一摸一样的,可以说没有好坏之分,如何选择,看个人习惯;
# 方式一
print(Users.objects.filter(id__gt=1, name__contains="c"))  # <QuerySet [<Users: 2 cfj>, <Users: 4 csw>]>
# 方式二
print(Users.objects.filter(id__gt=1).filter(name__contains="c"))  # <QuerySet [<Users: 2 cfj>, <Users: 4 csw>]>
# 方式三
print(Users.objects.filter(id__gt=1) & Users.objects.filter(name__contains="c"))  # <QuerySet [<Users: 2 cfj>, <Users: 4 csw>]>
# 方式四
from django.db.models import Q
print(Users.objects.filter(Q(id__gt=1), Q(name__contains="c")))  # <QuerySet [<Users: 2 cfj>, <Users: 4 csw>]>
# 方式五
from django.db.models import Q
print(Users.objects.filter(Q(id__gt=1) & Q(name__contains="c")))  # <QuerySet [<Users: 2 cfj>, <Users: 4 csw>]>
方式三
    可以看到方式三,它有点特殊,它是使用"&"符合联合两个filter查询对象来实现的,它是利用运算符重载的方式实现的,对结果集对运算符进行了重载;
    但是实际上,它并不会将前面的查询集拿到,再拿到后面的查询集然后进行重载,它是将两个查询集在正式生成查询SQL之前,将两个查询集做and处理,然后生成一条SQL语句发往数据库进行查询;
print(Users.objects.filter(id__gt=1) & Users.objects.filter(name__contains="c"))  # <QuerySet [<Users: 2 cfj>, <Users: 4 csw>]>
# 生成的SQL:SELECT `users`.`id`, `users`.`name`, `users`.`age`, `users`.`gender`, `users`.`brith_date` FROM `users` WHERE (`users`.`id` > 1 AND `users`.`name` LIKE BINARY '%c%') ORDER BY `users`.`id` ASC  LIMIT 21
方式四
    方式四和方式五本质没什么差别,它采用了一个Q对象,这个Q对象,就可以用来帮助我们来实现与(and)、或(or)、非(not)等条件的实现,它的内部原理其实和方式三是一样的,只不过它是将两个查询条件进行合并;
print(Users.objects.filter(Q(id__gt=1) & Q(name__contains="c")))  # <QuerySet [<Users: 2 cfj>, <Users: 4 csw>]>
# 生成的SQL语句:SELECT `users`.`id`, `users`.`name`, `users`.`age`, `users`.`gender`, `users`.`brith_date` FROM `users` WHERE (`users`.`id` > 1 AND `users`.`name` LIKE BINARY '%c%') ORDER BY `users`.`id` ASC  LIMIT 21

或条件查询

    或条件查询,即OR查询,对于或条件查询,也有多种选择,主要有三种,一种是用in来完成,第二种和第三种都是使用或条件运算符来实现的,如下;
# 方式一
print(Users.objects.filter(id__in=[1, 2]))  # <QuerySet [<Users: 1 cce>, <Users: 2 cfj>]>
# 方式二
print(Users.objects.filter(id=1) | Users.objects.filter(id=2)) # <QuerySet [<Users: 1 cce>, <Users: 2 cfj>]>
# 方式三
print(Users.objects.filter(Q(id=1) | Q(id=2)))  # <QuerySet [<Users: 1 cce>, <Users: 2 cfj>]>

非条件查询

    非条件查询,即NOT查询,非条件查询主要使用Q对象来实现,Q对象支持使用"~"取反运算符来实现取反操作,如下;
# 单条件取反
print(Users.objects.filter(~Q(id__in=[1, 2, 3])))  # <QuerySet [<Users: 3 yml>, <Users: 4 csw>]>
# # 多条件取反一
print(Users.objects.filter(~Q(id__gt=1) | ~Q(id__lte=3)))  # <QuerySet [<Users: 1 cce>, <Users: 4 csw>]>
# 多条件取反二
print(Users.objects.filter(~(Q(id__gt=1) & Q(id__lte=3))))  # <QuerySet [<Users: 1 cce>, <Users: 4 csw>]>

聚合查询

    到现在,我们通过结果集方法、Lockup表达式和条件查询,已经可以实现非常复杂的查询了,那么接下来就是我们在日常开发中常见的聚合查询,聚合查询属于SQL中的高等级查询,那么在Django的ORM框架中,也提供了相关支持,在Django的ORM中聚合主要是使用结果集的aggregate方法来实现的;
语法结构:结果集.aggregate(别名 = 聚合函数("聚合字段名"))
    在aggregate方法中,我们可以传入聚合函数,进行聚合查询,主要有常见聚合函数有,最大、最小、求和及总数几项,它们均在from django.db.models模块下,如下示例;
from django.db.models import Max, Min, Avg, Sum, Count

# 求总数
print(Users.objects.all().aggregate(Count('name')))  # {'name__count': 4}
# 求平均值
print(Users.objects.all().aggregate(Avg('age')))  # {'age__avg': 28.0}
# 求和
print(Users.objects.all().aggregate(Sum('age')))  # {'age__sum': 112}
# 求最大值
print(Users.objects.all().aggregate(Max('age')))  # {'age__max': 48}
# 求最小值
print(Users.objects.all().aggregate(Min('age')))  # {'age__min': 14}
# 聚合合并
print(Users.objects.all().aggregate(Sum('age'), Avg('age')))  # {'age__sum': 112, 'age__avg': 28.0}
# 取别名
print(Users.objects.all().aggregate(sum=Sum('age'), avg=Avg('age')))  # {'sum': 112, 'avg': 28.0}

分组查询

    Django当中的ORM框架分组主要是通过annotate函数来实现的,对于SQL的分组需要搭配聚合函数来实现,annotate函数也一样,需要搭配聚合函数来实现,但是annotate和aggregate不同的,aggregate的返回结果是一个字典,而annotate则返回一个QuerySet查询集,查询集里面的元素是对象,这个对象就是数据库中查询的结果,返回后由ORM模型类创建出的对象。语法如下;
语法结构:结果集.values("分组字段").annotate(聚合函数("聚合字段名"))
    使用annotate进行分组,需要搭配values方法来实现,values方法主要用来指定分组的字段名,需要注意的是,values方法必须在annotate方法之前,如下示例;
print(Users.objects.values('gender').annotate(count=Count('gender')))
# <QuerySet [{'count': 2, 'gender': 1}, {'count': 2, 'gender': 0}]>
    有时候,我们会发现,分组失效了,语法没问题,但是得到的分组结果不对,这主要是因为,我们在模型类中,指定了ordering元属性,默认的排序字段,这个字段会影响分组,如下;
# modles.py模型类
class Users(models.Model):
    id = models.IntegerField(primary_key=True)
    name = models.CharField(null=False, max_length=14)
    age = models.IntegerField(null=False)
    gender = models.SmallIntegerField(null=False)
    brith_date = models.DateField(null=False)

    class Meta():
        db_table = "users"
        ordering = ['id']  # 影响分组的,排序字段

    def __repr__(self):
        return "<Users: %s %s>" % (self.id, self.name)
# orm.py测试文件
print(Users.objects.values('gender').annotate(count=Count('gender')))
# <QuerySet [{'gender': 1, 'count': 1}, {'gender': 0, 'count': 1}, {'gender': 0, 'count': 1}, {'gender': 1, 'count': 1}]>
    出现这样的问题,主要是因为当我们在元属性里面指定了ordering属性之后,在进行分组时,默认会使用ordering指定的字段名进行分组,所以尽量少使用ordering属性,或者在annotate方法之后,加入order_by方法,忽略ordering属性里面的所有字段;
# 加入order_by方法
print(Users.objects.values('gender').annotate(count=Count('gender')).order_by())
# <QuerySet [{'gender': 1, 'count': 2}, {'gender': 0, 'count': 2}]>

去重

    对于Django框架的ORM来讲,它也提供了去重的手段,主要采用结果集的distinct方法来实现,但是需要注意的是,如果我们需要指定去重字段,那么就需要在distinct方法前面新增一个values或者values_list方法来指定去重字段;
    我们通过源码可以看到,distinct方法内部支持一个field_names参数来指定去重字段,但是我们需要知道的是这种方式只支持PostgreSQL数据库,其他类型的数据库均不支持;
# 模型类
class Users(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(null=False, max_length=14)

    class Meta():
        db_table = "users"

    def __str__(self):
        return "<Users: %s>" % (self.name)

# 插入测试数据
Users.objects.create(name="cce")
Users.objects.create(name="csw")
Users.objects.create(name="cce")

# 未去重
print(Users.objects.all().values('name')) # <QuerySet [{'name': 'cce'}, {'name': 'csw'}, {'name': 'cce'}]>
# 已去重
print(Users.objects.all().values('name').distinct()) # <QuerySet [{'name': 'cce'}, {'name': 'csw'}]>

关联结构

    前面上述的都是单表操作,而对于Django的ORM框架来讲,多表查询只支持三种方式,即一对一、多对一和多对多三种方式,它不支持SqlAchemy那种两表连接查询,不定义外健两表是不支持多表连接查询的,所以一对一、多对一和多对多都是通过外健来实现的;
    对于一对一来讲,其实很简单,比如一个身份证只能对应一个人,并且一个人也只能有一个身份证,这就是一对一的关系,在Django的ORM中创建就需要用到一对一的关系模型,而多对一来讲,比如一个家庭可以有多个人,但是这多个人都属于同一个家庭,这就是所谓的多对一关系,而多对多,也非常简单,比如一个学生可以报多门课程,而每门课程又可以有多个学生;
    这些所谓的关系模型,在Django的ORM框架中都是通过外键来实现的,那么在Django的ORM框架中,外键的建立主要是通过django.db.models模块下提供了ForeignKey类来定义,它将两个表建立外键关系,形成关联;
    那么根据关系模型的种类多样,以及外键的相关配置,ForeignKey类也提供多个参数可供设置,他们主要是用来描述外键关系的建立,如下;
to:设置要关联的表;
to_field:关联到需要关联对象的字段名称,默认情况下,会使用关联对象的主键作为关联字段;
db_constraint:只有在db_constraint=True时才会在数据库上建立外键约束, 在该值为False时不建立约束,默认为True;
related_name:该参数值为一个字符串,它的主要作用是,在进行反向查询时所使用的名称,所谓反向就是,在一的一方向多的一方查找;
on_delete:及联操作,表示当主表中的关联字段(被外键关联的字段)的数据被删除时,子表对应的关联数据行执行的处理操作,主要有如下几种;
    models.CASCADE:关联删除,表示主表中的数据被删除时,子表关联的数据会一起删除;
    models.PROTECT:阻止删除,如果主表的某条记录被子表关联,那么主表这条记录不允许删除,这是MySQL中的默认策略;
    models.SET_NULL:置空处理,表示主表中的数据被删除时,子表关联的字段,是"子表关联的字段,并非子表关联的整条记录",会设成null,所以子表必须设置blank=True;
    models.SET_DEFAULT:删除关联数据,与之关联的值设置为默认值,前提字段需要设置默认值;
    models.DO_NOTHING:如果主表的某条记录被子表关联,当主表中被关联的数据删除时,子表不做任何处理操作;
  • 重点:这里需要注意的唯一一个重点就是,不论是多对一,多对多,外键都在多的一方;

多对一

    上面说过,多对一,就是一方对应多方,比如日常生活中,一个职能部门存在多个人的,比如IT部门,这个部门下有多个IT工程师,那么这多个IT工程师都只属于这一个部门,这就是一种多对一的模型;
    那么在Django的ORM层面去建立这样一个多对一的模型也非常的简单,直接使用ForeignKey外键来实现的两张关联表,就默认是一种多对一的模型;
    但是需要注意的是,在关系映射模型下,ForeignKey所在的一方,我们称之为多的一方,如下就是一种多对一的模型表;
class Many(models.Model):
    '多方'
    id = models.AutoField(primary_key=True)
    name = models.CharField(null=False, max_length=14, unique=True, verbose_name='员工姓名')
    info = models.ForeignKey(to='One', to_field='id', on_delete=models.CASCADE, db_column='info')
    class Meta():
        db_table = "many"  # 表名
    def __str__(self):
        return "<Many: %s %s>" % (self.name, self.info)

class One(models.Model):
    '一方'
    id = models.AutoField(primary_key=True)
    department = models.CharField(null=False, max_length=14, verbose_name='部门名称')
    class Meta():
        db_table = "one"  # 表名
    def __str__(self):
        return "<One: %s>" % self.department
创建测试数据
    因为,我们使用了ForeignKey外键字段,也就是说,我们的Many表是需要关联到One表中的某一条记录的,那么为了更好的测试我们的关系映射模型操作,所以现在就创建一些基础数据,这些基础数据,我们只需要在ForeignKey关联的外键字段上面创建即可;
# 创建测试数据
One.objects.create(department="技术部")
One.objects.create(department="研发部")
One.objects.create(department="董事局")
Many.objects.create(name='caichangen',info=One.objects.filter(department='技术部').first())

增加操作
    正向操作就是在多的一方,向一的一方操作,换句话说,就是在有ForeignKey的一方,向没有ForeignKey的一方操作,那么正向增加操作,按照上述的模型来看,就是在Many表中增加数据,并和One表中的数据进行关联,如下;
# 新增员工
Many.objects.create(name='caifengjun', info=One.objects.filter(department='研发部').first())

删除操作
    正向删除操作就没什么好说的了,直接删除查询到的数据即可;
# 直接调用对象的delete方法进行删除
Many.objects.filter(name='caichangen').first().delete()

修改操作
    正向修改操作和正向新增操作,其实大同小异,首先需要先将要修改为哪个部门查到,然后将这个部门赋予给指定到人员即可;
# 更新操作
Many.objects.filter(name='caifengjun').update(info=One.objects.filter(department='董事局').first())

  • 注意:需要注意的是,update更新方法,是属于QuerySet对象的,而delete删除数据的方法是属于模型对象的;
正反查询
    所谓正反查询的意思就是,是在多的一方向一的一方操作还是,在一的一方向多的一方操作,对于多对一,来讲,在多的一方向一的一方操作,称之为正向操作,反之,在一的一方向多的一方操作,称之为反向操作,如果我们的模型存在反向操作的情况,那么最好在多的一方的ForeignKey里面加入related_name属性,来指定反向操作时,所使用的属性名;
正向查询操作
    正向查询也非常简单,正向查询对于多对一来讲,是从多的一方往一的一方查询,这就是正向查询,在多的一方里面ForeignKey字段所属的属性名的主要作用就是用来访问一的一方,那么结合上面的例子来讲,就是我们可以使用Many的对象的info属性来访问到,One的一方,如下所示;
# 正向查询,通过多的一方查询到一的一方
print(Many.objects.first().info) # <One: 董事局>
反向查询操作
    反向查询,和正向查询的查询入口相反,反向查询是从一的一方往多的一方查询,拿上述例子来将,我们可以通过部门来查询到这个部门内所有的员工;
    那么在一的一方访问到多的一方主要有两种途径,第一种是在多的一方的ForeignKey字段类型提供了related_name参数,第二种,是在多的一方的ForeignKey字段类型没有提供related_name参数;
    如果提供了related_name参数,那么就可以直接使用related_name参数值,来访问到多的一方,如果没有给定related_name参数,那么我们如果希望在,一的一方访问到多的一方,可以使用"多的一方映射类名_set"来访问,如下;
# 使用related_name参数值来访问
print(One.objects.filter(department='董事局').first().many.all()) # <QuerySet [<Many: caifengjun <One: 董事局>>]>
# 使用映射类名_set的访问来访问
print(One.objects.filter(department='董事局').first().many_set.all()) # <QuerySet [<Many: caifengjun <One: 董事局>>]>

一对一

    一对一其实非常简单,它是建立在我们的多对一的基础之上的,可以看到,我们多对一,ForeignKey所在的一方称之为多的一方,另一方则为一的一方,那么在这种情况之下,我们其实完全可以在ForeignKey所在的一方的外键字段属性里面新增一个,unique的属性,建立唯一所以,从而实现一对一结构;
class Users(models.Model):
    '一方'
    id = models.AutoField(primary_key=True)
    username = models.CharField(null=False, max_length=14, unique=True, verbose_name='账号名')
    info = models.ForeignKey(to='Info', to_field='id', on_delete=models.CASCADE, db_column='info', unique=True) # 一对一实现的关键要点,unique

    class Meta():
        db_table = "user"  # 表名

    def __str__(self):
        return "<One: %s %s>" % (self.username, self.info)

class Info(models.Model):
    '一方'
    id = models.AutoField(primary_key=True)
    id_card = models.CharField(null=False, max_length=14, verbose_name='身份证', unique=True) # 实现一对一结构

    class Meta():
        db_table = "info"  # 表名

    def __str__(self):
        return "<One: %s>" % self.id_card
  • 注意:对于一对一的CURD操作,和多对一一致,因此,不在此赘述;
一对一扩展
    我们在使用上述的方式来创建一对一结构时,我们可以看到Django会给我们给出,如下提示,大概的意思是,在ForeignKey字段上设置unique=True属性,实现一对一,带来的结果和,使用OneToOneField来实现一对一的效果相同;
WARNINGS:
user.Users.info: (fields.W342) Setting unique=True on a ForeignKey has the same effect as using a OneToOneField.
        HINT: ForeignKey(unique=True) is usually better served by a OneToOneField.
    换而言之,Django为了规范化操作,给我们提供了一个OneToOneField的字段,它的主要作用就是创建一对一结构,相比ForeignKey的方式来讲,使用OneToOneField来创建一对一结构,Django为我们提供了多的功能,所以,我们也可以直接使用OneToOneField字段来实现一对一结构;
class Users(models.Model):
    '一方'
    id = models.AutoField(primary_key=True)
    username = models.CharField(null=False, max_length=14, unique=True, verbose_name='账号名')
    info = models.OneToOneField(to='Info', to_field='id', on_delete=models.CASCADE, db_column='info') # 实现一对一结构

    class Meta():
        db_table = "user"  # 表名

    def __str__(self):
        return "<One: %s %s>" % (self.username, self.info)


class Info(models.Model):
    '一方'
    id = models.AutoField(primary_key=True)
    info = models.OneToOneField(to='Info', to_field='id', on_delete=models.CASCADE, db_column='info',
                                related_name="user") # 实现一对一结构

    class Meta():
        db_table = "info"  # 表名

    def __str__(self):
        return "<One: %s>" % self.id_card
增删改数据
    对于一对一结构,它的增加、删除和修改数据都和多对一一样,在此就做个简单的示例,如下;
# 新增用户
Users(username='cce', info=Info.objects.create(id_card="420")).save() # 直接给定外键表对象
Users(username='csw', info_id=Info.objects.create(id_card="410").id).save() # 给定外键表对象的id
# 删除数据
Users.objects.first().delete()
# 修改数据
UserObj=Users.objects.first()
UserObj.username="caidaye"
UserObj.save()
查询数据
    对于多对一的查询,根据不同的模型字段类型,划分为两种方式,第一种方式为ForeignKey字段类型的查询方式,该方式和多对一一样,第二种方式为OneToOneField的方式,该方式对ForeignKey字段进行了优化;

ForeignKey方式
    ForeignKey的方式,其实我们直接使用上门的多对一的查询方式即可,他们几乎完全一摸一样,通过反查询将得到一个只有一个元素的列表,如下;
# 正向查询
UserObj = Users.objects.first()
print(UserObj.info.id_card)  # 420

# 反向查询(没有给定related_name参数)
InfoObj = Info.objects.first()
print(InfoObj.users_set.first().username)  # cce

# 反向查询(给定related_name参数)
InfoObj = Info.objects.first()
print(InfoObj.users.first().username)  # cce
OneToOneField方式
    可以看到,对于ForeignKey的方式,在反向查询时,得到的其实是多个对象,需要使用查询集的first()方法,拿到其中一个对象,那么OneToOneField来讲,这一点做了优化,既然是一对一,不论是正向查询还是反向查询,应该都是一个对象,而不是多个对象,那么依旧沿用上门的模型示例,作出如下测试;
# 正向查询
UserObj = Users.objects.first()
print(UserObj.info.id_card)  # 420

# 反向查询(没有给定related_name参数,直接使用小写表名)
InfoObj = Info.objects.first()
print(InfoObj.users.username)  # cce

# 反向查询(给定related_name参数,直接使用related_name的参数值)
InfoObj = Info.objects.first()
print(InfoObj.user.username)  # cce

多对多

    对于多对多来讲,在日常开发中也是经常能够碰到,比如一个用户,可以有多个角色,那么同时,一个角色下面一般也存在多个用户,这就是典型的多对多的关系;
    对于多对多来讲,在Django的ORM框架下,模型类的实现方式主要有三种,第一种是使用ForeignKey来实现,第二种是使用Django的ORM框架提供的ManyToManyField字段类来实现半自动多对多结构,第三种是通过ManyToManyField字段类的方式来实现全自动的多对多结构;
    所谓全自动和半自动它们的主要区别是,第三张表的存在形式,因为ManyToManyField字段类可以在不创建第三张表的情况下建立多对多结构,那么它也支持自定义第三张表的多对多结构,由此衍生出两种对多对结构的创建形式,一种为全自动,无序第三张表,多对多关系交给ORM框架去处理,另一种则是去半自动,半自动形式需要我们手动创建第三张表,然后和ManyToManyField字段类建立联系;
    但是不管是哪种实现方式,对于多对多结构来讲,都需要一个第三张表来实现多对多关联,不管这个第三张表是存在还是不存在;
ForeignKey方式
    使用ForeignKey方式来创建多对多非常简单,既然我们都知道ForeignKey所在的一方为多的一方,那么我们可以创建一个第三张表分别与其他两张表建立外键关系,也就是两个对多一合起来,将多的一方都放在一张表里面,这样,两个一的一方,就可以在第三张表中建立关联关系,从而实现多对多结构,如下;
class Users(models.Model):
    id = models.AutoField(primary_key=True)
    username = models.CharField(null=False, max_length=14, unique=True, verbose_name='账号名')

    class Meta():
        db_table = "user"  # 表名

    def __str__(self):
        return "<Users: %s>" % (self.username)


class Roles(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(null=False, max_length=14, verbose_name='身份证', unique=True)

    class Meta():
        db_table = "roles"  # 表名

    def __str__(self):
        return "<Roles: %s>" % self.name


class UserRole(models.Model):
    id = models.AutoField(primary_key=True)
    user = models.ForeignKey(to='Users', to_field="id", db_column="user", on_delete=models.CASCADE, related_name="user_extension")
    role = models.ForeignKey(to='Roles', to_field='id', db_column="role", on_delete=models.CASCADE, related_name="role_extension")

    class Meta():
        db_table = "user_role"  # 表名
        unique_together = [("user", "role")]  # 创建联合唯一索引

    def __str__(self):
        return "<UserRole: %s %s>" % (self.user, self.role)
增删改查
    这种ForeignKey实现的方式创建的多对多关系表,做数据的增删该查,其实就和单表操作是一样的,相比使用ManyToManyField字段实现,它比较繁琐,ForeignKey这种方式,拿新增数据来说,我们需要一张表一张表的去插入,然后将这插入到多条数据使用第三张表进行关联;
# 新增数据
user = Users.objects.create(username="cce")
Roles.objects.create(name="管理员") # 用于修改
role = Roles.objects.create(name="操作员")
UserRole.objects.create(user=user, role=role)

# 删除数据
# Users.objects.first().delete() # 因为on_delete策略为CASCADE,所以,此处删除会直接删除关系表的数据

# 修改数据
role = Roles.objects.filter(name="管理员").first() # 获取一个准备修改成为的角色对象
user = Users.objects.filter(username="cce").first() # 获取要修改角色的用户
user_extension_obj = user.user_extension.first() # 通过用户拿到第三张表的关系对象
user_extension_obj.role = role # 将第三张表的关系对象中的角色修改为,上述的管理员对象
user_extension_obj.save() # 保存

# 通过用户查询角色
print(Users.objects.first().user_extension.first().role.name) # 管理员
# 通过角色查询用户
print(Roles.objects.first().role_extension.first().user.username) # cce
ManyToManyField
    可以看到上述,通过ForeignKey来实现多对多,极为不便,需要编写大量的代码来实现一个简单的数据增删改查,因此,Django的ORM框架就提供了这个ManyToManyField字段,做了大量的优化工作,极大的方便了我们日常开发,该字段主要有如下参数;
to:设置要关联的表;
related_name:同ForeignKey字段,只不过此处主要是用于从表访问主表,这里所谓的从表就是第二张表,并非第三张表关系表;
related_query_name:同ForeignKey字段;
symmetrical:仅用于多对多自关联时,指定内部是否创建反向操作的字段,默认为True;
through:在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系,我们也可以手动创建第三张表来管理多对多关系,此时就需要通过through来指定第三张表的表名;
through_fields:设置关联表中的字段,第一个为主表的关联字段,第二个为子表的关联字段,因为在我们自定义第三张表可能会添加一些其他的字段,所以这个时候,我们可能就需要用到这个参数来指定关联字段;
db_table:默认创建第三张表时,数据库中表的字段名称;
ManyToManyField全自动
    所谓全自动就是,直接将第三张表交给Django的ORM框架来处理,作为程序员,我们不关注第三张表到底是什么样,直接托管给Django的ORM框架来处理;
    Django的ORM会自动将我们的第三张表创建出来,但是我们需要知道的是,我们无法对第三张表进行任何单独的管理,比如添加字段,修改字段等,如下示例;
class Users(models.Model):
    id = models.AutoField(primary_key=True)
    username = models.CharField(null=False, max_length=14, unique=True, verbose_name='账号名')
    user_role = models.ManyToManyField("Roles", db_table="user_role", related_name="user")

    class Meta():
        db_table = "users"  # 表名

    def __str__(self):
        return "<Users: %s>" % (self.username)


class Roles(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(null=False, max_length=14, verbose_name='身份证', unique=True)

    class Meta():
        db_table = "roles"  # 表名

    def __str__(self):
        return "<Roles: %s>" % self.name

ManyToManyField半自动
    ManyToManyField提供了一个自定义第三张表的接口,所以我们也可以直接利用这个接口去手动创建第三张表,这样也更加的灵活,我们可以在第三张表新增一些其他附带信息,比如关联创建时间、修改时间,等等操作,如下示例;
class Users(models.Model):
    id = models.AutoField(primary_key=True)
    username = models.CharField(null=False, max_length=14, unique=True, verbose_name='账号名')
    user_role = models.ManyToManyField(to="Roles", through="UserRole")

    class Meta():
        db_table = "users"  # 表名

    def __str__(self):
        return "<Users: %s>" % (self.username)


class Roles(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(null=False, max_length=14, verbose_name='身份证', unique=True)

    class Meta():
        db_table = "roles"  # 表名

    def __str__(self):
        return "<Roles: %s>" % self.name


class UserRole(models.Model):
    id = models.AutoField(primary_key=True)
    user = models.ForeignKey(to='Users', to_field="id", db_column="user", on_delete=models.CASCADE,
                             related_name="user_extension")
    role = models.ForeignKey(to='Roles', to_field='id', db_column="role", on_delete=models.CASCADE,
                             related_name="role_extension")

    class Meta():
        db_table = "user_role"  # 表名

    def __str__(self):
        return "<UserRole: %s %s>" % (self.user, self.role)

增删改查
    对于ManyToManyField字段类实现的多对多结构下的增删改查来讲,它比ForeignKey实现的多对多结构使用起来方便得太多太多,ManyToManyField将这些增删改查的操作全部封装了,对于增、删、改都提供了相应的方法接口,至于查询操作大概和我们的一对多相似;
    使用ManyToManyField字段类实现的多对多结构查询得到的对象,都会新增如下几个方法,需要注意的是,如下这些方法在多对多场景下都有自己独特的用途,和我们之前使用的objects对象中的方法并不一样;
create():该方法的主要用途是,在第一张表中去创建第二张表的数据,当我们拿到第一张表的记录对象时,我们可以使用其关联字段的create()方法,实现跨表数据添加,并且,在创建完成跨表数据添加之后,会将这两张表中的两条数据在第三张表中建立多对多关联关系;
set():接收一个可迭代对象,它主要是更新当前(第一张表的对象)对象的多对多关联关系,直接覆盖当前对象的多对多关系;
remove():接收一个第二张表的对象,表示将指定对象(第二张表),从当前对象(第一张表)的多对多关系移除,但如果ForeignKey的null=False那么就清除不了;
clear():清空当前对象的多对多关系,但如果ForeignKey的null=False那么就清除不了;
    上述可能说得有点迷糊,那么下面我们就来具体看看,对于多对多场景下的,数据增删改查。下述的正向表示ManyToManyField所在的一方,而另一方则是反向;
# 正向新增数据(自动建立多对多关系)
user = Users.objects.create(username="cce")
user.user_role.create(name="操作员")
# 反向新增数据(自动建立多对多关系)
role = Roles.objects.create(name="管理员")
role.user.create(username="csw")

# 添加多对多关系
user = Users.objects.filter(username="cce").first()
role = Roles.objects.filter(name="管理员").first()
user.user_role.add(role)

# 移除多对多关系
user = Users.objects.filter(username="cce").first()
role = Roles.objects.filter(name="管理员").first()
user.user_role.remove(role)

# 清空多对多关系
user = Users.objects.filter(username="cce").first()
role = Roles.objects.filter(name="管理员").first()
user.user_role.clear()

# 更新多对多关系
user = Users.objects.filter(username="cce").first()
user.user_role.set(Roles.objects.all())

# 正向查询多对多关系
print(Users.objects.filter(username="csw").first().user_role.all())  # <QuerySet [<Roles: <Roles: 管理员>>]>
# 反向查询多对多关系
print(Roles.objects.filter(name="管理员").first().user.all()) # <QuerySet [<Users: <Users: csw>>]>

跨表引用

    在Django的ORM框架中,只要我们使用了ForeignKey实现了外键字段,我们可以都直接跨表引用其他字段,可以在当前表中,直接引用与之关联的外键表中对象的数据,听起来有点绕,说白了,就是我们在查询当前表时,使用filter给定的查询条件,可以是另外一张表的;
    这种跨表查询,使用双下划线,连接两个表的字段,如,当前表外键字段__外键表字段,如下;
# 跨表查询
print(Users.objects.filter(user_role__in=Roles.objects.filter(name="操作员"))) # 使用当前表外键字段__外键表字段实现跨表查询
# <QuerySet [<Users: <Users: cce>>]>

扩展

    在目前Web开发中,一般都是采用前后端分离的架构实现整个网站的设计,那么在这种场景下,作为前端来讲,主要负责数据的渲染,作为后端来讲,只需要负责数据的CURD即可,那么在这种场景下,前后端实现数据交互的手段,一般都是以JSON的形式进行交互;
    那么因为我们的ORM框架查询数据出来之后,会映射成对应模型类的对象,这就很麻烦了,一个对象转化成JSON,虽然不复杂,但是比较繁琐,会产生大量的重复代码,所以在这种场景下,可以直接编写一个工具类,能够统一的将所有模型类的对象数据,实现JSON化,如下;
import json
def to_dict(instance, exclude=[]):
    data = {}
    for field in instance._meta.fields:
        if field.name not in exclude:
            data[field.name] = getattr(instance, field.name)
    return json.dumps(data)

事物

    在软件开发的过程中,很多时候,我们可能会在一次处理都过程中,修改多张表,那么这就问题来了,如果出现这么一个情况,我们在修改第一个表的时候,很正常,由于模型类对象的save方法,不仅将数据持久化了,还将事务也提交了,所以我们第二个表在执行数据新增的时候,出现了问题,比如常见的问题,主键重复,提交的数据类型与字段不一致,所以第二张表的操作失败了;
    但是我们但第一张表是成功但,那么这就出现了数据不一致的情况,那么为了解决这种情况,我们就需要加入事务的处理机制,要么全部成功,要么全部失败,从而保证数据的一致性;
    那么Django的ORM框架中,在django.db.transaction下面提供了一个atomic()的方法实现事务处理的,同时,它还支持上下文,我们将我们的需要对ORM操作的语法,放在这个atomic()方法的内部即可,让atomic()方法来管理我们的事务提交,语法如下;
with transaction.atomic():
    # ORM代码段

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注