一、ajax登录示例

新建项目login_ajax

修改urls.py,增加路径

from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', views.login),
path('index/', views.index),
]

修改settings.py,指定static

STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR,'static'),
)

修改views.py,增加视图函数

from django.shortcuts import render, HttpResponse, redirect
import json # Create your views here.
def index(request):
return HttpResponse('this is index') def login(request):
if request.method == "POST":
user = request.POST.get("user")
pwd = request.POST.get("pwd")
ret = {"status": 0, 'url': ''}
if user == "xiao" and pwd == "":
ret['status'] = 1
ret['url'] = '/index/'
return HttpResponse(json.dumps(ret)) return render(request, "login.html")

在static目录下创建css,新建signin.css

body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
} .form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: normal;
}
.form-signin .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}

创建login.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登录</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/signin.css">
</head>
<body> <div class="container"> <form class="form-signin">
{% csrf_token %}
<h2 class="form-signin-heading">请登录</h2>
<label for="inputUser" class="sr-only">用户名</label>
<input type="text" id="inputUser" class="form-control" placeholder="用户名" required="" autofocus="" name="user">
<label for="inputPassword" class="sr-only">密码</label>
<input type="password" id="inputPassword" class="form-control" placeholder="密码" required="" name="pwd">
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me"> 记住我
</label>
</div>
<input class="btn btn-lg btn-primary btn-block" id="login" value="登陆">
</form> </div> <!-- /container --> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script> $('#login').click(function () {
$.ajax({
url: '/login/',
type: 'post',
data: {
csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
user: $('[name="user"]').val(),
pwd: $('[name="pwd"]').val()
},
success: function (data) {
data = JSON.parse(data);
if (data.status) {
//跳转指定页面
window.location = data.url
}
else {
alert('登陆失败')
}
}
})
})
</script> </body>
</html>

访问登录页面:http://127.0.0.1:8000/login/

错误时,提示登录失败

输入正确的用户名和密码,跳转首页

二、CSRF跨站请求伪造

方式一

将 csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() 放在POST的请求体中。

示例中就是使用的这种方式。

方式二

给ajax的请增加X-CSRFToken的请求头,对应的值只能是cookie中的csrftoken的值。

所以我们要从cookie中提取csrftoken的值,jQuery不能去cookie,我们使用jquery.cookie的插件。点击下载jquer.cookie插件

