Django内置的会话控制简称为Session,可为访问者提供基础的数据存储。数据主要存储在服务器上,并且网站的任意站点都能使用会话数据。当用户第一次访问网站时,网站的服务器将自动创建一个Session对象,该Session对象相当于该用户在网站的一个身份凭证,而且Session能存储该用户的数据信息。当用户在网站的页面之间跳转时,存储在Session对象中的数据不会丢失,只有Session过期或被清理时,服务器才将Session中存储的数据清空并终止该Sesion。
讲解Django的Sesion之前,需要理解 Session和Cookie之间的关系。
了解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实现购物车功能。
购物车功能的实现思路如下:
现在的网站都以动态网站为主,当网站访问量过大时,网站的响应速度必然会降低,这就有可能出现卡死的情况。为了解决网站访问量过大的问题,可以在网站上使用缓存机制。
缓存是将一个请求的响应内容保存到内存或者高速缓存系统(Memcache)中,若某个时间内再次发生同一个请求,则不再去执行请求响应过程,而是直接从内存或者高速缓存系统中获取该请求的响应内容返回给用户。
Django提供5种不同的缓存方式,每种缓存方式说明如下:
每种缓存方式都有一定的适用范围,因此选择缓存方式需要结合网站的实际情况而定。若在项目中使用缓存机制,则首先需要在配置文件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种,主要根据使用对象的不同来划分,具体说明如下。
全站缓存作用于整个网站,当用户向网站发送请求时,首先经过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(Cross-Site Request Forgery,跨站请求伪造)也称为One Click Attack或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用,窃取网站的用户信息来制造恶意请求。
Django为了防护这类攻击,在用户提交表单时,表单会自动加入csrftoken的隐含值,这个隐含值会与网站后台保存的csrftoken进行匹配,只有匹配成功,网站才会处理表单数据。这种防护机制称为CSRF防护,原理如下:
在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所示。
这里并没有添加样式表,所以这里的效果样式是基础的网页样式。