Django 之 ORM(全)
六、模型层(ORM)重难点
Django中内嵌了 ORM框架,不需要直接编写SQL语句进行数据库操作,而是通过定义模型类,操作模型来完成对数据库中表的增删改查和创建等操作。
O是 object,也就是 类对象 的意思。
R是 relation,关系的意思,也就是关系数据库中数据表的意思。
M是 mapping,是映射的意思。
映射:
类:sql语句 table表
类成员变量:table表中的字段类型和约束
类对象:sql表的表记录
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. 通过模型类对象提供的方法或属性完成数据表的增删改查操作
6.1 配置数据库连接
在 settings.py 中保存了数据库的连接配置信息,django默认初始配置使用sqlite数据库。
-
使用MySQL数据库首先需要安装驱动程序
pip3 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', }, } }
6.2 定义模型类
定义模型类
- 模型类被定义在 “子应用/model.py”文件中。
- 模型类必须直接或者间接 继承自 django.db.models.Model类。
接下来以学生管理为例进行演示。[系统大概 3-4个表,学生信息,课程信息,老师信息],创建子应用 student,注册子应用并引入子应用路由。
settings.py,代码:
INSTALLED_APPS = [
# ...
'student',
]
urls.py,总路由代码:
urlpatterns = [
# 省略,如果前面有重复的路由,改动如下。
path('student/', include('student.urls')),
]
在 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 # 显示的复数???名称??
# 3. 自定义数据库操作方法
def __str__(self):
"""定义每个数据对象的显示信息"""
return "<User %s>" % self.name
6.2.1 数据库表名
模型类如果未指明表名 db_table,Django默认以 小写app应用名_小写模型类名 为数据库表名。
可通过 db_table指明数据库表名。
6.2.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。
6.2.3 属性命名限制
-
不能是python的保留关键字。
-
不允许使用连续的2个下划线,这是由django的查询方式决定的。__是关键字来的,不能使用!!!!!
-
定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:
属性名 = models.字段类型(约束选项,verbose_name="注释")
6.2.4 字段类型
类型 | 说明 |
---|---|
AutoField | 自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为 id的自动增长属性 |
BooleanField | 布尔字段,值为True或False |
NullBooleanField | 支持Null,True,False三种值 |
CharField | 字符串,参数max_length表示最大字符个数,对应MySQL中的 varchar |
TextField | 大文本字段,一般打断文本(超过4000个字符)才使用。 |
IntegerField | 整数 |
DecimalField | 十进制浮点数,参数max_digits表示总位数,参数decimal_places表示小数位数,常用于表示分数和价格 Decimal(max_digits=7,decimal_places=2) ==> 99999.99 ~ 0.00 |
FloatField | 浮点数 |
DateField | 日期 参数auto_now表示每次保存对象时,自动设置该字段为当前时间。 参数auto_now_add表示当对象第一次被创建时自动设置当前。 参数auto_now_add和auto_now是相互排斥的,一起使用会发生错误。 |
TimeField | 时间,参数同DateField |
DateTimeField | 日期时间,参数同DateFiled |
FileField | 上传文件字段,django在文件字段中内置了文件上传保存类,django可以通过模型的字段存储自动保存上传文件,但是,在数据库中本质上保存的仅仅是文件在项目中的存储路径!! |
ImageField | 继承于FileField,对上传的内容进行校验,确保是有效的图片。 |
6.2.5 约束选项
选项 | 说明 |
---|---|
null | 如果为True,表示允许为空,默认值是False,相当于python的None |
blank | 如果为True,则该字段允许为空白,默认值是False,相当于python的空字符串,“ ” |
db_column | 字段的名称,如果未指定,则使用属性的名称。 |
db_index | 若值为True,则在表中会为此字段创建索引,默认值是False,相当于SQL语句中的key。 |
default | 默认值,当不填写数据时,使用该选项的值作为数据的默认值。 |
primary_key | 如果为True,则该字段会成为模型的主键,默认值是False,一般不用设置,系统默认设置。 |
unique | 如果为True,则该字段在表中必须有唯一值,默认值是False,相当于SQL语句中的unique |
注意:null是数据库范畴的概念,blank是表单验证范畴的。
6.2.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 medels 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 | 笔记本 | 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 被设置默认值。
6.3 数据迁移
将模型类中定义表架构的代码转换成 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
-- 作业:
-- 1. 完成学生的创建,导入上面的数据。
-- 2. 使用原来学过的SQL语句,然后对上面导入的学生信息,完成增删查改的操作。
-- 2.0 查询所有的学生信息(name,age)
SELECT name,age FROM db_student
-- 2.1 查询年龄在18-20之间的学生信息[name,age,sex]
select name,age,sex from db_student where age >=18 and age <=20;
-- 2.2 查询年龄在18-20之间的男生信息[name,age,sex]
select name,age,if(gender=1,'男','女') as sex from db_student where age >=18 and age <=20 and gender=1;
-- 2.3 查询401-406之间所有班级的学生信息[name,age,sex,class]
select name,age,sex,class from db_student where class between 401 and 406;
-- 2.4 查询401-406之间所有班级的总人数
select count(1) as c from db_student where class between 401 and 406;
-- 2.5 添加一个学生,男,刘德华,302班,17岁,"给我一杯水就行了。",'2020-11-20 10:00:00','2020-11-20 10:00:00'
insert into db_student (name,sex,class, age, description, created_time,updated_time) values ('刘德华',1,'302', 17, "给我一杯水就行了。",'2020-11-20 10:00:00','2020-11-20 10:00:00');
-- 2.6 修改刘德华的年龄,为19岁。
update db_student set age=19 where name='刘德华';
-- 2.7 刘德华毕业了,把他从学生表中删除
delete from db_student where name='刘德华';
-- 2.8 找到所有学生中,年龄最小的5位同学和年龄最大的5位同学
select * from db_student order by age asc limit 5;
select * from db_student order by age desc limit 5;
-- 2.9 【进阶】找到所有班级中人数超过4个人班级出来
select class,count(id) as total from db_student group by class having total >= 4;
-- 2.10【进阶】把上面2.8的要求重新做一遍,改成一条数据实现
(select * from db_student order by age asc limit 5) union all (select * from db_student order by age desc limit 5);
6.4 数据库基本操作
6.4.1 添加记录
注意:::
"在讲django1.x版本时,增删改查的模型类对象前都有一个 models 此处应该:"
student = models.Student.objects.filter(pk=1).first()
student.delete()
"模型类前面多了一个 models "
(1)save方法
通过创建模型类对象,执行对象的save()方法,保存到数据库中。
student = Student(
name="刘德华",
age = 50,
gender=1,
birthday="1968-12-23"
)
student.save()
print(student.id) # 判断是否新增有id
(2)create方法
通过模型类.objects.create()保存,返回生成的模型类对象。
student = Student.objects.create(
name="jack",
age=22,
gender=1,
birthday="2000-02-18"
)
print(student.id)
6.4.2 基础查询
ORM 中针对查询结果的限制,提供了一个查询集[QuerySet],这个QuerySet,是ORM 中针对查询结果进行保存数据的一个类型,我们可以通过了解这个QuerySet进行使用,达到查询优化,或者限制查询结果数量的作用。
queryset很重要
"""
l1 = [1,2,3]
准确的说 l1 叫做 列表对象!!
"""
"""
all函数: 返回的是一个queryset ==> 非常接近列表数据,里面的元素是模型类对象
about queryset: QuerySet(查询集)是一个类似于list的数据类型,里面的元素是统一类型,
如:模型类对象/字典...等
"""
(1)all()
查询所有对象,返回queryset对象,查询集,也称查询结果集,
QuerySet,表示从数据库中获取的对象集合。
# API 1. all函数: 返回的是一个queryset ==> 非常接近列表数据,里面的元素是模型类对象
# 查询所有学生
student_list = Student.objects.all()
print("student>>>:", student_list)
(2)first() last()
" : 返回的是模型类对象!!!!!"
# 查询第一个学生
stu = Student.objects.all()[0]
print(stu.name)
print(stu.age)
# print(student_list.name) # 肯定是不可以的,内部的对象才可以用 .name
stu = Student.objects.first()
print(stu.name)
stu = Student.objects.last()
print(stu.name)
**(3)filter() **
"""
filter()方法:where语句 ==> 返回的是queryset查询集
"""
# 查询所有的女生
student_list = Student.objects.filter(gender=0)
student_list = Student.objects.filter(gender=0, age=22) # 逻辑 与,
# student_list>>>: <QuerySet [<Student: 小雨 22>]>
print("student_list>>>:", student_list)
**(4)exclude() **
": 排除符合条件的记录 与 filter相反"
# 查询除了张三以外的所有学生记录
student_list = Student.objects.exclude(name="张三")
print("student_list>>>:", student_list)
(5)get()
"查询结果必须是有且只有一条符合条件的记录,所以返回的是一个查询到的模型类对象"
stu = Student.objects.get(gender=0) # 结果多了报错
stu = Student.objects.get(gender=2) # 没有结果报错
stu = Student.objects.get(id=5) # stu>>>: 王五 22
print("stu>>>:", stu)
(6)order_by()
" 是queryset类型的一个内置方法,返回是一个queryset"
# 将所有学生按照年龄从高到底排序
student_list = Student.objects.all().order_by('-age')
student_list = Student.objects.all().order_by('-age', '-id') # 默认升序,加 - 变成降序排列
print("student_list>>>:", student_list)
(7)count()
"返回int对象,也是queryset的内置方法"
# 查询学生个数
count = Student.objects.all().count()
print("count>>>:", count)
# 查询女生的个数
count1 = Student.objects.filter(gender=0).count()
print("count>>>:", count1)
(8)exist()
"也是queryset的内置方法,判断是否存在记录,返回的是一个布尔值"
# 相比于我们的判断,性能更高
# 查询学生表中是否存在记录
res = Student.objects.all().exists()
print("result>>>:", res) # True
(9)values() 和 values_list()
"""
翻译成的是sql中的 select语句
依然得到queryset,但是里面不是模型类对象,而是字典!!!
"""
student_list = Student.objects.all().values("name", "age")
student_list = Student.objects.all().values_list("name", "age")
print("student_list>>>:", student_list)
student_list>>>: <QuerySet [('张三', 18), ('李四', 33), ('王五', 22)]>
# values用的比较多!!!方便我们序列化
import json
print(json.dumps(list(student_list), ensure_ascii=False))
# 查询所有男生的姓名和年龄
student_list = Student.objects.filter(gender=1).values("name", "age")
print("student_list>>>:", student_list)
student_list>>>: <QuerySet [{'name': '张三', 'age': 18}, {'name': '李四', 'age': 33}, {'name': '王五', 'age': 22}]>
**(10)distinct() **
"""
去重方法,queryset类型内置方法
已经包含id主键的时候根本不可能重复,所以也就没有去重的必要
当查询有重复可能的字段的时候,就有必要了。
"""
Student.objects.all().distinct() # 无法去重
res = Student.objects.values("age").distinct() # 此时可能重复,才能去重
print("result>>>:", res)
6.4.3 模糊查询
(1)模糊查询 之 contains
说明:如果要包含%无需转义,直接写即可。
例:查询姓名包含 ’华‘ 的学生。
Student.objects.filter(name__contains="华")
(2)模式查询 之 startswith,endswith
例:查询姓名以 “文” 结尾的学生
Student.objects.filter(name__endswith="文")
以上运算符都区分大小写,在这些运算符前加上 i 表示不区分大小写,如:iexact,icontains,istartswith,iendswith。
(3)模糊查询 之 isnull
例:查询个性签名不为空的学生。
Student.objects.filter(description__isnull=True)
(4)模糊查询 之 比较查询
gt --- greater then
gte--- greater then equal
lt --- less then
lte --- less then equal
# gt lt gte lte
# 查询
stu1_list = Student.objects.filter(age__gt=30) # 年龄大于30
stu2_list = Student.objects.filter(age__gte=30) # 年龄大于等于30
stu3_list = Student.objects.filter(age__lte=30) # 年龄小于等于30
stu4_list = Student.objects.filter(age__lt=30) # 年龄小于30
print(stu1_list)
print(stu2_list)
print(stu3_list)
print(stu4_list)
(5)模糊查询 之 range
# 查询年龄在20-30之间的学生
stu_list = Student.objects.filter(age__range=(20, 30))
print(stu_list)
(6)模糊查询 之 in
# 查询年龄是 22, 33的所有学生
stu_list = Student.objects.filter(age__in=[22, 30])
print(stu_list)
(7)模糊查询 之 日期查询
year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。
# 查询出生在1992年2月的学生
stu_list = Student.objects.filter(birthday__year=1992, birthday__month=2)
print(stu_list)
6.4.4 进阶查询
比较难,只是用的没有上面两种方式多
(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)
# 1. F函数/对象:两个属性比较,使用F对象
查询语文成绩大于数学成绩的学生
stu_list = Student.objects.filter(chinese_score__gt=F("math_score"))
print(stu_list)
(2)Q查询
多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。
# 逻辑判断
stu_list = Student.objects.filter(gender=0, age=19) # 表示且
stu1_list = Student.objects.filter(gender=0).filter(age=25) # 表示且
# 年龄大于等于30 或 性别为女
stu2_list = Student.objects.filter(Q(age__gte=30) | Q(gender=0))
# 年龄大于等于30 且 性别非女
stu2_list = Student.objects.filter(Q(age__gte=30) & ~Q(gender=0))
print(stu2_list)
如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符,Q对象被义在django.db.models中。
语法如下:
Q(属性名__运算符=值)
Q(属性名__运算符=值) | Q(属性名__运算符=值)
Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或
例:查询年龄大于20,或编号小于30的学生,只能使用Q对象实现
Student.objects.filter(Q(age__gt=20) | Q(pk__lt=30))
Q对象左边可以使用~操作符,表示非not。但是工作中,我们只会使用Q对象进行或者的操作,只有多种嵌套复杂的查询条件才会使用&和~进行与和非得操作
。
(3)聚合函数 aggregate (重点!!!!!)
使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg 平均,Count 数量,Max 最大,Min 最小,Sum 求和,被定义在django.db.models中。
from django.db.models import Sum, Count, Avg, Max, Min
aggregate() 返回的是一个字典
# 查询所有学生的平均年龄
ret1 = Student.objects.aggregate(avg_age=Avg("age")) # 也可以自定键名
ret2 = Student.objects.aggregate(Avg("age"))
print(ret1) # {'avg_age': 24.1667}
print(ret2) # {'age__avg': 24.1667}
# 语文最高的成绩
ret = Student.objects.aggregate(max_chi_score=Max("chinese_score"))
print(ret) # {'max_chi_score': 100}
(4)分组函数 annotate() 重点!!!!
#annotate() values在annotate前对应==> group by字段
# 查询不同性别学生的语文平均成绩
ret = Student.objects.values("gender").annotate(avg_chi=Avg("chinese_score"))
print(ret)
# <QuerySet [{'gender': 1, 'avg_chi': 95.3333}, {'gender': 0, 'avg_chi': 99.6667}]>
# 查询每个班级的数学平均成绩
ret = Student.objects.values("classmate").annotate(avg_math=Avg("math_score"))
print(ret)
# 思考:按照所有的group by,也就是主键分组,也就是每个学生单独一个组,
# 在单表之下,没有什么意义,只有在关联表中才有意义
res = Student.objects.all().annotate(avg_math=Avg("math_score"))
print(res)
(5)原生SQL
# 原生sql,得到的结果只能循环提取,不能有很多其他操作
ret = Student.objects.raw("select id,name,age from db_student")
# 这样执行获取的结果无法通过queryset进行操作读取,只能循环提取
print(ret, type(ret))
for item in ret:
print(item, type(item), item.name)
6.4.5 修改记录
(1)使用save更新数据
# 方式1:基于模型对象 save操作 不推荐
stu = Student.objects.get(name="李四")
print(stu.name, stu.age)
stu.chinese_score = 88
stu.save() # 将stu中所有属性都更新一边,性能很差!!!!
# save之所以能提供给我们添加数据的同时,还可以更新数据的原因?
# save会找到模型的字段的主键id的值,
# 主键id的值如果是none,则表示当前数据没有被数据库,所以save会自动变成添加操作
# 主键id有值,则表示当前数据在数据库中已经存在,所以save会自动变成更新数据操作
(2)update更新(推荐)
使用模型类.objects.filter().update(),会返回受影响的行数
# 方式2:queryset对象的update方法,queryset才能调用,
# update是全局更新,只要符合更新的条件,则全部更新,因此强烈建议加上条件!!!
stu_list = Student.objects.filter(name="李四").update(chinese_score=100, math_score=99)
stu2_list = Student.objects.filter(age__gt=30).update(chinese_score=101, math_score=101)
print(stu_list)
print(stu2_list)
# 将年龄小于30岁的学生,语文成绩降低20分
from django.db.models import F, Q
Student.objects.filter(age__lt=30).update(chinese_score=F("chinese_score")-20)
6.4.6 删除记录
删除记录也有两种方法
(1)模型类对象.delete
# 1:基于模型类对象删除,内置于模型类对象中的delete方法
pk2=2 == id=2
#
(2)模型类.objects.filter().delete()
# 2:基于queryset删除,两个delete不同,内置于queryset中的delete方法
Student.objects.filter(chinese_score__lt=80).delete()
代码:
# 1. 先查询到数据模型对象。通过模型对象进行删除
# student = Student.objects.filter(pk=13).first()
# student.delete()
# 2. 直接删除
ret = Student.objects.filter(pk=100).delete()
print(ret)
# 务必写上条件,否则变成了清空表了。ret = Student.objects.filter().delete()
6.5 创建关联模型
-- student
id name age class_name class_tutor class_num
1 rain 22 s12 jack 1
2 alvin 24 s12 jack 1
-- 一对多,一定是在多的表中创建关联字段
-- 多对多,多对多的关系的确立是通过创建第三张关系表来完成的
-- 一对一,垂直分割,绝对的一对一
# 相当于把一个表分割成两个及以上部分,
create table student(
id int primary key auto_increment,
name varchar(32),
class_id int not null,
foreign key (class_id) references class(id) on delete cascade
);
create table class(
id int primary key auto_increment,
name varchar(32)
);
create table course(
id int primary key auto_increment,
name varchar(32)
);
create table stu2course(
id int primary key auto_increment,
student_id int not null,
course_id int not null,
foreign key (student_id) references student(id),
foreign key (course_id) references course(id)
);
实例:我们来假定下面这些概念,字段和关系
- 班级模型:班级名称,导师。
- 课程模型:课程名称,讲师。
- 学生模型:学生有 姓名,年龄,只有一个班级,所以和班级表是一对多的关系(one-to-many),选修了多个课程,所以和课程表是多对多的关系(many-to-many)
- 学生详情:学生的家庭地址,手机号,邮箱等详细信息,和学生模型应该是一对一的关系(one-to-one)。
模型建立如下:
from django.db import models
# Create your models here.
class Clas(models.Model):
name = models.CharField(max_length=32, unique=True, verbose_name="班级名称")
class Meta:
db_table = "db_class"
class Course(models.Model):
title = models.CharField(max_length=32, verbose_name="课程名称")
# 多对多时关系表的创建在哪个表中都可以
# students = models.ManyToManyField("Student", db_table="db_stu2course")
class Meta:
db_table = "db_course"
class Student(models.Model):
gender_choices = (
(0, '女'),
(1, '男'),
(2, '保密'),
)
name = models.CharField(max_length=32, unique=True, verbose_name="姓名")
age = models.SmallIntegerField(verbose_name="年龄", default=18)
gender = models.SmallIntegerField(choices=gender_choices)
# 建立一对多的关系:会在数据库中创建一个关联字段,名称叫做clas_id
# 一对多的关系必须在多的表中建立,多对多则不用
clas = models.ForeignKey(to="Clas", on_delete=models.CASCADE, db_constraint=False)
# db_constraint=False表示不加约束,则仅仅创建一个clas_id的字段,没有外键约束
# 建立多对多的关系:创建第三张关系表
courses = models.ManyToManyField("Course", db_table="db_stu2course")
# 这句话可以帮我们创建第三张关系表,里面有三个字段。
# 一对一的关系:建立关联字段,在数据库中创建一个关联字段,名称叫做stu_detail_id
stu_detail = models.OneToOneField("StudentDetail", on_delete=models.CASCADE)
class Meta:
db_table = "db_student"
class StudentDetail(models.Model):
tel = models.CharField(max_length=11)
addr = models.CharField(max_length=32)
class Meta:
db_table = "db_stu_detail"
6.6 关联添加
6.6.1 一对一 与 一对多
# 添加记录:一对多 与 一对一的关联属性
stu = Student.objects.create(name="张三", age=22, gender=1, clas_id=2, stu_detail_id=1,)
stu = Student.objects.create(name="李四", age=24, gender=1, clas_id=2, stu_detail_id=2,)
print(stu.name)
print(stu.age)
print(stu.clas_id) # 2
print(stu.stu_detail_id) # 2
print(stu.clas) # 模型类对象,因为一个学生只能有一个班级类对象 Clas objects (2)
# 该学生的班级对象的名称
print(stu.clas.name) # Clas.objects.get(pk=2)
# 计算机2班
stu = Student.objects.get(name="李四")
print(stu.name) # 李四
print(stu.age) # 24
print(stu.clas_id) # 2
print(stu.clas.name) # ORM可以帮调出来 计算机2班
print(stu.stu_detail.addr) # 北京
print(stu.stu_detail.tel) # 110
6.6.2 多对多
# ################多对多的关联记录的:增删改查!!!!
# 方式一:新添加的学生,绑定课程
stu = Student.objects.create(name="rain", age=28, gender=1, clas_id=3, stu_detail_id=3)
# stu是Student类的对象,所以就会有courses属性,stu.courses是第三张表的入口
# 添加多对多的数据,比如stu这个学生绑定两门课程,思修和逻辑学25
c1 = Course.objects.get(title="思修")
c2 = Course.objects.get(title="逻辑学")
stu.courses.add(c1, c2) # 这句话ORM会找到关系表,添加记录c1和c2,stuid 3绑定两个课程2和5
# 添加多对多方式二:给查询出来的 张三绑定课程
stu = Student.objects.get(name="张三")
stu.courses.add(3, 4) # 篮球和毛概,直接放课程id即可
# 添加多对多方式三
stu = Student.objects.get(name="李四")
stu.courses.add(*[1, 3]) # 学生绑定的课程不是写死的,后端处理完格式之后就是列表,所以就用此方法比较多
# 删除多对多记录
stu = Student.objects.get(name="李四")
stu.courses.remove(1) # 删除李四的课程1近代史
# clear清除方法
stu = Student.objects.get(name="rain")
stu.courses.clear()
# set 重置方法,将原来已有的绑定去掉,重新绑定新的
stu = Student.objects.get(name="李四")
stu.courses.set([1, 2])
# all 查
# 查询李四所报课程的名称
stu = Student.objects.get(name="李四")
courses = stu.courses.all() # 找到stu_id2的,查到course_id为1,2再去course中
print(courses)
# <QuerySet [<Course: Course object (1)>, <Course: Course object (2)>]>
# <QuerySet [<Course: 近代史>, <Course: 思修>]> 因为在course类中定义了str方法
print(courses.values("title"))
6.7 关联查询
6.7.1 基于对象查询(子查询)
"""
正向查询:通过关联属性查询 属于正向查询,反之则成为反向查询
反向查询
基于对象的关联查询(子查询)
:param request:
:return:
"""
"# ####################一对多的关联,学生和班级"
# 查询张三所在的的班级名称(正向)
stu = Student.objects.get(name="张三")
print(stu.clas.name) # 基于对象的关联字段
# 查询计算机2班有哪些学生 (反向)
clas = Clas.objects.get(name="计算机2班")
ret = clas.student_set.all() # _setORM要求加的,表名这是一个集合变量
# 反向查询方式一: 按照表名小写_set
print(ret) # <QuerySet [<Student: 张三>, <Student: 李四>]>
# 反向查询方式二::related_name
clas = Clas.objects.get(name="计算机2班")
print(clas.stu_list.all()) # <QuerySet [<Student: 张三>, <Student: 李四>]>
# stu_list是Student类中clas的related_name值,自定义的
"# ########################## 一对一的关联"
# 查询李四的手机号,正向查询
stu = Student.objects.get(name="李四")
print(stu.stu_detail.tel)
# 查询手机号为120的学生姓名和年龄,反向查询
stu_d = StudentDetail.objects.get(tel="120")
# 方式一:反向查询,表名小写
print(stu_d.student.name)
print(stu_d.student.age)
# 方式二:related_name
stu_d = StudentDetail.objects.get(tel=120)
print(stu_d.stu_det.name)
print(stu_d.stu_det.age)
"# ######################### 多对多关联查询"
# 查询张三所报课程的名称,正向
stu = Student.objects.get(name="张三")
print(stu.courses.all())
# 查询报名近代史的学生的姓名和年龄,反向
course = Course.objects.get(title="近代史")
# 反向查询方式一:表名小写_set
stu_lis = course.student_set.all()
print(stu_lis)
# 反向查询方式二:related_name
course = Course.objects.get(title="近代史")
print(course.stus_list.all().values("name", "age"))
- 正向查询按字段。
- 反向查询按表名小写,或者 related_name
6.7.2 基于双下划线查询(join查询)
"""
基于双下划线 -- join
:param request:
:return:
"""
# ############# 一对多案例
# 查询张三的年龄
ret = Student.objects.filter(name="张三").values("age")
print(ret) # <QuerySet [{'age': 22}]>
# 1. 查询年龄大于22的学生的姓名以及所在班级名称
# 方式一:Student作为基表,此时正向查询
ret = Student.objects.filter(age__gt=22).values("name", "clas__name")
print(ret)
# 方式二:Clas作为基表
ret2 = Clas.objects.filter(stu_list__age__gt=22).values("stu_list__name", "name")
print(ret2)
# 2. 查询计算机2班有哪些学生
ret = Clas.objects.filter(name="计算机2班").values("stu_list__name")
print(ret)
# #################### 多对多案例
# 3. 查询张三所报课程名称
ret = Student.objects.filter(name="张三").values("courses__title")
print(ret)
# 4. 查询选修了近代史这门课程的学生的姓名和年龄
ret = Course.objects.filter(title="近代史").values("stus_list__name","stus_list__age")
print(ret)
# 5. 一对一,查询李四的手机号
ret = Student.objects.filter(name="李四").values("stu_detail__tel")
print(ret)
# ################# 进阶
# 6. 查询手机号是120的学生的姓名和所在班级
# 方式1: 以StudentDetail作为基表,这种方式存在连续跨表的情况
ret = StudentDetail.objects.filter(tel="120").values("stu_det__name", "stu_det__clas__name")
print(ret)
# 方式2: 以Student作为基表就不存在连续跨表的问题
ret = Student.objects.filter(stu_detail__tel=110).values("name", "clas__name")
print(ret)
6.7.3 关联分组查询
# ############### 分组查询
from django.db.models import Avg, Count, Max,Min
ret = Student.objects.values("gender").annotate(c=Count("name"))
print(ret) # <QuerySet [{'gender': 0, 'c': 1}, {'gender': 1, 'c': 3}]>
# 1. 查询每一个班级的名称以及学生个数
from django.db.models import Avg, Count, Max, Min
res = Clas.objects.values("name").annotate(c=Count("stu_list__name"))
print(res)
"""
<QuerySet [{'name': '网络工程1班', 'c': 1}, {'name': '网络工程2班', 'c': 0},
{'name': '计算机1班', 'c': 1}, {'name': '计算机2班', 'c': 2}, {'name': '软件1班', 'c': 0}]>
"""
# 2. 查询每一个学生的姓名以及选修课程的个数
from django.db.models import Avg, Count, Max, Min
res = Student.objects.values("name").annotate(c=Count("courses__title"))
print(res)
# 2.1 查询每一个学生的姓名,年龄以及选修课程的个数
from django.db.models import Avg, Count, Max, Min
res = Student.objects.values("name", "age").annotate(c=Count("courses__title"))
print(res)
# 更优解法
res1 = Student.objects.all()
res2 = Student.objects.all().annotate(c=Count("courses__title"))
print(res1) # <QuerySet [<Student: 张三>, <Student: 李四>, <Student: rain>, <Student: 小雨>]>
print(res2) # 和上面的结果看似一样,但是实际上对象内部有一个c属性
"所以可以这样:"
res3 = Student.objects.all().annotate(c=Count("courses__title")).values("name", "age", "gender", "c")
print(res3)
# 3. 查询每一个课程名称以及选修学生的个数
res = Course.objects.all().annotate(c=Count("stus_list__name")).values("title", "c")
print(res)
"where分组之前过滤,having分组之后过滤"
# 4. 查询选修课程个数大于 1 的学生姓名以及 选修课程个数
res = Student.objects.all().annotate(c=Count("courses__title")).filter(c__gt=1).values("name", "c")
print(res)
# 5. 查询每一个学生的姓名以及选修的课程个数 并 按照选修课程个数进行排序低到高
res = Student.objects.all().annotate(c=Count("courses__title")).order_by("c").values("name", "c")
print(res)
6.8 项目练习
学生管理系统