HTML中导入jquery.cookie.js。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登录</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/signin.css">
</head>
<body> <div class="container"> <form class="form-signin">
{% csrf_token %}
<h2 class="form-signin-heading">请登录</h2>
<label for="inputUser" class="sr-only">用户名</label>
<input type="text" id="inputUser" class="form-control" placeholder="用户名" required="" autofocus="" name="user">
<label for="inputPassword" class="sr-only">密码</label>
<input type="password" id="inputPassword" class="form-control" placeholder="密码" required="" name="pwd">
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me"> 记住我
</label>
</div>
<input class="btn btn-lg btn-primary btn-block" id="login" value="登陆">
</form> </div> <!-- /container --> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
{#必须先引入jquery,再引入cookie#}
<script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script> $('#login').click(function () {
$.ajax({
url: '/login/',
type: 'post',
//增加X-CSRFToken的请求头
headers:{ "X-CSRFToken":$.cookie('csrftoken') },
data: {
//csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
user: $('[name="user"]').val(),
pwd: $('[name="pwd"]').val()
},
success: function (data) {
data = JSON.parse(data);
if (data.status) {
//跳转指定页面
window.location = data.url;
}
else {
alert('登陆失败');
}
}
})
})
</script> </body>
</html>

刷新页面

查看console,能得到cookie为csrftoken的值

再次登录

发送post数据时,会发送X-CSRFToken的请求头

方式三

使用$.ajaxSetup()给全局的ajax添加默认参数。

可以按照方式一设置data,也可以按照方式二设置请求头。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登录</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/signin.css">
</head>
<body> <div class="container"> <form class="form-signin">
{% csrf_token %}
<h2 class="form-signin-heading">请登录</h2>
<label for="inputUser" class="sr-only">用户名</label>
<input type="text" id="inputUser" class="form-control" placeholder="用户名" required="" autofocus="" name="user">
<label for="inputPassword" class="sr-only">密码</label>
<input type="password" id="inputPassword" class="form-control" placeholder="密码" required="" name="pwd">
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me"> 记住我
</label>
</div>
<input class="btn btn-lg btn-primary btn-block" id="login" value="登陆">
</form> </div> <!-- /container --> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
{#必须先引入jquery,再引入cookie#}
<script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script>
//方式一设置data
{#$.ajaxSetup({#}
{# data: {#}
{# csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),#}
{# }#}
{# });#} //方式二设置请求头
$.ajaxSetup({
headers: {"X-CSRFToken": $.cookie('csrftoken')},
}); $('#login').click(function () {
$.ajax({
url: '/login/',
type: 'post',
//增加X-CSRFToken的请求头
headers:{ "X-CSRFToken":$.cookie('csrftoken') },
data: {
user: $('[name="user"]').val(),
pwd: $('[name="pwd"]').val()
},
success: function (data) {
data = JSON.parse(data);
if (data.status) {
//跳转指定页面
window.location = data.url;
}
else {
alert('登陆失败');
}
}
})
})
</script> </body>
</html>

重新登录,效果同上!

方式四

官方推荐方法(用到jquery.cookie插件):

function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
} $.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
}
}
});

login.html完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登录</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/signin.css">
</head>
<body> <div class="container"> <form class="form-signin">
{% csrf_token %}
<h2 class="form-signin-heading">请登录</h2>
<label for="inputUser" class="sr-only">用户名</label>
<input type="text" id="inputUser" class="form-control" placeholder="用户名" required="" autofocus="" name="user">
<label for="inputPassword" class="sr-only">密码</label>
<input type="password" id="inputPassword" class="form-control" placeholder="密码" required="" name="pwd">
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me"> 记住我
</label>
</div>
<input class="btn btn-lg btn-primary btn-block" id="login" value="登陆">
</form> </div> <!-- /container --> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
{#必须先引入jquery,再引入cookie#}
<script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script>
//ajax在发送之前,做的header头。csrfSafeMethod是一个方法名,用来调用的
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
//这些HTTP方法不需要CSRF保护
// 匹配method的请求模式,js正则匹配用test
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
} // 为ajax请求做csrf_token
$.ajaxSetup({
//beforeSend 在向服务器发送请求前执行一些动作
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
// 在请求头设置一次csrf_token,除了上面正则匹配到的不带csrf_token,其他的都要带
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
}
}
}); $('#login').click(function () {
$.ajax({
url: '/login/',
type: 'post',
//增加X-CSRFToken的请求头
headers:{ "X-CSRFToken":$.cookie('csrftoken') },
data: {
user: $('[name="user"]').val(),
pwd: $('[name="pwd"]').val()
},
success: function (data) {
data = JSON.parse(data);
if (data.status) {
//跳转指定页面
window.location = data.url;
}
else {
alert('登陆失败');
}
}
})
})
</script> </body>
</html>

刷新页面,重新登录,效果同上!

三、Django的中间件

前戏

我们在前面的课程中已经学会了给视图函数加装饰器来判断是用户是否登录,把没有登录的用户请求跳转到登录页面。我们通过给几个特定视图函数加装饰器实现了这个需求。但是以后添加的视图函数可能也需要加上装饰器,这样是不是稍微有点繁琐。

学完今天的内容之后呢,我们就可以用更适宜的方式来实现类似给所有请求都做相同操作的功能了。

什么是中间件?

官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。

输入和输出,这里指的是请求和响应

但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。

说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在处理请求的特定的时间去执行这些方法。

我们一直都在使用中间件,只是没有注意到而已,打开Django项目的Settings.py文件,看到下图的MIDDLEWARE配置项。

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

MIDDLEWARE配置项是一个列表,列表中是一个个字符串,这些字符串其实是一个个类,也就是一个个中间件。

我们之前已经接触过一个csrf相关的中间件了?我们一开始让大家把他注释掉,再提交post请求的时候,就不会被forbidden了,后来学会使用csrf_token之后就不再注释这个中间件了。

原生的中间件,不要关闭!

那接下来就学习中间件中的方法以及这些方法什么时候被执行。

修改settings.py,导入一个csrf的中间件

from django.middleware.csrf import CsrfViewMiddleware
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

使用Ctrl+鼠标左键,点击CsrfViewMiddleware,查看源码。

发现它继承了MiddlewareMixin。所有中间件,必须继承MiddlewareMixin

将几个源码折叠,效果如下:

它使用了3个方法,下面会一一讲解

将上面的导入的CsrfViewMiddleware删掉,还原settings.py的状态!

自定义中间件

中间件可以定义五个方法,分别是:(主要的是process_request和process_response)

  • process_request(self,request)
  • process_view(self, request, view_func, view_args, view_kwargs)
  • process_template_response(self,request,response)
  • process_exception(self, request, exception)
  • process_response(self, request, response)

