Django模型层之单表操作

梦想的初衷 提交于 2019-12-02 06:57:05

Django模型层之单表操作

一 、ORM简介

我们在使用Django框架开发web应用的过程中,不可避免地会涉及到数据的管理操作(如增、删、改、查),而一旦谈到数据的管理操作,就需要用到数据库管理软件,例如mysqloracleMicrosoft SQL Server等。

如果应用程序需要操作数据(比如将用户注册信息永久存放起来),那么我们需要在应用程序中编写原生sql语句,然后使用pymysql模块远程操作mysql数据库,详见图一^①^

但是直接编写原生sql语句会存在两方面的问题,严重影响开发效率,如下

  • sql语句的执行效率:应用开发程序员需要耗费一大部分精力去优化sql语句
  • 数据库迁移:针对mysql开发的sql语句无法直接应用到oracle数据库上,一旦需要迁移数据库,便需要考虑跨平台问题

为了解决上述问题,django引入了ORM的概念,ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行,详见图二^②^

图片一

图片二

如此,开发人员既不用再去考虑原生SQL的优化问题,也不用考虑数据库迁移的问题,ORM都帮我们做了优化且支持多种数据库,这极大地提升了我们的开发效率,下面就让我们来详细学习ORM的使用吧

二 、单表操作

2.1 按步骤创建表

2.1.1 models.py

创建django项目,新建名为app01app,在app01models.py中创建模型

class Employee(models.Model): # 必须是models.Model的子类     id=models.AutoField(primary_key=True)      name=models.CharField(max_length=16)      gender=models.BooleanField(default=1)      birth=models.DateField()      department=models.CharField(max_length=30)      salary=models.DecimalField(max_digits=10,decimal_places=1)

2.1.2 settings.py

