Django为开发者提供了常见的Web应用程序,如会话控制、高速缓存、CSRF防护、消息提示和分页功能。内置的Web应用程序大大优化了网站性能,并且完善了安全防护机制,而且也提高了开发者的开发效率。
一、会话控制
Django内置的会话控制简称为Session,可为访问者提供基础的数据存储。数据主要存储在服务器上,并且网站的任意站点都能使用会话数据。当用户第一次访问网站时,网站的服务器将自动创建一个Session对象,该Session对象相当于该用户在网站的一个身份凭证,而且Session能存储该用户的数据信息。当用户在网站的页面之间跳转时,存储在Session对象中的数据不会丢失,只有Session过期或被清理时,服务器才将Session中存储的数据清空并终止该Sesion。
讲解Django的Sesion之前,需要理解 Session和Cookie之间的关系。
- Session是存储在服务器端,Cookie是存储在客户端,所以Session的安全性比Cookie高。
- 当获取Session的数据信息时,首先从会话Cookie里获取sessionid,然后根据sessionid找到服务器相应的Session。
- Session是存放在服务器的内存中的,所以Session里的数据不断增加会造成服务器的负担。因此,重要的信息才会选择存放在Session中,而一些次要的信息选择存放在客户端的Cookie。
在创建Django项目时,Django已默认启用Session功能,Session是通过Django的中间件实现的,可以在配置文件settings.py中找到相关信息,如下图所示:
当用户访问网站时,用户请求首先经过中间件的处理,而中间件SessionMiddleware会判断当前请求用户的身份是否存在,并根据判断情况执行相应的程序处理。中间件SessionMiddleware相当于用户请求接收器,根据请求信息做出相应的调度,而程序的执行是由配置文件settings.py中的INSTALLED_APPS完成的。而执行Session的处理由django.contrib.sessions完成。
django.contrib.sessions 默认使用数据库存储Session信息,发生数据迁移时,在数据库中可以看到数据表django_session。
当多个用户同时访问网站时,中间件SessionMiddleware分别获取客户端Cookie的sessionid 和数据表django_session的session_key进行匹配验证,从而区分每个用户的身份信息,保证每个用户存放在Session的数据信息不会发生絮乱。
从上述内容可以知道,Session默认使用数据库保存相关的数据信息,如果想变更Session的保存方式,我们可以在settings.py中添加配置信息SESSION_ENGINE,该配置可以指定Session的保存方式。Django提供5种Session的保存方式,如下所示: - #数据库保存方式,Django默认的保存方式,因此使用该方法无须在settings.py中设置SESSTON_ENGINE=‘django.contrib.sessions.backends.db’
- #以文件形式保存
SESSION ENGINE=‘django.contrib.sessions.backends.file’
#使用文本保存可设置文件保存路径,/MyDjango代表将文本保存在项目MyDjango的根目录SESSION_FILE_PATH=’/MyDjango’ - 以缓存形式保存
SESSION_FILE_PATH=‘django.contrib.sessions.backends.cache’
#设置缓存名,默认是内存缓存方式,此处的设置与缓存机制的设置相关
SESSION_CACHE_ALIAS=‘default’ - #以数据库+缓存形式保存
SESSION_ENGINE=‘django.contrib.sessions.backends.cached_db’ - #以cookies形式保存
SESSION_ENGINE=‘django.contrib.sessions.backends.signed_cookies’
了解Session的原理和相关配置后,最后讲解 Session的操作。Session的数据类型可理解为Python的字典类型,主要在视图函数中执行读写操作,并且从用户请求对象中获取,即来自视图函数的参数request。Session的读写如下:
#request为视图函数的参数request
#获取k1的值,若k1不存在则会报错
request.session['k1']
#获取k1的值,若k1不存在则为空值
#get和setdefault所实现的功能是一致
request.session.get['k1', '']
request.session.setdefault('k1','')
#设置Session的值,键为k1,值为123
request.session['k1']=123
#删除Session中k1的数据
del request.session['k1']
#删除整个Session
request.session.clear()
#获取Session的键
request.session.keys()
#获取Session的值
request.session.values()
#获取 Session的 session_key,即数据表django_session的字段session_key
request.session.session_key
实际中我们如何使用Session呢?比如Session实现购物车功能。
购物车功能的实现思路如下:
- 当用户单击“立即抢购”按钮时,该按钮会触发一个GET请求并将商品信息作为请求参数传给视图函数处理。
- 视图函数接收GET请求后,将请求参数保存在Session中并返回首页,即刷新当前网页。
- 当用户进入购物车页面时,程序获取Session里的数据,并将数据展示在购物车列表中。
- 用户在购物车页面单击某个商品的“移除商品”按钮时,程序在Session中删除该商品信息。
二、缓存机制
现在的网站都以动态网站为主,当网站访问量过大时,网站的响应速度必然会降低,这就有可能出现卡死的情况。为了解决网站访问量过大的问题,可以在网站上使用缓存机制。
缓存是将一个请求的响应内容保存到内存或者高速缓存系统(Memcache)中,若某个时间内再次发生同一个请求,则不再去执行请求响应过程,而是直接从内存或者高速缓存系统中获取该请求的响应内容返回给用户。
Django提供5种不同的缓存方式,每种缓存方式说明如下:
- Memcached:一个高性能的分布式内存对象缓存系统,用于动态网站,以减轻数据库负载。通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高网站的响应速度。使用Memcached需要安装系统服务器,Django通过python-memcached 或pylibmc模块调用Memcached系统服务器,实现缓存读写操作,适合超大型网站使用。
- 数据库缓存:缓存信息存储在网站数据库的缓存表中,缓存表可以在项目的配置文件中配置,适合大中型网站使用。
- 文件系统缓存:缓存信息以文本文件格式保存,适合中小型网站使用。
- 本地内存缓存:Django默认的缓存保存方式,将缓存存放在项目所在系统的内存中,只适用于项目开发测试。
- 虚拟缓存:Django内置的虚拟缓存,实际上只提供缓存接口,并不能存储缓存数据,只适用于项目开发测试。
每种缓存方式都有一定的适用范围,因此选择缓存方式需要结合网站的实际情况而定。若在项目中使用缓存机制,则首先需要在配置文件settings.py中设置缓存的相关配置。每种缓存方式的配置如下:
# Memcached配置
# BACKEND用于配置缓存引擎,LOCATION是Memcached服务器的IP地址
# django.core.cache.backends.memcached.MemcachedCache 使用python-memcached模块连接Memcached
# django.core.cache.backends.memcached.PyLibMCCache 使用pylibmc 模块连接Memcached
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
# 'BACKEND':'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': [
'1172.19.26.240:112111',
'1172.19.26.242: 112111',
]
}
}
#数据库缓存配置
#BACKEND用于配置缓存引擎,LOCATION用于数据表的命名
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}
}
#文件系统缓存
#BACKEND用于配置缓存引擎,LOCATION是文件保存的路径
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': 'e:/django cache',
}
}
#本地内存缓存
#BACKEND用于配置缓存引擎,LOCATION对存储器命名,用于识别单个存储器
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
#虚拟缓存
#BACKEND用于配置缓存引擎
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
'LOCATION': 'unique-snowflake',
}
}
上述缓存配置仅仅是基本配置,也就是说缓存参数BACKEND和LOCATION是必须配置的,其余的配置参数可自行选择。我们以数据库缓存配置为例,完整的缓存配置如下:
CACHES = {# 默认缓存数据表
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
# TIMEOUT设置缓存的生命周期,以秒为单位,若为None,则永不过期
'TIMEOUT': 60,
'OPTIONS': {
# MAX_ENTRIES代表最大缓存记录的数量
'MAX_ENTRIES': 1000,
# 当缓存到达最大数量之后,设置剔除缓存的数量
'CULL_FREQUENCY': 3,
}
},
# 设置多个缓存数据表
'MyDjango': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'MyDjango_cache_table',
}
}
在配置文件完成数据库缓存配置后,下一步是在数据库中创建缓存数据表,缓存数据表的生成依赖于配置文件中DATABASES的配置信息。需要注意的是,如果DATABASES配置了多个数据库,那么缓存数据表默认在DATABASES的default的数据库中生成。在PyCharm的Terminal中输入python manage.py createcachetable 指令创建缓存数据表,然后在数据库中查看缓存数据表,如图所示。
在项目中完成缓存的配置,创建缓存数据表之后,就可以在项目中使用缓存了。
缓存的使用方式有4种,主要根据使用对象的不同来划分,具体说明如下。
- 全站缓存:将缓存作用于整个网站的全部页面。一般情况下不采用这种方式实现,如果网站规模较大,缓存数据相应增多,就会对数据库或Memcached造成极大的压力。
- 视图缓存:当用户发送请求时,若该请求的视图函数已生成缓存,则返回缓存数据,这样可省去视图函数处理请求信息的时间和资源。
- 路由(URL)缓存:其作用与视图缓存相同,但两者是有区别的,例如有两个URL同时指向一个视图函数,分别访问这两个URL时,路由缓存会判断URL是否生成缓存而决定是否执行视图函数。
- 模板缓存:对模板某部分的数据设置缓存,常用于模板内容变动较少的情况,如HTML的<head>标签,设置缓存能省去模板引擎解析生成HTML页面的时间。
全站缓存作用于整个网站,当用户向网站发送请求时,首先经过Django的中间件进行处理。因此,使用全站缓存应在Django的中间件中配置,但实际中为了减少数据库或者Memcached的压力,并不会使用全站缓存,所以这里的配置略过。
视图缓存是将视图函数执行过程生成缓存数据,主要以装饰器的形式来实现。装饰器有三个参数,分别是timeout、cache和key_prefix,参数timeout是必选参数,其余两个参数都是可选参数,参数的含义与视图缓存的参数一致。其代码如下:
# App(index)的views.py
# 导入cache_page
from django.views.decorators.cache import cache_page
# 参数key_prefix用于同一个Django项目多个站点之间的共享缓存
@cache_page(timeout=10, cache='MyDjango', key_prefix='MyDjangoView')
@login_required(login_url='/user/login.html')
def ShoppingCarView(request):
pass
return render(request, 'Shoppingcar.html', locals())
在浏览器上访问购物车页面,打开数据库查看缓存数据表mydjango_cache_table的视图缓存信息。
路由缓存主要在路由配置urls.py中实现,路由缓存cache_page有三个参数,分别是timeout、cache和key_prefix,参数timeout是必选参数,其余两个参数都是可选参数,参数的含义与视图缓存的参数一致。实现代码如下:
from django.urls import path
from.import views
from django.views.decorators.cache import cache_page
urlpatterns = [
#首页的URL
path('', cache_page(10, 'MyDjango', 'MyDjangoURL')(views.index), name='index'),
#购物车
path('shoppingCar.html', views.ShoppingcarView, name='ShoppingCar')
]
在浏览器上访问购物车页面,打开数据库查看缓存数据表mydjango_cache_table的视图缓存信息。
三、CSRF防护
CSRF(Cross-Site Request Forgery,跨站请求伪造)也称为One Click Attack或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用,窃取网站的用户信息来制造恶意请求。
Django为了防护这类攻击,在用户提交表单时,表单会自动加入csrftoken的隐含值,这个隐含值会与网站后台保存的csrftoken进行匹配,只有匹配成功,网站才会处理表单数据。这种防护机制称为CSRF防护,原理如下:
- 在用户访问网站时,Django在网页的表单中生成一个隐含字段csrftoken,这个值是在服务器端随机生成的。
- 当用户提交表单时,服务器校验表单的csrftoken是否和自己保存的csrftoken一致,用来判断当前请求是否合法。
- 如果用户被CSRF攻击并从其他地方发送攻击请求,由于其他地方不可能知道隐藏的csrftoken信息,因此导致网站后台校验csrftoken失败,攻击就被成功防御。
在Django中使用CSRF防护功能,首先在配置文件settings.py中设置防护功能的配置信息。功能的开启由配置文件的中间件django.middleware.csrf.CsrfViewMiddleware实现,在创建项目时已默认开启,如图所示。
CSRF防护只作用于POST请求,并不防护GET请求,因为GET请求以只读形式访问网站资源,并不破坏和篡改网站数据。以MyDjango为例,在模板user.html的表单<form>标签中加入内置标签csrf_token即可实现CSRF防护,代码如下:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>用户注册</title>
<link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css">
</head>
<body>
<div class="flex-center">
<div class="container">
<div class="flex-center">
<div class="unit-1-2 unit-1-on-mobile">
<h1>MyDjango Auth</h1>
{%if tips%}
<div>{{tips}}</div>
{%endif%}
<form class="form" action="" method="post">
{%csrf_token%}
<div>用户名:{{user.username}}</div>
<div>邮箱:{{user.email}}</div>
<div>手机号码:{{user.mobile}}</div>
<div>QQ号码:{{user.qq}}</div>
<div>微信号码:{{user.weChat}}</div>
<div>密码:{{user.password1}}</div>
<div>确认密码:{{user.password2}}</div>
<button type="submit" class="btn btn-primary btn-block">注册</button>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
启动运行MyDjango,在浏览器中打开用户登录页面,然后查看页面的源码,可以发现表单新增隐藏域,隐藏域是由模板语法(%csrf_token%}所生成的,网站生成的csrftoken都会记录在隐藏域的value属性中。当用户每次提交表单时,csrftoken都会随之变化,如图所示。
如果想要取消表单的CSRF防护,可以在模板上删除{%csrf_token%},并且在相应的视图函数中添加装饰器@csrf_exempt。如果只是在模板上删除{%csrf_token%},并没有在相应的视图函数中设置过滤器@csrf_exempt,那么当用户提交表单时,程序因CSRF验证失败而抛出403异常的页面。
最后还有一种比较特殊的情况,如果在配置文件settings.py中删除中间件CsrfViewMiddleware,这样使整个网站都取消CSRF防护。在全站没有CSRF防护的情况下,又想对某些请求设置CSRF防护,那么在模板上添加模板语法%csrf_token%},然后在相应的视图函数中添加装饰器@csrf_protect即可实现。
四、消息提示
在网页应用中,当处理完表单或完成其他信息输入后,网站会有相应的操作提示。Django有内置消息提示功能供开发者直接使用,信息提示功能由中间件SessionMiddleware、MessageMiddleware 和INSTALLED_APPS的 django.contrib.messages共同实现。在创建Django项目时,消息提示功能已默认开启,如图所示。
消息提示必须依赖中间件SessionMiddleware,因为消息提示的引擎默认是SessionStorage,而SessionStorage是在Session的基础上实现的,同时说明了中间件SessionMiddleware为什么设置在MessageMiddleware的前面。
使用信息提示功能之前,需要了解消息提示的类型,Django提供了5种消息类型,说明如表所示。
若想在开发中使用消息提示,首先在视图函数中生成相关的信息内容,然后在模板中将信息内容展现在网页上。因此,在index中定义相关的URL地址和相应的视图函数,代码如下:
#index的urls.py
from django.urls import path, re_path
from.import views
urlpatterns = [
# path('', views.index),
# #添加带有字符类型、整型和slug的URL
# path('<year>/<int:month>/<slug:day>', views.mydate),
# re_path('(?P<year>[0-9]{4}).html', views.myyear, name='myyear'),
# re_path('dict/(?P<year>[0-9]{4}).html', views.myyear_dict, {'month': '05'}, name='myyear_dict'),
# path('download.html', views.download),
# path('login.html', views.login),
path('message.html', views.messageView, name='message'),
]
#index的views.py
from django.shortcuts import render, redirect
from django.contrib import messages
# from django.template import RequestContext
def messageView(request):
# 信息添加方法一
messages.info(request, '信息提示')
messages.success(request, '信息正确')
messages.warning(request, '信息警告')
messages.error(request, '信息错误')
# 信息添加方法二
messages.add_message(request, messages.INFO, '信息提示')
return render(request, 'message.html', locals())
# message.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>信息提示</title>
</head>
<body>
{% if messages %}
<ul>
{% for message in messages %}
<!-- {#message.tags代表信息类型#}-->
<li {% if message.tags %} class="{{message.tags}}"{% endif %}>{{message}}</li>
{%endfor%}
</ul>
{% else %}
<script>alert('暂无信息');</script>
{%endif%}
</body>
</html>
五、分页功能
在网页上浏览数据的时候,数据列表的下方都能看到翻页功能,而且每一页的数据都不相同。比如在淘宝上搜索某商品的关键字,淘宝会根据用户提供的关键字返回符合条件的商品信息,并且对这些商品信息进行分页处理,用户可以在商品信息的下方单击相应的页数按钮查看。
如果要实现数据的分页功能,需要考虑多方面因素:
- 当前用户访问的页数是否存在上(下)一页。
- 访问的页数是否超出页数上限。
- 数据如何按页截取,如何设置每页的数据量。
Django已为开发者提供了内置的分页功能,开发者无须自己实现数据分页功能,只需调用Django内置分页功能的函数即可实现。简单来说,django的分页功能分两部分,一是分页对象p,另一个是具体的分页对象page,两者说明如下:
(1)分页对象p:由模块Paginator实例化生成。在Paginator实例化时,需要传入参数object和per_page,参数object是待分页的数据对象,参数per_page用于设置每页的数据量。对象p提供表如下函数。
(2)某分页对象page:由对象p使用函数page所生成的对象。page提供如下的函数。
了解了方法之后,使用之前的例子,用例子说明分页功能的实现。
首先,数据库有产品表即之前的index_product数据表并确保数据表中有数据,然后在index的模板中添加pagination.html,完成之后在index的url.py、views.py、和pagination.html分别添加如下代码:
# url.py
from django.urls import path, re_path
from.import views
urlpatterns = [
path('', views.index),
path('message.html', views.messageView, name='message'),
path('pagination/<int:page>.html', views.paginationView, name='pagination')
]
# views.py
from index.models import Product
from django.shortcuts import render, redirect
from django.contrib import messages
from django.template import RequestContext
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def messageView(request):
# 信息添加方法一
messages.info(request, '信息提示')
messages.success(request, '信息正确')
messages.warning(request, '信息警告')
messages.error(request, '信息错误')
# 信息添加方法二
messages.add_message(request, messages.INFO, '信息提示')
return render(request, 'message.html', locals())
def paginationView(request, page):
# 获取数据表product的全部数据
Product_list = Product.objects.all()
# 设置每一页的数据量为3
paginator = Paginator(Product_list, 3)
try:
pageInfo = paginator.page(page)
except PageNotAnInteger:
# 如果参数page的数据类型不是整型,就返回第一页数据
pageInfo = paginator.page(1)
except EmptyPage:
# 若用户访问的页数大于实际页数,则返回最后一页的数据
pageInfo = paginator.page(paginator.num_pages)
return render(request, 'pagination.html', locals())
# pagination.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>分页功能</title>
</head>
<body>
<div class="wrapper clearfix" id="wrapper">
<div class="mod_songlist">
<ul class="songlist__header">
<li class="songlist__header_name">产品名称</li>
<li class="songlist__header_author">重量</li>
<li class="songlist__header_album">尺寸</li>
<li class="songlist__header_other">产品类型</li>
</ul>
<ul class="songlist__list">
{#列出当前分页所对应的数据内容#}
{%for item in pageInfo%}
<li class="js_songlist__child" mid="1425301" ix="6">
<div class="songlist__item">
<div class="songlist__songname">{{item.name}}</div>
<div class="songlist__artist">{{item.weight}}</div>
<div class="songlist__album">{{item.size}}</div>
<div class="songlist__other">{{item.type}}</div>
</div>
</li>
{%endfor%}
</ul>
{#分页导航#}
<div class="page-box">
<div class="pagebar" id="pageBar">
{#上一页的URL地址#}
{%if pageInfo.has_previous%}
<a href="{% url 'pagination' pageInfo.previous_page_number%}" class="prev"><i></i>上一页</a>
{%endif%}
{#列出所有的URL地址#}
{%for num in pageInfo.paginator.page_range %}
{%if num == pageInfo.number%}
<span class="sel">{{pageInfo.number}}</span>
{%else%}
<a href="{%url 'pagination' num% }"target="_self">{{num}}</a>
{%endif%}
{% endfor %}
{#下一页的URL地址#}
{%if pageInfo.has_next %}
<a href="{% url 'pagination' pageInfo.next_page_number%}" class="next">下一页<i></i></a>
{% endif %}
</div>
</div>
</div><!--end mod_songlist-->
</div><!--end wrapper-->
</body>
</html>
完成urls.py、views.py和pagination.html的代码编写后,最后测试功能是否正常运行。启动项目并在浏览器上访问http://127.0.0.1:8000/pagination/1.html,单击分页导航时,程序会自动跳转到相应的URL地址并返回对应的数据信息,运行结果如图10-20所示。
这里并没有添加样式表,所以这里的效果样式是基础的网页样式。