以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。

自定义一个中间件示例

举例1:

还是拿上面的login_ajax项目来测试

在app01目录下创建文件middlewares.py,这个名字无所谓

from django.utils.deprecation import MiddlewareMixin

class MD1(MiddlewareMixin):  # 中间件必须要继承MiddlewareMixin
# 方式名必须是上面定义的5个
# request比视图函数中的request要早一步
def process_request(self, request):
print("MD1里面的 process_request")
# response 是形参,名字无所谓
def process_response(self, request, response):
print("MD1里面的 process_response")
return response

注意:上面定义的2个方法,没有return,那么默认返回值为None

修改settings.py里面的MIDDLEWARE,最后一个元素添加

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 这里指的是app01目录下的middlewares模块,里面的MD1类
'app01.middlewares.MD1'
]

修改views.py里面的index视图函数

def index(request):
print("index")
return HttpResponse('this is index')

网页访问index页面:

查看pycharm控制台输出:

MD1里面的 process_request
[20/Jul/2018 09:38:18] "GET /index/ HTTP/1.1" 200 13
index
MD1里面的 process_response

可以看出,它的执行顺序如下:

MD1里面的 process_request --> index视图函数 --> MD1里面的 process_response

process_request

process_request有一个参数,就是request,这个request和视图函数中的request是一样的。

它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将响应对象返回给浏览器。

我们来看看多个中间件时,Django是如何执行其中的process_request方法的。

举例:

修改app01下面的middlewares.py

from django.utils.deprecation import MiddlewareMixin

class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1里面的 process_request") class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")

在settings.py的MIDDLEWARE配置项中注册上述两个自定义中间件:

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 这里指的是app01目录下的middlewares模块,里面的MD1类
'app01.middlewares.MD1', # 自定义中间件MD1
'app01.middlewares.MD2' # 自定义中间件MD2
]

修改views.py中的index视图函数

def index(request):
print("app01 中的 index视图")
return HttpResponse('this is index')

刷新页面,查看Pycharm控制台输出:

MD1里面的 process_request
MD2里面的 process_request
app01 中的 index视图

把MD1和MD2的位置调换一下,修改settings.py的MIDDLEWARE配置项

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'app01.middlewares.MD2', # 自定义中间件MD2
'app01.middlewares.MD1', # 自定义中间件MD1
]

刷新页面,会发现终端中打印的内容如下:

MD2里面的 process_request
[20/Jul/2018 09:49:18] "GET /index/ HTTP/1.1" 200 13
MD1里面的 process_request
app01 中的 index视图

看结果我们知道:视图函数还是最后执行的,MD2比MD1先执行自己的process_request方法。

在打印一下两个自定义中间件中process_request方法中的request参数,会发现它们是同一个对象。

由此总结一下:

  1. 中间件的process_request方法是在执行视图函数之前执行的。
  2. 当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
  3. 不同中间件之间传递的request都是同一个对象

process_response

它有两个参数,一个是request,一个是response,request就是上述例子中一样的对象,response是视图函数返回的HttpResponse对象。该方法的返回值也必须是HttpResponse对象。

给上述的M1和M2加上process_response方法:

修改app01下面的middlewares.py

from django.utils.deprecation import MiddlewareMixin

class MD1(MiddlewareMixin):

    def process_request(self, request):
print("MD1里面的 process_request") def process_response(self, request, response):
print("MD1里面的 process_response")
return response class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request") def process_response(self, request, response):
print("MD2里面的 process_response")
return response

刷新页面,查看Pycharm控制台输出:

MD2里面的 process_request
MD1里面的 process_request
app01 中的 index视图
MD1里面的 process_response
MD2里面的 process_response
[20/Jul/2018 09:51:57] "GET /index/ HTTP/1.1" 200 13

看结果可知:

process_response方法是在视图函数之后执行的,并且顺序是MD1比MD2先执行。(此时settings.py中 MD2比MD1先注册)

多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。

process_view

process_view(self, request, view_func, view_args, view_kwargs)

该方法有四个参数

request是HttpRequest对象。

view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)

view_args是将传递给视图的位置参数的列表.

view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。

Django会在调用视图函数之前调用process_view方法。

它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用适当的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。

给MD1和MD2添加process_view方法:

修改app01下面的middlewares.py

from django.utils.deprecation import MiddlewareMixin

class MD1(MiddlewareMixin):

    def process_request(self, request):