django的`orm支持多种数据库,如果想将上述模型转为mysql数据库中的表,需要settings.py

# 删除\注释掉原来的DATABASES配置项,新增下述配置 DATABASES = {     'default': {         'ENGINE': 'django.db.backends.mysql', # 使用mysql数据库         'NAME': 'db1',          # 要连接的数据库         'USER': 'root',         # 链接数据库的用于名         'PASSWORD': '',         # 链接数据库的用于名                           'HOST': '127.0.0.1',    # mysql服务监听的ip           'PORT': 3306,           # mysql服务监听的端口           'ATOMIC_REQUEST': True, #设置为True代表同一个http请求所对应的所有sql都放在一个事务中执行                                  #(要么所有都成功,要么所有都失败),这是全局性的配置,如果要对某个                                 #http请求放水(然后自定义事务),可以用non_atomic_requests修饰器          'OPTIONS': {             "init_command": "SET storage_engine=INNODB", #设置创建表的存储引擎为INNODB         }     } }

2.1.3 建立数据库

在连接mysql数据库前,必须事先创建好数据库

mysql> create database db1; # 数据库名必须与settings.py中指定的名字对应上

2.1.4 插曲

其实python解释器在运行django程序时,djangoorm底层操作数据库的python模块默认是mysqldb而非pymysql,然而对于解释器而言,python2.x解释器支持的操作数据库的模块是mysqldb,而python3.x解释器支持的操作数据库的模块则是pymysql,,毫无疑问,目前我们的django程序都是运行于python3.x解释器下,于是我们需要修改djangoorm默认操作数据库的模块为pymysql,具体做法如下

2.1.5 注册

确保配置文件settings.py中的INSTALLED_APPS中添加我们创建的app名称,django2.xdjango1.x处理添加方式不同

# django1.x版本,在下述列表中新增我们的app名字即可 INSTALLED_APPS = [     'django.contrib.admin',     'django.contrib.auth',     'django.contrib.contenttypes',     'django.contrib.sessions',     'django.contrib.messages',     'django.contrib.staticfiles',     'app01',     # 'app02' # 若有新增的app,依次添加即可 ]  # django2.x版本,可能会帮我们自动添加app,只是换了一种添加方式 INSTALLED_APPS = [     'django.contrib.admin',     'django.contrib.auth',     'django.contrib.contenttypes',     'django.contrib.sessions',     'django.contrib.messages',     'django.contrib.staticfiles',     'app01.apps.App01Config', # 如果默认已经添加了,则无需重复添加     # 'app02.apps.App02Config', # 若有新增的app,按照规律依次添加即可 ]

2.1.6 日志的配置

如果想打印orm转换过程中的sql,需要在settings中进行配置日志:

LOGGING = {     'version': 1,     'disable_existing_loggers': False,     'handlers': {         'console':{             'level':'DEBUG',             'class':'logging.StreamHandler',         },     },     'loggers': {         'django.db.backends': {             'handlers': ['console'],             'propagate': True,             'level':'DEBUG',         },     } }

2.1.7 数据库迁移

最后在命令行中执行两条数据库迁移命令,即可在指定的数据库db1中创建表 :

$ python manage.py makemigrations $ python manage.py migrate  # 注意: # 1、makemigrations只是生成一个数据库迁移记录的文件,而migrate才是将更改真正提交到数据库执行 # 2、数据库迁移记录的文件存放于app01下的migrations文件夹里 # 3、了解:使用命令python manage.py showmigrations可以查看没有执行migrate的文件

注意1:在使用的是django1.x版本时,如果报如下错误

django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None

那是因为MySQLclient目前只支持到python3.4,如果使用的更高版本的python,需要找到文件C:\Programs\Python\Python36-32\Lib\site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql
这个路径里的文件

# 注释下述两行内容即可 if version < (1, 3, 3):      raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)

注意2:当我们直接去数据库里查看生成的表时,会发现数据库中的表与orm规定的并不一致,这完全是正常的,事实上,orm的字段约束就是不会全部体现在数据库的表中,比如我们为字段gender设置的默认值default=1,去数据库中查看会发现该字段的default部分为null

mysql> desc app01_employee; # 数据库中标签前会带有前缀app01_ +------------+---------------+------+-----+---------+----------------+ | Field      | Type          | Null | Key | Default | Extra          | +------------+---------------+------+-----+---------+----------------+ | id         | int(11)       | NO   | PRI | NULL    | auto_increment | | name       | varchar(16)   | NO   |     | NULL    |                | | gender     | tinyint(1)    | NO   |     | NULL    |                | | birth      | date          | NO   |     | NULL    |                | | department | varchar(30)   | NO   |     | NULL    |                | | salary     | decimal(10,1) | NO   |     | NULL    |                | +------------+---------------+------+-----+---------+----------------+

虽然数据库没有增加默认值,但是我们在使用orm插入值时,完全为gender字段插入空,orm会按照自己的约束将空转换成默认值后,再提交给数据库执行

2.1.8 数据库之表的操作

在表生成之后,如果需要增加、删除、修改表中字段,需要这么做

# 一:增加字段 # 1.1、在模型类Employee里直接新增字段,强调:对于orm来说,新增的字段必须用default指定默认值 publish = models.CharField(max_length=12,default='人民出版社',null=True)  #1.2、重新执行那两条数据库迁移命令  # 二:删除字段 # 2.1 直接注释掉字段 # 2.2 重新执行那两条数据库迁移命令  # 三:修改字段 # 3.1 将模型类中字段修改 # 3.2 重新执行那两条数据库迁移命令

2.2 添加记录

  • 方式一:
# 1、用模型类创建一个对象,一个对象对应数据库表中的一条记录 obj = Employee(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1) # 2、调用对象下的save方法,即可以将一条记录插入数据库 obj.save()
  • 方式二:
# 每个模型表下都有一个objects管理器,用于对该表中的记录进行增删改查操作,其中增加操作如下所示 obj = Employee.objects.create(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1)

2.3 查询记录

2.3.1 查询API

模型Employee对应表app01_employee,表app01_employee中的每条记录都对应类Employee的一个对象,我们以该表为例,来介绍查询API,读者可以自行添加下述记录,然后配置url、编写视图测试下述API

mysql> select * from app01_employee; +----+-------+--------+------------+------------+--------+ | id | name  | gender | birth      | department | salary | +----+-------+--------+------------+------------+--------+ |  1 | Egon  |      0 | 1997-01-27 | 财务部     |  100.1 | |  2 | Kevin |      1 | 1998-02-27 | 技术部     |   10.1 | |  3 | Lili  |      0 | 1990-02-27 | 运营部     |   20.1 | |  4 | Tom   |      1 | 1991-02-27 | 运营部     |   30.1 | |  5 | Jack  |      1 | 1992-02-27 | 技术部     |   11.2 | |  6 | Robin |      1 | 1988-02-27 | 技术部     |  200.3 | |  7 | Rose  |      0 | 1989-02-27 | 财务部     |   35.1 | |  8 | Egon  |      0 | 1997-01-27 | 财务部     |  100.1 | |  9 | Egon  |      0 | 1997-01-27 | 财务部     |  100.1 | +----+-------+--------+------------+------------+--------+

每个模型表下都有一个objects管理器,用于对该表中的记录进行增删改查操作,其中查询操作如下所示

  • Part1

!!!强调!!!下述方法(除了count外)的返回值都是一个模型类Employee的对象,为了后续描述方便,我们统一将模型类的对象称为“记录对象”,每一个”记录对象“都唯一对应表中的一条记录,

# 1. get(**kwargs) # 1.1: 有参,参数为筛选条件 # 1.2: 返回值为一个符合筛选条件的记录对象(有且只有一个),如果符合筛选条件的对象超过一个或者没有都会抛出错误。 obj=Employee.objects.get(id=1) print(obj.name,obj.birth,obj.salary) #输出:Egon 1997-01-27 100.1  # 2、first() # 2.1:无参 # 2.2:返回查询出的第一个记录对象 obj=Employee.objects.first() # 在表所有记录中取第一个 print(obj.id,obj.name) # 输出:1 Egon  # 3、last() # 3.1: 无参 # 3.2: 返回查询出的最后一个记录对象 obj = Employee.objects.last() # 在表所有记录中取最后一个 print(obj.id, obj.name)  # 输出:9 Egon  # 4、count(): # 4.1:无参 # 4.2:返回包含记录对象的总数量 res = Employee.objects.count() # 统计表所有记录的个数 print(res) # 输出:9  # 注意:如果我们直接打印Employee的对象将没有任何有用的提示信息,我们可以在模型类中定义__str__来进行定制 class Employee(models.Model):     ......     # 在原有的基础上新增代码如下     def __str__(self):         return "<%s:%s>" %(self.id,self.name) # 此时我们print(obj)显示的结果就是: <本条记录中id字段的值:本条记录中name字段的值>

  • Part2

!!!强调!!!下述方法查询的结果都有可能包含多个记录对象,为了存放查询出的多个记录对象,djangoORM自定义了一种数据类型Queryeset,所以下述方法的返回值均为QuerySet类型的对象,QuerySet对象中包含了查询出的多个记录对象

# 1、filter(**kwargs): # 1.1:有参,参数为过滤条件 # 1.2:返回值为QuerySet对象,QuerySet对象中包含了符合过滤条件的多个记录对象 queryset_res=Employee.objects.filter(department='技术部') # print(queryset_res) # 输出: <QuerySet [<Employee: <2:Kevin>>, <Employee: <5:Jack>>, <Employee: <6:Robin>>]>  # 2、exclude(**kwargs) # 2.1: 有参,参数为过滤条件 # 2.2: 返回值为QuerySet对象,QuerySet对象中包含了不符合过滤条件的多个记录对象 queryset_res=Employee.objects.exclude(department='技术部')  # 3、all() # 3.1:无参 # 3.2:返回值为QuerySet对象,QuerySet对象中包含了查询出的所有记录对象 queryset_res = Employee.objects.all() # 查询出表中所有的记录对象  # 4、order_by(*field): # 4.1:有参,参数为排序字段,可以指定多个字段,在字段1相同的情况下,可以按照字段2进行排序,以此类推,默认升序排列,在字段前加横杆代表降序排(如"-id") # 4.2:返回值为QuerySet对象,QuerySet对象中包含了排序好的记录对象 queryset_res = Employee.objects.order_by("salary","-id") # 先按照salary字段升序排,如果salary相同则按照id字段降序排  # 5、values(*field) # 5.1:有参,参数为字段名,可以指定多个字段 # 5.2:返回值为QuerySet对象,QuerySet对象中包含的并不是一个个的记录对象,而上多个字典,字典的key即我们传入的字段名 queryset_res = Employee.objects.values('id','name') print(queryset_res) # 输出:<QuerySet [{'id': 1, 'name': 'Egon'}, {'id': 2, 'name': 'Kevin'}, ......]> print(queryset_res[0]['name']) # 输出:Egon  # 6、values_list(*field): # 6.1:有参,参数为字段名,可以指定多个字段 # 6.2:返回值为QuerySet对象,QuerySet对象中包含的并不是一个个的记录对象,而上多个小元组,字典的key即我们传入的字段名 queryset_res = Employee.objects.values_list('id','name') print(queryset_res) # 输出:<QuerySet [(1, 'Egon'), (2, 'Kevin'),), ......]> print(queryset_res[0][1]) # 输出:Egon
  • Part3

<u>Part2中所示查询API的返回值都是QuerySet类型的对象,QuerySet类型是django ORM自定义的一种数据类型,专门用来存放查询出的多个记录对象,该类型的特殊之处在于

  1. queryset类型类似于python中的列表,支持索引操作
# 过滤出符合条件的多个记录对象,然后存放到QuerySet对象中 queryset_res=Employee.objects.filter(department='技术部')  # 按照索引从QuerySet对象中取出第一个记录对象 obj=queryset_res[0] print(obj.name,obj.birth,obj.salary)
  1. 管理器objects下的方法queryset下同样可以调用,并且djangoORM支持链式操作,于是我们可以像下面这样使用
# 简单示范: res=Employee.objects.filter(gender=1).order_by('-id').values_list('id','name') print(res) # 输出:<QuerySet [(6, 'Robin'), (5, 'Jack'), (4, 'Tom'), (2, 'Kevin')]> 
  • Part4

其他查询API

# 1、reverse(): # 1.1:无参 # 1.2:对排序的结果取反,返回值为QuerySet对象 queryset_res = Employee.objects.order_by("salary", "-id").reverse()  # 2、exists(): # 2.1:无参 # 2.2:返回值为布尔值,如果QuerySet包含数据,就返回True,否则返回False res = Employee.objects.filter(id=100).exists() print(res)  # 输出:False  # 3、distinct(): # 3.1:如果使用的是Mysql数据库,那么distinct()无需传入任何参数 # 3.2:从values或values_list的返回结果中剔除重复的记录对象,返回值为QuerySet对象 res = Employee.objects.filter(name='Egon').values('name', 'salary').distinct() print(res) # 输出:<QuerySet [{'name': 'Egon', 'salary': Decimal('100.1')}]>  res1 = Employee.objects.filter(name='Egon').values_list('name', 'salary').distinct() print(res1) # 输出:<QuerySet [('Egon', Decimal('100.1'))]> 

2.3.2 基于双下划线的模糊查询

2.3.3 FQ查询

2.3.3.1F查询

在上面所有的例子中,我们在进行条件过滤时,都只是用某个字段与某个具体的值做比较。如果我们要对两个字段的值做比较,那该怎么做呢?

Django提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较两个不同字段的值,如下

# 一张书籍表中包含字段:评论数commentNum、收藏数keepNum,要求查询:评论数大于收藏数的书籍 from django.db.models import F Book.objects.filter(commnetNum__lt=F('keepNum')) 

Django 支持 F() 对象之间以及F()对象和常数之间的加减乘除和取模的操作

# 查询评论数大于收藏数2倍的书籍 from django.db.models import F Book.objects.filter(commnetNum__lt=F('keepNum')*2) 

修改操作也可以使用F函数,比如将每一本书的价格提高30元:

Book.objects.all().update(price=F("price")+30)  

filter() 等方法中逗号分隔开的多个关键字参数都是逻辑与(AND)的关系。 如果我们需要使用逻辑或(OR)来连接多个条件,就用到了DjangoQ对象

可以将条件传给类Q来实例化出一个对象,Q的对象可以使用&| 操作符组合起来,&等同于and|等同于or

from django.db.models import Q Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"))  # 等同于sql:select * from app01_employee where id < 5 or name = 'Egon'; 
2.3.3.2 Q查询

Q 对象可以使用~ 操作符取反,相当于NOT

from django.db.models import Q Employee.objects.filter(~Q(id__gt=5) | Q(name="Egon"))  # 等同于sql:select * from app01_employee where not (id < 5) or name = 'Egon'; 

当我们的过滤条件中既有or又有and,则需要混用Q对象与关键字参数,但Q 对象必须位于所有关键字参数的前面

from django.db.models import Q Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"),salary__lt=100)  # 等同于sql:select * from app01_employee where (id < 5 or name = 'Egon') and salary < 100; 

2.3.4 聚合查询

聚合查询aggregate()是把所有查询出的记录对象整体当做一个组,我们可以搭配聚合函数来对整体进行一个聚合操作

from django.db.models import Avg, Max, Sum, Min, Max, Count # 导入聚合函数  # 1. 调用objects下的aggregate()方法,会把表中所有记录对象整体当做一组进行聚合 res1=Employee.objects.aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee; print(res1) # 输出:{'salary__avg': 70.73}  # 2、aggregate()会把QuerySet对象中包含的所有记录对象当成一组进行聚合 res2=Employee.objects.all().aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee; print(res2) # 输出:{'salary__avg': 70.73}  res3=Employee.objects.filter(id__gt=3).aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee where id > 3; print(res3) # 输出:{'salary__avg': 71.0} 

aggregate()的返回值为字典类型,字典的key是由聚合字段的名称___聚合函数的名称合成的,例如

Avg("salary") 合成的名字为 'salary__avg' 

若我们想定制字典的key名,我们可以指定关键参数,如下

res1=Employee.objects.all().aggregate(avg_sal=Avg('salary')) # select avg(salary) as avg_sal from app01_employee;  print(res1) # 输出:{'avg_sal': 70.73} # 关键字参数名就会被当做字典的key 

如果我们想得到多个聚合结果,那就需要为aggregate传入多个参数

res1=Employee.objects.all().aggregate(nums=Count('id'),avg_sal=Avg('salary'),max_sal=Max('salary'))  # 相当于SQL:select count(id) as nums,avg(salary) as avg_sal,max(salary) as max_sal from app01_employee;  print(res1) # 输出:{'nums': 10, 'avg_sal': 70.73, 'max_sal': Decimal('200.3')} 

2.3.5 分组查询

分组查询annotate()相当于sql语句中的group by,是在分组后,对每个组进行单独的聚合,需要强调的是,在进行单表查询时,annotate()必须搭配values()使用:values(“分组字段”).annotate(聚合函数),如下

# 表中记录 mysql> select * from app01_employee; +----+-------+--------+------------+------------+--------+ | id | name  | gender | birth      | department | salary | +----+-------+--------+------------+------------+--------+ |  1 | Egon  |      0 | 1997-01-27 | 财务部     |  100.1 | |  2 | Kevin |      1 | 1998-02-27 | 技术部     |   10.1 | |  3 | Lili  |      0 | 1990-02-27 | 运营部     |   20.1 | |  4 | Tom   |      1 | 1991-02-27 | 运营部     |   30.1 | |  5 | Jack  |      1 | 1992-02-27 | 技术部     |   11.2 | |  6 | Robin |      1 | 1988-02-27 | 技术部     |  200.3 | |  7 | Rose  |      0 | 1989-02-27 | 财务部     |   35.1 | +----+-------+--------+------------+------------+--------+  # 查询每个部门下的员工数 res=Employee.objects.values('department').annotate(num=Count('id'))  # 相当于sql: # select department,count(id) as num from app01_employee group by department;  print(res)  # 输出:<QuerySet [{'department': '财务部', 'num': 2}, {'department': '技术部', 'num': 3}, {'department': '运营部', 'num': 2}]> 

跟在annotate前的values方法,是用来指定分组字段,即group by后的字段,而跟在annotate后的values方法,则是用来指定分组后要查询的字段,即select 后跟的字段

res=Employee.objects.values('department').annotate(num=Count('id')).values('num') # 相当于sql: # select count(id) as num from app01_employee group by department;  print(res) # 输出:<QuerySet [{'num': 2}, {'num': 3}, {'num': 2}]> 

跟在annotate前的filter方法表示where条件,跟在annotate后的filter方法表示having条件,如下

# 查询男员工数超过2人的部门名 res=Employee.objects.filter(gender=1).values('department').annotate(male_count=Count("id")).filter(male_count__gt=2).values('department')  print(res) # 输出:<QuerySet [{'department': '技术部'}]>  # 解析: # 1、跟在annotate前的filter(gender=1) 相当于 where gender = 1,先过滤出所有男员工信息 # 2、values('department').annotate(male_count=Count("id")) 相当于group by department,对过滤出的男员工按照部门分组,然后聚合出每个部门内的男员工数赋值给字段male_count # 3、跟在annotate后的filter(male_count__gt=2) 相当于 having male_count > 2,会过滤出男员工数超过2人的部门 # 4、最后的values('department')代表从最终的结果中只取部门名 

总结

1、values()在annotate()前表示group by的字段,在后表示取值 1、filter()在annotate()前表示where条件,在后表示having 

需要注意的是,如果我们在annotate前没有指定values(),那默认用表中的id字段作为分组依据,而id各不相同,如此分组是没有意义的,如下

res=Employee.objects.annotate(Count('name'))  # 每条记录都是一个分组 res=Employee.objects.all().annotate(Count('name'))  # 同上 

2.4 修改记录

2.4.1 直接修改单条记录

可以修改记录对象属性的值,然后执行save方法从而完成对单条记录的直接修改

# 1、获取记录对象 obj=Employee.objects.filter(name='Egon')[0] # 2、修改记录对象属性的值 obj.name='EGON' obj.gender=1 # 3、重新保存 obj.save() 

2.4.2 修改QuerySet中的所有记录对象

QuerySet对象下的update()方法可以更QuerySet中包含的所有对象,该方法会返回一个整型数值,表示受影响的记录条数(相当于sql语句执行结果的rows

queryset_obj=Employee.objects.filter(id__gt=5) rows=queryset_obj.update(name='EGON',gender=1) 

2.5 删除记录

2.5.1 直接删除单条记录

可以直接调用记录对象下的delete方法,该方法运行时立即删除本条记录而不返回任何值,如下

obj=Employee.objects.first() obj.delete() 

2.5.2 删除QuerySet中的所有记录对象

每个 QuerySet下也都有一个 delete()方法,它一次性删除 QuerySet中所有的对象(如果QuerySet对象中只有一个记录对象,那也就只删一条),如下

queryset_obj=Employee.objects.filter(id__gt=5) rows=queryset_obj.delete() 

需要强调的是管理objects下并没有delete方法,这是一种保护机制,是为了避免意外地调用 Employee.objects.delete() 方法导致所有的记录被误删除从而跑路。但如果你确认要删除所有的记录,那么你必须显式地调用管理器下的all方法,拿到一个QuerySet对象后才能调用delete方法删除所有

Employee.objects.all().delete() 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!