五、模型层(ORM)
Django中内嵌了ORM框架,不需要直接编写SQL语句进行数据库操作,而是通过定义模型类,操作模型类来完成对数据库中表的增删改查和创建等操作。当然,其内部是通过pymysql完成的,发送一些mysql语句。
O是object,也就类对象的意思。
R是relation,翻译成中文是关系,也就是关系数据库中数据表的意思。
M是mapping,是映射的意思。
ORM的优点
数据模型类都在一个地方定义,更容易更新和维护,也利于重用代码。
ORM 有现成的工具,很多功能都可以自动完成,比如数据消除、预处理、事务等等。
它迫使你使用 MVC 架构,ORM 就是天然的 Model,最终使代码更清晰。
基于 ORM 的业务代码比较简单,代码量少,语义性好,容易理解。
新手对于复杂业务容易写出性能不佳的 SQL,有了ORM不必编写复杂的SQL语句, 只需要通过操作模型对象即可同步修改数据表中的数据.
开发中应用ORM将来如果要切换数据库.只需要切换ORM底层对接数据库的驱动【修改配置文件的连接地址即可】
ORM 也有缺点
ORM 库不是轻量级工具,需要花很多精力学习和设置,甚至不同的框架,会存在不同操作的ORM。
对于复杂的业务查询,ORM表达起来比原生的SQL要更加困难和复杂。
ORM操作数据库的性能要比使用原生的SQL差。
ORM 抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的 SQL。【自己使用pymysql另外操作即可,用了ORM并不表示当前项目不能使用别的数据库操作工具了。】
我们可以通过以下步骤来使用django的数据库操作
1. 配置数据库连接信息
2. 在models.py中定义模型类
3. 生成数据库迁移文件并执行迁文件[注意:数据迁移是一个独立的功能,这个功能在其他web框架未必和ORM一块的]
4. 通过模型类对象提供的方法或属性完成数据表的增删改查操作
配置数据库链接
在settings.py中保存了数据库的连接配置信息,Django默认初始配置使用sqlite数据库。
- 使用MySQL数据库首先需要安装驱动程序
pip install PyMySQL
- 在Django的工程同名子目录的
__init__
.py文件中添加如下语句
from pymysql import install_as_MySQLdb install_as_MySQLdb() # 让pymysql以MySQLDB的运行模式和Django的ORM对接运行
- 作用是让Django的ORM能以mysqldb的方式来调用PyMySQL。
- 修改DATABASES配置信息
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'HOST': '127.0.0.1', # 数据库主机 'PORT': 3306, # 数据库端口 'USER': 'root', # 数据库用户名 'PASSWORD': '123', # 数据库用户密码 'NAME': 'student' # 数据库名字 } }
在MySQL中创建数据库
create database student; # mysql8.0默认就是utf8mb4; create database student default charset=utf8mb4; # mysql8.0之前的版本
- 注意3: 如果想打印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', }, } }
定义模型类
定义模型类
- 模型类被定义在"子应用/models.py"文件中。
- 模型类必须直接或者间接继承自django.db.models.Model类。
接下来以学生管理为例进行演示。[系统大概3-4表,学生信息,课程信息,老师信息]
在models.py 文件中定义模型类。
from django.db import models from datetime import datetime # 模型类必须要直接或者间接继承于 models.Model class BaseModel(models.Model): """公共模型[公共方法和公共字段]""" # created_time = models.IntegerField(default=0, verbose_name="创建时间") created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") # auto_now_add 当数据添加时设置当前时间为默认值 # auto_now= 当数据添加/更新时, 设置当前时间为默认值 updated_time = models.DateTimeField(auto_now=True) class Meta(object): abstract = True # 设置当前模型为抽象模型, 当系统运行时, 不会认为这是一个数据表对应的模型. class Student(BaseModel): """Student模型类""" #1. 字段[数据库表字段对应] SEX_CHOICES = ( (0,"女"), (1,"男"), (2,"保密"), ) # 字段名 = models.数据类型(约束选项1,约束选项2, verbose_name="注释") # SQL: id bigint primary_key auto_increment not null comment="主键", # id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk # SQL: name varchar(20) not null comment="姓名" # SQL: key(name), name = models.CharField(max_length=20, db_index=True, verbose_name="姓名" ) # SQL: age smallint not null comment="年龄" age = models.SmallIntegerField(verbose_name="年龄") # SQL: sex tinyint not null comment="性别" # sex = models.BooleanField(verbose_name="性别") sex = models.SmallIntegerField(choices=SEX_CHOICES, default=2) # SQL: class varchar(5) not null comment="班级" # SQL: key(class) classmate = models.CharField(db_column="class", max_length=5, db_index=True, verbose_name="班级") # SQL: description longtext default "" not null comment="个性签名" description = models.TextField(default="", verbose_name="个性签名") #2. 数据表结构信息 class Meta: db_table = 'tb_student' # 指明数据库表名,如果没有指定表明,则默认为子应用目录名_模型名称,例如: users_student verbose_name = '学生信息表' # 在admin站点中显示的名称 verbose_name_plural = verbose_name # 显示的复数名称 # 除此之外还可以设置联合索引 联合唯一索引等(注意可能会与unique=True发生冲突) #3. 自定义数据库操作方法 def __str__(self): """定义每个数据对象的显示信息""" return "<User %s>" % self.name
真正的项目,一些登陆注册等的功能,对于用户表,后面会讲到用户认证组件Auth模块,我们一般在models.py里自定义一个class User()继承自带的Auth表。
(1) 数据库表名
模型类如果未指明表名db_table,Django默认以 小写app应用名_小写模型类名 为数据库表名。
可通过db_table 指明数据库表名。
(2) 关于主键
django会为表创建自动增长的主键列,每个模型只能有一个主键列。
如果使用选项设置某个字段的约束属性为主键列(primary_key)后,django不会再创建自动增长的主键列。
class Student(models.Model): # django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # 设置主键
默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。
(3) 属性命名限制
不能是python的保留关键字。
不允许使用连续的2个下划线,这是由django的查询方式决定的。__ 是关键字来的,不能使用!!!
定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:
属性名 = models.字段类型(约束选项, verbose_name="注释")
(4)字段类型
注:手机号不要用 IntegerField 超过了范围。
(5)约束选项
verbose_name admin里显示的名字
注意:null是数据库范畴的概念,blank是表单验证范畴的(同理default也是)。更改一些class属性参数的时候可以不用重新数据库迁移 ,而修改数据库结构的需要重新迁移。
数据可范畴出错会发生报错,用户输入范畴不符合规定会提示不能为空,或者自增、默认值。
后加东西要是设置默认值
(6) 外键
在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:
CASCADE 级联,删除主表数据时连通一起删除外键表中数据
PROTECT 保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据
SET_NULL 设置为NULL,仅在该字段null=True允许为null时可用
SET_DEFAULT 设置为默认值,仅在该字段设置了默认值时可用
SET() 设置为特定值或者调用特定方法,例如:
from django.conf import settings from django.contrib.auth import get_user_model from django.db import models def get_sentinel_user(): return get_user_model().objects.get_or_create(username='deleted')[0] class UserModel(models.Model): user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET(get_sentinel_user), )
DO_NOTHING 不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常
商品分类表
id | category | |
1 | 蔬菜 | |
2 | 电脑 |
商品信息表
id | goods_name | cid |
1 | 冬瓜 | 1 |
2 | 华为笔记本A1 | 2 |
3 | 茄子 | 1 |
当模型字段的on_delete=CASCADE, 删除蔬菜(id=1),则在外键cid=1的商品id1和3就被删除。
当模型字段的on_delete=PROTECT,删除蔬菜,mysql自动检查商品信息表,有没有cid=1的记录,有则提示必须先移除掉商品信息表中,id=1的所有记录以后才能删除蔬菜。
当模型字段的on_delete=SET_NULL,删除蔬菜以后,对应商品信息表,cid=1的数据的cid全部被改成cid=null
当模型字段的on_delete=SET_DEFAULT,删除蔬菜以后,对应商品信息表,cid=1的数据记录的cid被被设置默认值。
数据库迁移
将模型类定义表架构的代码转换成SQL同步到数据库中,这个过程就是数据迁移。django中的数据迁移,就是一个类,这个类提供了一系列的终端命令,帮我们完成数据迁移的工作。
(1)生成迁移文件
所谓的迁移文件, 是类似模型类的迁移类,主要是描述了数据表结构的类文件.
python manage.py makemigrations
(2)同步到数据库中
python manage.py migrate
补充:在django内部提供了一系列的功能,这些功能也会使用到数据库,所以在项目搭建以后第一次数据迁移的时候,会看到django项目中其他的数据表被创建了。其中就有一个django内置的admin站点管理。(这个后面会写)
# admin站点默认是开启状态的,我们可以通过http://127.0.0.1:8000/admin
# 这个站点必须有个管理员账号登录,所以我们可以在第一次数据迁移,有了数据表以后,就可以通过以下终端命令来创建一个超级管理员账号。
python manage.py createsuperuser
后续:稍微补充下admin知识
(后台超级管理员看到的表需要注册,且一些自增字段不会显示)
这里我注册用户名root 密码root123 进入admin
这是因为我之前用auth组件设置过一个普通用户,浏览器中还保存着sessionid,这个普通用户显然不是管理员,无权登录。
我们进入自己的root账户
在admin.py里注册表
记得在models.py里加上def__str__ 这样后台也就能显示相应的中文表示的对象了。
Django自带数据库可视化工具
数据库基本操作
脚本orm操作数据库
注意导入的顺序 不然会报错
(父级models类里有__str__和__repr__方法,也可以把__repr__重写)
import os os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'homework06.settings') import django django.setup() from students.models import * ret = Course.objects.all()# <QuerySet [<Course: 课程1>, <Course: 课程2>, <Course: 课程3>]> ret = Course.objects.get(pk=1) # 课程1 ret = Course.objects.filter(pk=1)# <QuerySet [<Course: 课程1>]>
(1)save方法
通过创建模型类对象,执行对象的save()方法保存到数据库中。
student = Student( name="刘德华", age=17, sex=True, clasmate=301, description="一手忘情水" ) student.save() print(student.id) # 判断是否新增有ID
(2)create方法
通过模型类.objects.create()保存,返回生成的模型类对象。
student = Student.objects.create( name="赵本山", age=50, sex=True, class_number=301, description="一段小品" ) print(student.id)
1、基础查询
ORM中针对查询结果的限制,提供了一个查询集[QuerySet].这个QuerySet,是ORM中针对查询结果进行保存数据的一个类型,我们可以通过了解这个QuerySet进行使用,达到查询优化,或者限制查询结果数量的作用。
后面会详细讲QuerySet,orm进阶及优化搜索,到这里要先了解一些基本操作。
def index(request): print(Students.objects, type(Students.objects))#app01.Students.objects <class 'django.db.models.manager.Manager'> return HttpResponse('ok')
(1)all()
查询所有对象,返回queryset对象。查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。
students = Student.objects.all() print("students:",students)
(2)filter()
筛选条件相匹配的多个对象,返回queryset对象。
# 查询所有的女生 students = Student.objects.filter(sex=0) print(students)
(3)get()
返回与所给筛选条件相匹配的对象,返回结果有且只有一个, 如果符合筛选条件的对象超过一个或者没有都会抛出错误。
student = Student.objects.get(pk=1) print(student) print(student.description) get使用过程中的注意点:get是根据条件返回多个结果或者没有结果,都会报错 try: student = Student.objects.get(name="刘德华") print(student) print(student.description) except Student.MultipleObjectsReturned: print("查询得到多个结果!") except Student.DoesNotExist: print("查询结果不存在!")
(4)first()、last()
分别为查询集的第一条记录和最后一条记录
# 没有结果返回none,如果有多个结果,则返回模型对象 students = Student.objects.all() # print(students.name) print(students[0].name) stu01 = Student.objects.first() stu02 = Student.objects.last() print(stu01.name) print(stu02.name)
(5)exclude()
筛选条件不匹配的对象,返回queryset对象。
# 查询张三以外的所有的学生 students = Student.objects.exclude(name="张三") print(students)
(6)order_by()
对查询结果排序
# order_by("字段") # 按指定字段正序显示,相当于 asc 从小到大 # order_by("-字段") # 按字段倒序排列,相当于 desc 从大到小 # order_by("第一排序","第二排序",...) # 查询所有的男学生按年龄从高到低展示 # students = Student.objects.all().order_by("-age","-id") students = Student.objects.filter(sex=1).order_by("-age", "-id") print(students)
(7)count()
查询集中对象的个数
结果其实与len(Student.objects.all())一样
# 查询所有男生的个数 count = Student.objects.filter(sex=1).count() print(count)
(8)exists()
判断查询集中是否有数据,如果有则返回True,没有则返回False
# 查询Student表中是否存在学生 print(Student.objects.exists())
(9)values()、values_list()
value()把结果集中的模型对象转换成字典,并可以设置转换的字段列表,达到减少内存损耗,提高性能
values_list(): 把结果集中的模型对象转换成元组(即没有键只有值),并可以设置转换的字段列表,达到减少内存损耗,提高性能
二者都返回QuerySet 只是里面的类型不同
# values 把查询结果中模型对象转换成字典 student_list = Student.objects.filter(classmate="301") student_list = student_list.order_by("-age") student_list = student_list.filter(sex=1) ret1 = student_list.values() # 默认把所有字段全部转换并返回 ret2 = student_list.values("id","name","age") # 可以通过参数设置要转换的字段并返回 ret3 = student_list.values_list() # 默认把所有字段全部转换并返回 ret4 = student_list.values_list("id","name","age") # 可以通过参数设置要转换的字段并返回 print(ret4) return JsonResponse({},safe=False)
def index(request): print(Students.objects.values('name')) return HttpResponse('ok') #<QuerySet [{'name': '张三'}, {'name': '李四'}, {'name': '王五'}]>
(10)distinct()
从返回结果中剔除重复纪录。返回queryset。
# 查询所有学生出现过的年龄 print(Student.objects.values("age").distinct())
(11)reverse()
对已经排序了的queryset进行反转 相当于order_by(’加个负号‘)
students = Student.objects.all().order_by("-age").reverse()
小总结:
返回QuerySet:
all()、filter()、exclude()、order_by()、values()、values_list()、reverse、distinct()
直接 Students.objects相当于.all() 可以在后面根对QuerySet的操作
返回对象:
get()、first()、last()
返回数字:
count()
返回布尔:
exist()
2、模糊查询
(1)模糊查询之contains
说明:如果要包含%无需转义,直接写即可。
例:查询姓名包含'华'的学生。
Student.objects.filter(name__contains='华')
(2)模糊查询之startswith、endswith
例:查询姓名以'文'结尾的学生
Student.objects.filter(name__endswith='文')
以上运算符都区分大小写,在这些运算符前加上i表示不区分大小写,如iexact、icontains、istartswith、iendswith.
(3)模糊查询之isnull
例:查询个性签名不为空的学生。
# 修改Student模型description属性允许设置为null,然后数据迁移 description = models.TextField(default=None, null=True, verbose_name="个性签名") # 添加测试数据 NSERT INTO student.db_student (name, age, sex, class, description, created_time, updated_time) VALUES ('刘德华', 17, 1, '407', null, '2020-11-20 10:00:00.000000', '2020-11-20 10:00:00.000000'); # 代码操作 tudent_list = Student.objects.filter(description__isnull=True)
4)模糊查询之in
例:查询编号为1或3或5的学生
Student.objects.filter(id__in=[1, 3, 5])
(5)模糊查询之比较查询
gt 大于 (greater then)
gte 大于等于 (greater then equal)
lt 小于 (less then)
lte 小于等于 (less then equal)
例:查询编号大于3的学生
Student.objects.filter(id__gt=3)
(6)模糊查询之日期查询
year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。
例:查询2010年被添加到数据中的学生。
Student.objects.filter(born_date__year=1980)
例:查询2016年6月20日后添加的学生信息。
from django.utils import timezone as datetime student_list = Student.objects.filter(created_time__gte=datetime.datetime(2016,6,20),created_time__lt=datetime.datetime(2016,6,21)).all() print(student_list)
3、进阶查询
(1) F查询
之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢? 答:使用F对象,被定义在django.db.models中。
语法如下:
"""F对象:2个字段的值比较""" # 获取从添加数据以后被改动过数据的学生 from django.db.models import F # SQL: select * from db_student where created_time=updated_time; student_list = Student.objects.exclude(created_time=F("updated_time")) print(student_list)
(2) Q查询
多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。
例:查询年龄大于20,并且编号小于30的学生。
Student.objects.filter(age__gt=20,id__lt=30) #或 Student.filter(age__gt=20).filter(id__lt=30)
如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符,Q对象被义在django.db.models中。
语法如下:
Q(属性名__运算符=值) Q(属性名__运算符=值) | Q(属性名__运算符=值)
例:查询年龄小于19或者大于20的学生,使用Q对象如下。
from django.db.models import Q student_list = Student.objects.filter( Q(age__lt=19) | Q(age__gt=20) ).all()
Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或**
例:查询年龄大于20,或编号小于30的学生,只能使用Q对象实现
Student.objects.filter(Q(age__gt=20) | Q(pk__lt=30))
Q对象左边可以使用~操作符,表示非not。但是工作中,我们只会使用Q对象进行或者的操作,只有多种嵌套复杂的查询条件才会使用&和~进行与和非得操作
。
例:查询编号不等于30的学生。
Student.objects.filter(~Q(pk=30))
(3)聚合查询
使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg 平均,Count 数量,Max 最大,Min 最小,Sum 求和,被定义在django.db.models中。
例:查询学生的平均年龄。
from django.db.models import Sum,Count,Avg,Max,Min Student.objects.aggregate(Avg('age'))
注意:aggregate的返回值是一个字典类型,格式如下:
{'属性名__聚合类小写':值} 如{'age__avg': 13.2}
使用count时一般不使用aggregate()过滤器。
例:查询学生总数。
Student.objects.count() # count函数的返回值是一个数字。
(4)分组查询
annotate() 返回值依然是 queryset对象,增加了分组统计后的键值对
QuerySet对象.annotate() # annotate() 进行分组统计,按前面select 的字段进行 group by 模型对象.objects.values("id").annotate(course=Count('course__sid')).values('id','course') # 查询指定模型, 按id分组 , 将course下的sid字段计数,返回结果是 name字段 和 course计数结果 # SQL原生语句中分组之后可以使用having过滤,在django中并没有提供having对应的方法,但是可以使用filter对分组结果进行过滤 # 所以filter在annotate之前,表示where,在annotate之后代表having # 同理,values在annotate之前,代表分组的字段,在annotate之后代表数据查询结果返回的字段
(5)原生查询
执行原生SQL语句,也可以直接跳过模型,才通用原生pymysql.
ret = Student.objects.raw("SELECT id,name,age FROM db_student") # student 可以是任意一个模型 # 这样执行获取的结果无法通过QuerySet进行操作读取,只能循环提取 print(ret,type(ret)) for item in ret: print(item,type(item))