print("MD1里面的 process_request") def process_response(self, request, response):
print("MD1里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
# view_func.__name__ 表示查看函数名
print(view_func, view_func.__name__) class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass def process_response(self, request, response):
print("MD2里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD2 中的process_view")
print(view_func, view_func.__name__)

刷新页面,查看Pycharm控制台输出:

MD2里面的 process_request
[20/Jul/2018 09:56:24] "GET /index/ HTTP/1.1" 200 13
MD1里面的 process_request
--------------------------------------------------------------------------------
MD2 中的process_view
<function index at 0x000002CEAA9C9E18> index
--------------------------------------------------------------------------------
MD1 中的process_view
<function index at 0x000002CEAA9C9E18> index
app01 中的 index视图
MD1里面的 process_response
MD2里面的 process_response

process_view方法是在process_request之后,视图函数之前执行的,执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的

process_exception

process_exception(self, request, exception)

该方法两个参数:

一个HttpRequest对象

一个exception是视图函数异常产生的Exception对象。

这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。

给MD1和MD2添加上这个方法:

修改app01下面的middlewares.py

from django.utils.deprecation import MiddlewareMixin

class MD1(MiddlewareMixin):

    def process_request(self, request):
print("MD1里面的 process_request") def process_response(self, request, response):
print("MD1里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__) def process_exception(self, request, exception):
print(exception)
print("MD1 中的process_exception") class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request") def process_response(self, request, response):
print("MD2里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD2 中的process_view")
print(view_func, view_func.__name__) def process_exception(self, request, exception):
print(exception)
print("MD2 中的process_exception")

如果视图函数中无异常,process_exception方法不执行。

想办法,在视图函数中抛出一个异常:

修改views.py中的index视图函数,raise表示主动报错

def index(request):
print("app01 中的 index视图")
raise ValueError("呵呵")
return HttpResponse("O98K")

在MD1的process_exception中返回一个响应对象:

修改app01下面的middlewares.py,注意:要导入HttpResponse。完整代码如下:

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, HttpResponse, redirect class MD1(MiddlewareMixin): def process_request(self, request):
print("MD1里面的 process_request") def process_response(self, request, response):
print("MD1里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__) def process_exception(self, request, exception):
print(exception)
print("MD1 中的process_exception")
return HttpResponse(str(exception)) # 返回一个响应对象 class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request") def process_response(self, request, response):
print("MD2里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD2 中的process_view")
print(view_func, view_func.__name__) def process_exception(self, request, exception):
print(exception)
print("MD2 中的process_exception")

刷新页面,网页效果如下:

查看Pycharm控制台输出:

MD2里面的 process_request
MD1里面的 process_request
--------------------------------------------------------------------------------
MD2 中的process_view
<function index at 0x000002A8A993AE18> index
--------------------------------------------------------------------------------
MD1 中的process_view
<function index at 0x000002A8A993AE18> index
app01 中的 index视图
呵呵
MD1 中的process_exception
MD1里面的 process_response
MD2里面的 process_response
[20/Jul/2018 10:03:52] "GET /index/ HTTP/1.1" 200 6

注意,这里并没有执行MD2的process_exception方法,因为MD1中的process_exception方法直接返回了一个响应对象。

因为HTTP是一次请求,一次响应,连接就断开了。所以MD2中的process_exception方法没有执行。

process_template_response(用的比较少)

process_template_response(self, request, response)

它的参数,一个HttpRequest对象,response是TemplateResponse对象(由视图函数或者中间件产生)。

process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。

修改app01下面的middlewares.py

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, HttpResponse, redirect class MD1(MiddlewareMixin): def process_request(self, request):
print("MD1里面的 process_request") def process_response(self, request, response):
print("MD1里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__) def process_exception(self, request, exception):
print(exception)
print("MD1 中的process_exception")
return HttpResponse(str(exception)) def process_template_response(self, request, response):
print("MD1 中的process_template_response")
return response class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass def process_response(self, request, response):
print("MD2里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD2 中的process_view")
print(view_func, view_func.__name__) def process_exception(self, request, exception):
print(exception)
print("MD2 中的process_exception") def process_template_response(self, request, response):
print("MD2 中的process_template_response")
return response

修改views.py中的index视图函数,自定义一个render方法。注意:它不是django自带的render

def index(request):
print("app01 中的 index视图") # 定义一个render方法,注意:它不是django自带的render
def render():
print("in index/render")
return HttpResponse("O98K") rep = HttpResponse("OK")
# 给rep变量增加一个方法render,这是面向对象的知识点
rep.render = render return rep

刷新页面,网页输出:

查看Pycharm控制台输出:

MD2里面的 process_request
[20/Jul/2018 10:12:52] "GET /index/ HTTP/1.1" 200 4
MD1里面的 process_request
--------------------------------------------------------------------------------
MD2 中的process_view
<function index at 0x000001661AC4AE18> index
--------------------------------------------------------------------------------
MD1 中的process_view
<function index at 0x000001661AC4AE18> index
app01 中的 index视图
MD1 中的process_template_response
MD2 中的process_template_response
in index/render
MD1里面的 process_response
MD2里面的 process_response

从结果看出:

视图函数执行完之后,立即执行了中间件的process_template_response方法,顺序是倒序,先执行MD1的,在执行MD2的,接着执行了视图函数返回的HttpResponse对象的render方法,返回了一个新的HttpResponse对象,接着执行中间件process_response方法。

中间件的执行流程

上一部分,我们了解了中间件中的5个方法,它们的参数、返回值以及什么时候执行,现在总结一下中间件的执行流程。

请求到达中间件之后,先按照正序执行每个注册中间件的process_reques方法,process_request方法返回的值是None,就依次执行,如果返回的值是HttpResponse对象,不再执行后面的process_request方法,而是执行当前对应中间件的process_response方法,将HttpResponse对象返回给浏览器。也就是说:如果MIDDLEWARE中注册了6个中间件,执行过程中,第3个中间件返回了一个HttpResponse对象,那么第4,5,6中间件的process_request和process_response方法都不执行,顺序执行3,2,1中间件的process_response方法。

process_request方法都执行完后,匹配路由,找到要执行的视图函数,先不执行视图函数,先执行中间件中的process_view方法,process_view方法返回None,继续按顺序执行,所有process_view方法执行完后执行视图函数。假如中间件3 的process_view方法返回了HttpResponse对象,则4,5,6的process_view以及视图函数都不执行,直接从最后一个中间件,也就是中间件6的process_response方法开始倒序执行。

process_template_response和process_exception两个方法的触发是有条件的,执行顺序也是倒序。总结所有的执行流程如下:

总结:

process_request:
1. 是在视图执行前执行的
2. 它的顺序是从上往下
3. 返回值是None,继续往后执行
4. 返回值是HttpResponse的对象,执行对应中间件的process_response方法
,接着往上,返回给浏览器 process_response:
1. 是在视图执行后执行的
2. 它的顺序是从下往上
3. 返回必须是HttpResponse的对象,继续往上执行 process_view:
1. 是在视图执行前执行的,process_request之后
2. 它的顺序是从上往下
3. 返回值是None,继续往后执行
4. 返回值是HttpResponse的对象,执行最后一个中间件的process_response方法
,接着往上,返回给浏览器,视图不走了 process_exception:
1. 报错了才执行
2. 在视图函数之后,process_response之前
3. 它的顺序是从下往上
4. 返回值是HttpResponse的对象,执行最后一个中间件的process_response方法
,接着往上,返回给浏览器 process_template_response:
1. 视图返回的对象有render方法 才执行
2. 在视图函数之后,process_response之前
3. 它的顺序是从下往上
4. 返回值是HttpResponse的对象。
5. 执行完所有的中间件的process_template_response之后,
才执行对象.render()得到一个新的HttpResponse的对象,执行往交给process_response继续

中间件版登录验证

中间件版的登录验证需要依靠session,所以数据库中要有django_session表。
执行2个命令,生成django默认的表
python manage.py makemigrations
python manage.py migrate

修改urls.py,增加路径

urlpatterns = [
path('admin/', admin.site.urls),
path('login/', views.login),
path('logout/', views.logout),
path('index/', views.index),
path('home/', views.home),
]

修改views.py

from django.shortcuts import render, HttpResponse, redirect
from django.contrib import auth
import json # Create your views here.
def login(request):
if request.method == "POST":
user = request.POST.get("user")
pwd = request.POST.get("pwd")
ret = {"status": 0, 'url': ''}
if user == "xiao" and pwd == "":
# 设置session
request.session["user"] = user
ret['status'] = 1
# 跳转到index页面
ret['url'] = '/index/' return HttpResponse(json.dumps(ret)) return render(request, "login.html") def logout(request): # 注销
auth.logout(request)
return redirect("/login/") def index(request):
return HttpResponse('this is index <a href="/logout/">注销</a>') def home(request):
return HttpResponse('this is home')

修改app01下面的middlewares.py

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse class AuthMD(MiddlewareMixin): # 验证登录
white_list = ['/login/', ] # 白名单
black_list = ['/black/', ] # 黑名单
ret = {"status": 0, 'url': '', 'msg': ''} # 默认状态 def process_request(self, request): # 请求之前
request_url = request.path_info # 获取请求路径
# get_full_path()表示带参数的路径
print(request.path_info, request.get_full_path())
# 黑名单的网址限制访问
if request_url in self.black_list:
self.ret['msg'] = "这是非法请求"
self.ret['url'] = "/login/"
# 白名单的网址或者登陆用户不做限制
# 判断是否在白名单内或者已经有了session(表示已经登录了)
elif request_url in self.white_list or request.session.get("user"):
return None
else:
self.ret['msg'] = "请登录后,再访问!"
self.ret['url'] = "/login/" # 错误页面提示
return render(request, "jump.html", self.ret)

修改settings.py的MIDDLEWARE配置项

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'app01.middlewares.AuthMD', # 自定义中间件AuthMD
]

新建文件jump.html,用来做中间件不通过时,js跳转

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<link rel="stylesheet" href="http://mishengqiang.com/sweetalert/css/sweetalert.css">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="http://mishengqiang.com/sweetalert/js/sweetalert-dev.js"></script>
<div>
{#获取错误信息#}
<input type="hidden" id="msg" value="{{ msg }}">
<input type="hidden" id="url" value="{{ url }}">
</div> <script>
$(function () {
var msg = $("#msg").val();
var url = $("#url").val();
console.log(msg);
console.log(url); if (msg.length > 0) { //判断是否有错误信息
swal({
title: msg,
text: "2秒后自动关闭。",
type: 'error',
timer: 2000,
showConfirmButton: false
}, function () {
window.location.href = url; //跳转指定url
}); } })
</script> </body>
</html>

login.html,这个还是用的第4种csrf

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登录</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/signin.css">
</head>
<body> <div class="container"> <form class="form-signin">
{% csrf_token %}
<h2 class="form-signin-heading">请登录</h2>
<label for="inputUser" class="sr-only">用户名</label>
<input type="text" id="inputUser" class="form-control" placeholder="用户名" required="" autofocus="" name="user">
<label for="inputPassword" class="sr-only">密码</label>
<input type="password" id="inputPassword" class="form-control" placeholder="密码" required="" name="pwd">
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me"> 记住我
</label>
</div>
<input class="btn btn-lg btn-primary btn-block" id="login" value="登陆">
</form> </div> <!-- /container --> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
{#必须先引入jquery,再引入cookie#}
<script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
{#sweetalert插件#}
<link rel="stylesheet" href="http://mishengqiang.com/sweetalert/css/sweetalert.css">
<script src="http://mishengqiang.com/sweetalert/js/sweetalert-dev.js"></script>
<script>
//ajax在发送之前,做的header头。csrfSafeMethod是一个方法名,用来调用的
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
//这些HTTP方法不需要CSRF保护
// 匹配method的请求模式,js正则匹配用test
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
} // 为ajax请求做csrf_token
$.ajaxSetup({
//beforeSend 在向服务器发送请求前执行一些动作
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
// 在请求头设置一次csrf_token,除了上面正则匹配到的不带csrf_token,其他的都要带
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
}
}
}); $('#login').click(function () {
$.ajax({
url: '/login/',
type: 'post',
//增加X-CSRFToken的请求头
headers:{ "X-CSRFToken":$.cookie('csrftoken') },
data: {
user: $('[name="user"]').val(),
pwd: $('[name="pwd"]').val()
},
success: function (data) {
data = JSON.parse(data);
if (data.status) {
swal({
title: '登录成功',
type: 'success', //展示成功的图片
timer: 500, //延时500毫秒
showConfirmButton: false //关闭确认框
}, function () {
window.location.href = data.url; //跳转后台首页
});
}
else {
sweetAlert("登录失败!", data.msg, "error");
}
}
})
})
</script> </body>
</html>

访问login页面:

访问index页面,注意:此时还没有session

访问黑名单页面

登录页面,输入正确的用户名和密码

这个时候,有了sessinon,可以直接访问home

四、自定义分页

分页功能在每个网站都是必要的,django自带了分页组件Paginator,就可以实现分页功能。

那么为什么要自定义分页呢?比如flask框架是没有分页组件的,那么就需要我们自己去写分页功能!

对于分页来说,其实就是根据用户的输入计算出应该在数据库表中的起始位置。

1、设定每页显示数据条数

2、用户输入页码(第一页、第二页...)

3、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置

4、在数据表中根据起始位置取值,页面上输出数据


需求又来了,需要在页面上显示分页的页面。如:[上一页][1][2][3][4][5][下一页]

1、设定每页显示数据条数

2、用户输入页码(第一页、第二页...)

3、设定显示多少页号

4、获取当前数据总条数

5、根据设定显示多少页号和数据总条数计算出,总页数

6、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置

7、在数据表中根据起始位置取值,页面上输出数据

8、输出分页html,如:[上一页][1][2][3][4][5][下一页]

举例:

创建项目paging_demo,修改urls.py,增加路径

from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('user_list/', views.user_list),
]

修改views.py,增加视图函数

from django.shortcuts import render
from . import paging # Create your views here.
# 全局临时列表
data = []
for i in range(1, 302):
data.append({'name': 'xx-%s' % i, 'age': i}) def user_list(request): try:
page_num = int(request.GET.get('page', 1))
except Exception:
page_num = 1 page_obj =paging.Pagination(page_num,len(data)) data1 = data[page_obj.start:page_obj.end]
return render(request, 'user_list.html', {'user_list': data1, 'page_html': page_obj.page_html})

在app01目录下创建paging.py

class Pagination:
def __init__(self, page_num, total_num, per_page_num=10, max_show=11):
self.page_num = page_num # 当前页面数
self.total_num = total_num # 总数据量
self.per_page_num = per_page_num # 每页显示数据条数
self.max_show = max_show # 最多显示页码数
self.half_show = self.max_show // 2 # 页面取一半,用来判断始终显示11页
# divmod() 函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组
self.total_page, more = divmod(total_num, per_page_num) # 总页码数 if more: # 如果有余数的情况下
self.total_page += 1 # 总页数加1
if self.page_num > self.total_page: # 如果当前页面数大于总页码数时
self.page_num = self.total_page # 当前页面数设置为总页码数
elif self.page_num < 1: # 如果当前页面数小于1时
self.page_num = 1 # 当前页面数设置为1 @property
def start(self): # 起始位置
return (self.page_num - 1) * self.per_page_num @property
def end(self): # 结束位置
return self.page_num * self.per_page_num @property
def page_html(self): # 分页html代码
page_start = self.page_num - self.half_show
page_end = self.page_num + self.half_show + 1
if self.page_num + self.half_show >= self.total_page:
page_end = self.total_page + 1
page_start = self.total_page - self.max_show + 1
if self.page_num <= self.half_show:
page_start = 1
page_end = self.max_show + 1
page_num_list = [] # 添加首页
page_num_list.append('<li><a href="/user_list/?page={0}">首页</a></li>'.format(1))
# 添加上一页
pre_page_num = self.page_num - 1 # 当上一页小于1时,禁止点击
if pre_page_num < 1:
page_num_list.append(
'<li class="disabled"><a href="/user_list/?page={0}">上一页</a></li>'.format(pre_page_num))
else:
page_num_list.append('<li><a href="/user_list/?page={0}">上一页</a></li>'.format(pre_page_num)) # 判断页面选择状态
for i in range(page_start, page_end):
if i == self.page_num:
page_num_list.append('<li class="active"><a href="/user_list/?page={0}">{0}</a></li>'.format(i))
else:
page_num_list.append('<li><a href="/user_list/?page={0}">{0}</a></li>'.format(i))
# 添加下一页
next_page_num = self.page_num + 1 # 当下一页大于总页码数时,禁止点击
if next_page_num > self.total_page:
page_num_list.append(
'<li class="disabled"><a href="/user_list/?page={0}">下一页</a></li>'.format(next_page_num))
else:
page_num_list.append('<li><a href="/user_list/?page={0}">下一页</a></li>'.format(next_page_num)) # 添加尾页
page_num_list.append('<li><a href="/user_list/?page={0}">尾页</a></li>'.format(self.total_page)) page_html = ''.join(page_num_list) # 列表转换为字符串 return page_html

创建user_list.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<table class="table table-bordered">
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
</tr>
</thead>
<tbody>
{% for user in user_list %}
<tr>
<td>{{ user.name }}</td>
<td>{{ user.age }}</td>
</tr>
{% endfor %} </tbody>
</table>
</div> <div class="text-center">
<nav aria-label="Page navigation">
<ul class="pagination"> {{ page_html|safe }}
</ul>
</nav> </div>
</body>
</html>

访问页面,效果如下:

总结,分页时需要做三件事:

  • 创建处理分页数据的类
  • 根据分页数据获取数据
  • 输出分页HTML,即:[上一页][1][2][3][4][5][下一页]

python 全栈开发,Day87(ajax登录示例,CSRF跨站请求伪造,Django的中间件,自定义分页)的更多相关文章

  1. 巨蟒python全栈开发django10:ajax&&登录认证

    通过题目进行知识点回顾: 聚合查询 From django.db.models import Avg,Min,Max,F,Q,Count,Sum #查询书籍的平均值 Ret= Models.Book. ...

  2. python CSRF跨站请求伪造

    python CSRF跨站请求伪造 <!DOCTYPE html> <html lang="en"> <head> <meta chars ...

  3. ajax向Django前后端提交请求和CSRF跨站请求伪造

    1.ajax登录示例 urls.py from django.conf.urls import url from django.contrib import admin from app01 impo ...

  4. Django框架 之 基于Ajax中csrf跨站请求伪造

    Django框架 之 基于Ajax中csrf跨站请求伪造 ajax中csrf跨站请求伪造 方式一 1 2 3 $.ajaxSetup({     data: {csrfmiddlewaretoken: ...

  5. Python菜鸟之路:Django CSRF跨站请求伪造

    前言 CSRF,Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对 ...

  6. Python全栈开发:Ajax全套

    概述 对于WEB应用程序:用户浏览器发送请求,服务器接收并处理请求,然后返回结果,往往返回就是字符串(HTML),浏览器将字符串(HTML)渲染并显示浏览器上. 1.传统的Web应用 一个简单操作需要 ...

  7. 巨蟒python全栈开发django11:ajax&&form表单上传文件contentType

    回顾: 什么是异步? 可以开出一个线程,我发出请求,不用等待返回,可以做其他事情. 什么是同步? 同步就是,我发送出了一个请求,需要等待返回给我信息,我才可以操作其他事情. 局部刷新是什么? 通过jq ...

  8. python全栈开发day87~91-整个流程梳理、CRM功能、知识点梳理

    1.流程 1. stark组件 1. 启动 2.注册 3.url设计 4.视图函数设计 1.展示数据头 2.展示数据 3.list_display功能实现 4.list_display_links 功 ...

  9. python全栈开发day58-mysql存储过程,权限,索引,慢日志,执行计划,分页优化处理

    1.存储过程 delimiter // create procedure insert_data(in rows int) begin DECLARE n INT DEFAULT 1; drop ta ...

随机推荐

  1. JS 求一组数中所有数的和以及平均值

    var arr = [10,20,30]; //计数器思想 var sum = 0; for(var i=0;i<arr.length;i++){ sum += arr[i]; } consol ...

  2. scala基本语法和单词统计

    scala 基本语法 1.声明变量 (1)val i = 1 使用val声明的变量值是不可变的,相当于java里final修饰的变量,推荐使用. (2)var i = "hello" ...

  3. IDEA中设置Tab多行显示、打开过多自动关闭的方法

    IDEA中默认Tab是单行显示的,而且默认允许同时打开10个Tab,继续打开Tab会根据规则关闭之前打开的Tab.但是可以通过设置来符合自己的使用习惯. 一.打开Tab过多自动关闭 1.选择File→ ...

  4. Java POI 读取word文件

    Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能. 1.读取word 2003及word 2007需要 ...

  5. 10 SpringBoot全面接管SpringMVC

    Spring Boot官方文档描述 If you want to keep Spring Boot MVC features and you want to add additional MVC co ...

  6. 2.SpringBoot HelloWorld详解

    1.POM文件 父项目 <parent> <groupId>org.springframework.boot</groupId> <artifactId> ...

  7. requsets模块和beautifulsoup模块

    2.requests模块方法 requests是基于Python开发的HTTP库,使用Requests可以轻而易举的完成浏览器可有的任何操作. request.get() request.post() ...

  8. C语言中#ifdef,#ifndef和#endif的作用

    现在规定一下头文件书写规范, 根据陈皓编写的跟我一起些makefile,一定要记住:头文件中应该只是声明,而定义应该放在C/C++文件中.否则如果出现有定义,比如头文件中有int a =2;如果有多个 ...

  9. 位运算&,逻辑与and

    在python中,0是否 >>> True and True True >>> True and True True >>> 2 and 4 4 ...

  10. python manage.py syncdb Unknown command: 'syncdb'问题解决方法

    在django1.9后的版本中,python manage.py syncdb命令修改为python manage.py migrate,执行正常. 选择sqlite可视化sqlitestudio-3 ...