第十章 常用的Web应用程序
Django为开发者提供了常见的Web应用程序,如会话控制、高速缓存、CSRF防护、消息提示和分页功能。内置的Web应用程序大大优化了网站性能,并且完善了安全防护机制,而且也提高了开发者的开发效率。
10.1 会话控制
Django内置的会话控制简称为Session,可为访问者提供基础的数据存储。数据主要存储在服务器上,并且网站的任意站点都能使用会话数据。当用户第一次访问网站时,网站的服务器将自动创建一个Session对象,该Session对象相当于该用户在网站的一个身份凭证,而且Session能存储该用户的数据信息。当用户在网站的页面之间跳转时,存储在Session对象中的数据不会丢失,只有Session过期或被清理时,服务器才将Session中存储的数据清空并终止该Session。
讲解Django的Session之前,需要理解Session和Cookie之间的关系。
1、Session是存储在服务器端,Cookie是存储在客户端,所以Session的安全性比Cookie高。
2、当获取Session的数据信息时,首先从会话Cookie里获取sessionid,然后根据sessionid找到服务器相应的Session。
3、Session是存放在服务器的内存中的,所以Session里的数据不断增加会造成服务器的负担。因此,重要的信息才会选择存放在Session中,而一些次要的信息选择存放在客户端的Cookie。
在创建Django项目时,Django已默认启用Session功能,Session是通过Django的中间件实现的,可以在配置文件settings.py中找到相关信息,如下图:
Session功能配置
当用户访问网站时,用户请求首先经过中间件的处理,而中间件SessionMiddleware会判断当前请求用户的身份是否存在,并根据判断情况执行相应的程序处理。中间件SessionMiddleware相当于用户请求接收器,根据请求信息做出相应的调度,而程序的执行时由配置文件settings.py中的INSTALLED_APPS完成的。而执行Session的处理由django.contrib.sessions完成,其配置信息如图所示:
Session功能处理
django.contrib.sessions默认使用数据库存储Session信息,发送数据迁移时,在数据库中可以看到数据表django_session,如下图:
数据表django_session
当多个用户同时访问网站时,中间件SessionMiddleware分别获取客户端Cookie的sessionid和数据表django_session的session_key进行匹配验证,从而区分每个用户的身份信息,保证每个用户存放在Session的数据信息不会发生絮乱,如下图:
Session验证机制
从上述内容可以知道,Session默认使用数据库保存相关的数据信息,如果想变更Session的保存方式,我们可以在settings.py中添加配置信息SESSION_ENGINE,该配置可以指定Session的保存方式。Django提供5种Session的保存方式,如下所示:
#数据库保存方式,Django默认的保存方式,因此使用该方法无须在settings.py中设置 SESSION_ENGINE = 'django.contrib.sessions.backends.db' #以文件形式保存 SESSION_ENGINE = 'django.contrib.sessions.backends.file' #使用文本保存可设置文件保存路径,/MyDjango代表将文本保存在项目MyDjango的根目录 SESSION_FILE_PATH = '/MyDjango' #以缓冲形式保存 SESSION_ENGINE = 'django.contrib.sessions.backends.cache' #设置缓冲名,默认是内存缓存方式,此处的设置与缓存机制的设置相关 SESSION_CACHE_ALIAS = 'default' #以数据库+缓存形式保存 SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' #以cookie形式保存 SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
SESSION_ENGINE用于配置服务器Session的保存方式,如果想要配置Cookie的Session的保存方式,可以在settings.py中添加如下表所示的配置:
配置信息 | 说明 |
SESSION_COOKIE_NAME = "sessionid" | 设置Cookie里Session的键,默认值为sessionid |
SESSION_COOKIE_PATH = "/" | 设置Cookie里Session的保存域名,默认值为"/" |
SESSION_COOKIE_DOMAID = None | 设置Cookie里Session的保存域名,默认值为None |
SESSION_COOKIE_SECURE = False | 是否使用HTTPS传输Cookie,默认值为False |
SESSION_COOKIE_HTTPONLY = True | 是否只支持HTTP传输,默认值为True |
SESSION_COOKIE_AGE = 1209600 | 设置Cookie里Session的有效期,默认时间2周 |
SESSION_EXPIRE_AT_BROWSER_CLOSE = False | 是否关闭浏览器使得Session过期,默认值为False |
SESSION_SAVE_EVERY_REQUEST = False | 是否每次发送后保存Session,默认值为False |
settings.py需要添加的配置
了解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。以MyDjango为例,使用Session实现购物车功能,在首页找到4个商品信息,每个商品有"立即抢购"按钮,如下图:
购物车功能的实现思路如下:
1、当用户单击"立即抢购"按钮时,该按钮会触发一个GET请求并将商品信息作为请求参数传给视图函数处理。
2、视图函数接收GET请求后,将请求参数保存在Session中并返回首页,即刷新当前网页。
3、当用户进入购物车页面时,程序获取Session里的数据,并将数据展示在购物车列表中。
4、用户在购物车页面单击某个商品的"移除商品"按钮时,程序在Session中删除该商品信息。
在实现购物车功能之前,我们对MyDjango的目录架构进行细微的调整,在index中新增模板ShoppingCar.html以及模板文件相应的JS和CSS文件,如下图:
调整项目架构
购物车功能由index的urls.py、views.py、index.html和ShoppingCar.html共同实现,网站的首页主要实现Session的写入,购物车页面实现Session的读取和删除操作。首先编写urls.py和views.py的代码,分别对首页和购物车页面配置URL地址和相应的视图函数,代码如下:
#index/urls.py from django.urls import path from . import views urlpatterns = [ # 首页的URL path('', views.index, name='index'), #购物车 path('ShoppingCar.html', views.ShoppingCarView, name='ShoppingCar') ] #index/views.py from django.shortcuts import render,redirect from django.contrib.auth.decorators import login_required, permission_required # 使用login_required和permission_required分别对用户登录验证和用户权限验证 @login_required(login_url='/user/login.html') @permission_required(perm='index.visit_Product', login_url='/user/login.html') def index(request): # 获取GET请求参数 product = request.GET.get('product', '') price = request.GET.get('price', '') if product: # 获取存储在Session的数据,如果Session不存在product_info,则返回一个空列表 product_list = request.session.get('product_info', []) # 判断当前请求参数是否已存储在Session if not product in product_list: # 将当前参数存储在列表product_list product_list.append({'price': price, 'product': product}) # 更新存储在Session的数据 request.session['product_info'] = product_list return redirect('/') return render(request, 'index.html', locals()) # 购物车 #视图函数ShoppihngCarView @login_required(login_url='/user/login.html') def ShoppingCarView(request): # 获取存储在Session的数据,如果Session不存在product_info,则返回一个空列表 product_list = request.session.get('product_info', []) # 获取GET请求参数,如果没有请求参数,返回空值 del_product = request.GET.get('product', '') # 判断是否为空,若非空,删除Session里的商品信息 if del_product: # 删除Session里某个商品数据 for i in product_list: if i['product'] == del_product: product_list.remove(i) # 将删除后的数据覆盖原来的Session request.session['product_info'] = product_list return redirect('/ShoppingCar.html') return render(request, 'ShoppingCar.html', locals())
函数index的功能实现如下:
1、首先获取GET请求的请求参数,若当前请求没有请求参数,则变量product和price为空,否则分别将请求参数赋予变量product和price。
2、获取当前用户Session的product_info数据信息并赋值给变量product_list。
3、判断product_list是否已存有product和price,若不存在,则将product和price写入product_list。
4、最后produtct_list重新写入Session并重定向首页地址。
函数ShoppingCarView的功能实现如下:
1、首先获取当前用户Session的product_info数据信息。
2、获取当前请求参数product并赋值给变量del_product,若不存在请求参数,则变量del_product为空值。
3、如果变量del_product不为空,而且product_list已存在变量del_product,那么在product_list中删除变量del_product。
4、最后将product_list重新写入Session的product_info,并重定向购物车的网页地址。
从函数index和ShoppingCarView的功能实现过程中可以发现,两者对Session的处理有相似的地方;首先从Session中获取数据内容,然后对数据内容进行读写处理,最后将处理后的数据内容重新写入Session。
当视图函数处理用户请求后,再由模板生成相应的网页返回给用户。我们分别对模板index.html和ShoppingCar.html进行修改,由于模板文件代码较长,此处只展示修改的代码,完整的代码可在本书下载资源中查看,代码修改如下:
![](http://img.e-com-net.com/image/info8/b8d97b5613f94ed2ba791cad57d0b2ed.gif)
![](http://img.e-com-net.com/image/info8/2f88dd3f1cd145f59c0e47b51acdbd4b.gif)
#设置购物车的网页地址 <div id="top_main"> <div class="lf top_main_left"> <img src="{% static "img/newLogo.png" %}" alt="" /> div> <div class="lf search_box"> <div class="search"> <input type="text" class="text" id="txtSearch"/> <input type="button" class="button" value="搜索"/> div> <div class="hot_words"> <span>热门搜索:span> <a href="#"> 5Ca> <a href="#"> HUAWEI P9a> <a href="#"> 5Xa> <a href="#">荣耀7a> <a href="#"> Mate 8a> div> div> <div class="lf" id="my_hw">我的商城div> <div class="lf" id="settle_up"><a href="{% url 'ShoppingCar' %}">我的购物车a>div> div> <li class="channel-pro-item"> <div class="p-img"> <img src="{% static "img/phone01.png" %}" alt=""/> div> <div class="p-name lf"><a href="#">HUAWEI P9 Plusa>div> <div class="p-shining"> <div class="p-slogan">一上手,就爱不释手div> <div class="p-promotions">5月6日10:08 火爆开售div> div> <div class="p-price"> <em>¥em> <span>3988span> div> <div class="p-button lf"> <a href="{% url 'index' %}?product=HUAWEI P9 Plus&price=3988">立即抢购a> div> li> <li class="channel-pro-item" style="background-color:#E2F9FB"> <div class="p-img"> <img src="{% static "img/phone02.png" %}" alt=""/> div> <div class="p-name lf"><a href="#">荣耀畅玩5Ca>div> <div class="p-shining"> <div class="p-slogan">16纳米8核芯千元普及风暴div> <div class="p-promotions">5月10日10:08震撼开售div> div> <div class="p-price"> <em>¥em> <span>899span> div> <div class="p-button lf"> <a href="{% url 'index' %}?product=荣耀畅玩5C&price=899">立即抢购a> div> li>
![](http://img.e-com-net.com/image/info8/b8d97b5613f94ed2ba791cad57d0b2ed.gif)
![](http://img.e-com-net.com/image/info8/2f88dd3f1cd145f59c0e47b51acdbd4b.gif)
#模板ShoppingCar.html <div class="order_content"> {% for info in product_list %} <ul class="order_lists"> <li class="list_chk"> <input type="checkbox" id="checkbox_4" class="son_check"> <label for="checkbox_4">label> li> <li class="list_con"> <div class="list_text"><a href="javascript:;">{{ info.product }}a>div> li> <li class="list_price"> <p class="price">¥{{ info.price }}p> li> <li class="list_amount"> <div class="amount_box"> <a href="javascript:;" class="reduce reSty">-a> <input type="text" value="1" class="sum"> <a href="javascript:;" class="plus">+a> div> li> <li class="list_sum"> <p class="sum_price">¥{{ info.price }}p> li> <li class="list_op"> <p class="del"> <a href="{% url 'ShoppingCar' %}?product={{ info.product }}" class="delBtn">移除商品a> p> li> ul> {% endfor %} div>
至此,我们已完成index的urls.py、view.py、index.html、和ShoppingCar.html的代码编写。最后启动MyDjango测试购物车功能,测试方式如下:
1、在浏览器中访问http://127.0.0.1:8000/user/login.html?next=/,输入用户信息完成用户登录。
2、在首页的商品信息中单击"立即抢购"按钮,同一商品的"立即抢购"按钮可以重复点击。
3、回到首页顶部,单击“我的购物车”,进入购物车页面,查看购物车中的商品信息是否有重复。
4、在购物车页面单击某商品的"移除商品"按钮并刷新网页,观察购物车总的商品是否已删除。
在上述例子中,Django只实现了商品列表生成、"移除商品"按钮和设置首页地址,其余的功能都是由前端的JavaScript实现的。购物车页面如下图:
10.2 缓存机制
缓存是将一个请求的响应内容保存到内存或者高速缓存系统(Memcache)中,若某个时间内再次发生同一个请求,则不再去执行请求响应过程,而是直接从内存或者告诉缓存系统中获取该请求的响应内容返回给用户。
Django提供5种不同的缓存方式,每种缓存方式说明如下:
1、Memcached:一个高性能的分布式内存对象缓存系统,用于动态网站,以减轻数据库负载。通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高网站的响应速度。使用Memcached需要安装系统服务器,Django通过python-memcached或pylibmc模块调用Memcached系统服务器,实现缓存读写操作,适合超大型网站使用。
2、数据库缓存:缓存信息存储在网站数据库的缓存表中,缓存表可以在项目的配置文件中配置,适合大中型网站使用。
3、文件系统缓存:缓存信息以文本文件格式保存,适合中小型网站使用。
4、本地内存缓存:Django默认的缓存保存方式,将缓存存放在项目所在系统的内存中,只适用于项目开发测试。
5、虚拟缓存:Django内置的虚拟缓存,实际上只提供缓存接口,并不能存储缓存数据,只适用于项目开发测试。
每种缓存方式都有一定的使用范围,因此选择缓存方式需要结合网站的实际情况而定,若在项目中使用缓存机制,则首先需要在配置文件settings.py中设置缓存的相关配置。每种缓存方式的配置如下:
#Memcached配置 #BACKEND用户配置缓存引擎,LOCATION是Memcached服务器的IP地址 #django.core.cache.backends.memcached.MemcachedCache使用python-memcached模块连接Memcached #django.core.cache.backends.memcached.PyLibMCCache使用pylibmc模块连接Mememcached CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '127.0.0.1:11211', '127.0.0.2:11211', ] } } #数据库缓存配置 #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', } }
上述缓存配置仅仅是基本配置,也就是说缓存参数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种,主要根据使用对象的不同来划分,具体说明如下:
1、全站缓存:将缓存作用于整个网站的全部页面。一般情况下不采用这种方式实现,如果网站规模较大,缓存数据相应增多,就会对数据库或Memcached造成极大的压力。
2、视图缓存:当用户发送请求时,若该请求的视图函数已生成缓存,则返回缓存数据,这样可省去视图函数处理请求信息的时间和资源。
3、路由(URL)资源:其作用与视图缓存相同,但两者是有区别的,例如有两个URL同时指向一个视图函数,分别访问这两个URL时,路由缓存会判断URL是否生成缓存而决定是否执行视图函数。
4、模板缓存:对模板某部分的数据设置缓存,常用于模板内容变动较少的情况,如HTML的
标签,设置缓存能省去模板引擎解析生成HTML页面的时间。全站缓存作用于整个网站,当用户向网站发送请求时,首先经过Django的中间件进行处理。因此,使用全站缓存应在Django的中间件中配置,配置信息如下:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', # 使用中文 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', #配置全站缓存 'django.middleware.cache.FetchFromCacheMiddleware', ]
#设置缓存的生命周期
CACHE__MIDDLEWARE_SECONDS = 15
#设置缓存数据保存在数据表my_cache_table中,属性值default来自于缓存配置CACHES的default属性
CACHE_MIDDLEWARE_ALLAS = 'default'
#设置缓存表字段cache_key的值,用于同一个Django项目多个站点之间的共享缓存
CACHE_MIDDLEWARE_KEY_PREFIX = 'MyDjango'
全站缓存配置说明如下:
1、在中间件的最上方和末端分别添加中间件UpdateCacheMiddleware和FetchFromCacheMiddleware。
2、CACHE__MIDDLEWARE_SECONDS设置全站缓存的生命周期。若在缓存配置CACHES中设置TIMEOUT属性,则程序优先选择CACHE__MIDDLEWARE_SECONDS的设置。
3、CACHE_MIDDLEWARE_ALIAS设置缓存的保存路径,默认为default。因为在缓存配置CACHES中设置两个缓存数据表,而属性值default对应缓存配置CACHES的default属性,所以全站缓存将保存在数据表my_cache_table中。
4、CACHE_MIDDLEWARE_KEY_PREFIX指定某个Django站点的名称。在一些大型网站中都会采用分布式站点实现负载均衡,就是将同一个Django项目部署在多个服务器上,当网站访问量过大的时候,可以将访问量分散到各个服务器,提高网站的整体性能。如果多个服务器使用共享缓存,那么该属性的作用是为了区分各个服务器的缓存数据,这样每个服务器只能使用自己的缓存数据。
启动MyDjango,在浏览器访问的页面都会在缓存数据表my_cache_table上生成相应的缓存信息,如下图:
缓存数据表my_cache_table
视图缓存是将视图函数执行过程生成缓存数据,主要以装饰器的形式来实现。装饰器有三个参数,分别是timeout、cache和key_prefix,参数timeout是必选参数,其余两个参数都是可选参数,参数的含义与视图缓存的参数一致。代码如下:
#index/views.py #导入cache_page from django.views.decorators.cache import cache_page #参数cache与全站缓存CACHE_MIDDLEWARE_ALLAS相同 #参数key_prefix与全站缓存CACHE_MIDDLEWARE_KEY_PREFIX相同 @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(timeout=10, cache='MyDjango', key_prefix='MyDjangoURL')(views.index), name='index'),
#购物车
path('ShoppingCar.html', views.ShoppingCarView, name='ShoppingCar') ]
在浏览器上访问某个页面,打开数据库查看缓存数据表mydjango_cache_table的路由缓存信息,如下图:
缓存数据表mydjango_cache_table
模板缓存是通过Django的缓存标签实现的,缓存标签只支持两个参数:timeout和key_prefix,以模板index.html为例实现模板缓存,代码如下:
<header id="top"> <div id="top_box"> <ul class="lf"> <li><a href="#">华为官网a>li> <li><a href="#">华为荣耀a>li> ul> <ul class="rt"> {# 设置缓存 #} {% load cache %} {% cache 10 MyDjangoTemp %} {# 在模版中使用user变量是一个User或者AnoymousUser对象,该对象由模型MyUser实例化 #} {% if user.is_authenticated %} <li>用户名: {{ user.username }}li> <li><a href="{% url 'logout' %}">退出登录a>li> {% endif %} {# 在模版中使用perms变量是Permission对象,该对象由模型Permission实例化 #} {% if perms.index.add_product %} <li>添加产品信息li> {% endif %} {# 缓存结束 #} {% endcache %} ul> div> header>
模板缓存的缓存信息会默认存储在数据表my_cachez_table中,打开数据库查看数据表my_cache_table的模板缓存信息,如下图:
缓存数据表my_cache_table