Django进阶¶
0. 前置知识¶
常用数据库有2大阵营:
1. 关系型数据库[RDB]: 数据库中存储数据的表之间存在某种内在的关联关系,因为这种关系,所以我们称这一类型的数据为关系型数据库。
常见的数据库:mysql[MariaDB],PostgreSQL(简称PG),Oracle,MSSQLServer,DB2,sqlite,Access,SequoiaDB
共同的特点:都使用了SQL语句进行数据库操作。
2. 非关系型数据库[NOSQL(Not Only SQL)]:泛指那些不适用SQL语句进行数据库操作的所有其他数据库。
常见的数据库:Redis,MongoDB, Memcached,Elasticsearch,HBase/CouchDB, Neo4j、FlockDB、AllegroGrap,rethinkDB
表与表之间的内在关联关系,一般就是表之间的外键关系
Student 学生模型
| student_id | name | class_id |
|---|---|---|
| 1 | 小明 | 1 |
| 2 | 小黑 | 1 |
| 3 | 小红 | 2 |
Course 课程模型
| class_id | class_name | |
|---|---|---|
| 1 | 计算机科学1班 | |
| 2 | 商务英语1班 | |
1. ORM框架¶
django中内置了一个基于面向对象的强大的ORM框架给开发者对数据库进行操作的。
O是Object,也就类对象的意思。
R是Relational,翻译成中文是关系,也就是关系型数据库中数据表的意思。
M是mapping,是映射的意思,表示类对象和数据表之间的映射关系。
ORM框架会帮我们把类对象和数据表进行了一对一的映射,让我们可以通过类对象来操作对应的数据表。
ORM框架还可以根据我们设计的类自动帮我们生成数据库中的数据表,也就是使用代码生成数据库建表语句,省去了我们自己建表的过程。
django中内嵌了ORM框架,不需要直接编写SQL语句进行数据库操作,而是通过定义模型类,操作模型类来完成对数据库中数据的增删改查和数据表的创建删除等操作。
1.1 ORM的优点¶
数据模型类都在一个地方定义,更容易更新和维护,也利于重用代码。
ORM 有现成的工具,很多功能都可以自动完成,比如数据消除、预处理、事务等等。
ORM迫使开发人员使用 MVC 架构,ORM 就是天然的 Model,最终使代码结构更清晰易懂。
基于 ORM 的业务代码比较简单,代码量少,语义性好,容易理解。
新手对于复杂业务容易写出性能不佳的 SQL,有了ORM不必编写复杂的SQL语句, 只需要通过操作模型对象即可同步修改数据表中的数据.
开发中应用ORM将来如果要切换数据库.只需要切换ORM底层对接数据库的驱动【修改配置文件的连接地址即可】
1.2 ORM 也有缺点¶
- ORM 库不是轻量级工具,需要花很多精力学习和设置,甚至不同的项目框架会存在不同操作的ORM。
- 对于复杂的业务查询,ORM表达起来比原生的SQL要更加困难和复杂。ORM并非万能的,总有些功能需要我们使用原生SQL语句,当然ORM也提供执行原生SQL语句的功能
- ORM操作数据库的性能要比使用原生pymysql执行SQL差。[ORM内部要拼接SQL语句]
- ORM 抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的 SQL。【自己使用pymysql另外操作即可,用了ORM并不表示当前项目不能使用别的数据库操作工具。】
1.3 配置数据库连接¶
在settings.py中保存了数据库的连接配置信息,Django默认初始配置使用sqlite数据库。
我们可以通过以下步骤来使用django的数据库操作
1. settings.py配置数据库连接信息
2. 在目标子应用下的models.py中定义模型类
3. 生成数据库迁移文件并执行迁移文件[注意:数据迁移是一个独立的用于创建数据表的功能,这个功能在其他web框架未必和ORM一块的]
第3步非必要,有些公司直接由DBA(数据库管理员)设计与管理数据表的,那么就不需要在django中数据迁移了。直接开撸代码
4. 通过模型类对象提供的方法或属性完成数据表的增删改查操作
- 使用MySQL数据库首先需要安装驱动程序
- 在Django的主应用目录的
__init__.py文件中添加如下语句,djdemo/__init__.py,代码:
作用是让Django的ORM能以mysqldb的方式来调用PyMySQL。
- 修改DATABASES配置信息,settings.py配置文件配置数据库的连接信息。
djdemo/settings.py
# Database
# https://docs.djangoproject.com/zh-hans/4.2/ref/settings/#databases
# 数据库配置
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': 'django.db.backends.mysql', # ORM的底层对接pymysql的核心引擎类
'NAME': 'school', # 数据库名
'PORT': 3306, # 端口
'HOST': '127.0.0.1', # 数据库IP
'USER': 'root', # 账号
'PASSWORD': '123', # 密码
'POOL_OPTIONS': { # pool表示数据库连接池配置,主要为了节省连接数据库的开销,临时存储数据库连接对象
'POOL_SIZE': 10, # 默认情况下,打开的数据库连接对象的数量 [1,2,3,4,5,6,7,8,9,10]
'MAX_OVERFLOW': 30, # 负载情况下,允许溢出的连接数量 [11,12,13,14,15,16,17,18,19,20]
}
# "TEST": {
#
# }
}
}
- 在MySQL中创建数据库
1.4 定义模型类¶
- 模型类被定义在"子应用/models.py"文件中。
- 模型类必须直接或者间接继承于django.db.models.Model类【Model相当于我们之前学习mysql基础时封装的DB类一样,里面提供了大量对数据库的操作方法】。
接下来以学校的学生管理为例进行演示。[系统大概3-4表,学生信息,课程信息,老师信息],之前我们在mysql基础时直接使用SQL来建表,现在我们可以使用django提供的数据迁移来生成建表SQL语句,并创建数据表。使用数据迁移,必须先定义模型。
再次强调,工作中并非每个公司都使用数据迁移的!
1.5 模型定义¶
创建子应用student,注册子应用并引入子应用路由.
settings.py,代码:
djdemo/urls.py,总路由代码:
student.urls,代码:
在student/models.py 文件中定义模型类。
from django.db import models
"""
1. django中所有的模型,必须直接或间接继承models.Model模型基类
"""
"""
-- auto-generated definition
create table student
(
id bigint auto_increment
primary key,
name varchar(15) not null,
age smallint not null,
sex tinyint(1) not null,
class varchar(50) not null,
mobile varchar(20) not null,
description longtext null,
status int null,
created_time datetime(6) null,
updated_time datetime(6) null,
constraint mobile
unique (mobile)
);
create index student_class_ad9a3931
on student (class);
create index student_name_68e26583
on student (name);
"""
class BaseModel(models.Model):
# auto_now_add 设置新建数据时,把当前时间戳作为默认值保存到当前字段中
created_time = models.DateTimeField(auto_now_add=True, null=True, verbose_name="创建时间")
# auto_now 设置更新数据时,把当前时间戳作为默认值保存到当前字段中
updated_time = models.DateTimeField(auto_now=True, null=True, verbose_name="更新时间")
class Meta:
# 设置当前类为抽象模型,表示当前模型并不是一个真正的表,django就不会跟踪识别这个模型了。
abstract = True
# Create your models here.
class Student(BaseModel):
STATUS = (
# (数据库值, "程序显示给外界看的文本"),
(0, "正常"),
(1, "未入学"),
(2, "已毕业"),
)
# django模型中不需要自己单独声明主键,模型会自动创建主键ID,将来直接可以通过模型对象.id 或者 模型对象.pk就可以调用主键了。
name = models.CharField(max_length=15, db_index=True, verbose_name="姓名")
age = models.SmallIntegerField(default=0, verbose_name="年龄")
sex = models.BooleanField(default=True, verbose_name="性别")
classmate = models.CharField(max_length=50, db_column="class", default="", db_index=True, verbose_name="班级编号")
mobile = models.CharField(max_length=20, unique=True, verbose_name="手机号码")
description = models.TextField(blank=True, null=True, verbose_name="个性签名")
status = models.IntegerField(choices=STATUS, default=1, null=True, verbose_name="毕业状态")
class Meta:
db_table = "student"
verbose_name = "学生信息"
verbose_name_plural = verbose_name
def __str__(self):
# 当使用print打印django模型对象时的输出内容,返回值必须是字符串,方法名固定
return self.name
1) 数据库表名¶
可通过Meta.db_table 指明数据库表名。
模型类如果未指明表名Meta.db_table,Django默认以 小写子应用目录名_小写模型类名 为数据库表名。
2) 关于主键¶
django会为模型自动声明一个自动增长的主键列,每个模型只能有一个主键列。
如果使用选项设置某个字段的约束属性为主键列(primary_key)后,django不会再创建自动增长的主键列。
class Student(models.Model):
# django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk
id = models.BigAutoField(primary_key=True, verbose_name="主键") # 设置主键
默认创建的主键列属性名为id,也可以使用pk代替id,pk全拼为primary key。
3) 属性命名限制¶
-
不能是python的关键字或保留字。
-
不允许使用连续的2个下划线,这是因为django的查询方式提供了很多以
__xx开头的方法或属性,所以__xx是django的模型内部的关键字,不能使用! -
定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:
属性名 = models.字段类型(约束选项, verbose_name="注释")
# 在mysql数据表中的字段名如果在python是一个关键字/保留字。则选项中需要通过db_column()来进行关联绑定
class_name = models.SmallIntegerField(db_column="class", verbose_name="班级")
# class就是实际上在mysql数据表中的真实字段名
# class_name 就是python中将来用于操作数据的属性名
4)字段类型¶
文档:https://docs.djangoproject.com/zh-hans/4.2/ref/models/fields/#field-types
| 类型 | 说明 |
|---|---|
| AutoField | 自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性,并且提供了一个pk属性与主键进行关联。django3.0以后,系统默认采用了BigAutoField,从原来的int类型变成了bigint类型,其他不变。 |
| BooleanField | 布尔字段,值为True或False,实际上在数据库中存储的是smallint,采用0/1表示False/True |
| NullBooleanField | 支持None、True、False三种值,在4.0版本的django中已经被声明废弃了。 |
| CharField | 字符串,参数max_length表示最大字符个数,对应mysql中的varchar |
| TextField | 大文本字段,一般大段文本(超过4000个字符)才使用,对应mysql中的bigtext。 |
| IntegerField | 整数,对应mysql中的int |
| BigIntegerField | 大整型,对应mysql的bitint |
| DecimalField | 十进制定点数, 参数maxdigits表示总位数, 参数decimalplaces表示小数位数,常用于表示分数和价格 Decimal(maxdigits=7, decimalplaces=2) ==> 99999.99~ 0.00 |
| FloatField | 浮点数 |
| DateField | 日期,年-月-日 参数autonow表示每次创建/更新对象时,自动设置该字段的值为当前时间。 参数autonowadd表示当对象第一次被创建时自动设置该字段的值为当前时间。 参数autonowadd和autonow是相互排斥的,一起使用会发生错误。 |
| TimeField | 时间,时分秒,参数同DateField |
| DateTimeField | 日期时间,年月日时分秒,参数同DateField |
| FileField | 继承于CharField,上传文件字段类型,但是django在FileField中内置了文件上传保存类, django可以通过模型的字段存储自动保存上传文件, 但是在数据库中本质上保存的仅仅是文件在项目中的存储路径!!参数upload_to,表示设置当前上传文件的存储路径位置,当前路径如果不存在,django会自动生成路径。 |
| ImageField | 继承于FileField,对上传的内容进行校验,确保是有效的图片 |
| EmailField | 继承于CharField,邮件字段类型,但是增加字段值的验证是否为邮件格式 |
| UUIDField | 继承于CharField,UUID字段类型,保存内容格式字符串,但是会验证当前字段是否是UUID格式。 |
5)约束选项¶
文档:https://docs.djangoproject.com/zh-hans/4.2/ref/models/fields/#field-options
| 选项 | 说明 |
|---|---|
| null | 如果为True,表示允许为空,默认值是False。相当于python的None |
| blank | 如果为True,则该字段允许为空白,默认值是False。 相当于python的空字符串:"" |
| db_column | 数据表中真实的字段名称,如果未指定,则使用模型类属性的名称。防止数据字段是python的关键字。 |
| db_index | 若值为True, 则在表中会为此字段创建索引,默认值是False。 相当于SQL语句中的key |
| default | 默认值,当不填写数据时,使用该选项的值作为字段的默认值。 |
| primary_key | 如果为True,则该字段在表中设置为主键,默认值是False,一般不用设置,django会自动默认设置id作为主键。 |
| unique | 如果为True,则该字段在表中创建唯一索引,默认值是False。相当于SQL语句中的unique |
| choices | 设置当前字段值的候选值选项,必须是二维序列,子序列必须是2个成员。 |
| help_text | 设置当前字段的辅助提示文本信息,是一个人类可读的文本信息。一般显示给客户端(前台)的。 |
| verbose_name | 设置当前字段的提示字段名,是一个人类可读的名称,一般显示给服务端(后台)的。 |
注意:null是数据库范畴的概念,blank是表单验证范畴的
完成了模型的创建以后,因为之前我们在数据库中已通过SQL语句在school创建了student这个数据表了。所以我们此时只要模型的字段和数据表的字段名对应一致,则直接可以在视图中对模型进行操作了。
-- 1. 安装数据库驱动类,
-- pip install pymysql
-- 进入数据库中创建一个测试的数据表
-- mysql -uroot -p123
create database school;
use school;
-- 完成上面的步骤以后,建表有2种方式:手动SQL语句建表与数据迁移
-- 数据迁移
-- cd 项目根目录[manage.py所在位置]
python manage.py makemigrations
python manage.py migrate
-- 建表语句生成如下:
-- auto-generated definition
create table student
(
id bigint auto_increment
primary key,
name varchar(15) not null,
age smallint not null,
sex tinyint(1) not null,
class varchar(50) not null,
mobile varchar(20) not null,
description longtext null,
status int null,
created_time datetime(6) null,
updated_time datetime(6) null,
constraint mobile
unique (mobile)
);
create index student_class_ad9a3931
on student (class);
create index student_name_68e26583
on student (name);
1.6 添加测试数据¶
在数据库终端添加测试数据,SQL语句:
INSERT INTO `student` (id, name, sex, class, age, description, created_time, updated_time, mobile, status)
VALUES
(1,'赵华',1,307,22,'对于勤奋的人来说,成功不是偶然;对于懒惰的人来说,失败却是必然。', NOW(), NOW(), "13312345601", 0),
(2,'程星云',1,301,20,'人生应该如蜡烛一样,从顶燃到底,一直都是光明的。',NOW(), NOW(), "13312345602", 0),
(3,'陈峰',1,504,21,'在不疯狂,我们就老了,没有记忆怎么祭奠呢?',NOW(), NOW(), "133123456703", 0),
(4,'苏礼就',1,502,20,'不要为旧的悲伤,浪费新的眼泪。',NOW(), NOW(), "13312345604", 0),
(5,'张小玉',2,306,18,'没有血和汗水就没有成功的泪水。',NOW(), NOW(), "13312345605", 0),
(6,'吴杰',1,307,19,'以大多数人的努力程度之低,根本轮不到去拼天赋',NOW(), NOW(), "13312345606", 0),
(7,'张小辰',2,405,19,'人生的道路有成千上万条, 每一条路上都有它独自的风景。',NOW(), NOW(), "13312345607", 0),
(8,'王丹丹',2,502,22,'平凡的人听从命运,坚强的人主宰命运。',NOW(), NOW(), "13312345608", 0),
(9,'苗俊伟',1,503,22,'外事找谷歌,内事找百度。',NOW(), NOW(), "13312345609", 0),
(10,'娄镇明',1,301,22,'不经三思不求教,不动笔墨不读书。',NOW(), NOW(), "13312345610", 0),
(11,'周梦琪',2,306,19,'学习与坐禅相似,须有一颗恒心。',NOW(), NOW(), "13312345611", 0),
(12,'欧阳博',1,503,23,'春去秋来,又一年。What did you get ?',NOW(), NOW(), "13312345612", 0),
(13,'颜敏莉',2,306,20,'Knowledge makes humble, ignorance makes proud.',NOW(), NOW(), "13312345613", 0),
(14,'柳宗仁',1,301,20,'有志者事竟成。',NOW(), NOW(), "13312345614", 0),
(15,'谢海龙',1,402,22,'这世界谁也不欠谁,且行且珍惜。',NOW(), NOW(), "13312345615", 0),
(16,'邓士鹏',1,508,22,'青,取之于蓝而青于蓝;冰,水为之而寒于水。',NOW(), NOW(), "13312345616", 0),
(17,'宁静',2,502,23,'一息若存 希望不灭',NOW(), NOW(), "13312345617", 0),
(18,'上官屏儿',2,502,21,'美不自美,因人而彰。',NOW(), NOW(), "13312345618", 0),
(19,'孙晓静',2,503,20,'人生本过客,何必千千结;无所谓得失,淡看风和雨。',NOW(), NOW(), "13312345619", 0),
(20,'刘承志',1,306,20,'good good study,day day up! ^-^',NOW(), NOW(), "13312345620", 0),
(21,'王浩',1,503,21,'积土而为山,积水而为海。',NOW(), NOW(), "13312345621", 0),
(22,'钟无艳',2,303,19,'真者,精诚之至也,不精不诚,不能动人。',NOW(), NOW(), "13312345622", 0),
(23,'莫荣轩',1,409,22,'不管发生什么事,都请安静且愉快地接受人生,勇敢地、大胆地,而且永远地微笑着。',NOW(), NOW(), "13312345623", 0),
(24,'张裕民',1,303,21,'伟大的目标形成伟大的人物。',NOW(), NOW(), "13312345624", 0),
(25,'江宸轩',1,407,22,'用最少的悔恨面对过去。',NOW(), NOW(), "13312345625", 0),
(26,'谭季同',1,305,21,'人总是珍惜未得到的,而遗忘了所拥有的。',NOW(), NOW(), "13312345626", 0),
(27,'李松风',1,504,19,'明天的希望,让我们忘了今天的痛苦。',NOW(), NOW(), "13312345627", 0),
(28,'叶宗政',1,407,20,'因害怕失败而不敢放手一搏,永远不会成功。',NOW(), NOW(), "13312345628", 0),
(29,'魏雪宁',2,306,20,'成功与失败只有一纸之隔',NOW(), NOW(), "13312345629", 0),
(30,'徐秋菱',2,404,19,'年轻是我们唯一拥有权利去编织梦想的时光。',NOW(), NOW(), "13312345630", 0),
(31,'曾嘉慧',2,301,19,'有一分热,发一分光。就令萤火一般,也可以在黑暗里发一点光,不必等候炬火。',NOW(), NOW(), "13312345631", 0),
(32,'欧阳镇安',1,408,23,'青春虚度无所成,白首衔悲补何及!',NOW(), NOW(), "13312345632", 0),
(33,'周子涵',2,309,19,'青春是一个普通的名称,它是幸福美好的,但它也是充满着艰苦的磨炼。',NOW(), NOW(), "13312345633", 0),
(34,'宋应诺',2,501,23,'涓滴之水终可以磨损大石,不是由于它力量强大,而是由于昼夜不舍的滴坠。',NOW(), NOW(), "13312345634", 0),
(35,'白瀚文',1,305,19,'一个人假如不脚踏实地去做,那么所希望的一切就会落空。',NOW(), NOW(), "13312345635", 0),
(36,'陈匡怡',2,505,19,'一份耕耘,一份收获。',NOW(), NOW(), "13312345636", 0),
(37,'邵星芸',2,503,22,'冰冻三尺非一日之寒。',NOW(), NOW(), "13312345637", 0),
(38,'王天歌',2,302,21,'任何的限制,都是从自己的内心开始的。',NOW(), NOW(), "13312345638", 0),
(39,'王天龙',1,302,22,'再长的路,一步步也能走完,再短的路,不迈开双脚也无法到达。',NOW(), NOW(), "13312345639", 0),
(40,'方怡',2,509,23,'智者不做不可能的事情。',NOW(), NOW(), "13312345640", 0),
(41,'李伟',1,505,19,'人之所以能,是相信能。',NOW(), NOW(), "13312345641", 0),
(42,'李思玥',2,503,22,'人的一生可能燃烧也可能腐朽,我不能腐朽,我愿意燃烧起来。',NOW(), NOW(), "13312345642", 0),
(43,'赵思成',1,401,18,'合抱之木,生于毫末;九层之台,起于累土。',NOW(), NOW(), "13312345643", 0),
(44,'蒋小媛',2,308,22,'不积跬步无以至千里,不积细流无以成江河。',NOW(), NOW(), "13312345644", 0),
(45,'龙华',1,510,19,'只要持续地努力,不懈地奋斗,就没有征服不了的东西。',NOW(), NOW(), "13312345645", 0),
(46,'牧婧白夜',2,501,21,'读不在三更五鼓,功只怕一曝十寒。',NOW(), NOW(), "13312345646", 0),
(47,'江俊文',1,304,19,'立志不坚,终不济事。',NOW(), NOW(), "13312345647", 0),
(48,'李亚容',2,304,18,'Keep on going never give up.',NOW(), NOW(), "13312345648", 0),
(49,'王紫伊',2,301,22,'最可怕的敌人,就是没有坚强的信念。',NOW(), NOW(), "13312345649", 0),
(50,'毛小宁',1,501,19,'要从容地着手去做一件事,但一旦开始,就要坚持到底。',NOW(), NOW(), "13312345650", 0),
(51,'董 晴',2,507,19,'常常是最后一把钥匙打开了门。贵在坚持',NOW(), NOW(), "13312345651", 0),
(52,'严语',2,405,18,'逆水行舟,不进则退。',NOW(), NOW(), "13312345652", 0),
(53,'陈都灵',2,503,19,'无论什么时候,不管遇到什么情况,我绝不允许自己有一点点灰心丧气。',NOW(), NOW(), "13312345653", 0),
(54,'黄威',1,301,23,'我的字典里面没有“放弃”两个字',NOW(), NOW(), "13312345654", 0),
(55,'林佳欣',2,308,23,'梦想就是一种让你感到坚持,就是幸福的东西。',NOW(), NOW(), "13312345655", 0),
(56,'翁心颖',2,303,19,'有目标的人才能成功,因为他们知道自己的目标在哪里。',NOW(), NOW(), "13312345656", 0),
(57,'蒙毅',1,502,22,'所谓天才,就是努力的力量。',NOW(), NOW(), "13312345657", 0),
(58,'李小琳',2,509,22,'每天早上对自己微笑一下。这就是我的生活态度。',NOW(), NOW(), "13312345658", 0),
(59,'伍小龙',1,406,19,'一路上的点点滴滴才是我们的财富。',NOW(), NOW(), "13312345659", 0),
(60,'晁然',2,305,23,'人的价值是由自己决定的。',NOW(), NOW(), "13312345660", 0),
(61,'端木浩然',1,507,18,'摔倒了爬起来再哭。',NOW(), NOW(), "13312345661", 0),
(62,'姜沛佩',2,309,21,'Believe in yourself.',NOW(), NOW(), "13312345662", 0),
(63,'李栋明',1,306,19,'虽然过去不能改变,但是未来可以。',NOW(), NOW(), "13312345663", 0),
(64,'柴柳依',2,508,23,'没有实践就没有发言权。',NOW(), NOW(), "13312345664", 0),
(65,'吴杰',1,401,22,'人生有两出悲剧。一是万念俱灰;另一是踌躇满志',NOW(), NOW(), "13312345665", 0),
(66,'杜文华',1,507,19,'有智者立长志,无志者长立志。',NOW(), NOW(), "13312345666", 0),
(67,'邓珊珊',2,510,18,'Action is the proper fruit of knowledge.',NOW(), NOW(), "13312345667", 0),
(68,'杜俊峰',1,507,23,'世上无难事,只要肯登攀。',NOW(), NOW(), "13312345668", 0),
(69,'庄信杰',1,301,22,'知识就是力量。',NOW(), NOW(), "13312345669", 0),
(70,'宇文轩',1,402,23,'如果你想要某样东西,别等着有人某天会送给你。生命太短,等不得。',NOW(), NOW(), "13312345670", 0),
(71,'黄佳怿',2,510,19,'Learn and live.',NOW(), NOW(), "13312345671", 0),
(72,'卫然',1,510,18,'神于天,圣于地。',NOW(), NOW(), "13312345672", 0),
(73,'耶律齐',1,307,23,'如果不是在海市蜃楼中求胜,那就必须脚踏实地去跋涉。',NOW(), NOW(), "13312345673", 0),
(74,'白素欣',2,305,18,'欲望以提升热忱,毅力以磨平高山。',NOW(), NOW(), "13312345674", 0),
(75,'徐鸿',1,403,23,'最美的不是生如夏花,而是在时间的长河里,波澜不惊。',NOW(), NOW(), "13312345675", 0),
(76,'上官杰',1,409,19,'生活之所以耀眼,是因为磨难与辉煌会同时出现。',NOW(), NOW(), "13312345676", 0),
(77,'吴兴国',1,406,18,'生活的道路一旦选定,就要勇敢地走到底,决不回头。',NOW(), NOW(), "13312345677", 0),
(78,'庄晓敏',2,305,18,'Never say die.',NOW(), NOW(), "13312345678", 0),
(79,'吴镇升',1,509,18,'Judge not from appearances.',NOW(), NOW(), "13312345679", 0),
(80,'朱文丰',1,304,19,'每个人都比自己想象的要强大,但同时也比自己想象的要普通。',NOW(), NOW(), "13312345680", 0),
(81,'苟兴妍',2,508,18,'Experience is the best teacher.',NOW(), NOW(), "13312345681", 0),
(82,'祝华生',1,302,21,'浅学误人。',NOW(), NOW(), "13312345682", 0),
(83,'张美琪',2,404,23,'最淡的墨水,也胜过最强的记性。',NOW(), NOW(), "13312345683", 0),
(84,'周永麟',1,308,21,'All work and no play makes Jack a dull boy.',NOW(), NOW(), "13312345684", 0),
(85,'郑心',2,404,21,'人生就像一杯茶,不会苦一辈子,但总会苦一阵子。',NOW(), NOW(), "13312345685", 0),
(86,'公孙龙馨',1,510,21,'Experience is the father of wisdom and memory the mother.',NOW(), NOW(), "13312345686", 0),
(87,'叶灵珑',2,401,19,'读一书,增一智。',NOW(), NOW(), "13312345687", 0),
(88,'上官龙',1,501,21,'别人能做到的事,自己也可以做到。',NOW(), NOW(), "13312345688", 0),
(89,'颜振超',1,303,19,'如果要飞得高,就该把地平线忘掉。',NOW(), NOW(), "13312345689", 0),
(90,'玛诗琪',2,409,22,'每天进步一点点,成功不会远。',NOW(), NOW(), "13312345690", 0),
(91,'李哲生',1,309,22,'这不是偶然的失误,是必然的结果。',NOW(), NOW(), "13312345691", 0),
(92,'罗文华',2,408,22,'好走的都是下坡路。',NOW(), NOW(), "13312345692", 0),
(93,'李康',1,509,19,'Deliberate slowly, promptly.',NOW(), NOW(), "13312345693", 0),
(94,'钟华强',1,405,19,'混日子很简单,讨生活比较难。',NOW(), NOW(), "13312345694", 0),
(95,'张今菁',2,403,23,'不经一翻彻骨寒,怎得梅花扑鼻香。',NOW(), NOW(), "13312345695", 0),
(96,'黄伟麟',1,407,19,'与其诅咒黑暗,不如燃起蜡烛。没有人能给你光明,除了你自己。',NOW(), NOW(), "13312345696", 0),
(97,'程荣泰',1,406,22,'明天不一定更好,。但更好的明天一定会来。',NOW(), NOW(), "13312345697", 0),
(98,'范伟杰',1,508,19,'水至清则无鱼,人至察则无徒。凡事不能太执着。',NOW(), NOW(), "13312345698", 0),
(99,'王俊凯',1,407,21,'我欲将心向明月,奈何明月照沟渠。',NOW(), NOW(), "13312345699", 0),
(100,'白杨 ',1,406,19,'闪电从不打在相同的地方.人不该被相同的方式伤害两次。',NOW(), NOW(), "13312345100", 0);
视图中, 快速调用模型获取数据表中的数据。student/views.py,代码:
from django.views import View
from django.http.response import JsonResponse
# 1. 先导入对应的模型
from . import models
# Create your views here.
class StudentView(View):
def get(self, request):
"""获取多个学生数据"""
# # 获取指定模型对应的数据表里面所有的数据记录
"""
模型类名.objects.all() # 获取模型对应的数据表的模型类对象
"""
object_list = models.Student.objects.all()
# print(type(object_list))
"""
QuerySet是django的ORM中提供給我們使用的查询集对象【伪列表】,支持使用索引来限制查询结果的数量,但是不支持使用负数索引
<class 'django.db.models.query.QuerySet'>
"""
# 要获取单个模型对象
# print(object_list[0], type(object_list[0])) # 赵华 <class 'student.models.Student'>
# student = object_list[0]
# # 获取模型对象的字段属性
# print(student.id, student.pk) # 获取主键
# print(student.name, student.description) # 获取其他属性
# print(student.created_time.strftime("%Y-%m-%d %H:%M:%S")) # 获取日期格式的内容
# # 当字段声明中,使用choices可选值选项以后,在模型对象里面就可以通过get_<字段名>_display() 来获取当前选项的文本提示
# print(student.status, student.get_status_display())
# QuerySet里面的成员是模型对象,不能直接被json转换成数据,所以我们需要先转换对象为字典,然后经过json处理才可以给客户端
student_list = []
for object in object_list:
student_list.append({
"id": object.id,
"name": object.name,
"age": object.age,
"sex": object.sex,
"classmate": object.classmate,
"description": object.description,
"created_time": object.created_time,
"updated_time": object.updated_time,
"status": object.get_status_display(),
})
# # 上面的代码也可以使用推导式一句话搞定,但是不好排查错误
# student_list = [{
# "id": object.id,
# "name": object.name,
# "age": object.age,
# "sex": object.sex,
# "classmate": object.classmate,
# "description": object.description,
# } for object in object_list]
"""
all()返回的是模型对象列表,如果要获取字典列表,则可以使用values()
values() 调用时没有传递参数,则默认获取所有字段内容
"""
# student_list = models.Student.objects.values("id", "name")
# student_list = models.Student.objects.values()
return JsonResponse(list(student_list), safe=False)
def post(self,request):
"""添加数据"""
data = {}
return JsonResponse(data, status=201)
def put(self,request):
"""更新数据"""
data = {}
return JsonResponse(data, status=201)
def delete(self,request):
"""删除数据"""
data = {}
return JsonResponse(data, status=204)
路由,student/urls,代码:
from django.urls import path
from . import views
urlpatterns = [
path("student/", views.StudentView.as_view()),
]
通过浏览器直接访问视图对应的URL,http://127.0.0.1:8000/student/student/
上面就可以看到我们已经在视图根据Student模型来读取了MySQL中的student这张数据表的信息了。
1.7 数据迁移¶
将模型类定义表结构的代码转换成SQL同步到数据库中的过程,这个过程就是数据迁移,数据迁移有好处有坏处。
django中的数据迁移就是一个类,这个类提供了一系列的终端命令,帮我们完成数据迁移的工作。
好处:
-
不需要我们开发者自己编写表结构的SQL语句,数据迁移过程中自动生成
-
数据迁移内部自动根据模型类的改动而自动生成SQL语句,并且产生一个记录文件。
因为这个记录文件,所以开发者可以清晰的看到数据模型的改变过程。在数据库中还会生成一张django_migrations的迁移记录表。
- 数据迁移提供了简单的终端命令来实现,所以开发者不存在大的学习成本。
只有2个命令,分别是python manage.py makemigations 与 python manage.py migrate
坏处:
- 数据迁移毕竟是程序内部根据模型类来生成建表的SQL语句,所以有时候生成的SQL语句有语法问题,反而让开发者因为迁移文件的错误而产生维护成本,当然多数存在于模型与模型之间的多对多外键关系中,当然随着django的不断完善,现在基本没有这个问题了,但是低版本的django中还可能存在的。
- 数据迁移节省了编写SQL语句的时间,但是复杂的表结构,数据迁移也可能无法实现,无法迁移到数据库中。
- 如果公司里面有专门的数据库管理员(DBA),则这个功能则完全没有任何作用了,但是ORM会继续运行而产生性能消耗问题。如果使用了原生SQL语句进行数据库的建表操作,那么数据迁移记录就混乱,甚至会报错,所以一旦决定要使用数据迁移,那么后续的全部数据库相关的表结构操作都要使用数据迁移才行。
1)生成迁移文件¶
所谓的迁移文件, 是类似模型类的迁移类,主要是描述了数据表结构的类文件.相当于生成SQL语句
2)同步到数据库中¶
相当于执行SQL语句
3. 回滚迁移操作¶
django中针对数据迁移的每一次操作都会在数据库中的django_migrations表中有历史记录。django_migrations表中的app字段表示本次数据迁移的子应用目录名,name字段本次同步数据的迁移文件。那么如果对于前面已经执行的数据迁移操作要进行回滚操作,则可以使用python manage.py migration <app> <name>来完成。
# 如果针对上面的操作想要回滚,可以打开数据库django_migrations 查看当前数据迁移的历史记录,找到app与name字段。
python manage.py migrate <app> zero # 把指定子应用目录下所有数据迁移进行历史回滚,接着只需要在当前子应用目录下删除掉数字开头的迁移文件即可。
python manage.py migrate <app> <name> # 把当前项目中的数据迁移历史记录回滚指定版本。
4. 补充说明¶
django是一个重量级的web框架,所以其内部提供了一系列的默认功能,这些功能也会使用到数据库,所以在项目搭建以后第一次数据迁移时,会看到一次性有十多张数据表被创建了。其中就有一个django内置的admin站点管理。
# admin站点默认是开启状态的,我们可以通过http://127.0.0.1:8000/admin
# 这个站点必须有个管理员账号登录,所以我们可以在第一次数据迁移,有了数据表以后,就可以通过以下终端命令来创建一个超级管理员账号。
python manage.py createsuperuser
root
123
2. 数据库基本操作¶
CURD:create(创建)、update(更新)、read(读取)、delete(删除)
2.1 增加数据¶
增加数据有两种方法。
save¶
通过创建模型类对象,执行对象的save()方法保存到数据库中。
"""添加一条数据"""
# ORM对于数据的所有操作都是有子类objects来提供的。objects,可以被修改,开发中一般叫objects为"模型管理器"
student = Student(
name="刘德华",
age=17,
xingbie=True,
classmate=301,
description="一杯忘情水",
)
# 自动执行添加数据
student.save()
# 添加后的模型会多出一个主键数据,可以通过id或者pk来读取
print(student.id) # print(student.pk)
create¶
通过模型类.objects.create()保存。
"""使用create来完成添加一条数据"""
# 返回值就是添加后的模型对象,会有ID主键的
student = Student.objects.create(
name="赵本山",
age=50,
xingbie=True,
classmate=301,
description="一段小品"
)
print(student)
print(student.pk) # print(student.id)
bulk_create¶
通过模型类.objects.bulk_create()批量添加数据
"""基于bulk_create添加多条数据"""
stu1 = Student(name="小黄人1号", age=17, xingbie=True, classmate=301, description="卜乃奈")
stu2 = Student(name="小黄人2号", age=17, xingbie=False, classmate=301, description="卜乃奈")
stu3 = Student(name="小黄人3号", age=17, xingbie=True, classmate=301, description="卜乃奈")
stu4 = Student(name="小黄人3号", age=17, xingbie=True, classmate=301, description="卜乃奈")
stu_list = [stu1,stu2,stu3,stu4]
ret = Student.objects.bulk_create(stu_list)
print(ret)
return HttpResponse("ok")
作业:
- 基于下列表格中的信息,声明模型与合理字段以及约束,并使用数据迁移完成图书信息表的创建。
title price author pubdate size descpition
基于上面创建的数据表模型,创建BookView视图类,并在视图类中实现post方法来完成数据添加功能。
基于上面创建的数据表模型,创建BookListView视图类,并在视图类中实现post方法来完成批量数据的添加功能。
基于上面创建的BookListView视图类,创建get方法,提供图书列表信息并以json结构返回给客户端。
基于上面BookView视图类,配置django模板引擎,使用get方法从数据库中读取所有图书列表数据并在HTML模板页面中展示出来。【不要求美观】
2.2 基本查询¶
get¶
查询单一结果,如果不存在或者返回多个结果会抛出异常。
查询不到, 则返回模型类.DoesNotExist异常。
查询多个, 则返回模型类.MultipleObjectsReturned异常。
def get(self,request):
"""基本查询数据"""
"""get 获取一条数据"""
try:
# student = Student.objects.get(name="小黄人4号")
# student = Student.objects.get(name="小白")
student = Student.objects.get(name="小黄人")
print(student, type(student))
print(student.name)
print(student.description)
except Student.DoesNotExist:
print("没有查询结果!")
except Student.MultipleObjectsReturned:
print("当前数据不是唯一的结果!")
return HttpResponse("OK")
first¶
查询一个结果, 查询不到,则返回None,查询多个,返回查询结果列表的第一个。
django还提供了last方法,可以获取结果列表中最后一个成员。
"""基于first来查询第一条数据"""
# first不会报错!
# 当查询结果为多个结果时,只会返回查询到的第一个结果,作为模型对象返回
# 当查询结果为0个结果时,则返回None
student = Student.objects.first()
# SQL: select * from tb_student limit 1;
print(student)
if student: # 判断如果student不是None
print(student.pk, student.id) # pk是id的别名
print(student.description)
"""使用filter来过滤数据,并返回一条"""
# student = Student.objects.filter(classmate=301).first()
student = Student.objects.filter(classmate=333).first()
print(student, type(student)) # None <class 'NoneType'> 对于没有结果的查询,使用first则会返回一个None
all¶
查询所有结果。查询不到,则返回空列表对象
"""all 获取所有数据"""
student_list = Student.objects.all()
print(student_list) # 获取结果列表
print(len(student_list)) # 获取结果列表的长度
# 把结果列表中的所有模型对象转化成字典结构
student_list = Student.objects.all().values()
print(student_list)
# 把结果列表中的所有模型对象转换成元组结构
student_list = Student.objects.all().values_list()
print(student_list)
"""使用filter查询过滤数据,并返回所有符合条件的结果
filter(字段名=条件值)
filter(字段名=条件值, 字段名=条件值) # 相当于and
"""
student_list = Student.objects.filter(classmate=302).all()
print(student_list)
# <QuerySet [<Student: 王天歌>, <Student: 王天龙>, <Student: 祝华生>]>
count¶
查询结果数量。实际上是一个聚合函数来的
"""count 获取结果总数"""
# 没有任何条件的统计当前数据表的结果
total = Student.objects.count()
print(f"学生总人数:{total}")
# 也可以添加where条件
total = Student.objects.filter(classmate="301").count()
print(f"学生总人数:{total}")
2.3 更新数据¶
save¶
修改模型类对象的属性,然后执行save()方法同步到数据库中
"""save 更新一条数据"""
# 先把要更新的数据查询出来,得到一个模型类对象
student = Student.objects.filter(name="小白").first()
# SQL: select * from tb_student where name='小白' limit1;
if student:
student.name = "小黑"
student.age = 18
student.save() # 把当前模型的中字段值同步到数据库
# SQL: update student set name='小黑', age=18, sex=True, description='xxx' where id=103;
update¶
使用模型类.objects.filter().update(),基于update来完成更新满足条件的所有数据,结果是受影响的行数
"""update 更新多条数据"""
# 基于update这种操作在数据库操作中,一般称之为"乐观锁"
# update操作的执行效率比save要高!
# update如果条件设置宽松,可以修改多条数据
Student.objects.filter(name="刘德华").update(name="刘福荣")
# SQL: update tb_student set name="刘福荣" where name="刘德华";
2.4 删除数据¶
删除有两种方法,分别是删一条或删多条数据。
模型类对象.delete()¶
def get(self,request):
"""删除数据"""
"""删除一条数据"""
# 先查询要删除的数据
student = Student.objects.filter(name="小白").first()
if student:
# 调用模型对象的delete方法进行删除
student.delete()
return HttpResponse("Ok")
模型类.objects.filter().delete()¶
"""删除多条"""
# 把符合条件的数据全部删除,注意:这种删除操作务必加上filter过滤条件,否则会变成全表删除
# 返回值是删除的数量
Student.objects.filter(name="小黄人").delete()
3. 数据库进阶操作¶
3.1 过滤条件¶
ORM在内部生成SQL中的where子句时,提供3个方法可以帮我们实现where过滤操作,包括:
- filter 过滤出符合条件的多个结果
- exclude 排除掉符合条件的多个结果,与filter相反,与filter互斥。
- get 过滤单一结果, 结果不是一个,会报错。
对于过滤条件的使用,上述三个方法相同,但是互斥的,只能使用任意1个,故仅以filter进行讲解。
文档:https://docs.djangoproject.com/zh-hans/4.2/ref/models/querysets/#field-lookups
ORM使用过程中,设置查询过滤条件的表达语法,如下:
# 单表的过滤:
模型类.objects.filter(属性名称__运算符=值) # 此处的运算符是django的ORM提供的英文单词的运算符,与python的运算符不一样,例如ORM的大于是gt,大于等于是gte。
模型类.objects.exclude(属性名称__运算符=值)
模型类.objects.get(属性名称__运算符=值)
# 多表的过滤
模型类.objects.filter(外键属性名称__外键模型的属性名称__运算符=值)
模型类.objects.exclude(外键属性名称__外键模型的属性名称__运算符=值)
模型类.objects.get(外键属性名称__外键模型的属性名称__运算符=值)
# 属性名称和比较运算符间使用两个英文下划线,所以这就是为什么,之前声明模型类,规定了属性名不能包括多个下划线
3.1.1. 相等¶
exact:表示判断值是否相等。
例:查询学生信息。
"""exact 过滤相等的条件"""
# student_list = Student.objects.filter(name__exact="吴杰").all()
student_list = Student.objects.filter(name="吴杰").all() # 简写方式,这个最常用!!
print(student_list)
return HttpResponse("OK")
3.1.2. 模糊查询¶
contains:是否包含。
说明:
如果要包含%无需转义,直接写即可。
例:查询姓名包含'华'的学生。
"""模糊查询[简单数据表才使用这个,数据量大了不要使用这个,改用全文搜索,使用专业的搜索引擎]"""
# name__contains -> 包含 ---> name like "%华%"
student1 = Student.objects.filter(name__contains='华')
# print(student1)
startswith、endswith:以指定值开头或结尾。
例:查询姓名以'华'结尾的学生
# name__endswith -> 结尾 ---> name like "%华"
student2 = Student.objects.filter(name__endswith='华')
# print(student2)
# name__startswith->开头 ---> name like "江%"
student3 = Student.objects.filter(name__startswith="江")
print(student3)
以上运算符都区分大小写,在这些运算符前加上i表示不区分字母大小写,如iexact、icontains、istartswith、iendswith.
3.1.3. 空查询¶
isnull:字段值是否为null。
例:查询个性签名不为空的学生。
# 添加测试数据,SQL语句
INSERT INTO student (name, age, sex, class, description, status, created_time, updated_time, mobile) VALUES ('刘德华', 17, 1, '407', null, 0, '2020-11-20 10:00:00.000000', '2020-11-20 10:00:00.000000', '15067895432');
# 代码操作
"""空查询"""
student = Student.objects.filter(description__isnull=True)
print(student)
3.1.4. 范围查询¶
in:是否包含在范围内。
例:查询班级编号为301,302或303的学生
"""范围查询"""
student = Student.objects.filter(classmate__in=[301, 302, 303]).all().values("id", "name", "classmate")
print(student)
3.1.5. 取值范围¶
range 设置开始值与结束值范围,进行数值判断,符合范围的数据被查询出来。不仅可以设置数值范围,也可以设置时间范围。
例如:查询出学号id在51~67之间的。
"""取值范围"""
# SQL: SELECT ... WHERE id BETWEEN 51 and 67;
student_list = Student.objects.filter(id__range=(51, 67)).values("id", "name")
print(student_list)
return HttpResponse("ok")
3.1.6. 比较查询¶
- gt 大于 (greater then)
- gte 大于等于 (greater then equal)
- lt 小于 (less then)
- lte 小于等于 (less then equal)
例:查询编号大于3的学生
"""比较查询"""
# # 年龄大于20的
# # age__gt=20 ---->>> where age > 20
# student_list = Student.objects.filter(age__gt=22).values("name", "age") # 后面任何方法,默认补充all()
# print(student_list)
# # 年龄小于19的
# # age__lt=19 ---->>> where age < 19
# student_list = Student.objects.filter(age__gt=19).all().values("name","age")
# print(student_list)
# 年龄不等于19的
# 使用exclude把符合条件的排除掉
student_list = Student.objects.exclude(age=19).all().values("name","age")
print(student_list)
不等于的运算符,使用exclude()过滤器。
3.1.7. 日期查询¶
注意:
django的ORM中提供了许多方法用于进行日期的查询过滤,例如:year、month、day、week_day、hour、minute、second都可以对日期时间类型的属性进行运算。
要进行日期时间的过滤查询,必须保证python代码中使用的时间时区与mysql数据库中的时间时区是对应的!如果mysql的时区与python代码的时区不对应,则得到的结果纯在时区的差异。
例:查询2017年被添加到数据中的学生。
"""日期过滤查询
需要调整settings.py的时区配置项为:USE_TZ = False
"""
# 查询2017年被加入数据表的信息
# created_time__year=2017 ---> where year(created_time)=2017
student_list = Student.objects.filter(created_time__year=2017).values("name", "created_time")
print(student_list)
# 查询11月份被加入数据表的信息
# SQL: where month(created_time) = 11;
student_list = Student.objects.filter(created_time__month=7).values("name", "created_time")
print(student_list)
# 查询出2022年07月份的学生
# SQL: WHERE month(created_time) = 7 AND year(created_time) = 2022;
student_list = Student.objects.filter(created_time__year=2022, created_time__month=7).values("name", "created_time")
print(student_list)
# 查询出2017年11月20号的学生
# SQL: WHERE month(created_time) = 7 AND year(created_time) = 2022 AND day(created_time);
student_list = Student.objects.filter(
created_time__year=2017,
created_time__month=7,
created_time__day=20
).values("name", "created_time")
print(student_list)
# 查询07月20号的学生
student_list = Student.objects.filter(
created_time__month=7,
created_time__day=20
).values("name", "created_time")
print(student_list)
3.1.7.1 业务场景:时间判断¶
例:查询2021-08-18 16:19:38 这个时间点上添加的学生信息。
例:查询时间范围在 "2021-08-18 16:19:38" ~ "2021-08-18 16:21:56" 的数据。
"""精确时间查询"""
# 方式1:当在模型使用datetime指定字段的数据类型以后,就不能直接通过字符串的比较来过滤查询了,因为字符串时间格式无法与datetime对象来进行很精确的判断比较
student_objs = models.Student.objects.filter(created_time="2021-08-18 16:19:38").all()
print(student_objs)
# 方式2:把字符窜格式的时间转换成datetime对象,也可以查询。
from django.utils.timezone import datetime
# 把字符串格式时间转换成datetime时间戳对象
timestamp = datetime.strptime("2021-08-18 16:19:38", "%Y-%m-%d %H:%M:%S")
student_objs = models.Student.objects.filter(created_time=timestamp).all()
print(student_objs)
"""判断两个时间范围"""
time1 = "2020-11-20 9:00:00"
time2 = "2020-11-20 11:00:00"
# 查询添加时间在time1与time2之间的学生信息
student_objs = models.Student.objects.filter(
created_time__gte=time1,
created_time__lte=time2,
).all()
print(student_objs) # <QuerySet [<Student: 刘德华>]>
3.1.8 F对象¶
F对象,主要用于在SQL语句中针对字段之间的值进行比较的查询。
之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢? 答:使用F对象,被定义在django.db.models中。
语法如下:
"""F对象,字段间的值比较查询"""
from django.db.models import F
# 查询出入学以后,数据没有被修改过的学生信息
student = Student.objects.filter(created_time=F("updated_time")).values("name","created_time","updated_time")
print(student)
return HttpResponse("ok")
# 除了上面例子,我们还可以从工作中针对公司每月的营收(支出和收入)、股票(每月涨和跌)都可以使用F对象进行查询。
3.1.9 Q对象¶
多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。
例:查询年龄大于20,并且编号小于30的学生。
如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符,Q对象被义在django.db.models中。
语法如下:
# And
filter( Q(属性名__运算符=值) )
filter(Q(属性名__运算符=值, 属性名__运算符=值, ....))
# OR
filter(Q(属性名__运算符=值) | Q(属性名__运算符=值))
filter(Q(属性名__运算符=值) | Q(属性名__运算符=值) | ....)
# NOT
filter(~Q(属性名__运算符=值))
例子。
"""Q对象,复杂逻辑查询,针对多条件进行与或非处理"""
from django.db.models import Q
# 多个与 and Q(条件) & Q(条件)
# 查询出301班的男生
# student = Student.objects.filter(Q(classmate=301) & Q(xingbie=1)).values("name","xingbie","classmate")
# 上面完全可以简写成
student = Student.objects.filter(classmate=301, xingbie=1).values("name","xingbie","classmate")
"""单纯的多个条件并立的情况下,没必要使用到Q对象进行处理,直接编写多个条件,使用逗号串联即可"""
# 多个或,or Q(条件) | Q(条件)
# 查询出301班的男生 或者 302班的男生
student = Student.objects.filter(Q(classmate=301, xingbie=1) | Q(classmate=302, xingbie=1)).values("name","xingbie","classmate")
# print(student)
# 上面完全可以简写成
student = Student.objects.filter(classmate__in=[301,302], xingbie=1).values("name","xingbie","classmate")
# print(student)
# 如果是这样则不能简写了
# 查询出301班年龄大于21男生,或者 302班年龄小于19岁的女生
student = Student.objects.filter( Q(classmate=301, age__gt=21, xingbie=1) | Q(classmate=302, age__lt=22, xingbie=2) ).values("name","age","xingbie","classmate")
# print(student)
Q对象可以使用& 表示逻辑与(and),| 表示逻辑或(or),~表示逻辑非(not)
例:查询年龄大于20,或编号小于30的学生,只能使用Q对象实现
Q对象左边可以使用~操作符,表示非not。但是工作中,我们只会使用Q对象进行或者的操作,只有多种嵌套复杂的查询条件才会使用&和~进行与和非得操作。
例:查询出年龄不是20岁的
student = Student.objects.filter(~Q(age=20)).values("name", "age")
print(student)
# 相当于 exclude
student = Student.objects.exclude(age=20).values("name", "age")
print(student)
3.2 结果排序¶
使用order_by对结果进行排序
"""结果排序"""
# order_by("第一排序字段","第二排序字段",....)
# 当前第一字段的值一样时,参考第二字段进行排序,第二字段的值一样时,参考第三字段进行排序,....
# 如果没有声明order_by()来查询,而值又是一样的时候,则根据MySQL在内部执行查询计划的顺序进行排列,也就是随机排列
# 字段排序写法:
# order_by("id") # 表示按id字段的值进行升序排序,id数值从小到大
# order_by("-id") # 表示按id字段的值进行降序排序,id数值从大到小
# 先按班级进行第一排序降序处理,当班级数值一样时,再按id进行第二排序升序处理
# student = Student.objects.order_by("-classmate","id").values("classmate","id","name")
# 调用了order_by以后,如果没有后续声明返回结果的all方法时,默认使用all()进行结果查询
student = Student.objects.order_by("-id")
print(student)
3.3 限制查询¶
ORM中针对查询结果的数量限制,提供了一个查询集对象[QuerySet].这个QuerySet,是ORM中针对查询结果进行临时保存数据的一个容器对象,我们可以通过了解这个QuerySet进行使用,达到查询优化的目的,也或者限制查询结果数量的作用。
3.3.1 查询集 QuerySet¶
查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。
当调用如下ORM提供的过滤器方法时,Django会返回查询集(而不是简单的列表):
- all():返回所有数据。
- filter():返回满足条件的数据。filter会默认调用all方法。
- exclude():返回满足条件之外的数据。exclude会默认调用all方法
- orderby():对结果进行排序。orderby会默认调用all方法
对查询集可以再次调用过滤器进行过滤,如
也就意味着查询集可以含有零个、一个或多个filter过滤器。过滤器基于所给的参数限制查询的结果。
从SQL的角度讲,查询集与select语句等价,过滤器像where、limit、order by子句。
判断某一个查询集中是否有数据:
- exists():判断查询集中是否有数据,如果有则返回True,没有则返回False。
- values(): 把结果集中的模型对象转换成字典,并可以设置转换的字段列表,达到减少内存损耗,提高性能
- values_list(): 把结果集中的模型对象转换成列表,并可以设置转换的字段列表(元祖),达到减少内存损耗,提高性能
注意:工作中如果需要返回数据的过程中进行优化,则一般我们选择使用values()或values_list()返回字段,比我们操作模型对象效率更高!当然从提供的功能操作上来说,肯定模型对象更强大灵活。
# 所有基于all方法返回的结果,都是Query(查询集),路径: django.db.models.query.QuerySet
# 查询出301班是否有男生
# student_list = Student.objects.filter(classmate="301")
# student_list = student_list.order_by("-age")
# student_list = student_list.filter(sex=1)
# ret = student_list.exists()
# 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)
3.3.1.1 QuerySet的两大特性¶
学习之前的准备工作
为了观察QuerySet的2个特性,我们可以直接到mysql配置mysql.ini中配置查看SQL的运行日志
"""
说明:
开启mysql数据库日志有2种方式:
临时开启, 通过mysql交互终端临时设置,如果服务器重启或者mysql重启,则日志的配置信息还原。
永久开启, 通过mysql的配置文件进行参数设置,将来即便服务器重启或mysql重启,都不会关闭日子功能
此处我们设置的打印日志是输入临时开启,用于辅助学习之用,数据库关闭或重启以后就失效了
"""
-- 查看日志功能是否开启了,接下来在mysql终端下开启日志显示,根据下面提示输入命令和SQL
-- 进入数据库终端[账户与密码自己修改下]
mysql -uroot -p123
show variables like "%general_log%";
-- +------------------+------------------------------------------------------+
-- | Variable_name | Value |
-- +------------------+------------------------------------------------------+
-- | general_log | OFF | # OFF表示没有开启普通日志功能
-- | general_log_file | C:\tool\mysql-8.0.28-winx64\data\WIN-Q0O96JBBIJ2.log | # 这里记录的是普通日志开启以后,日志文件路径
-- +------------------+------------------------------------------------------+
set global general_log = 'ON'; -- 设置临时开启,mysql重启后关闭
set global general_log_file = 'C:/Users/Administrator/Desktop/rg2305/day06/general.log'; -- 可以不设置日志路径,默认日志路径[路径不要包括特殊字符也不要出现中文,路径分隔符使用正斜杠]
-- +------------------+---------------------------------------------------------+
-- | Variable_name | Value |
-- +------------------+---------------------------------------------------------+
-- | general_log | ON | # 日志功能也开启了
-- | general_log_file | C:/Users/Administrator/Desktop/rg2305/day06/general.log | # 可以看到路径发生改变了,。
-- +------------------+---------------------------------------------------------+
-- mysql日志文件中时间一般跟系统时间是对不上的。原因是mysql的时区是0时区,我们这边是东八区。
select @@log_timestamps;
-- +------------------+
-- | @@log_timestamps |
-- +------------------+
-- | UTC | # 可以发现是UTC,0时区
-- +------------------+
set global log_timestamps=SYSTEM; # 如果要永久设置,在上面配置文件中,添加 log_timestamps = SYSTEM
-- +------------------+
-- | @@log_timestamps |
-- +------------------+
-- | SYSTEM | # 时区参考当前操作系统
-- +------------------+
-- 退出数据库终端
exit
接下来,我们只需要打开上面配置好的日志文件就可以了。
1)惰性执行¶
QuerySet查询集在创建时是不会访问数据库执行SQL语句,直到模型对象被调用输出或者调用模型对象的属性时,才会真正的访问数据库执行SQL语句,调用模型的情况包括循环迭代、序列化、与if合用,print的时候。
例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集对象student_list,并没有执行SQL语句的。
继续执行遍历迭代、或打印操作之后操作后,才真正的进行了数据库的查询
惰性执行,可以让重复的查询操作,只执行一次。
2)缓存结果¶
使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。
情况一:如下是两个查询集,无法重用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载。
from .models import Student
[student.id for student in Student.objects.all()] # 因为没有缓存查询集到变量中,所以此处第一次执行了SQL语句
[student.id for student in Student.objects.all()] # 因为没有缓存查询集到变量中,所以此处第一次执行了SQL语句
情况二:经过存储后,可以重用查询集,第二次使用缓存中的数据。
student_list=Student.objects.all()
[student.id for student in student_list] # 因为上面保存到查询到变量中,所以此处执行了SQL语句
[student.id for student in student_list] # 此处调用了之前的缓存数据
3.3.1.2 限制结果数量¶
django中还可以对查询集QuerySet进行取下标或切片操作,等同于SQL中的limit和offset子句。
注意:QuerySet毕竟不是真正的列表,所以它不支持负数索引。
对查询集QuerySet进行切片后返回一个新的查询集,但还是不会立即执行数据库查询。
如果获取一个对象,直接使用[0],等同于[0:1].get(),但是如果没有数据,[0]引发IndexError异常,[0:1].get()如果没有数据引发DoesNotExist异常。
示例:获取第1、2项,运行查看。
代码:
# 查询集结果数量的下标和切片操作
qs = Student.objects.all()
# print(qs[0]) # 第1条数据, ORM会自动识别这个操作并转化成SQL语句的 limit 1
# print(qs[2]) # 第3条数据, ORM会自动识别这个操作并转化成SQL语句的 limit 1 offset 2
# print(qs[:2]) # 前2条数据, ORM会自动识别这个操作并转化成SQL语句的 limit 2
# print(qs[1:4]) # 第1,2,3 数据, ORM会自动识别这个操作并转化成SQL语句的 limit 3 offset 1
# print( qs[-1] ) # 报错!!!不能使用负数
3.4 聚合分组¶
3.4.1 聚合函数¶
django中,可以使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg 平均,Count 总数,Max 最大,Min 最小,Sum 求和,被定义在django.db.models中。
例:查询301班学生的平均年龄。
"""聚合函数"""
from django.db.models import Avg,Max,Min,Sum,Count
# 查询301班学生的平均年龄
ret = Student.objects.filter(classmate=301).aggregate(Avg("age"))
# print(ret) # {'age__avg': 19.6364}
# 查询301班年龄最大的学生
ret = Student.objects.filter(classmate=301).aggregate(Max("age"))
# print(ret) # {'age__max': 23}
# 查新301班入学最早的学生[也就是ID最小的]
ret = Student.objects.filter(classmate=301).aggregate(c=Min("id"))
print(ret) # {'id__min': 2} ==> {'c': 2}
注意:aggregate的返回值是一个字典类型,格式如下:
使用count时一般不使用aggregate()过滤器。
例:查询301班的学生总数。
# 查询301班的总人数
ret = Student.objects.filter(classmate=301).aggregate(t=Count("id"))
print(ret) # {'t':11}
# 统计总数,完全可以调用count方法即可,不需要经过aggregate的调用
ret = Student.objects.filter(classmate=301).count()
print(ret) # 11
注意:count函数的返回值是一个数字。
3.4.2 分组查询¶
| id | name | class |
|---|---|---|
| 1 | xiaoming | 301 |
| 2 | xiaohong | 301 |
| 3 | xiaoli | 302 |
QuerySet对象.annotate()
# annotate() 进行分组统计,按前面values的字段进行 group by
# annotate() 返回值依然是 queryset对象,增加了分组统计后的键值对
# SQL原生语句中分组之后可以使用having过滤,在django中并没有提供having对应的方法,但是可以使用filter对分组结果进行过滤
# 所以filter在annotate之前,表示分组查询之间的where子句,在annotate之后代表分组结果的having子句
# 同理,values在annotate之前,代表分组的字段,在annotate之后代表数据查询结果返回的字段列
代码:
# 查询各个班级中的学生数量
from django.db.models import Count,Min
# 针对单个字段进行分组 values("classmate").annotate(total=Count("id") 按班级统计人数
ret = Student.objects.values("classmate").annotate(total=Count("id"))
# print(ret)
"""
<QuerySet [
{'classmate': '307', 'total': 3},
{'classmate': '301', 'total': 11},
{'classmate': '504', 'total': 2},
....
]>
"""
# 针对多个字段进行分组 values("classmate","xingbie").annotate(total=Count("id")) 按 班级和性别 统计人数
ret = Student.objects.values("classmate","xingbie").annotate(total=Count("id"))
# print(ret)
"""
<QuerySet [
{'classmate': '307', 'xingbie': 1, 'total': 3},
{'classmate': '301', 'xingbie': 1, 'total': 8},
{'classmate': '301', 'xingbie': 2, 'total': 2},
{'classmate': '301', 'xingbie': 0, 'total': 1},
{'classmate': '504', 'xingbie': 1, 'total': 2},
...
]>
"""
# 查询出每一个班级中年龄最小的学生信息
ret = Student.objects.values("classmate").annotate(min=Min("age"))
# print(ret)
"""
<QuerySet [
{'classmate': '307', 'min': 19},
{'classmate': '301', 'min': 17},
{'classmate': '504', 'min': 19},
....
]>
"""
ret = Student.objects.values("classmate").annotate(min=Min("age")).values("classmate","min").order_by("classmate")
# print(ret)
"""
<QuerySet [
{'classmate': '301', 'min': 17},
{'classmate': '302', 'min': 21},
{'classmate': '303', 'min': 19},
...
]>
"""
# 查询出人数在4个人以上(包括4个人)的班级
# 写在annotate后面的filter实际上表示的是having,表示对于分组后的数据结果进行过滤
# 写在annotate前面的filter实际上表示的是where, 表示对于分组前的数据结果进行过滤
ret = Student.objects.values("classmate").annotate(total=Count("id")).values("classmate","total").filter(total__gte=4)
# print(ret)
"""
<QuerySet [
{'classmate': '301', 'total': 11},
{'classmate': '502', 'total': 5},
{'classmate': '306', 'total': 6},
{'classmate': '503', 'total': 7},
{'classmate': '508', 'total': 4},
....
]>
"""
# 查询出女生数量在2个以上的班级
ret = Student.objects.filter(sex=2).values("classmate").annotate(total=Count("id")).values("classmate","total").filter(total__gte=2)
print(ret)
"""
<QuerySet [
{'classmate': '306', 'total': 4},
{'classmate': '405', 'total': 2},
{'classmate': '502', 'total': 3},
....
]>
"""
3.5 原生查询¶
执行原生SQL语句,在django中我们可以自己引入pymysql执行SQL,也可以调用ORM提供的raw方法来执行SQL语句
如果使用raw方法执行SQL语句,则返回结果是QuerySet,这个返回结果在操作字段时,会有额外性能损耗。
# 查询所有学生的班级、年龄、姓名和性别
sql = "SELECT id,name,sex,age,class FROM `db_student`"
ret = Student.objects.raw(sql)
# 针对原生SQL语句中已经查询出来的字段,只会查询一遍
# 但是如果SQL语句没有查询出来的字段,而在模型中调用,则会由ORM再次调用数据库查询,把数据临时查询出来。
for student in ret:
print(student)
print(student.description)
3.6 多库共存¶
在django中,settings.py配置的DATABASES配置项允许注册多个数据库,当然也就支持在项目中随时切换操作不同的数据库了。
def get(self, request):
"""
多库共存
1. 先到`djdemo/settings.py`里面,在DATABASES配置项中新增一个数据库
2. 为了方便快速演示,所以我们直接把school里面的student复制到student数据库
use students; # 切换数据
create table student (
id bigint auto_increment primary key,
created_time datetime(6) null,
updated_time datetime(6) null,
name varchar(15) not null,
age smallint not null,
sex tinyint(1) not null,
class varchar(50) not null,
mobile varchar(20) not null,
description longtext null,
status int null,
constraint mobile unique (mobile)
);
# 使用以下语句复制student表的所有数据
insert into `students`.`student` (id, created_time, updated_time, name, age, sex, class, mobile, description, status)
select id, created_time, updated_time, name, age, sex, class, mobile, description, status from `school`.`student`;
"""
"""多库共存下,基于django底层安装的pymysql来使用原生SQL语句操作的切换数据连接,完成数据库查询的过程"""
# from django.db import connections
# with connections["djdemo"].cursor() as cursor:
# # 让游标执行SQL语句
# cursor.execute("select * from student")
# # 通过游标获取查询结果
# result = cursor.fetchall()
# print(result)
"""多库共存下,基于django的ORM模型操作,来切换数据连接完成数据库操作的过程"""
student_objs = models.Student.objects.using("default").values("name", "age")
print(student_objs)
return JsonResponse({})
4. 关联模型¶
关联模型实际上就是ORM提供给开发者用于操作多表数据的功能。因为多个表之间的存在的关联关系,往往都是基于建库建表之初的实体关系分析( ER图 ) 和 范式理论 梳理出来的。
构建数据库和构建数据表:实体、属性、关系。
实体:在现实世界中,客观存在的能够被区分的人事物或集体概念。
属性:具有描述性、修饰性的词语,用于描述实体的特征的。
goods
| id | title | price | sale 销量 |
|---|---|---|---|
| 1 | meta40 | 3299 | 15 |
| 2 | |||
order
| id | status | goods_id |
|---|---|---|
| 1 | 已成交 | 1 |
| 2 | 未支付 | |
| 已取消 |
4.1 范式理论(三范式、逆范式)¶
范式理论是关系型模型建库建表的参考标准。
第一范式(1NF,原子性):数据表的每一列都是不可分割,不能重复。如果出现重复的属性,就可能需要定义一个新的实体(也就是新建一张数据表来进行保存)。
第二范式(2NF,依赖性):数据表的每一行都是唯一,需要创建ID来进行区分每一行数据。
第三范式(3NF,冗余性):数据表的数据不能冗余,针对冗余数据应该单独创建一个数据表,并把ID写到新表中进行关联。
逆范式指的是通过增加冗余或重复的数据来提高数据库的读取速度。
往往逆范式遵循的原则是:空间换时间。本质就是: 以廉价的硬盘空间,换取珍贵的数据库查询时间,以更快的数据从数据库读取数据返回给客户端。
ORM中针对数据表之间进行的关联操作,也可以外键绑定的操作方式,其中根据数据库表与表之间的关系, 可以有三种以上的关联方式:1对1,1对多,多对多。其中针对外键设置的模型代码,在django中一般有2种操作方式:
# 正向查询按字段,从主模型查询外键模型的数据
模型对象 = 当前模型.filter(xxx).first() # 例如,获取商品,查询商品的分类,假设分类属性 category
模型对象.外键 # 商品.category ---> 商品分类
# 反向查询按表名,从外键模型查询主模型数据
主键对象 = 主键模型.filter(xxx).first() # 先查询分类 --> 当前分类下有多少商品
主键对象.外键模型表名_set # _set方法可以使用related_name代替 [_set操作在一对一里面是没有的]
set方法文档:https://docs.djangoproject.com/zh-hans/4.0/ref/models/relations/
针对以下关联模型操作的内容,我们新建一个子应用来进行操作演示。
注册子应用到项目配置中,djdemo/settings.py,代码:
创建子应用的子路由文件,orm/urls.py,代码:
总路由中注册子路由信息,djdemo/urls.py,代码:
4.2 一对一关联(OneToOneField)¶
创建模型的关联关系,orm/models.py,代码:
from django.db import models
# Create your models here.
"""
一对一模型关联
"""
class Student(models.Model):
name = models.CharField(max_length=20, db_index=True, verbose_name="姓名")
age = models.IntegerField(verbose_name="年龄")
sex = models.BooleanField(null=True, blank=True, default=None, verbose_name="性别")
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
db_table = 'orm_student'
verbose_name = "学生信息"
verbose_name_plural = verbose_name
def __str__(self):
return str({"id": self.pk, "name": self.name, "age": self.age})
class StudentProfile(models.Model):
# 设置外键[1对1,models.OneToOneField("主键模型类名", on_delete="外键约束类型", ....)]
# 设置外键以后,在数据库中会自动生成一个"属性_id"的真实字段给开发者进行外键记录
# related_name 主要声明给主模型来使用的,用于反向查询。
# StudentProfile.student = 学生信息
# Student.profile = 学生详细信息
student = models.OneToOneField("Student", related_name="profile", on_delete=models.CASCADE, verbose_name="学生")
description = models.TextField(default="", verbose_name="个性签名")
address = models.CharField(max_length=500, verbose_name="家庭住址")
mobile = models.CharField(max_length=15, verbose_name="紧急联系电话")
class Meta:
db_table = "orm_student_profile"
verbose_name = "学生详细信息"
verbose_name_plural = verbose_name
def __str__(self):
return str({"address": self.address, "mobile": self.mobile})
进行数据迁移
课堂代码:
class StudentView(View):
"""1:1模型关联"""
def get1(self, request):
"""添加数据操作"""
"""
唯一方式:先添加主模型数据Student, 后添加外键模型数据
"""
# student = models.Student.objects.create(
# name="小白",
# age=17,
# sex=True,
# )
# profile = models.StudentProfile.objects.create(
# # student=student, # 通过指定对象的方式,可以帮我们自动绑定外键
# student_id=student.id, # 等价于上一句
# description="一段长长长长的个性签名....",
# address="学生小白的家庭住址",
# mobile="13312345618",
# )
#
"""注意:不要使用以下写法,外键模型的数据无法提添加,但是不会报错。"""
# student = models.Student.objects.create(
# name="小辉",
# age=17,
# sex=True,
# profile=models.StudentProfile(
# description="一段长长长长的个性签名....",
# address="学生小辉的家庭住址",
# mobile="13312345668",
# )
# )
return JsonResponse({})
def get2(self, request):
"""查询数据"""
"""
方式1:从主模型(主表, orm_student)查询到外键模型(附加表, orm_student_profile)
"""
# # 例如,小明今天没上学,查询他的紧急联系电话和家庭地址
# student = models.Student.objects.filter(name="小明").first()
# if student:
# # profile就是在StudentProfile里面定义的 related_name
# print(student.profile.mobile)
# print(student.profile.address)
"""
方式2:使用外键模型中查询数据,以主键模型作为条件
"""
# # 例如,小白今天没上学,查询他的紧急联系电话和家庭地址
# profile = models.StudentProfile.objects.filter(student__name="小白").first()
# print(profile.mobile)
"""
方式3:从外键模型(附加表, orm_student_profile)查询到主模型(主表, orm_student)
"""
# # 例如,根据手机号,13312345618 是谁的手机号码
# student_profile = models.StudentProfile.objects.filter(mobile="13312345618").first()
# if student_profile:
# print(student_profile.student.name)
"""
方式4:查询主模型数据,以外键模型作为条件
"""
# 例如,根据手机号,13312345618 是谁的手机号码
student = models.Student.objects.filter(profile__mobile="13312345618").first()
print(student.name)
return JsonResponse({})
def get3(self, request):
"""更新数据"""
"""方式1:从主模型更新外键模型数据(save)"""
# # 例如,修改小明的家庭地址
# student = models.Student.objects.filter(name="小明").first()
# if student:
# print(student)
# print(student.profile)
# print(student.profile.address)
# # 直接修改,并保存即可
# student.profile.address = "学生小明的新家庭地址"
# # 修改的address是StudentProfile的字段,所以使用StudentProfile的save方法
# student.profile.save()
"""方式2:修改外键模型数据,使用主模型作为修改条件(update)"""
# # 例如,修改小白的家庭地址
# models.StudentProfile.objects.filter(student__name="小白").update(address="学生小白的新家庭地址")
"""方式3:从外键模型更新主模型数据(save)"""
# # 例如,修改手机号:"13312345618" 的学生的年龄为21岁
# student_profile = models.StudentProfile.objects.filter(mobile="13312345618").first()
# if student_profile:
# print(student_profile)
# print(student_profile.student)
# print(student_profile.student.age)
# student_profile.student.age = 21
# student_profile.student.save()
"""方式4:修改主模型数据,使用外键模型作为修改条件(update)"""
# # 例如,修改手机号:"13312345118" 的学生的年龄为18岁
# models.Student.objects.filter(profile__mobile="13312345118").update(age=18)
return JsonResponse({})
def get(self,request):
"""删除操作"""
"""当on_delete=models.CASCADE时,删除主模型数据,mysql会自动删除外键模型数据"""
# 例如,删除小明的数据记录
# models.Student.objects.filter(name="小明").delete()
"""当on_delete=models.CASCADE时,删除外键模型数据,不会影响主模型数据的"""
# 例如,删除手机号为13312345118的学生附加资料
models.StudentProfile.objects.filter(mobile="13312345118").delete()
return JsonResponse({})
4.2.1 外键约束选项¶
在设置外键时,需要通过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异常
作业:
按以下表格声明文章信息模型与文件详情模型,并设置主外键关系进行数据迁移建表。
Article文章信息模型
id title desc 主键ID 文章标题 文章描述信息 ArticleInfo 文章详情模型
id aid created_time content flowers footer size 主键ID 文章ID 文章发表时间 文章内容 文章点赞次数 文章踩次数 字数
- 查询文章id=1的内容
- 查询 ArticleInfo 模型中id=1的文章的作者
4.3 一对多关联(ForeignKey)¶
一对多模型关联,例如学生和成绩,文章分类和文章,商品分类和商品,商品品牌和商品之间的关系。
class Author(models.Model):
name = models.CharField(max_length=20, db_index=True, verbose_name="姓名")
age = models.IntegerField(verbose_name="年龄")
sex = models.BooleanField(null=True, blank=True, default=None, verbose_name="性别")
class Meta:
db_table = 'orm_author'
verbose_name = "作者信息"
verbose_name_plural = verbose_name
def __str__(self):
return str({"id": self.pk, "name": self.name, "age": self.age, "sex": self.sex})
class Article(models.Model):
title = models.CharField(max_length=50, verbose_name="文章标题")
content = models.TextField(null=True, verbose_name="文章内容")
pubdate = models.DateTimeField(null=True, verbose_name="发布时间")
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="创建时间")
author = models.ForeignKey("Author", on_delete=models.DO_NOTHING, related_name="article_list", verbose_name="作者")
class Meta:
db_table = "orm_article"
verbose_name = "文章信息"
verbose_name_plural = verbose_name
def __str__(self):
return str({"id": self.id, "title": self.title, "pubdate": self.pubdate})
进行数据迁移
关联操作
# from .models import Achievement
# student = Student.objects.get(pk=10)
# achievement_list = [
# Achievement(student=student,score=100),
# Achievement(student=student,score=100),
# Achievement(student=student,score=100),
# Achievement(student=student,score=100),
# ]
#
# Achievement.objects.bulk_create(achievement_list)
# 获取一个学生的所有成绩
# student = Student.objects.get(pk=10)
# print(student.score_list.all().values("id","score")) # 必须在外键模型中设置related_name="score_list",否则无法获取外键关系
# print(student.achievement_set.all().values("id","score")) # 如果外键模型中没有设置related_name则可以通过模型_set提供给主模型调用数据
# 从外键数据查找主模型
from .models import Achievement
student_achievement = Achievement.objects.filter(score=80).first()
print(student_achievement.student) # 查找外键属性字段
print(student_achievement.student_id) # 直接查数据表中的数值ID
代码:
class ArticleView(View):
"""1:多模型关联"""
def get1(self,request):
"""添加数据"""
"""1. 先添加主模型,根据主模型添加外键模型"""
# author = models.Author.objects.create(
# name="小明",
# age=23,
# sex=True
# )
# article_list = [
# models.Article(title="文章标题1", content="文章内容1", pubdate="2023-03-15 10:30:30", author=author),
# models.Article(title="文章标题2", content="文章内容2", pubdate="2023-03-16 10:30:30", author_id=author.id),
# ]
# models.Article.objects.bulk_create(article_list)
"""如果已经有了主模型,则直接通过查询主模型提取主键ID,再添加外键模型数据即可。"""
# # 例如,给小明记录多2片文章
# author = models.Author.objects.filter(name="小明").first()
# if author:
# article_list = [
# models.Article(title="文章标题3", content="文章内容3", pubdate="2023-03-15 10:30:33", author=author),
# models.Article(title="文章标题4", content="文章内容4", pubdate="2023-03-16 10:30:34", author_id=author.id),
# ]
# models.Article.objects.bulk_create(article_list)
"""添加主模型,再添加外键模型的另一种写法"""
# models.Article.objects.create(
# title="文章标题5",
# content="文章内容5",
# pubdate="2023-10-03 12:05:00",
# author=models.Author.objects.create(name="小白", age=23, sex=False)
# )
return JsonResponse({})
def get2(self, request):
"""查询数据"""
"""通过主模型查找外键模型"""
# # 例如,查询小明的所有文章
# author = models.Author.objects.filter(name="小明").first()
# if author:
# print(author)
# # 获取外键,article_list就是Article中related_name定义,提供给Author反向查询使用的
# print(author.article_list.all())
"""使用主模型作为条件,直接查询外键模型的数据"""
# # 例如,查询小白的所有文章
# article_list = models.Article.objects.filter(author__name="小白").all()
# print(article_list)
"""通过外键模型查询主模型"""
# # 例如,查询文章标题为《文章标题5》的作者
# article = models.Article.objects.filter(title="文章标题5").first()
# if article:
# print(article.author, type(article.author))
# print(article.author.name)
"""使用外键模型作为条件,直接查询主模型的数据"""
# 例如,查询文章标题为《文章标题3》的作者
author = models.Author.objects.filter(article_list__title="文章标题3").first()
print(author)
print(author.name)
return JsonResponse({})
def get3(self, request):
"""更新操作"""
"""获取主模型,再改动外键模型[外键模型可以是1个或多个,如果是多个,则需要循环]"""
# # 把小明的所有文章发布时间修改成 2023-10-15 10:00:00
# author = models.Author.objects.filter(name="小明").first()
# for article in author.article_list.all():
# article.pubdate = "2023-10-15 10:00:00"
# article.save()
"""以主模型作为更新条件,更新外键模型数据"""
# # 把小白的所有文章发布时间修改成 2023-05-05 10:00:00
# models.Article.objects.filter(author__name="小白").update(pubdate="2023-05-05 10:00:00")
"""获取到外键模型,再改动主模型"""
# # 例如,修改文章标题《文章标题4》的作者的年龄为27岁
# article = models.Article.objects.filter(title="文章标题4").first()
# article.author.age = 27
# article.author.save()
"""以外键模型作为更新条件,更新主模型数据"""
# # 例如,修改文章标题《文章标题1》的作者的年龄为22岁
# models.Author.objects.filter(article_list__title="文章标题1").update(age=22)
"""外键是可以在开发中更改绑定关系"""
# # 例如,修改文章标题《文章标题3》的作者为小白
# article = models.Article.objects.filter(title="文章标题3").first()
# author = models.Author.objects.filter(name="小白").first()
# article.author = author
# article.save()
# # 例如,修改文章标题《文章标题3》的作者为小明
# models.Article.objects.filter(title="文章标题3").update(
# author=models.Author.objects.filter(name="小明").first()
# )
return JsonResponse({})
def get(self,request):
"""删除数据"""
"""当前模型的关联属性为 DO_NOTHING 所以,删除主模型并不会影响外键模型数据"""
models.Author.objects.filter(name="小白").delete()
return JsonResponse({})
4.4 多对多关联(ManyToManyField)¶
多对多模型关联, 例如:网课和学生,活动和商品,软件应用和服务器。
"""多对多"""
# 一个老师可以授课多个课程
# 一个课程也可以由多个老师授课
# 多对多关联以后,在数据迁移时,在数据库中实际上会创建三张表,分别是:2个模型对象实体表,1张关联2个模型的关系表。
# 一对一,把主模型的主键id,作为外键字段声明在外键模型中【所以外键模型多出一个关联属性字段】
# 一对多,把主模型的主键id,作为外键字段声明在外键模型中【所以外键模型多出一个关联属性字段】
# 多对多,把2个主模型的主键id,作为外键字段记录第三张关系表中,这张表主要有2个外键字段。而且ORM中,我们无法直接访问和操作这个表的,只能通过主模型来操作
class Teacher(models.Model):
name = models.CharField(max_length=15,verbose_name="老师")
# 注意:Teacher模型中设置了course外键,就不要设置Course的teacher外键了。2个冲突的。
# course = models.ManyToManyField("Course", related_name="to_teacher")
class Meta:
db_table = "tb_teacher"
verbose_name = '老师信息表'
verbose_name_plural = verbose_name
class Course(models.Model):
name = models.CharField(max_length=20, verbose_name="课程")
# 多对多不需要我们设置on_delete,直接默认就是models.CASCADE级联了。
teacher = models.ManyToManyField("Teacher", related_name="to_course")
class Meta:
db_table = "tb_course"
verbose_name = '课程信息表'
verbose_name_plural = verbose_name
进行数据迁移
关联操作
class TeacherView(View):
"""多对多关联操作"""
def get1(self, request):
"""添加数据"""
"""先添加实体模型,然后通过外键使用add进行关联两个模型"""
# # 1. 先添加实体模型
# teacher = models.Teacher.objects.create(name="大明", age=32, sex=True)
# course = models.Course.objects.create(name="python基础课")
# # 2. 通过外键使用add绑定关系
# teacher.course.add(course)
# # 也可以给已有的模型绑定关系
# teacher = models.Teacher.objects.filter(name="大明").first()
# course1 = models.Course.objects.create(name="python框架")
# course2 = models.Course.objects.create(name="python项目实战")
# teacher.course.add(course1, course2)
return JsonResponse({})
def get2(self, request):
"""查询数据"""
"""先查其中一个模型,接着通过外键,查询另一个模型的数据"""
# # 例如,查询大明的授课列表
# teacher = models.Teacher.objects.filter(name="大明").first()
# print(teacher.course.all())
"""使用其中一个模型作为条件,查询另一个模型的数据"""
# # 例如,查询大明的授课列表
# course_objs = models.Course.objects.filter(teacher__name="大明").all()
# print(course_objs)
"""反过来,也是如此"""
# 例如,python基础课的授课老师列表
# course = models.Course.objects.filter(name="python基础课").first()
# print(course.teacher.all())
# teacher_list = models.Teacher.objects.filter(course__name="python基础课").all()
# print(teacher_list)
return JsonResponse({})
def get(self,request):
"""更新数据"""
# 把大明的所有授课课程的名字后面加上(大明专讲)
# teacher = models.Teacher.objects.filter(name="大明").first()
# if teacher:
# for course in teacher.course.all():
# course.name = course.name + "(大明专讲)"
# course.save()
# # 通过update修改【目前只支持整型】
from django.db.models import F, Value
from django.db.models.functions import Concat
models.Course.objects.filter(teacher__name="大白").update(name=Concat(F("name"), Value("[精讲]")))
return JsonResponse({})
def get4(self,request):
"""删除数据"""
"""删除模型表记录时,对应的关系也会被删除"""
# 删除大明的信息,mysql会自动删除绑定关系
# models.Teacher.objects.filter(name="大明").delete()
"""解绑关系"""
teacher = models.Teacher.objects.filter(name="大白").first()
course = models.Course.objects.filter(name="java基础课").first()
teacher.course.remove(course) # 注意:这并非删除课程,而是解除绑定关系而已
return JsonResponse({})
自关联¶
自关联就是1张数据表中,主键和外键都在一张表上。一般会在多级部门,多级菜单,多级权限,省市区行政区划,粉丝关注,好友关系,这些业务中使用到。举例:
行政区划表 tb_area
| id | name | parent_id/pid |
|---|---|---|
| 1 | 河南省 | |
| 2 | 河北省 | |
| 3 | 郑州市 | 1 |
| 4 | 开封市 | 1 |
| 5 | 石家庄 | 2 |
| 6 | 邯郸市 | 2 |
| 7 | 二七区 | 3 |
| 8 | 新郑区 | 3 |
| 9 | 郑东新区 | 3 |
要理解自关联,就要这个数据表理解成2张或者3张表就可以了。
省级别,province,
| id | name | |
|---|---|---|
| 1 | 河南省 | |
| 2 | 河北省 |
市级别,city,
| id | name | parent_id/pid |
|---|---|---|
| 3 | 郑州市 | 1 |
| 4 | 开封市 | 1 |
| 5 | 石家庄 | 2 |
| 6 | 邯郸市 | 2 |
区级别,area,
| id | name | parent_id/pid |
|---|---|---|
| 7 | 二七区 | 3 |
| 8 | 新郑区 | 3 |
| 9 | 郑东新区 | 3 |
上面很明显是1个省份有多个城市,1个城市有多个地区,这就是属于1对多的自关联
用户与用户之间的好友关系,这就是属于多对多的自关联。
用户表 user
| id | username |
|---|---|
| 1 | 小明 |
| 2 | 小红 |
| 3 | 小黑 |
| 4 | 小白 |
| 5 | 小辉 |
好友关系表 user_friend
| id | user1 | user2 |
|---|---|---|
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 4 |
| 4 | 2 | 3 |
| 5 | 2 | 4 |
| 6 | 2 | 5 |
| 7 | 3 | 4 |
模型,代码:
class Area(models.Model):
"""一对多的自关联"""
name = models.CharField(max_length=50)
parent = models.ForeignKey("self", on_delete=models.SET_NULL, related_name="to_son", null=True, blank=True)
class Meta:
db_table = "orm_area"
verbose_name = '行政区划表'
verbose_name_plural = verbose_name
def __str__(self):
return str({"id":self.id, "name":self.name})
class Member(models.Model):
"""多对多的自关联"""
name = models.CharField(max_length=50, unique=True, verbose_name="用户名")
age = models.SmallIntegerField(default=0, verbose_name="年龄")
# symmetrical=True 默认值,表示双向关系,绑定的关系是双向,一般用于好友关系
# 这种情况下,django不提供反向查询
friends = models.ManyToManyField(to="self", symmetrical=True)
# symmetrical=False 表示单向关系,绑定的关系是单向,一般用于单向关注,黑名单
focus = models.ManyToManyField(to="self", symmetrical=False, related_name="fans_list")
class Meta:
db_table = "orm_member"
verbose_name = '会员信息表'
verbose_name_plural = verbose_name
def __str__(self):
return str({"id":self.id, "name":self.name})
数据迁移
分析:
代码:
class Student4View(View):
"""自关联"""
def get1(self, request):
"""添加数据"""
# # 添加省份数据,因为没有上级辖区,所以不需要声明其他字段
# area1 = Area.objects.create(name="河南省")
# area2 = Area.objects.create(name="河北省")
#
# # 添加城市数据
# area3 = Area.objects.get(name="河南省")
# area3.to_son.add(
# Area.objects.create(name="郑州市"),
# Area.objects.create(name="开封市")
# )
#
# area4 = Area.objects.get(name="河北省")
# Area.objects.create(name="石家庄", parent=area4)
# Area.objects.create(name="邯郸市", parent_id=area4.id)
# # 添加地区数据
# area5 = Area.objects.get(name="郑州市")
# area5.to_son.add(*[
# Area.objects.create(name="二七区"),
# Area.objects.create(name="新郑区"),
# Area.objects.create(name="郑东新区")
# ])
province = Area.objects.create(name="广东省")
area_list = [
Area(name="佛山市"),
Area(name="广州市"),
Area(name="珠海市"),
Area(name="深圳市"),
]
# bulk属性只有在一对多的时候存在,多对多是没有。bulk允许列表中出现没有保存到数据库中的模型对象,django会自动创建到数据库中
province.to_son.add(*area_list, bulk=False)
return HttpResponse("ok")
def get2(self, request):
"""查询数据"""
"""通过子级记录查找父级记录,得到唯一的父级"""
# area = Area.objects.get(name="二七区")
# print(area.parent) # {'id': 3, 'name': '郑州市'}
# print(area.parent.parent) # {'id': 1, 'name': '河南省'}
"""通过父级记录查找子级记录,得到多个子级"""
# area = Area.objects.get(name="河南省")
# son_list = area.to_son.all()
# print(son_list) # <QuerySet [<Area: {'id': 3, 'name': '郑州市'}>, <Area: {'id': 4, 'name': '开封市'}>]>
#
# data = []
# for son_area in son_list:
# grandson_list = son_area.to_son.all()
# data.extend(list(grandson_list))
# print(data)
"""使用子级记录作为查询条件,查询数据"""
# area = Area.objects.filter(to_son__name__in=["郑东新区"]).first() # 因为同一个父级下有多个子级记录,所以必须使用in来查找
# print(area) # {'id': 3, 'name': '郑州市'}
#
# # 通过孙子找爷爷
# area = Area.objects.filter(to_son__to_son__name__in=["郑东新区"]).first()
# print(area) # {'id': 1, 'name': '河南省'}
"""使用父级记录作为查询条件,查询数据"""
# son_list = Area.objects.filter(parent__name="河南省").all()
# print(son_list) # <QuerySet [<Area: {'id': 3, 'name': '郑州市'}>, <Area: {'id': 4, 'name': '开封市'}>]>
grandson_list = Area.objects.filter(parent__parent__name="河南省").all()
print(grandson_list) # <QuerySet [<Area: {'id': 7, 'name': '二七区'}>, <Area: {'id': 8, 'name': '新郑区'}>, <Area: {'id': 9, 'name': '郑东新区'}>]>
# SQL: SELECT `orm_area`.`id`, `orm_area`.`name`, `orm_area`.`parent_id` FROM `orm_area` INNER JOIN `orm_area` T2 ON (`orm_area`.`parent_id` = T2.`id`) INNER JOIN `orm_area` T3 ON (T2.`parent_id` = T3.`id`) WHERE T3.`name` = '河南省' LIMIT 21
return HttpResponse("ok")
def get3(self, request):
"""多对多的自关联[双向]:添加数据"""
# # 添加数据
# member1 = Member.objects.create(name="小明", age=16)
# member2 = Member.objects.create(name="小红", age=15)
# member3 = Member.objects.create(name="小白", age=15)
# member4 = Member.objects.create(name="小黑", age=15)
# member5 = Member.objects.create(name="小兰", age=15)
# # 小红添加好友
# member1 = Member.objects.get(name="小红")
# member2 = Member.objects.get(name="小黑")
# member3 = Member.objects.get(name="小白")
# member1.friends.add(member2, member3)
# # 小明添加好友
# member1 = Member.objects.get(name="小明")
# member2 = Member.objects.get(name="小黑")
# member3 = Member.objects.get(name="小白")
# member4 = Member.objects.get(name="小红")
# member1.friends.add(member2, member3, member4)
# # 小兰添加好友
# member1 = Member.objects.get(name="小兰")
# member2 = Member.objects.get(name="小黑")
# member3 = Member.objects.get(name="小白")
# member4 = Member.objects.get(name="小红")
# member5 = Member.objects.get(name="小明")
# member1.friends.add(member2, member3, member4, member5)
# # 小白添加好友
# member1 = Member.objects.get(name="小白")
# member2 = Member.objects.get(name="小黑")
# member3 = Member.objects.get(name="小明")
# member4 = Member.objects.get(name="小红")
# member1.friends.add(member2, member3, member4)
return HttpResponse("OK")
def get4(self, request):
"""多对多的自关联[双向]:查询数据"""
member = Member.objects.get(name="小黑")
# 查找小黑的朋友?
print(member.friends.all())
return HttpResponse("OK")
def get5(self,request):
"""多对多的自关联[单向]: 添加数据"""
# # 小红的关注
# member1 = Member.objects.get(name="小红")
# member2 = Member.objects.get(name="小黑")
# member3 = Member.objects.get(name="小白")
# member1.focus.add(member2, member3)
# # 小明的关注
# member1 = Member.objects.get(name="小明")
# member2 = Member.objects.get(name="小黑")
# member3 = Member.objects.get(name="小白")
# member4 = Member.objects.get(name="小红")
# member1.focus.add(member2, member3, member4)
# # 小兰的关注
# member1 = Member.objects.get(name="小兰")
# member2 = Member.objects.get(name="小黑")
# member3 = Member.objects.get(name="小白")
# member4 = Member.objects.get(name="小红")
# member5 = Member.objects.get(name="小明")
# member1.focus.add(member2, member3, member4, member5)
# # 小白的关注
# member1 = Member.objects.get(name="小白")
# member2 = Member.objects.get(name="小黑")
# member3 = Member.objects.get(name="小明")
# member4 = Member.objects.get(name="小红")
# member1.focus.add(member2, member3, member4)
"""自关联的添加关系与普通多表关联的关系操作类似,不仅支持add,也支持set,clear,remove等操作"""
return HttpResponse("OK")
def get(self,request):
"""多对多的自关联[单向]: 查询数据"""
# 查询小红的关注列表
# member = Member.objects.get(name="小红")
# print(member.focus.all())
# # 查询小红的粉丝列表
# member = Member.objects.get(name="小红")
# print(member.fans_list.all())
return HttpResponse("OK")
5. ORM优化¶
5.1 虚拟外键¶
在前面所有的关联查询操作中,我们使用的外联手段都是依靠数据库本身维护的物理外键,但是这在一定程度上会增加数据库的运行成本,消耗数据库性能,因为数据量大了之后DB在高并发情况会产生大量锁。所以在外界就存在了相当一部分公司(50%左右)为了追求性能,舍弃了物理外键(就是在数据库建表操作中不再创建外键索引),改用ORM提供的虚拟外键(逻辑外键,这种外键关系是由ORM代码来维护的)来进行关联查询操作。当然,如果没有数据库本身维护的物理外键,肯定也会存在对数据库一致性的风险。
要在Django中使用虚拟外键,只需要在模型声明外键字段中设置属性db_constraint=False即可。
补充:
db_constraint=True 表示当前外键使用物理外键
db_constraint=False 表示当前外键使用虚拟外键
代码举例:
# 一对一的逻辑外键
student = models.OneToOneField("Student", related_name="profile", on_delete=models.CASCADE, db_constraint=False, verbose_name="学生")
# 一对多的逻辑外键
student = models.ForeignKey("Student", on_delete=models.CASCADE, db_constraint=False, verbose_name="学生")
# 多对多的逻辑外键
teacher = models.ManyToManyField("Teacher", db_constraint=False, related_name="to_course")
当我们在模型声明时,使用虚拟外键,则将来生成的数据表中DDL(表定义语句)则不会出现外键的约束声明,只会出现普通索引的声明。
课堂代码:
from django.db import models
"""1对1的虚拟外键设置"""
class Student(models.Model):
name = models.CharField(max_length=20, verbose_name="学生")
age = models.SmallIntegerField(verbose_name="年龄")
sex = models.BooleanField(default=True, verbose_name="性别")
class Meta:
db_table = "tb_student"
verbose_name = "学生信息表"
verbose_name_plural = verbose_name
class StudentProfile(models.Model):
student = models.OneToOneField("Student", on_delete=models.CASCADE, db_constraint=False, verbose_name="学生")
mobile = models.CharField(max_length=15, verbose_name="紧急电话")
address = models.CharField(max_length=200, verbose_name="联系地址")
class Meta:
db_table = "tb_student_profile"
verbose_name = "学生详情表"
verbose_name_plural = verbose_name
"""1对多的虚拟外键设置"""
class Author(models.Model):
name = models.CharField(max_length=20, db_index=True, verbose_name="姓名")
age = models.IntegerField(verbose_name="年龄")
sex = models.BooleanField(null=True, blank=True, default=None, verbose_name="性别")
class Meta:
db_table = 'tb_author'
verbose_name = "作者信息"
verbose_name_plural = verbose_name
class Article(models.Model):
title = models.CharField(max_length=50, verbose_name="文章标题")
content = models.TextField(null=True, verbose_name="文章内容")
pubdate = models.DateTimeField(null=True, verbose_name="发布时间")
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="创建时间")
author = models.ForeignKey("Author", on_delete=models.DO_NOTHING, db_constraint=False, verbose_name="作者")
class Meta:
db_table = "tb_article"
verbose_name = "文章信息"
verbose_name_plural = verbose_name
"""多对多的虚拟外键设置"""
class Teacher(models.Model):
name = models.CharField(max_length=20, db_index=True, verbose_name="姓名")
age = models.IntegerField(verbose_name="年龄")
sex = models.BooleanField(null=True, blank=True, default=None, verbose_name="性别")
# course = models.ManyToManyField("Course", related_name="teacher", db_constraint=False)
class Meta:
db_table = 'tb_teacher'
verbose_name = "老师信息"
verbose_name_plural = verbose_name
class Course(models.Model):
name = models.CharField(max_length=20, db_index=True, verbose_name="课程名称")
teacher = models.ManyToManyField("Teacher", related_name="course", db_constraint=False)
class Meta:
db_table = 'tb_course'
verbose_name = "课程信息"
verbose_name_plural = verbose_name
5.2 查询优化¶
django在关联查询中为了减少SQL查询的数量,提供了2个优化方法,prefetchrelated()和selectrelated()。2个优化方法的功能作用类似,使用方式也一样,但是内部实现方式不同(也就是内部生成的SQL语句不同,使用场合不同)。
select_related() 是通过JOIN语句,在查询时减少SQL查询数量,一般适用于一对一的连表查询中,不适用在多对多。对于多对多关系使用SQL语句JOIN得到的SQL将会很长,会导致SQL语句运行时间和内存占用比例的增加。
prefetch_related()是通过 IN 语句分别查询关联的每个表的数据,然后在ORM中使用python处理他们之间的关联关系,通过这种方式来达到减少SQL连表查询数量。但是这样也有弊端,在QuerySet中的对象数量过多时,根据数据库特性的不同有可能造成性能额外损耗。
对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
对于一对一和多对一关系字段,可以使用select_related() 来进行优化。
2个优化函数是好的,但是使用不当会导致比原来还要糟心,那还不如别优化,所以最好慎用。
举例:实现用户的足迹功能,拿用户到过那些省份和城市以及他的家乡来举例,模型代码:
class Province(models.Model):
name = models.CharField(max_length=50, verbose_name="省份")
class Meta:
db_table = "tb_province"
verbose_name = "省份信息"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class City(models.Model):
name = models.CharField(max_length=50, verbose_name="城市")
province = models.ForeignKey("Province", on_delete=models.DO_NOTHING, verbose_name="省份")
class Meta:
db_table = "tb_city"
verbose_name = "城市信息"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Person(models.Model):
firstname = models.CharField(max_length=10, verbose_name="姓")
lastname = models.CharField(max_length=10, verbose_name="名")
hometown = models.ForeignKey("City", on_delete=models.DO_NOTHING, related_name="hometown_peoples", verbose_name="家乡")
living = models.ForeignKey("City", on_delete=models.DO_NOTHING, related_name="living_peoples", verbose_name="现居地")
visitation = models.ManyToManyField("City", related_name="visit_peoples", verbose_name="旅游地")
class Meta:
db_table = "tb_person"
verbose_name = "用户信息"
verbose_name_plural = verbose_name
def __str__(self):
return self.firstname + self.lastname
class PersonProfile(models.Model):
mobile = models.CharField(max_length=20, verbose_name="联系电话")
wechat = models.CharField(max_length=50, verbose_name="微信号")
person = models.OneToOneField("Person", on_delete=models.CASCADE, related_name="profile")
class Meta:
db_table = "tb_person_profile"
verbose_name = "用户详细信息"
verbose_name_plural = verbose_name
def __str__(self):
return self.person.firstname + self.person.lastname
数据迁移
测试数据,代码:
INSERT INTO school.tb_province (name) VALUES ("河南省");
INSERT INTO school.tb_province (name) VALUES ("广东省");
INSERT INTO school.tb_city (name, province_id) VALUES ('开封市', 1);
INSERT INTO school.tb_city (name, province_id) VALUES ('郑州市', 1);
INSERT INTO school.tb_city (name, province_id) VALUES ('广州市', 2);
INSERT INTO school.tb_city (name, province_id) VALUES ('深圳市', 2);
INSERT INTO school.orm_person (firstname, lastname, hometown_id, living_id) VALUES ('张', '三丰', 1, 1);
INSERT INTO school.orm_person (firstname, lastname, hometown_id, living_id) VALUES ('张', '雷锋', 1, 2);
INSERT INTO school.orm_person (firstname, lastname, hometown_id, living_id) VALUES ('张', '翠山', 2, 3);
INSERT INTO school.orm_person (firstname, lastname, hometown_id, living_id) VALUES ('张', '无忌', 3, 4);
INSERT INTO school.orm_person (firstname, lastname, hometown_id, living_id) VALUES ('张', '老六', 3, 2);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (1, 1);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (1, 2);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (1, 3);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (2, 2);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (2, 3);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (2, 4);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (3, 1);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (3, 2);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (3, 3);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (3, 4);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (4, 3);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (4, 4);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (5, 2);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (5, 3);
INSERT INTO school.orm_person_visitation (person_id, city_id) VALUES (5, 4);
5.2.1 select_related¶
# 获取主表信息的同时,也把外键表数据也获取到
模型.objects.all().select_related() # 默认主表数据时,获取全部的外键字段
模型.objects.all().select_related('外键字段')
模型.objects.all().select_related('外键字段1').select_related('外键字段2')....select_related('外键字段n') #一次查询,连表操作性能低
模型.objects.all().select_related('外键字段__外键字段') #一次查询,连表操作性能低
举例:
# # 获取张三丰到过的城市
# person = Person.objects.filter(firstname="张", lastname="三丰").first()
# # print(person)
# print(person.living)
# """
# SELECT `orm_person`.`id`, `orm_person`.`firstname`, `orm_person`.`lastname`, `orm_person`.`hometown_id`, `orm_person`.`living_id` FROM `orm_person` WHERE (`orm_person`.`firstname` = '张' AND `orm_person`.`lastname` = '三丰') ORDER BY `orm_person`.`id` ASC LIMIT 1
# SELECT `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id` FROM `orm_city` WHERE `orm_city`.`id` = 1 LIMIT 21
# """
# # select_related 全外键关联优化
# person = Person.objects.filter(firstname="张", lastname="三丰").select_related().first()
# """
# SELECT `orm_person`.`id`, `orm_person`.`firstname`, `orm_person`.`lastname`, `orm_person`.`hometown_id`, `orm_person`.`living_id`, `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id`, `orm_province`.`id`, `orm_province`.`name`, T4.`id`, T4.`name`, T4.`province_id`, T5.`id`, T5.`name`
# FROM `orm_person`
# INNER JOIN `orm_city` ON (`orm_person`.`hometown_id` = `orm_city`.`id`)
# INNER JOIN `orm_province` ON (`orm_city`.`province_id` = `orm_province`.`id`)
# INNER JOIN `orm_city` T4 ON (`orm_person`.`living_id` = T4.`id`)
# INNER JOIN `orm_province` T5 ON (T4.`province_id` = T5.`id`)
# WHERE (`orm_person`.`firstname` = '张' AND `orm_person`.`lastname` = '三丰')
# ORDER BY `orm_person`.`id` ASC LIMIT 1
# """
# print(person.living)
# # 限定外键的优化查找
# person = Person.objects.filter(firstname="张", lastname="三丰").select_related("living").first()
# print(person.living)
# print(person.living.province)
# """
# SELECT `orm_person`.`id`, `orm_person`.`firstname`, `orm_person`.`lastname`, `orm_person`.`hometown_id`, `orm_person`.`living_id`, `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id`
# FROM `orm_person`
# INNER JOIN `orm_city` ON (`orm_person`.`living_id` = `orm_city`.`id`)
# WHERE (`orm_person`.`firstname` = '张' AND `orm_person`.`lastname` = '三丰')
# ORDER BY `orm_person`.`id` ASC LIMIT 1
#
# SELECT `orm_province`.`id`, `orm_province`.`name` FROM `orm_province` WHERE `orm_province`.`id` = 1 LIMIT 21
# """
# 限定外键的优化查找
person = Person.objects.filter(firstname="张", lastname="三丰").select_related("hometown").select_related("living__province").first()
print(person.living)
print(person.living.province)
print(person.living.hometown)
"""
SELECT `orm_person`.`id`, `orm_person`.`firstname`, `orm_person`.`lastname`, `orm_person`.`hometown_id`, `orm_person`.`living_id`, `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id`, `orm_province`.`id`, `orm_province`.`name`
FROM `orm_person`
INNER JOIN `orm_city` ON (`orm_person`.`living_id` = `orm_city`.`id`)
INNER JOIN `orm_province` ON (`orm_city`.`province_id` = `orm_province`.`id`)
WHERE (`orm_person`.`firstname` = '张' AND `orm_person`.`lastname` = '三丰')
ORDER BY `orm_person`.`id` ASC LIMIT 1
"""
5.2.2 prefetch_related¶
模型.objects.prefetch_related('外键字段') #不连表,一次性多次查询
模型.objects.all().select_related('外键字段__外键字段')
举例
"""[查询优化] prefetch_related"""
# 查询所有人的足迹
person_list = Person.objects.all()
for person in person_list:
print(person.visitation.all())
"""
SELECT `orm_person`.`id`, `orm_person`.`firstname`, `orm_person`.`lastname`, `orm_person`.`hometown_id`, `orm_person`.`living_id` FROM `orm_person`
SELECT `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id` FROM `orm_city` INNER JOIN `orm_person_visitation` ON (`orm_city`.`id` = `orm_person_visitation`.`city_id`) WHERE `orm_person_visitation`.`person_id` = 1 LIMIT 21
SELECT `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id` FROM `orm_city` INNER JOIN `orm_person_visitation` ON (`orm_city`.`id` = `orm_person_visitation`.`city_id`) WHERE `orm_person_visitation`.`person_id` = 2 LIMIT 21
SELECT `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id` FROM `orm_city` INNER JOIN `orm_person_visitation` ON (`orm_city`.`id` = `orm_person_visitation`.`city_id`) WHERE `orm_person_visitation`.`person_id` = 3 LIMIT 21
SELECT `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id` FROM `orm_city` INNER JOIN `orm_person_visitation` ON (`orm_city`.`id` = `orm_person_visitation`.`city_id`) WHERE `orm_person_visitation`.`person_id` = 4 LIMIT 21
SELECT `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id` FROM `orm_city` INNER JOIN `orm_person_visitation` ON (`orm_city`.`id` = `orm_person_visitation`.`city_id`) WHERE `orm_person_visitation`.`person_id` = 5 LIMIT 21
"""
# 多对多中使用prefetch_related来减少SQL语句
person_list = Person.objects.prefetch_related("visitation").all()
for person in person_list:
print(person.visitation.all())
"""
SELECT `orm_person`.`id`, `orm_person`.`firstname`, `orm_person`.`lastname`, `orm_person`.`hometown_id`, `orm_person`.`living_id` FROM `orm_person` WHERE (`orm_person`.`firstname` = '张' AND `orm_person`.`lastname` = '三丰') ORDER BY `orm_person`.`id` ASC LIMIT 1
SELECT (`orm_person_visitation`.`person_id`) AS `_prefetch_related_val_person_id`, `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id` FROM `orm_city` INNER JOIN `orm_person_visitation` ON (`orm_city`.`id` = `orm_person_visitation`.`city_id`) WHERE `orm_person_visitation`.`person_id` IN (1, 2, 3, 4, 5)
"""
# prefetch_related也支持多级外键
person_list = Person.objects.prefetch_related("visitation__province").all()
for person in person_list:
print(person.visitation.all())
"""
SELECT `orm_person`.`id`, `orm_person`.`firstname`, `orm_person`.`lastname`, `orm_person`.`hometown_id`, `orm_person`.`living_id` FROM `orm_person`
SELECT (`orm_person_visitation`.`person_id`) AS `_prefetch_related_val_person_id`, `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id` FROM `orm_city` INNER JOIN `orm_person_visitation` ON (`orm_city`.`id` = `orm_person_visitation`.`city_id`) WHERE `orm_person_visitation`.`person_id` IN (1, 2, 3, 4, 5)
SELECT `orm_province`.`id`, `orm_province`.`name` FROM `orm_province` WHERE `orm_province`.`id` IN (1, 2)
"""
6. 模型扩展¶
6.1 模型管理器¶
objects是 模型管理器(Manager)的实例对象,而模型管理器(Manager)是models.Model提供给模型进行数据库操作的接口对象,因为数据库模型类都必须要继承models.Model,所以django中每个模型类都拥有至少一个模型管理器对象(objects)。
我们在通过模型类的objects属性提供的方法操作数据库时,即是在使用一个模型管理器的实例对象objects。当没有为模型类定义管理器时,Django会为每一个模型类生成一个名为objects的管理器对象,它是models.Manager类的实例对象。
在源代码中models.Model的元类ModelBase提供了一个_prepare方法,在_prepare方法中创建了objects对象并补充到Model类中。django/db/models/base.py,代码:
def _prepare(cls):
"""Create some methods once self._meta has been populated."""
opts = cls._meta
opts._prepare(cls)
if opts.order_with_respect_to:
cls.get_next_in_order = partialmethod(
cls._get_next_or_previous_in_order, is_next=True
)
cls.get_previous_in_order = partialmethod(
cls._get_next_or_previous_in_order, is_next=False
)
# Defer creating accessors on the foreign class until it has been
# created and registered. If remote_field is None, we're ordering
# with respect to a GenericForeignKey and don't know what the
# foreign class is - we'll add those accessors later in
# contribute_to_class().
if opts.order_with_respect_to.remote_field:
wrt = opts.order_with_respect_to
remote = wrt.remote_field.model
lazy_related_operation(make_foreign_order_accessors, cls, remote)
# Give the class a docstring -- its definition.
if cls.__doc__ is None:
cls.__doc__ = "%s(%s)" % (
cls.__name__,
", ".join(f.name for f in opts.fields),
)
get_absolute_url_override = settings.ABSOLUTE_URL_OVERRIDES.get(
opts.label_lower
)
if get_absolute_url_override:
setattr(cls, "get_absolute_url", get_absolute_url_override)
if not opts.managers:
if any(f.name == "objects" for f in opts.fields):
raise ValueError(
"Model %s must specify a custom Manager, because it has a "
"field named 'objects'." % cls.__name__
)
manager = Manager()
manager.auto_created = True
cls.add_to_class("objects", manager)
# Set the name of _meta.indexes. This can't be done in
# Options.contribute_to_class() because fields haven't been added to
# the model at that point.
for index in cls._meta.indexes:
if not index.name:
index.set_name_with_model(cls)
class_prepared.send(sender=cls)
同时,Manager模型管理器,实际上是继承了一个元类,这个元类就是QuerySet对象,从而提供了所有的数据库查询操作。
查找from_queryset方法的源代码,可以发现,此处在构建元类(我们在python面向对象高级中曾经讲过这块)
@classmethod
def from_queryset(cls, queryset_class, class_name=None):
if class_name is None:
class_name = "%sFrom%s" % (cls.__name__, queryset_class.__name__)
return type(
class_name,
(cls,),
{
"_queryset_class": queryset_class,
**cls._get_queryset_methods(queryset_class),
},
)
6.1.1 自定义模型管理器¶
我们可以自定义模型管理器,并应用到我们的模型类上,以增加一些自定义数据库查询操作。但是一旦为模型类指明自定义模型管理器以后,Django不再提供默认的模型管理器对象objects了。
自定义管理器类主要用于两种情况,分别是重写objects的现有方法与新增数据库操作方法。
准备一个用户模型,代码:
class User(models.Model):
"""用户信息表"""
nickname = models.CharField(max_length=50, verbose_name="昵称")
username = models.CharField(max_length=50, verbose_name="用户名")
password = models.CharField(max_length=255, verbose_name="密码")
created_time = models.DateTimeField(auto_now_add=True, verbose_name="注册时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
deleted_time = models.DateTimeField(null=True, blank=True, verbose_name="删除时间")
class Meta:
db_table = "orm_user"
verbose_name = "用户信息"
verbose_name_plural = verbose_name
def __str__(self):
return str({"name": self.nickname, "deleted_time": self.deleted_time})
数据迁移
6.1.2 重写objects的现有方法¶
打开orm/models.py文件,定义类UserManager
from django.db.models import Manager
"""
1. 自定义模型管理器
模型管理器,必须直接或间接继承于 Manager
注意:filter的返回值并非QuerySet,所以跟在filter后面的调用的方法无法重写。
"""
class UserManager(Manager):
def create(self, **kwargs):
if "password" in kwargs:
from hashlib import sha256
hash = sha256()
hash.update(kwargs["password"].encode())
kwargs["password"] = str(hash.hexdigest())
return super().create(**kwargs)
def all(self):
return super().filter(deleted_time__isnull=True).all()
在模型类User中注册模型管理器
class User(models.Model):
"""用户信息表"""
nickname = models.CharField(max_length=50, verbose_name="昵称")
username = models.CharField(max_length=50, verbose_name="用户名")
password = models.CharField(max_length=255, verbose_name="密码")
created_time = models.DateTimeField(auto_now_add=True, verbose_name="注册时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
deleted_time = models.DateTimeField(null=True, blank=True, verbose_name="删除时间")
"""
2. 作为类属性注册到模型类中
"""
objects = UserManager()
class Meta:
db_table = "orm_user"
verbose_name = "用户信息"
verbose_name_plural = verbose_name
def __str__(self):
return str({"name": self.nickname, "deleted_time": self.deleted_time})
视图中调用模型管理器
class Student6View(View):
def get(self, request):
"""修改objects原有方法"""
# from hashlib import sha256
# hash = sha256()
# hash.update("123456".encode())
# user = User.objects.create(username="root", nickname="超级管理员", password=str(hash.hexdigest()))
# user = User.objects.create(username="root", nickname="超级管理员", password="123456")
user_list = User.objects.all()
print(user_list)
return HttpResponse("ok")
6.1.3 在管理器类中补充定义新的查询方法¶
a)打开models.py文件,定义方法create。
from django.db.models import Manager
class UserManager(Manager):
def create(self, **kwargs):
if "password" in kwargs:
from hashlib import sha256
hash = sha256()
hash.update(kwargs["password"].encode())
kwargs["password"] = str(hash.hexdigest())
return super().create(**kwargs)
def all(self):
return super().filter(deleted_time__isnull=True).all()
def soft_delete(self, **kwargs):
"""逻辑删除,并非真实删除数据,而是给数据设置了一个删除时间"""
from datetime import datetime
return self.filter(**kwargs).update(deleted_time=datetime.now())
视图代码调用:
class Student6View(View):
def get1(self, request):
"""修改objects原有方法"""
# from hashlib import sha256
# hash = sha256()
# hash.update("123456".encode())
# user = User.objects.create(username="root", nickname="超级管理员", password=str(hash.hexdigest()))
# user = User.objects.create(username="root", nickname="超级管理员", password="123456")
user_list = User.objects.all()
print(user_list)
return HttpResponse("ok")
def get(self, request):
"""新增objects没有的方法"""
User.objects.soft_delete(username="root")
return HttpResponse("ok")
6.2 代理模型¶
Django提供的ORM模型声明中,一共有3种不同类型的模型。我们前面已经了解并使用了2种。
from django.db import models
from django.db.models.base import Manager
class UserManager(Manager):
def get_user_list(self):
return self.filter(age__gte=18).all()
# Create your models here.
class User(models.Model):
name = models.CharField(max_length=20, db_index=True, verbose_name="姓名")
age = models.IntegerField(verbose_name="年龄")
sex = models.BooleanField(null=True, blank=True, default=None, verbose_name="性别")
# 覆盖设置了django默认的模型管理器
objects2 = UserManager() # 模型的对象管理器,是否叫objects都不影响django取消内部的objects,因为我们自定义了
class Meta:
db_table = 'tb_user'
verbose_name = "用户信息"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
@classmethod
def get_user(cls):
"""获取成年人列表"""
return cls.objects.filter(age__gte=18).all()
class Female(User):
class Meta:
proxy = True # 设置当前模型为代理模型,共享父模型的数据和操作方法
@classmethod
def all(cls):
return cls.objects2.filter(sex=False).all()
class Male(User):
class Meta:
proxy = True
@classmethod
def all(cls):
return cls.objects2.filter(sex=True).all()
作业:
基于上面课件的 《实现用户的足迹功能》使用django的ORM操作完成以下数据查询:
- 张无忌的家乡在哪个省份哪个城市?
- 张无忌的老乡都有谁?
- 张无忌都去过了哪些城市?
- 张三丰与张无忌有没有去过同样的城市?如果有,都有哪些城市?




