python 全棧開發,Day87(ajax登陸示例,CSRF跨站請求僞造,Django的中間件,自定義分頁)

1、ajax登陸示例

新建項目login_ajaxcss

修改urls.py,增長路徑html

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

修改settings.py,指定staticpython

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

修改views.py,增長視圖函數jquery

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 == "123":
            ret['status'] = 1
            ret['url'] = '/index/'
        return HttpResponse(json.dumps(ret))

    return render(request, "login.html")
View Code

在static目錄下建立css,新建signin.cssgit

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;
}
View Code

建立login.htmlgithub

<!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>
View Code

訪問登陸頁面:http://127.0.0.1:8000/login/web

錯誤時,提示登陸失敗ajax

輸入正確的用戶名和密碼,跳轉首頁數據庫

 

2、CSRF跨站請求僞造

方式一

將 csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() 放在POST的請求體中。django

示例中就是使用的這種方式。

方式二

給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>
View Code

刷新頁面

查看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>
View Code

從新登陸,效果同上!

 

方式四

官方推薦方法(用到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>
View Code

刷新頁面,從新登陸,效果同上!

 

3、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',
]
View Code

使用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
View Code

注意:上面定義的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'
]
View Code

修改views.py裏面的index視圖函數

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

網頁訪問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")
View Code

在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
]
View Code

修改views.py中的index視圖函數

def index(request):
    print("app01 中的 index視圖")
    return HttpResponse('this is index')
View Code

刷新頁面,查看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
]
View Code

刷新頁面,會發現終端中打印的內容以下:

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
View Code

刷新頁面,查看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__)
View Code

刷新頁面,查看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")
View Code

若是視圖函數中無異常,process_exception方法不執行。

想辦法,在視圖函數中拋出一個異常:

修改views.py中的index視圖函數,raise表示主動報錯

def index(request):
    print("app01 中的 index視圖")
    raise ValueError("呵呵")
    return HttpResponse("O98K")
View Code

在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")
View Code

刷新頁面,網頁效果以下:

查看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
View Code

注意,這裏並無執行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
View Code

修改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
View Code

刷新頁面,網頁輸出:

查看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繼續
View Code

 

中間件版登陸驗證 

中間件版的登陸驗證須要依靠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),
]
View Code

修改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 == "123":
            # 設置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')
View Code

修改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)
View Code

修改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
]
View Code

新建文件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>
View Code

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>
View Code

訪問login頁面: 

訪問index頁面,注意:此時尚未session

訪問黑名單頁面

登陸頁面,輸入正確的用戶名和密碼

這個時候,有了sessinon,能夠直接訪問home

 

4、自定義分頁

分頁功能在每一個網站都是必要的,django自帶了分頁組件Paginator,就能夠實現分頁功能。

那麼爲何要自定義分頁呢?好比flask框架是沒有分頁組件的,那麼就須要咱們本身去寫分頁功能!

 

對於分頁來講,其實就是根據用戶的輸入計算出應該在數據庫表中的起始位置。

一、設定每頁顯示數據條數

二、用戶輸入頁碼(第一頁、第二頁...)

三、根據設定的每頁顯示條數和當前頁碼,計算出須要取數據表的起始位置

四、在數據表中根據起始位置取值,頁面上輸出數據


需求又來了,須要在頁面上顯示分頁的頁面。如:[上一頁][1][2][3][4][5][下一頁]

一、設定每頁顯示數據條數

二、用戶輸入頁碼(第一頁、第二頁...)

三、設定顯示多少頁號

四、獲取當前數據總條數

五、根據設定顯示多少頁號和數據總條數計算出,總頁數

六、根據設定的每頁顯示條數和當前頁碼,計算出須要取數據表的起始位置

七、在數據表中根據起始位置取值,頁面上輸出數據

八、輸出分頁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),
]
View Code

修改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})
View Code

在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
View Code

建立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>
View Code

訪問頁面,效果以下:

 

總結,分頁時須要作三件事:

  • 建立處理分頁數據的類
  • 根據分頁數據獲取數據
  • 輸出分頁HTML,即:[上一頁][1][2][3][4][5][下一頁]
相關文章
相關標籤/搜索