一、模型层
1.1. 模型层定义
Django MTV
模型层:负责跟数据库之间进行通信
1.2. Django配置MySQL
-
安装
mysqlclient
【版本mysqlclient 1.3.13以上,官网目前为2.0.x】 -
安装前确认Ubuntu是否已安装
python3-dev
和default-libmysqlclient-dev
sudo apt list --installed|grep -E 'libmysqlclient-dev|python3-dev'
- 若命令无输出则需要安装 -
sudo apt-get install python3-dev default-libmysqlclient-dev
-
安装
mysqlclient
:sudo pip3 install mysqlclient
-
检查
sudo pip3 freeze|grep -i 'mysql'
1.3. 创建数据库
进入MySQL数据库执行
create database 数据库名 default charset utf8
通常数据库名称与项目名称保持一致
1.4. 配置数据库
setting.py里进行数据库的配置
修改DATABASES
配置项的内容,由sqlite3变为mysql
-
ENGINE:指定数据库存储引擎
-
NAME:指定要连接的数据库的名称
-
USER:指定登录到数据库的用户名
-
PASSWORD:指定登录到数据库的密码
-
HOST/PORT:连接具体数据库的IP和端口
1.5. 模型
- 模型是一个Python类,它是由django.db.models.Model派生出的子类
- 一个模型类代表数据库中的一张数据表
- 模型类中每一个类属性都代表数据库中的一个字段
- 模型是数据交互的接口,是表示和操作数据库的方法和方式
二、ORM框架
2.1. 定义
ORM(Object Relational Mapping)即对象关系映射,它是一种程序技术,它允许你使用类和对象对数据库进行操作,从而避免通过SQL语句操作数据库
2.2. 作用
- 建立模型类和表之间的对应关系,允许我们通过面向对象的方式来操作数据库
- 根据设计的模型类生成数据库中的表格
- 通过简单的配置就可以进行数据库的切换
2.3. 优点
-
只需要面向对象编程,不需要面向数据库编写代码。
- 对数据库的操作都转换成对类属性和方法的操作
- 不用编写各种数据库的sql语句
-
实现了数据模型与数据库的解耦,屏蔽了不同数据库操作上的差异。
- 不再关注用的是MySQL、Oracle…等数据库的内部细节
- 通过简单的配置就可以轻松更换数据库,而不需要修改代码
2.4. 缺点
- 对于复杂业务,使用成本较高
- 根据对象的操作转换成SQL语句,根据查询的结果转换成对象,在映射过程中有性能损失
2.5. 映射图
2.6. 模型示例
此示例为添加一个bookstore_book数据表来存放图书馆中的书目信息
-
添加一个bookstore的app
python3 manage.py startapp bookstore
-
注册应用app
-
添加模型类
模型类代码示例
# file : bookstore/models.py from django.db import models class Book(models.Model): title = models.CharField("书名", max_length=50, default='') price = models.DecimalField("定价", max_digits=7, decimal_places=2, default=0.0)
-
数据库迁移
迁移是Django同步对模型所做的更改(添加字段、删除模型等)到数据库模式的方式
-
生成迁移文件 — 执行 python3 manage.py makemigrations
将应用下的models.py文件生成一个中间文件,并保存在migrations文件夹中
-
执行迁移脚本程序 — 执行 python3 manage.py migrate
执行迁移程序实现迁移。将每个应用下的migrations目录中的中间文件同步回数据库
-
-
效果
三、模型类
3.1. 创建
-
创建应用
-
在应用下的models.py中编写模型类
from django.db import models class 模型类名(models.Model): 字段名 = models.字段类型(字段选项)
-
迁移同步 makemigrations & migrate
-
任何关于表结构,务必在对应模型类上修改
例:为bookstore_book表添加一个名为info的字段varchar(100)
解决方案:
- 模型类中添加对应类属性
- 执行数据库迁移
3.2. 基础字段
-
**BooleanField() **
- 数据库类型:tinyint(1)
- Django类型:使用True或False来表示值
- 在数据库中,使用1或者0来表示具体的值
-
CharField()
- 数据库类型:varchar
- 注意: 必须要指定max_length参数值
-
DateField()
- 数据库类型:date
- 作用:表示日期
- 参数:
- auto_now:每次保存对象时,自动设置该字段为当前时间(取值:True / False)
- auto_now_add:当对象第一次被创建时自动设置当前时间(取值:True / False)
- default:设置当前时间(取值:字符串格式时间,如:‘2019-6-1’)
- 以上三个参数只能多选一
-
DateTimeField()
- 数据库类型:datetime(6)
- 作用:表示日期和时间
- 参数同DateField
-
FloatField()
- 数据库类型:double
- Django、数据库中都是用小数表示值
-
DecimalField()
-
数据库类型:decimal(x,y)
-
Django、数据库中都是用小数表示值
-
参数:
- max_digits:位数总数,包括小数点后的位数。该数值必须大于等于decimal_places
- decimal_places:小数点后的数字量数
-
-
EmailField()
- 数据库类型:varchar
- Django、数据库中都是用字符串表示值
-
IntegerField()
- 数据库类型:int
- Django、数据库中都是用整数表示值
-
ImageField()
- 数据库类型:varchar(100)
- 作用:在数据库中为了保存图片的路径
- Django、数据库中都是用字符串表示值
-
TextField()
- 数据库类型:longtext
- 作用:表示不定长的字符数据
-
官方文档
https://docs.djangoproject.com/en/2.2/ref/models/fields/#field-types
3.3. 创建模型类测试
在bookstore/models.py应用中,添加一个模型类
-
Author - 作者
- name CharField 姓名 长度最大11
- age IntegerField 年龄
- email EmailField 邮箱
-
代码:
class Author(models.Model): name = models.CharField("姓名", max_length=11) age = models.IntegerField("年龄") email = models.EmailField("邮箱")
-
迁移:
3.4. 字段选项
-
字段选项,指定创建的列的额外的信息
-
允许出现多个字段选项,多个选项之间使用
,
隔开-
primary_key:如果设置为True,表示该列为主键,如果指定一个字段为逐渐,则此数据表不会创建id字段。
-
blank:设置为True时,字段可以为空。设置为False时,字段是必须填写的。
-
null:如果设置True,表示该列值允许为空。默认为False,如果此选项为False,建议加入default选项来设置默认值。
-
default:设置所在列的默认值,如果字段选项null=False,建议添加此项。
-
db_index:如果设置为True,表示为该列增加索引。
-
unique:如果设置为True,表示该字段在数据库中的值必须是唯一(不能重复出现)。
-
db_column:指定列的名称,如果不指定的话则采用属性名作为列名。
-
verbose_name:设置此字段在admin界面上的显示名称
-
3.5. 字段选项样例
# 创建一个属性,表示用户长度,长度30个字符,必须是唯一的,不能为空,添加索引
name = models.CharField(max_length=30, unique=True, null=False, db_index=True)
官方文档:
https://docs.djangoproject.com/en/2.2/ref/models/fields/#field-options
3.6. 注意
修改过字段选项、添加或更改均要执行makemigrations和migrate
3.7. Meta类
使用内部Meta类,来给模型赋予属性,Meta类下面有很多内建的类属性,可以对模型类做一些控制:
示例:
# file : bookstore/models.py
from django.db import modele
class Book(models.Model):
title = models.CharField("书名", max_length=50, default='')
price = models.DecimalField("定价", max_digits=7, decimal_places=2, default=0.0)
info = models.CharField("信息", max_length=100, default='')
class Meta:
db_table = 'book' # 可改变当前模型类对应的表名
迁移后:
表名变成了book
练习:
class Book(models.Model):
title = models.CharField("书名", max_length=50, default='', unique=True)
pub = models.CharField("出版社", max_length=100, default='', null=False)
price = models.DecimalField("价格", max_digits=7, decimal_places=2)
marker_price = models.DecimalField("零售价", max_digits=7, decimal_places=2, default=0.0)
class Meta:
db_table = 'book' # 可改变当前模型类对应的表名
class Author(models.Model):
name = models.CharField("姓名", max_length=11, null=False)
age = models.IntegerField("年龄", default="1")
email = models.EmailField("邮箱", null=True)
class Meta:
db_table = 'author' # 可改变当前模型类对应的表名
四、常见问题及解决方法
-
问题1:当执行
python3 manage.py makemigrations
出现如下迁移错误时的处理方法 -
数据库的迁移文件混乱的解决方法
-
数据库中django_migrations表记录了migrate的’全过程’,项目个应用中的migrate文件与之对应,否则migrate会报错。
-
解决方案:
-
删除所有migrations里所有的000?XXXX.py(__init_.py除外)
-
删除数据库
drop database mywebdb;
-
重新创建数据库
create database mywebdb default charset...;
-
重新生成migrations里面所有的000?_XXXX.py
python3 manage.py makemigrations
-
重新更新数据库
python3 manage.py migrate
-
-
五、Django Shell
Django提供了一个交互式的操作项目叫Django Shell,它能够在交互模式用项目工程的代码执行相应的操作
利用Django Shell可以代替编写view的代码来进行直接操作
注意:项目代码发生变化时,重新进入Django shell
启动方式:
python3 manage.py shell
六、ORM操作
基本操作包括增删改查操作,即(CURD操作)
CURD是指在计算处理时的增加(Create)、读取查询(Read)、更新(Update)和删除(Delete)
ORM CURD核心 → 模型类.管理器对象
6.1. 管理器对象
每个继承自models.Model的模型类,都会有一个objects对象被同样继承下来。这个对象叫管理器对象。
数据库的增删改查可以通过模型的管理器实现。
class MyModel(models.Model):
···
MyModel.objects.create(...) # objects 是管理器对象
6.2. 创建/插入数据
Django ORM使用一种直观的方式把数据库表中的数据表示成Python对象,创建数据中每一条记录就是创建一个数据对象
6.2.1. 方案1
MyModel.objects.create(属性1=值1,属性2=值2...)
成功:返回创建好的实体对象
失败:抛出异常
测试:
6.2.2. 方案2
创建MyModel实例对象,并调用save()进行保存
obj = MyModel(属性=值,属性=值..)
obj.属性 = 值
obj.save()
测试:
6.2.3. 练习
结果:
6.3. 查询数据
6.3.1. 查询简介
- 数据库的查询需要使用管理器对象进行
- 通过MyModel.objects管理器方法调用查询方法
方法 | 说明 |
---|---|
all() | 查询全部记录,返回QuerySet查询对象 |
get() | 查询符合条件的单一记录 |
filter() | 查询符合条件的多条记录 |
exclude() | 查询符合条件之外的全部记录 |
··· |
6.3.2. all()方法
用法:MyModel.objects.all()
作用:查询MyModel实体中所有的数据
等同于select * from table
返回值:QuerySet容器对象,内部存放MyModel实例
from bookstore.models import Book
books = Book.objects.all()
for book in books:
print("书名", book.title, "出版社", book.pub)
6.3.3. 查询技巧
可以在模型类中定义__str__方法,自定义QuerySet中的输出格式
例如 在Book模型类下定义如下:
def __str__(self):
return "%s_%s_%s_%s"%(self.title,self.price,self.pub,self.market_price)
则在Django shell中可得如下显示输出
6.3.4. values(…)
用法:
MyModel.objects.values('列1','列2'..)
作用:查询部分列的数据并返回,等同于select 列1,列2 from xxx
返回值:QuerySet
返回查看结果容器,容器内存字典,每个字典代表一条数据
格式为:
{
"列1":值1,
"列2":值2
}
6.3.5. values_list(…)
用法:
MyModel.objects.values_list('列1','列2'...)
作用:返回元组形式的查询结果,等同于select 列1,列2 from xxx
返回值:QuerySet容器对象,内部存放元组
会将查询出来的数据封装到元组中,在封装到查询集合QuerySet中
6.3.6. order_by()
用法:
MyModel.objects.order_by('-列','列')
作用:
与all()方法不同,它会用SQL语句的ORDER BY子句对查询结果进行根据某个字段选择性的排序
说明:
-
默认是按照升序排序,降序排序则需要在列前面增加
'-'
表示
-
方法之间支持链路调用
6.3.7. query
可以使用QuerySet.query
查询原始SQL语句
6.3.8. 练习1
6.3.9. filter(条件)
语法:
MyModel.objects.filter(属性1=值1,属性2=值2...)
作用:返回包含此条件的全部的数数据集
返回值:QuerySet容器对象,内部存放MyModel实例
说明:当多个属性在一起时,为"与"关系,即当
样例:
# 查询书中出版社为"清华大学出版社"的图书
from bookstore.models import Book
books = Book.objects.filter(pub = "清华大学出版社")
for book in books:
print("书名:", book.title)
# 查询Author实体中name为王老师并且age是28岁的
from bookstore.models import Author
author = Author.objects.filter(name='王老师', age=28)
6.3.10. exclude(条件)
语法:
MyModel.objects.exclude(条件)
作用:返回不包含此条件的全部的数据集
示例:
# 查询清华大学出版社,定价等于50以外的全部图书
from bookstore.models import Book
books = Book.objects.exclude(pub="清华大学出版社", price=50)
for book in books:
print(book)
6.3.11. get(条件)
语法:
MyModel.objects.get(条件)
作用:返回满足该条件的唯一一条数据
说明:该方法只能返回一条数据,查询结果多余一条数据则抛出Model.MultipleObjectsReturned
异常。查询结果如果没有数据则抛出Model.DoecNotExist
异常。
6.3.12. 查询谓词
思考:如何做非等值的过滤查询,即 where id > 1
尝试:Book.objects.filter(id > 1) ?
解决方案:查询谓词
定义:做更灵活的条件查询时需要使用查询谓词
说明:每一个查询谓词是一个独立的查询功能
查询谓词 | 含义 | 示例 | 等价SQL |
---|---|---|---|
__exact | 等值查询 | Author.objects.filter(id__exact=1) | select * from author where id = 1 |
__contains | 包含指定值 | Author.objects.filter(name__contains=‘w’) | select * from author where name like ‘%w%’ |
__startswith | 以XXX开始 | Author.objects.filter(name__startswith=‘w’) | select * from author where name like ‘w%’ |
__endswith | 以XXX结束 | Author.objects.filter(name__endswith=‘w’) | select * from author where name like ‘%w’ |
__gt | 大于指定值 | Author.objects.filter(age__gt=50) | select * from author where age > 50 |
__gte | 大于等于指定值 | Author.objects.filter(age__gte=30) | select * from author where age >= 50 |
__lt | 小于指定值 | Author.objects.filter(age__lt=30) | select * from author where age < 50 |
__lte | 大于等于指定值 | Author.objects.filter(age__lte=30) | select * from author where age <= 50 |
__in | 查询数据是否在指定范围内 | Author.objects.filter(country__in=[‘中国’,‘日本’,‘韩国’]) | select * from author where country in (‘中国’,‘日本’,‘韩国’) |
__range | 查找数据是否在指定区间范围内 | Author.objects.filter(age__range=(30,50)) | select * from author where age between 35 and 50 |
官方文档:https://docs.djangoproject.com/en/2.2/ref/models/querysets/#field-lookups
6.4. 更新数据
6.4.1. 单个数据
修改单个实体的某些字段值的步骤:
- 查:通过
get()
得到要修改的实体对象 - 改:通过
对象.属性
的方式修改数据 - 存:通过
对象.save()
保存数据
6.4.2. 批量更新
直接调用QuerySet的update(属性=值)实现批量修改,返回更新成功的条数
示例:
# 将id大于3的所有图书价格定为0元
books = Book.objects.filter(id__gt=3)
books.update(price=0)
# 将所有书的零售价定为100元
books = Book.objects.all()
books.update(market_price=100)
6.4.3. 练习2
-
列表界面
-
更新界面
-
视图函数
-
路由
6.5. 删除数据
6.5.1. 干个数据
步骤:
- 查找查询结果对应的一个数据对象
- 调用这个数据对象的
delete()
方法实现删除
try:
auth = Author.objects.get(id=1)
auth.delete()
except:
print(删除失败)
6.5.2. 批量数据
步骤:
- 查找查询结果集中满足条件的全部QuerySet查询集合对象
- 调用查询集合对象的delete()方法实现删除
# 删除全部作者中,年龄大于65的全部信息
auths = Author.objects.filter(age__gt=65)
auths.delete()
6.5.3. 伪删除
-
通常不会轻易在业务中把数据真正删掉,取而代之的是做伪删除。
即在表中添加一个布尔型字段(is_active),默认是True;执行删除时,将欲删除数据的is_active字段置为False
-
注意:用伪删除时,确保显示数据的地方,均加了is_active = True的过滤查询
6.6. F对象和Q对象
6.6.1. F对象
一个F对象代表数据库中某条记录的字段的信息
作用:
- 通常是对数据库的字段值在不获取的情况下进行操作
- 用于类属性(字段)之间的比较
语法:
from django.db.models import F
F('列名')
示例1:更新Book实例中所有的零售价涨10元
Book.objects.all().update(market_price=F('market_price')+10)
'UPDATE `bookstore_book` SET `market_price` = (`bookstore_book`.`market_price` + 10)'
# 以上代码好于如下代码
books = Book.objects.all()
for book in books:
book.market_price = book.market_price + 10
book.save()
示例2:对数据库中两个字段的值进行比较,列出哪些书的零售价高于定价
from django.db.models import F
from bookstore.models import Book
books = Books.objects.filter(market_price__gt=F('price'))
'SELECT * FROM `bookstore_book` WHERE `bookstore_book`.`market_prict` > `bookstore_book`.`price`'
for book in books:
print(book.title, "定价:", book.price, "现价:", book.market_price)
6.6.2. Q对象
当在获取查询结果集使用复杂的逻辑或|、逻辑非~等操作时可以借助Q对象进行操作
如:想找出定价低于20元 或 清华大学出版社的全部书,可以写成
Book.objects.filter(Q(price__lt=20)|Q(pub="清华大学出版社"))
Q对象在数据包 django.db.models中,需要先导入再使用。
作用:在条件中用来实现除 and(&) 以外的 or(|) 或 not(~) 操作
运算符:
& 与运算符
-
| 或运算符
- 非运算符
语法:
from django.db.models import Q
Q(条件1)|Q(条件2) # 条件1成立或条件2成立
Q(条件1)&Q(条件2) # 条件1和条件2同时成立
Q(条件1)&~Q(条件2) # 条件1成立且条件2不成立
···
6.7. 聚合查询
6.7.1. 定义
聚合查询是指对一个数据表中的一个字段的数据进行部分或全部进行统计查询,查bookstore_book数据表中的全部书的平均价格,查询所有书的总个数等,都要使用聚合查询
聚合查询分为:
- 整表聚合
- 分组聚合
6.7.2. 整表聚合
不带分组的聚合查询是将全部数据进行集中统计查询
聚合函数[需要导入]:
-
导入方法:
from django.db.models import *
-
聚合函数:
Sum、Avg、Count、Max、Min
-
语法:
MyModel.objects.aggregate(结果变量名=聚合函数('列'))
-
返回结果:结果变量名和值组成的字典
格式为:
{ "结果变量名":值 }
-
示例:
6.7.3. 分组聚合
分组聚合是指通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。
-
语法:
QuerySet.annotate(结果变量名=聚合函数('列'))
-
返回值:QuerySet
-
流程:
-
通过先用查询结果MyModel.objects.values查找查询要分组聚合的列
MyModel.objects.values(‘列1’,‘列2’)
如:
pub_set = Book.objects.values('pub') print(pub_set) # <QuerySet [{'pub':'清华大学出版社'},{'pub':'清华大学出版社'},{'pub_hou'{'pub':'机械工业出版社'},{'pub'}:'清华大学出版社'}]>
-
通过返回结果的QuerySet.annotate方法分组聚合得到分组结果
QuerySet.annotate(名=聚合函数(‘列’))
pub_count_set = pub_set.annotate(myCount=Count('pub')) print(pub_count_set) # <QuerySet [{'pub':'清华大学出版社','myCount':7},{'pub':'机械工业出版社', 'myCount':3}]
-
七、原生数据库操作
Django也可以支持直接使用sql语句的方式通信数据库
查询:使用MyModel.object.raw()
进行数据库查询操作查询
语法:
MyModel.objects.raw(sql语句,拼接参数)
返回值:
RawQuerySet集合对象【只支持基础操作,比如循环】
books = Book.objects.raw('select * from book')
for book in books:
print(book)
使用原生SQL语句时小心SQL注入
定义:用户通过数据上传,将恶意的SQL语句提交给服务器,从而达到攻击的效果
案例1:用户在搜索好友的表单框里输入’1 or 1=1’
s1 = Book.objects.raw('select * from book where id = %s'%('1 or 1=1'))
攻击结果:可查询出所有的用户数据
sql注入防范:
错误:
s1 = Book.objects.raw('select * from book where id = %s'%('4 or 1=1'))
for s in s1:
print(s)
正确:
s1 = Book.objects.raw('select * from book where id = %s',['4 or 1=1'])
for s in s1:
print(s)
八、cursor
完全跨过数据类操作数据库 — 查询/更新/删除
-
导入cursor所在的包
from django.db import connection
-
用创建cursor类的构造函数创建cursor对象,再使用cursor对象,为保证在出现异常时能释放cursor资源,通常使用with语句进行创建操作
from django.db import connection with connection.cursor() as cur: cur.execute('执行SQL语句','拼接参数')
-
示例:
# 用SQL语句将id为4的书的出版社改为"xxx出版社" from django.db import connection with connection.cursor() as cur: cur.execute('update book set pub_house="xxx出版社" where id = 4;') # 删除id为1的一条记录 with connection.cursor() as cur: cur.execute('delete from book where id = 1;')