文章目录
一.缓存
1.概述
目的:
在Django中,当用户请求到达视图后,视图会先从数据库提取数据放到模板中进行动态渲染,渲染后的结果就是用户看到的网页。如果用户每次请求都从数据库提取数据并渲染,将极大降低性能,不仅服务器压力大,而且客户端也无法即时获得响应。如果能将渲染后的结果放到速度更快的缓存中,每次有请求过来,先检查缓存中是否有对应的资源,如果有,直接从缓存中取出来返回响应,节省取数据和渲染的时间,不仅能大大提高系统性能,还能提高用户体验。
应用场景:
缓存主要适用于对页面实时性要求不高的页面。存放在缓存的数据,通常是频繁访问的,而不会经常修改的数据。
我们来举几个应用例子:
博客文章。假设用户一天更新一篇文章,那么可以为博客设置1天的缓存,一天后会刷新。
购物网站。商品的描述信息几乎不会变化,而商品的购买数量需要根据用户情况实时更新。我们可以只选择缓存商品描述信息。
缓存网页片段。比如缓存网页导航菜单和脚部(Footer)。
2.Django内置缓存实现
(1)装饰器缓存
这是系统封装的,需要注意的是装饰器参数不需要写timeout
创建子路由:
url(r'^testCache/',views.testCache),
生成视图函数:
def testCache(request):
time.sleep(5)
return HttpResponse('敌军还有5秒抵达战场')
运行结果:
我们会发现每次刷新都需要等待5秒页面才显示。
使用装饰器缓存:
@cache_page(30)
def testCache(request):
time.sleep(5)
return HttpResponse('敌军还有5秒抵达战场')
我们会发现除了第一次运行是等待5秒后,在30秒内不需要再等待了
(2)数据库缓存
基于数据库,在真实的数据库中去创建缓存表
创建缓存表:
python manage.py createcachetable my_cache_table
创建成功后,我们会发现数据库中有my_cache_table表了
在settings中配置缓存信息:
CACHES={
'default':{
'BACKEND':'django.core.cache.backends.db.DatabaseCache',
#缓存的位置
'LOCATION':'my_cache_table',
#过期时间
'TIMEOUT':60,
#前缀
'KEY_PREFIX':'python2001',
}
}
创建子路由:
url(r'^testCache1/',views.testCache1),
生成视图函数:
def testCache1(request):
#导入 django.core.cache.cache
value = cache.get('ip')
if value:
return HttpResponse(value)
else:
# ip地址
ip = request.META.get('REMOTE_ADDR')
cache.set('ip',ip)
return HttpResponse('来了老弟')
60秒后,再次刷新:
(3)内存级数据库缓存(redis)
基于redis-内存级数据库
Django-redis-cache
使用redis实现django-cache的扩展
操作缓存的API没有发生任何变更
变更的就是连接缓存的配置
安装 django-redis 和 django-redis-cache:
在settings中配置缓存信息:
CACHES={
'default':{
'BACKEND':'django_redis.cache.RedisCache',
# 缓存的位置
# redis一共有16个数据库 0 ~ 15
# 6379:redis端口号
'LOCATION':'redis://127.0.0.1:6379/1',
'OPTIONS':{
'CLIENT_CLASS':'django_redis.client.DefaultClient'
},
'KEY_PREFIX': 'django2001',
},
}
创建子路由:
url(r'^testCache2/',views.testCache2),
生成视图函数:
def testCache2(request):
value = cache.get('ip')
if value:
return HttpResponse('你在缓存中拿到的数据')
else:
ip = request.META.get('REMOTE_ADDR')
cache.set('ip',ip)
return HttpResponse('你是从数据库拿的数据')
运行结果:
如果你的redis中报错,端口号已经被占用,那么就是因为你的这个服务已经开启
可以使用ps -ef | grep -i redis 来查看,如果已经开启,不要急于杀死进程
解决策略:将settings中的配置中redis的主机地址设置为127.0.0.1
如果你连接redis的时候出现转圈的情况:
(1)查看云主机的入方向和出方向的端口 必须要开放6379端口
基本端口 22:ssh , 80:http , 443:https , 3306:mysql , 6379:redis , 8000:django的默认端接口
入方向和出方向,必须一致
以下策略基本上在linux操作系统出现的概率不大,如果有按照以下方案来改:
(2)配置文件中不允许远程连接
56行代码是 bind 127.0.0.1 将这行代码注释,因为默认是只允许本地连接
76行代码是protect_model = yes 修改为no
3.多级缓存
写多套配置,定义不同的名字,存入缓存的时候,获取不同的缓存对象,想使用哪个缓存就创建哪个缓存的实例对象
在settings中配置缓存信息:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
'TIMEOUT': 60 * 5,
'KEY_PREFIX':'databaseCache2001',
},
'django2001': {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
'KEY_PREFIX':'redis2001',
'TIMEOUT': 60 * 5,
}
}
创建主路由:
url(r'^final/',include('FinalApp.urls',namespace='final')),
(1)多级缓存 - 装饰器缓存
创建子路由:
url(r'^testCache/',views.testCache),
生成视图函数:
#可以使用装饰器,指定想要的数据库
@cache_page(30,cache='default')
def testCache(request):
time.sleep(5)
return HttpResponse('欢迎来到英雄联盟')
运行结果:
查看缓存表中也有记录:
(2)多级缓存 - 缓存的基本应用
创建子路由:
url(r'^testCache1/',views.testCache1),
生成视图函数:
def testCache1(request):
cache = caches['django2001']
value = cache.get('ip')
if value:
return HttpResponse('有值')
else:
ip = request.META.get('REMOTE_ADDR')
cache.set('ip',ip)
return HttpResponse('没值')
第一次运行结果:
第二次运行结果:
查看是否存入redis:
(3)多级缓存 - 缓存时间就近原则
思考:在settings设置了缓存时间’TIMEOUT’: 60 * 5,如果又在装饰器缓存中设置了缓存时间,那么是执行哪个???
创建子路由:
url(r'^testCache2/',views.testCache2),
生成视图函数:
@cache_page(30,cache='django2001')
def testCache2(request):
time.sleep(5)
return HttpResponse('测试缓存的优先级')
运行结果:
当我们等30s后还没到5分钟的时候,再次刷新页面,会发现我们需要等待5s,所以缓存的时间以视图函数为主,就近原则
二.中间件
1.介绍
中间件:是一个轻量级的,底层的插件,可以介入到Django的请求和响应过程(面向切面编程)
中间件的本质就是一个python类
面向切面编程(Aspect Oriented Programming)简称AOP。AOP的主要实现目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合的隔离效果。
django内置的一个底层插件
从属于面向切面编程AOP
在不修改源代码的情况下,动态去添加一些业务逻辑处理
中间件的典型实现:装饰器
中间件就是使用类装饰实现的
面向切面编程
切点
(1)process_request
process_request(self,request):在执行视图前被调用,每个请求上都会调用,不主动进行返回或返回HttpResponse对象
(2)process_view
process_view(self,request,view_func,view_args,view_kwargs):调用视图之前执行,每个请求都会调用,不主动进行返回或返回HttpResponse对象
(3)process_template_response
process_template_response(self,request,response):在视图刚好执行完后进行调用,每个请求都会调用,不主动进行返回或返回HttpResponse对象
(4)process_response
process_response(self,request,response):所有响应返回浏览器之前调用,每个请求都会调用,不主动进行返回或返回HttpResponse对象
(5)process_exception
process_exception(self,request,exception):当视图抛出异常时调用,不主动进行返回或返回HttpResponse对象
切面
切点处切开可以获得的数据
2.基本使用
(1)在工程目录下创建middleware目录
(2)目录中创建一个python文件
(3)在python文件中导入中间件的基类
from django.utils.deprecation import MiddlewareMixin
(4)在类中根据功能需求,创建切入需求类,重写切入点方法:
class TestMiddle(MiddlewareMixin):
def process_request(self,request):
print('i am first middleware')
(5)启用中间件,在settings中进行配置,MIDDLEWARE中添加: middleware.文件名.类名
创建子路由:
url(r'^testMiddleware/',views.testMiddleware),
生成视图函数:
def testMiddleware(request):
return HttpResponse('天道酬勤')
运行结果:
思考:若中间件中有返回值,视图函数中有返回值,那么返回哪个???
class TestMiddle(MiddlewareMixin):
def process_request(self,request):
print('i am first middleware')
return HttpResponse('欢迎来到英雄联盟')
运行结果:
中间件是在执行视图函数之前执行,一般情况下,中间件没有返回值
如果中间件中有返回值了,那么就不在执行视图函数了
3.过滤资源路径
以增删改查为例
创建子路由:
url(r'add/',views.add),
url(r'delete/', views.delete),
url(r'update/', views.update),
url(r'find/', views.find),
生成视图函数:
def add(request):
print('连接数据库')
return HttpResponse('add')
def delete(request):
print('连接数据库')
return HttpResponse('delete')
def update(request):
print('连接数据库')
return HttpResponse('update')
def find(request):
print('连接数据库')
return HttpResponse('find')
由上可以发现,每次都需连接数据库,耦合度太高,这时就可以使用中间件来减少耦合度
def add(request):
return HttpResponse('add')
def delete(request):
return HttpResponse('delete')
def update(request):
return HttpResponse('update')
def find(request):
return HttpResponse('find')
在middleware.py文件中:
class TestMiddle(MiddlewareMixin):
# 中间件是在执行视图函数之前执行,一般情况下,中间件没有返回值
# 如果中间件中有返回值了,那么就不在执行视图函数了
def process_request(self,request):
print('中间件连接数据库')
运行结果:
我们可以发现增删改查都可以通过中间件来连接数据库,但是如果我们调用TestMiddle函数,会出现什么情况???
我们会发现在我们不需要连接数据库的时候,中间件也连接了数据库, 这是因为在默认情况下,中间件是作用在所有的视图函数的前面,所以我们需要过滤资源路径
class TestMiddle(MiddlewareMixin):
def process_request(self,request):
CONN_REQ = [
'/final/add/',
'/final/delete/',
'/final/update/',
'/final/find/',
]
if request.path in CONN_REQ:
print('中间件连接数据库')
运行结果:
4.应用-白名单
创建子路由:
url(r'^testWhite/',views.testWhite),
生成视图函数:
def testWhite(request):
if random.randrange(100) > 70:
return HttpResponse('恭喜你获得三等奖,获得英雄联盟全英雄')
else:
return HttpResponse('很遗憾,英雄体验卡一张')
运行结果:
设置白名单:
class TestMiddle(MiddlewareMixin):
def process_request(self,request):
# 白名单IP
IP_REQ = ['xxx.xxx.xxx.xxx','xxx.xxx.xxx.xxx']
ip = request.META.get('REMOTE_ADDR')
print(ip)
if ip in IP_REQ:
if random.randrange(100) > 50:
return HttpResponse('恭喜你中了一等奖,英雄联盟全英雄全皮肤全手办')
运行结果:
5.中间件异常
当某一段业务逻辑发生了错误,那么就会执行process_exception方法,process_exception使得界面友好化,应用交互友好化。
创建子路由:
url(r'^testException/',views.testException),
生成视图函数:
def testException(request):
a = 1
b = 0
print(a / b)
return HttpResponse('测试异常')
运行结果:
这个报错界面不是很友好,我们可以进行修改:
在中间件添加:
class TestMiddle(MiddlewareMixin):
def process_request(self,request):
pass
# 判断所有的视图函数,如果视图函数中有异常,然后就要执行process_exception
def process_exception(self, request, exception):
return HttpResponse('服务器中进入了一个只耗子,稍等片刻。。。')
运行结果:
我们也可以将它跳转到其他页面:
class TestMiddle(MiddlewareMixin):
def process_request(self,request):
pass
def process_exception(self, request, exception):
return redirect(reverse('final:index'))
运行结果:
6.中间件的执行顺序
创建新的中间件:
class SecondMiddleware(MiddlewareMixin):
def process_request(self,request):
print('i am second middleware')
在settings中再次注册一个中间件:
'middleware.middleware.SecondMiddleware',
创建子路由:
url(r'^testMiddleSort/',views.testMiddleSort),
生成视图函数:
def testMiddleSort(request):
return HttpResponse('欢迎来到英雄联盟')
运行结果:
思考:执行顺序是书写顺序还是注册顺序???
当我们修改一下注册顺序试试:
'middleware.middleware.SecondMiddleware',
'middleware.middleware.TestMiddle',
运行结果:
由上可知,中间件的执行顺序是按照注册顺序执行的
思考:如果一个中间件进行了返回,其他的中间件是否会执行???
在settings中,中间件的注册顺序:
'middleware.middleware.TestMiddle',
'middleware.middleware.SecondMiddleware',
中间件:
class TestMiddle(MiddlewareMixin):
# 中间件是在执行视图函数之前执行,一般情况下,中间件没有返回值
# 如果中间件中有返回值了,那么就不在执行视图函数了
def process_request(self,request):
print('i am first middleware')
return HttpResponse('敌军还有5秒抵达战场')
class SecondMiddleware(MiddlewareMixin):
def process_request(self,request):
print('i am second middleware')
运行结果:
由上可知,如果一个中间件进行了返回,其他的中间件不会执行
总结:
中间件的执行顺序
中间件注册的时候是一个列表
如果我们没有在切点处直接进行返回,中间件会依次执行
如果我们直接进行了返回,后续中间件就不再执行了
三.分页器
分页是为了提升用户体验,并且减小服务器的负担而开发的
分页:
真分页:每一次点击下一页或者上一页,都会向数据库发送请求,并且返回数据,访问数据库次数过多
假分页:一次性读取所有数据,然后在内存中进行分页
企业级开发中常用的是真分页
1.原生实现
创建模型:
class Student(models.Model):
name = models.CharField(max_length=32)
class Meta:
db_table = 'student'
添加数据:
创建子路由:
url(r'^testGetPage/',views.testGetPage),
生成视图函数:
def testGetPage(request):
#页数
page = int(request.GET.get('page',1))
#设置每页5条数据
per_page = int(request.GET.get('per_page',5))
# 规律
# 1 0 5
# 2 5 10
# 3 10 15
# 4 15 20
#查询
student_list = Student.objects.all()[(page-1)*per_page:per_page*page]
for student in student_list:
print(student.id,student.name)
return HttpResponse('分页原生实现')
运行结果:
2.封装实现
使用Django自带的Paginator
Paginator(分页工具)
对象创建
:Paginator(数据集,每一页数据数)
paginator = Paginator(students, per_page)
属性
count对象总数
num_pages:页面总数
page_range: 页码列表,从1开始 *
方法:
page(整数): 获得一个page对象
该方法的返回值类型是Page
常见错误:
InvalidPage:page()传递无效页码
PageNotAnInteger:page()传递的不是整数
Empty:page()传递的值有效,但是没有数据
Page(具体哪一页)
对象获得
通过Paginator的page()方法获得
属性
object_list:当前页面上所有的数据对象
number:当前页的页码值
paginator:当前page关联的Paginator对象
方法
has_next():判断是否有下一页
has_previous():判断是否有上一页
has_other_pages():判断是否有上一页或下一页
next_page_number():返回下一页的页码
previous_page_number():返回上一页的页码
len():返回当前页的数据的个数
应用场景:
paginator对象:适用于页码的遍历 eg 上一页 xxx 下一页
page对象:适用于是否有上一页 、下一页或上一页页码、下一页页码
创建子路由:
url(r'^testDjangoPage/',views.testDjangoPage),
生成视图函数:
Paginator对象:
def testDjangoPage(request):
pag = request.GET.get('page',1)
per_page = request.GET.get('per_page',5)
student_list = Student.objects.all()
# Paginator的参数有object_list,per_page
paginator = Paginator(student_list,per_page)
# 适用于上一页和下一页之间的页码
for i in paginator.page_range:
print(i)
# 总页数
print(paginator.num_pages)
# 一共有多少条(不常用)
print(paginator.count)
return HttpResponse('django的分页')
运行结果:
page对象:
def testDjangoPage(request):
pag = request.GET.get('page',1)
per_page = request.GET.get('per_page',5)
student_list = Student.objects.all()
# Paginator的参数有object_list,per_page
paginator = Paginator(student_list,per_page)
# page方法返回了一个Page对象
pa = paginator.page(pag)
# 判断是否有上一页
print(pa.has_previous())
# 判断是否有下一页
print(pa.has_next())
# 判断是否有上一页页码
print(pa.previous_page_number())
# 判断是否有下一页页码
print(pa.next_page_number())
return HttpResponse('django的分页')
运行结果:
四.富文本
富文本:Rich Text Format(RTF),是有微软开发的跨平台文档格式,大多数的文字处理软件都能读取和保存RTF文档,其实就是可以添加样式的文档,和HTML有很多相似的地方,通常在写论坛,博客时使用的一种带样式的文本插件。
安装插件:
pip install django-tinymce==2.8.0
基本使用:
在instatlled_app中添加:tinymce
初始化:
在settings中注册tinymce应用
设置默认的配置文件
TINYMCE_DEFAULT_CONFIG = {
'theme':'advanced',
'width':800,
'height':600,
}
创建模型:
from tinymce.models import HTMLField
class Blog(models.Model):
sBlog = HTMLField()
创建子路由:
url(r'^testRTF/',views.testRTF),
生成视图函数:
def testRTF(request):
if request.method == 'GET':
return render(request,'testRTF.html')
模板文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="/static/tiny_mce/tiny_mce.js"></script>
<script type="text/javascript">
tinyMCE.init({
"mode": "textareas",
"theme": "advanced",
"width": 800,
"height": 600
})
</script>
</head>
<body>
测试富文本
<form action="" method="post">
<textarea></textarea>
</form>
</body>
</html>
运行结果: