1. 程式人生 > >Python自動化開發學習21-Django

Python自動化開發學習21-Django

python django

補充知識-路由系統(URL)

URL傳遞額外的參數

在url.py裏,除了默認會傳一個request給處理函數,還可以傳遞額外的參數,把一個字典作為第三個參數傳入,之後就可以在處理函數裏取到對應的值:

from django.urls import path
from app01 import views

urlpatterns = [
    path(‘extra-options/‘, views.extra_options, {‘foo‘: ‘bar‘}),
]

處理函數views.py

from django.shortcuts import HttpResponse

def extra_options(request, foo):
    return HttpResponse(foo)

對於include()的情況,可以寫在最外層,也可以在裏層分別寫一個:

# main.py
from django.urls import include, path

urlpatterns = [
    path(‘blog/‘, include(‘inner‘), {‘blog_id‘: 3}),
]

# inner.py
from django.urls import path
from mysite import views

urlpatterns = [
    path(‘archive/‘, views.archive),
    path(‘about/‘, views.about),
]

這個的效果和上面的一樣:

# main.py
from django.urls import include, path

urlpatterns = [
    path(‘blog/‘, include(‘inner‘)),
]

# inner.py
from django.urls import path
from mysite import views

urlpatterns = [
    path(‘archive/‘, views.archive, {‘blog_id‘: 3}),
    path(‘about/‘, views.about, {‘blog_id‘: 3}),
]

命名空間

做路由分發的時候,可以把兩條路由指向同一個app,像這樣:

# urls.py
from django.urls import path, include

urlpatterns = [
    path(‘author-app01/‘, include(‘app01.urls‘, namespace=‘author-app01‘)),
    path(‘publisher-app01/‘, include(‘app01.urls‘, namespace=‘publisher-app01‘)),
]

# app01\urls.py
from django.urls import path
from app01 import views

app_name = ‘app01‘
urlpatterns =[
    path(‘view/‘, views.view, name=‘view‘),
    path(‘template/‘, views.template, name=‘template‘),
]

這裏的 include() 裏多了一個參數 namespace 。現在 http://127.0.0.1:8000/author-app01/view/http://127.0.0.1:8000/publisher-app01/view/ 訪問的是同一個頁面,我們需要根據 namespace 區分這個請求具體是通過那個路由發過來的。下面是在處理函數 views.py 以及html頁面文件 template.html 獲取到路由的方法:

def view(request):
    v = reverse(‘app01:view‘)  # 這個v就是請求的路由
    return HttpResponse(v)

def template(request):
    # 返回一個頁面,在頁面裏通過模板語言獲取到路由信息
    return render(request, ‘template.html‘)

template.html

<div>{% url ‘app01:template‘ %}</div>

知識點就是這樣,沒講應用場景

補充知識-視圖(View)

視圖裏還有一個裝飾器,應用場景比如是用戶認證,某些頁面一定要用戶登錄成功後才能訪問。這部分內容會在將cookie和session的時候再學習。

請求的其他信息

用戶發來請求的時候,不僅有數據,還有請求頭。比如調出控制臺可以有下面這些信息。這些信息也都是客戶端發出來的。
技術分享圖片

所有的信息都封裝在了request這個對象裏,現在就把他們找出來。先用下面的處理函數打印出request這個對象:

def get_request(request):
    print(type(request))
    return HttpResponse("OK")

打印出來的結果如下,可以看到這個確實是個類。

<class ‘django.core.handlers.wsgi.WSGIRequest‘>

現在就去看看這個類的源碼,是一個wsgi.py裏的類,類名是WSGIRequest。可以用PyCharm方便的找到這個文件並且定位到這個類。像下面這樣先導入這個模塊,按住Shift點擊WSGIRequest,就可以跳轉過去。

# 打印結果:django.core.handlers.wsgi.WSGIRequest
from django.core.handlers.wsgi import WSGIRequest
def get_request(request):
    print(type(request))
    return HttpResponse("OK")

部分源碼,只有開頭的構造函數:

class WSGIRequest(HttpRequest):
    def __init__(self, environ):
        script_name = get_script_name(environ)
        path_info = get_path_info(environ)
        if not path_info:
            # Sometimes PATH_INFO exists, but is empty (e.g. accessing
            # the SCRIPT_NAME URL without a trailing slash). We really need to
            # operate as if they‘d requested ‘/‘. Not amazingly nice to force
            # the path like this, but should be harmless.
            path_info = ‘/‘
        self.environ = environ
        self.path_info = path_info
        # be careful to only replace the first slash in the path because of
        # http://test/something and http://test//something being different as
        # stated in http://www.ietf.org/rfc/rfc2396.txt
        self.path = ‘%s/%s‘ % (script_name.rstrip(‘/‘),
                               path_info.replace(‘/‘, ‘‘, 1))
        self.META = environ
        self.META[‘PATH_INFO‘] = path_info
        self.META[‘SCRIPT_NAME‘] = script_name
        self.method = environ[‘REQUEST_METHOD‘].upper()
        self.content_type, self.content_params = cgi.parse_header(environ.get(‘CONTENT_TYPE‘, ‘‘))
        if ‘charset‘ in self.content_params:
            try:
                codecs.lookup(self.content_params[‘charset‘])
            except LookupError:
                pass
            else:
                self.encoding = self.content_params[‘charset‘]
        self._post_parse_error = False
        try:
            content_length = int(environ.get(‘CONTENT_LENGTH‘))
        except (ValueError, TypeError):
            content_length = 0
        self._stream = LimitedStream(self.environ[‘wsgi.input‘], content_length)
        self._read_started = False
        self.resolver_match = None

構造函數裏傳入了一個environ參數,信息都在這個裏面。構造函數裏有一句 self.environ = environ 。所以這個值也傳到實例化的對象裏了,用下面的方法可以打印出來看看:

# 打印結果:django.core.handlers.wsgi.WSGIRequest
from django.core.handlers.wsgi import WSGIRequest
def get_request(request):
    # print(type(request))
    # print(request.environ)
    for k, v in request.environ.items():
        print("%s: %s" % (k, v))
    return HttpResponse("OK")

這裏封裝了所有的用戶請求信息,都是原生的數據。其中一部分常用的,Django已經幫我們處理好了,比如下面的3個:

  • request.POST
  • request.GET
  • request.COOKIES

剩下的如果要用就要自己提取處理了。比如打印出的結構中有一條是顯示用戶訪問使用的終端的信息的:

HTTP_USER_AGENT: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299

通過這個信息可以知道用戶是用什麽終端發來的請求,可以知道用戶用的是iPhong還是用安卓系統。還可以判斷用戶是手機端就發回給一個手機端的頁面。如果是PC端就返回一個PC端的頁面。這是一個字典,用 request.environ[‘HTTP_USER_AGENT‘] 就可以獲取到這個信息了。

補充知識-模板(Templates )

模板的繼承-extends

首先先寫一個完整的html頁面,master.html:

<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        body{
            margin: 0;
        }
        .pg-header{
            height: 48px;
            background-color: red;
        }
        .pg-content .menu{
            position: absolute;
            top: 48px;
            left: 0;
            width: 200px;
            background-color: blue;
            color: white;
        }
        .pg-content .content{
            position: absolute;
            top: 48px;
            right: 0;
            left: 200px;
            background-color: yellow;
            /*overflow: auto;*/
            /*bottom: 0;*/
        }
    </style>
    {% block css %} {% endblock %}
</head>
<body>
<div class="pg-header"></div>
<div class="pg-content">
    <div class="menu">
        <script>
            for (var i=0; i<10; i++){
                document.writeln("<p>菜單</p>")
            }
        </script>
    </div>
    {% block content %}
    <div class="content">
        <script>
            for (var i=0; i<100; i++){
                document.writeln("<p>test</p>")
            }
        </script>
    </div>
    {% endblock %}
</div>
{% block js %} {% endblock %}
</body>

也面裏加了幾個模板語言的block標簽,這個並比影響這個頁面的訪問。去寫好對應關系和處理函數後這個頁面就能正常訪問了。
現在又有另外幾個頁面,頁面的頭部以及左側菜單都是一樣的,就不需要每個頁面都寫了。可以繼承這個頁面僅僅替換掉需要替換的部分,也就是content的內容。這裏已經把content的內容用 {% block content %} {% endblock %} 包起來了。
新的頁面tpl1.html,首先要聲明繼承哪個頁面,然後把要替換的內容用相同名字的block標簽包起來:

{#聲明繼承一個頁面#}
{% extends ‘master.html‘ %}

{#追加css的內容#}
{% block css %}
    <style>
        .pg-content .tpl1{
            position: absolute;
            top: 48px;
            right: 0;
            left: 200px;
            background-color: brown;
            color: white;
        }
    </style>
{% endblock %}

{#替換掉content的內容#}
{% block content %}
    <div class="tpl1">
        <span>替換掉原來的內容</span>
    </div>
{% endblock %}

{#追加js的內容#}
{% block js %}
    <script>
        alert(‘測試js‘)
    </script>
{% endblock %}

如上面的例子中那樣,把需要替換的一個或多個部分用block包起來。
註意css(style標簽)和js(script標簽),一般也都會需要追加css樣式和js。css就接在模板的css後面寫,js就還是寫在最後的位置,如果有jQuery,必須要在導入jQuery靜態文件的後面。
只能繼承一個模板,不能繼承多個。

模板的導入-include

這次寫一個組件的html代碼tag.html,比如這樣:

<h1>這是一個小組件</h1>
<h2>沒有什麽內容</h2>

然後在去寫完成的頁面,在頁面裏用模板語言的include標簽導入這個組件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% include ‘tag.html‘ %}
{% include ‘tag.html‘ %}
{% include ‘tag.html‘ %}
</body>
</html>

上面的例子中看到了,模板的導入可以導入多次的。實際的應用中可能會結合模板語言的for循環,每條數據都通過這個組件渲染然後輸出到頁面。

內置函數

在頁面裏使用雙大括號 {{ value }} 取值的時候,還可以加上管道符,對結果進行處理後在輸出。
比如輸出一個日期,首先得先有一個日期對象輸出到頁面:

def get_data(request):
    import datetime
    now = datetime.datetime.now()
    print(now)
    return render(request, ‘get-data.html‘, {‘now‘: now})

然後頁面裏使用模板語言可以對日期的格式進行設置:

<h3>默認的日期格式:{{ now }}</h3>
<h3>模板語句加工後:{{ now|date:"Y-m-d H:i:s" }}</h3>

其他的方法還有
{{ str|truncatechars:‘10‘ }} :只輸出10個字符,實際是只截取了前面7個字符,之後跟3個點(...)
使用內置函數不是重點,重點是可以自定義函數

自定義函數

要自定義函數,按照下面的步驟操作:

  1. 在APP下,創建templatetags目錄,目錄名字很重要不能錯。
  2. 創建任意 .py 文件,這裏文件名隨意,比如:myfun.py。
  3. 文件裏創建一個template.Library()對象,名字是register。這裏的對象名字必須是register。
  4. 然後寫自己的函數,但是都用@register.simple_tag這個裝飾器裝飾好:
from django import template

register = template.Library()

@register.simple_tag
def my_fun1(a1, a2, a3):
    return "%s-%s-%s" % (a1, a2, a3)

@register.simple_tag
def my_fun2():
    return "my_fun2"
  1. 頁面文件中加載你的文件{% load myfun %}。放在頂部就好了。只要在你使用前加載加可以,不一定要在上面。如果有extends({% extends ‘master.html‘ %}),放在extends的下面。
  2. 使用你的函數 {% 函數名 參數1 參數2 %}
{% load myfun %}
<h3>{% my_fun1 ‘A‘ ‘B‘ ‘C‘ %}</h3>
<h3>{% my_fun2 %}</h3>

上面的是simple_tag,使用裝飾器 @register.simple_tag
還有一種filter,只要換成這個裝飾器 @register.filter

from django import template

register = template.Library()

@register.simple_tag
def my_fun3(arg1, arg2):
    return "%s: %s" % (arg1, arg2)

@register.filter
def my_fun4(arg1, arg2):
    return "%s: %s" % (arg1, arg2)

之後在頁面裏使用的時候,傳參的方式也是不同的,並且filter最多只能傳入2個參數,並且中間連空格都不能有:

<h3>{% my_fun3 ‘abc‘ ‘123‘ %}</h3>
<h3>{{ ‘abc‘|my_fun4:‘123‘ }}</h3>

如果一定要用filter,並且還要傳入多個參數,只能自己處理了,比如 {{ ‘abc‘|my_fun4:‘123,456,789‘ }} 這樣還是傳2個參數,在我們自己的函數裏做分割處理。
只傳入一個參數也是可以的,第二個不寫就好了。但是不能沒參數。像 {{ my_fun }} 這樣的用法是獲取通過 return render() 給的字典裏查找這個key來獲取值。
單獨使用,明顯是simple_tag更方便,參數沒有限制。
但是filter能夠作為if的條件:

{% if ‘abc‘|my_fun4:‘123‘ == ‘abc: 123‘ %}
    <h1>filter能夠作為if的條件</h1>
{% endif %}

上面傳參都是加了引號,表示傳入的是字符串。不加引號,就是直接傳入數字,類型是int。
也可以傳入變量或者嵌套使用,比如處理函數最後這樣返回 return render(request, ‘my-fun.html‘, {‘str‘: "987"})

<h3>{{ ‘abc‘|my_fun4:‘def‘|my_fun4:‘456‘ }}</h3>
<h3>{{ ‘xyz‘|my_fun4:str }}</h3>

示例-分頁

先把頁面搞出來,比如要顯示100條數據,現在是一次全部顯示出來。
處理函數,views.py

LIST = range(100)  # 測試數據就不走數據庫了
def show_list(request):
    return render(request, ‘show-list.html‘, {‘li‘: li})

頁面,show-list.html

<ul>
    {% for item in li %}
        <li>{{ item }}</li>
    {% endfor %}
</ul>

把分頁的效果弄出來,修改處理函數,一次只返回頁面10條數據,通過get請求返回對應的頁數,默認返回第一頁。

LIST = range(100)  # 測試數據就不走數據庫了
def show_list(request):
    current_page = request.GET.get(‘p‘, 1)  # 設一個默認值,如果不提交p,默認就是1
    current_page = int(current_page)  # 通過get獲取到的是字符串,轉成數字
    start = (current_page-1)*10
    end = current_page*10
    li = LIST[start:end]
    page_str = """
    <a href=‘/show-list/?p=1‘>1</a>
    <a href=‘/show-list/?p=2‘>2</a>
    <a href=‘/show-list/?p=3‘>3</a>
    <a href=‘/show-list/?p=4‘>4</a>
    <a href=‘/show-list/?p=5‘>5</a>
    """
    # from django.utils.safestring import mark_safe
    # page_str = mark_safe(page_str)
    return render(request, ‘show-list.html‘, {‘li‘: li, ‘page_str‘: page_str})

頁面也加上選擇頁數的a連接的內容:

<body>
<ul>
    {% for item in li %}
        <li>{{ item }}</li>
    {% endfor %}
</ul>
{{ page_str }}
{#{{ page_str|safe }}#}
</body>

這裏是有問題的,這裏的a連接的html代碼是處理函數傳過來了,之後在頁面裏再用模板語言把內容加載進來。這裏會有個XSS攻擊的問題。
XSS攻擊,大致就是你提供一個輸入框給用戶輸入內容,然後可以把用戶輸入的內容在頁面中顯示出來,比如說論壇、評論。那麽用戶可以在這裏輸入源碼,比如js腳本,然後你的後臺直接不做處理就把代碼返回給前端,那麽前端就可能執行這段代碼了。
所以默認模板語言認為加載的內容都是不安全的,所以都作為字符串加載。有2中方法可以聲明這段內容是安全的,那麽就能正常的在前端按照我們寫的標簽的樣子展示出來。

  • 方法一:在處理函數裏使用 mark_safe(page_str) 來轉一下,使用前先導入模塊 from django.utils.safestring import mark_safe
  • 方法二:在前端的模板語言裏,在字符串後面使用管道符調用一個內置的filter方法,{{ page_str|safe }}

兩種方法可以任選一種使用,在例子裏都註釋掉了,現在可以放開其中一種方法。兩個方法一起用試下來也不會報錯

根據數據的數量,自動計算一共有多少頁

from django.utils.safestring import mark_safe
LIST = range(100)  # 測試數據就不走數據庫了
def show_list(request):
    current_page = request.GET.get(‘p‘, 1)  # 設一個默認值,如果不提交p,默認就是1
    current_page = int(current_page)  # 通過get獲取到的是字符串,轉成數字
    start = (current_page-1)*10
    end = current_page*10
    li = LIST[start:end]
    count, remainder = divmod(len(LIST), 10)  # 取除後的整數和余數
    count = count+1 if remainder else count  # 如果除後有余數,結果就加一
    page_list = []
    for i in range(1, count+1):
        page_list.append("<a style=‘margin: 0 3px‘‘ href=‘/show-list/?p=%s‘>%s</a>" % (i, i))
    page_str = ‘‘.join(page_list)
    page_str = mark_safe(page_str)
    return render(request, ‘show-list.html‘, {‘li‘: li, ‘page_str‘: page_str})

如果不是100條數據,而是1000條甚至更多,那麽下面的頁碼也會很多。實際應用中,一般值顯示當前頁以及前面後後面多少頁,而不是所有的頁碼。簡單優化一下:

from django.utils.safestring import mark_safe

LIST = range(1100)  # 測試數據就不走數據庫了
def show_list(request):
    current_page = request.GET.get(‘p‘, 1)  # 設一個默認值,如果不提交p,默認就是1
    current_page = int(current_page)  # 通過get獲取到的是字符串,轉成數字
    per_page_count = 15  # 每頁顯示多少條記錄,也是可以設置成通過get來提交的
    start = (current_page-1)*per_page_count
    end = current_page*per_page_count
    li = LIST[start:end] 
    count, remainder = divmod(len(LIST), per_page_count)  # 取除後的整數和余數
    count = count+1 if remainder else count  # 如果除後有余數,結果就加一
    start_index = 1 if current_page < 6 else current_page-5
    end_index = count if current_page+5 > count else current_page+5
    page_list = []
    for i in range(start_index, end_index+1):
        page_list.append("<a style=‘margin: 0 3px‘‘ href=‘/show-list/?p=%s‘>%s</a>" % (i, i))
    page_str = ‘‘.join(page_list)
    page_str = mark_safe(page_str)
    return render(request, ‘show-list.html‘, {‘li‘: li, ‘page_str‘: page_str})

另外上面還把每頁顯示多少條記錄也作為一個變量,並且可以通過get請求提交。
這裏固定顯示11頁,這個也可以作為一個標量,不寫死,方便調整
還可以進一步優化,比如前面加一個上一頁,後面加一個下一頁,還要直接去第一頁和最後一頁。還可以搞個input框,直接輸入頁碼,跳轉的那一頁。
下面是部分優化的版本,有上一頁和下一頁,固定顯示11頁(可用變量調整)內容:

from django.utils.safestring import mark_safe

LIST = range(1000)  # 測試數據就不走數據庫了
def show_list(request):
    current_page = request.GET.get(‘p‘, 1)  # 設一個默認值,如果不提交p,默認就是1
    current_page = int(current_page)  # 通過get獲取到的是字符串,轉成數字
    per_page_count = 15  # 每頁顯示多少條記錄,也是可以設置成通過get來提交的
    page_num = 11  # 每頁顯示11條記錄,可修改
    page_num_before = (page_num-1)//2  # 算出來前面放幾頁
    page_num_after = page_num-1-page_num_before  # 後面放幾頁
    start = (current_page-1)*per_page_count
    end = current_page*per_page_count
    li = LIST[start:end]
    count, remainder = divmod(len(LIST), per_page_count)  # 取除後的整數和余數
    count = count+1 if remainder else count  # 如果除後有余數,結果就加一
    if count <= page_num: page_num = count
    if current_page <= page_num_before:
        start_index, end_index = 1, page_num
    elif current_page + page_num_after >= count:
        start_index, end_index = count-page_num+1, count
    else:
        start_index = current_page - page_num_before
        end_index = start_index + page_num - 1
    page_list = list()
    if current_page == 1:
        page_list.append("<a style=‘margin: 0 3px‘‘ href=‘javascript:viod(0);‘>上一頁</a>")
    else:
        page_list.append("<a style=‘margin: 0 3px‘‘ href=‘/show-list/?p=%s‘>上一頁</a>" % (current_page-1))
    for i in range(start_index, end_index+1):
        page_list.append("<a style=‘margin: 0 3px‘‘ href=‘/show-list/?p=%s‘>%s</a>" % (i, i))
    if current_page == count:
        page_list.append("<a style=‘margin: 0 3px‘‘ href=‘javascript:viod(0);‘>下一頁</a>")
    else:
        page_list.append("<a style=‘margin: 0 3px‘‘ href=‘/show-list/?p=%s‘>下一頁</a>" % (current_page+1))
    page_str = ‘‘.join(page_list)
    page_str = mark_safe(page_str)
    return render(request, ‘show-list.html‘, {‘li‘: li, ‘page_str‘: page_str})

自定義功能模塊

上面的分頁功能代碼比較多(還可以繼續優化),而且別的頁面裏也會需要用到分頁的功能。把分頁的功能單獨提取出來,封裝到一個單獨的類裏,做成一個功能模塊。這種功能模塊也集中存放在一個文件夾裏,在項目目錄下創建utils文件夾,再創建一個py文件pagination.py作為模塊。要用的時候,處理函數裏只需要實例化這個類,調用類中的方法就可以了:

# 功能模塊 utils/pagination.py 中的內容

from django.utils.safestring import mark_safe

class Page:

    def __init__(self, current_page, data_count, per_page_count=10, page_num=11):
        self.current_page = current_page
        self.data_count = data_count
        self.per_page_count = per_page_count
        self.page_num = self.total_count if self.total_count <= page_num else page_num

        self.page_num_before = (self.page_num-1)//2
        self.page_num_after = self.page_num - 1 - self.page_num_before

    @property
    def start(self):
        return (self.current_page-1) * self.per_page_count

    @property
    def end(self):
        return self.current_page * self.per_page_count

    @property
    def total_count(self):
        count, remainder = divmod(self.data_count, self.per_page_count)
        return count + 1 if remainder else count

    def page_str(self, base_url):
        if self.current_page <= self.page_num_before:
            start_index, end_index = 1, self.page_num
        elif self.current_page + self.page_num_after >= self.total_count:
            start_index, end_index = self.total_count - self.page_num + 1, self.total_count
        else:
            start_index = self.current_page - self.page_num_before
            end_index = start_index + self.page_num - 1
        page_list = list()
        if self.current_page == 1:
            page_list.append("<a style=‘margin: 0 3px‘‘ href=‘javascript:viod(0);‘>上一頁</a>")
        else:
            page_list.append("<a style=‘margin: 0 3px‘‘ href=‘%s?p=%s‘>上一頁</a>" % (base_url, self.current_page-1))
        for i in range(start_index, end_index+1):
            page_list.append("<a style=‘margin: 0 3px‘‘ href=‘%s?p=%s‘>%s</a>" % (base_url, i, i))
        if self.current_page == self.total_count:
            page_list.append("<a style=‘margin: 0 3px‘‘ href=‘javascript:viod(0);‘>下一頁</a>")
        else:
            page_list.append("<a style=‘margin: 0 3px‘‘ href=‘%s?p=%s‘>下一頁</a>" % (base_url, self.current_page+1))
        page_str = ‘‘.join(page_list)
        return mark_safe(page_str)

# 處理函數 views.py 中的內容
LIST = range(1000)  # 測試數據就不走數據庫了
from utils import pagination
def show_list(request):
    current_page = request.GET.get(‘p‘, 1)  # 設一個默認值,如果不提交p,默認就是1
    current_page = int(current_page)  # 通過get獲取到的是字符串,轉成數字
    page_obj = pagination.Page(current_page, len(LIST))
    li = LIST[page_obj.start:page_obj.end]
    page_str = page_obj.page_str(‘/show-list/‘)
    return render(request, ‘show-list.html‘, {‘li‘: li, ‘page_str‘: page_str})

Cookie

Cookie是存放在客戶端瀏覽器上的,以key, value 的形式保存

示例-登錄

這個例子中,先通過登錄頁面將登錄成功的用戶名發送給客戶端保存到cookie中。然後在歡迎頁面請求客戶的的cookie拿到客戶端登錄成功的用戶名。
先把如下的2個頁面做出來,login登錄頁面,登錄成功後跳轉到welcome。這次如果未登錄成功想要直接訪問welcome是不可以的,會跳轉到login頁面,要求登錄。

    path(‘login/‘, views.login),
    path(‘welcome/‘, views.welcome),

頁面簡單一點

<!-- 登錄頁面,login.html -->
<form action="/login/" method="POST">
    <p><input type="text" name="username" placeholder="用戶名"></p>
    <p><input type="password" name="password" placeholder="密碼"></p>
    <p><input type="submit" value="登錄"></p>
</form>

<!-- 歡迎頁面,welcome.html -->
<h1>Welcome, {{ username }}</h1>

處理函數:

# 測試數據不走數據庫了
user_info = {
    ‘user‘: {‘password‘: ‘user123‘},
    ‘test‘: {‘password‘: ‘test123‘},
}
def login(request):
    if request.method == ‘GET‘:
        return render(request, ‘login.html‘)
    if request.method == ‘POST‘:
        usr = request.POST.get(‘username‘)
        pwd = request.POST.get(‘password‘)
        dic = user_info.get(usr)
        if not dic:
            return render(request, ‘login.html‘, {‘error_msg‘: "用戶不存在"})
        if dic[‘password‘] == pwd:
            # return redirect(‘/welcome/‘)
            # 先不返回對象,把對象拿到,加上cookie的內容之後再返回給客戶端
            res = redirect(‘/welcome/‘)  # 這個是原本返回給客戶端對對象
            res.set_cookie(‘username‘, usr)  # 給返回的對象加上cookie的內容
            return res
        else:
            return render(request, ‘login.html‘, {‘error_msg‘: "用戶名或密碼錯誤"})

def welcome(request):
    print(type(request.COOKIES), request.COOKIES)  # 這就是一個字典
    user = request.COOKIES.get(‘username‘)  # 這步是像客戶端發請求,要求獲取這個值
    if not user:
        return redirect(‘/login/‘)
    return render(request, ‘welcome.html‘, {‘username‘: user})

嘗試直接訪問welcome,還是會跳轉到login。只有登錄成功後才會顯示歡迎頁面。這裏的用戶名是向客戶端的瀏覽器請求獲取到的。可以打開瀏覽器的F12開發人員工具在網絡裏查看到:
技術分享圖片

Cookie的語法

獲取Cookie
request.COOKIES.get(‘key‘)request.COOKIES[‘key‘] :這就是個字典,獲取值使用字典的操作
設置Cookie
設置就是在把對象返回給客戶端之前,先拿到這個對象:rep = render(request, ...) 或 rep = return HttpResponse(...)
拿到之後設置:rep.set_cookie(key,value) 參數不止這兩個,所有參數如下:

  • key :鍵
  • value=‘‘ :值
  • max_age=None :超時時間,單位是秒。不設置就是瀏覽器關閉就馬上失效
  • expires=None :超時時間節點,設置一個具體的日期。rep.set_cookie(key, value, expires=datetime.datetime.strptime(‘2018-4-11 11:23:00‘, ‘%Y-%m-%d %H:%M:%S‘)) ,記得先 import datetime 模塊
  • path=‘/‘ :Cookie生效的路徑,/ 表示根路徑,根路徑的cookie可以被任何url的頁面訪問
  • domain=None :Cookie生效的域名
  • secure=False :https傳輸,如果網站是https的,寫cookie的時候把這個參數設置為True
  • httponly=Fals :只能http協議傳輸,無法被JavaScript獲取(不是絕對,底層抓包可以獲取到也可以被覆蓋)。document.cookie 獲取不到設置了這個參數的cookie。

如果要註銷,清除cookie,那麽就 rep.set_cookie(key) ,給你的key設置個空值,並且超時時間設置為馬上失效。
客戶端操作Cookie

  • document.cookie :獲取到cookie,返回的是字符串 "key1=value1; key2=value2"
  • document.cookie = "key=value;" :添加一個cookie的鍵值,其他參數也能加,不過說是很麻煩,就沒講。

jQuery有一個插件,叫jQuery.cookie,可以方便的操作cookie。要使用就先去把js文件加載到你的頁面:

  • $.cookie(key) :獲取值
  • $.cookie(key, vaule) :設置值
  • $.cookie(key, vaule, {options}) :其他參數都以字典的形式寫在第三個參數裏

分頁-定制每頁顯示的數量

利用cookie,在上前面的分頁的例子的基礎上,增加一個功能,用戶可以定制每頁顯示多少條數據。Web界面上值需要追加一個select框,然後綁定事件:

<select id="page-size" onchange="changPageSize(this)">
    <option value=10>10</option>
    <option value=20>20</option>
    <option value=30>30</option>
    <option value=50>50</option>
</select>
<script src="/static/js/jquery-1.12.4.js"></script>
<script src="/static/js/jquery.cookie.js"></script>
<script>
    $(function () {
        // 當頁面框架加載完成後,要獲取一個cookie的值,設置select框的默認選項
        var per_page_count = $.cookie(‘per_page_count‘);  // 獲取cookie值
        per_page_count && $(‘#page-size‘).val(per_page_count);  // 設置select的選項,獲取不到這個cookie就不設置了
    });
    function changPageSize(ths) {
        var per_page_count = $(ths).val();
        $.cookie(‘per_page_count‘, per_page_count, {‘path‘: ‘/show-list/‘});
        location.reload()
    }
</script>

上面給select框綁定了一個事件,當選項改變時,會獲取當前select的值,然後將這個值傳給cookie。這裏限定了生效的路徑,只有這個頁面按這個cookie的值顯示數據的數量,如果還有別的頁面取不到這個值。貌似沒什麽卵用,別的頁面如果有同樣需求,再開一個cookie的key記錄就好了,而且你加了不同的path參數,修改的應該還是用個key的內容
上面還有一段當頁面加載完成後要執行的代碼,沒有這個會有點小問題,就是你的select選項永遠是10,但是你的頁面設置中cookie裏取到的可能是20。就是select選項沒有同步,並且會造成你無法把顯示數量設置成10。
接下來去處理函數出稍加修改:

def show_list(request):
def show_list(request):
    # 增加內容:獲取cookie的值
    per_page_count = request.COOKIES.get(‘per_page_count‘, ‘10‘)  # 獲取cookie的值
    per_page_count = int(per_page_count)  # 獲取到的永遠是字符串,轉為整數
    # print(type(per_page_count), per_page_count)
    current_page = request.GET.get(‘p‘, 1)
    current_page = int(current_page)
    # 實例化類的時候傳入獲取的值
    page_obj = pagination.Page(current_page, len(LIST), per_page_count)
    li = LIST[page_obj.start:page_obj.end]
    page_str = page_obj.page_str(‘/show-list/‘)
    return render(request, ‘show-list.html‘, {‘li‘: li, ‘page_str‘: page_str})

用下來發現還有點用戶體驗的小問題,這個得去修改類了,不過這不是重點。重點就是學習掌握cookie的操作。

加密的Cookie

就是帶簽名的cookie。之前使用的cookie都是明文保存在客戶端的,還可以對cookie加密。

  • request.get_signed_cookie(key, default=RAISE_ERROR, salt=‘‘, max_age=None) :獲取加密cookie,max_age是後臺控制過期時間
  • rep.set_signed_cookie(key, value, salt=‘‘, **kwargs) :設置加密cookie

用的時候基本就是換個方法的名字,參數裏多加一個salt的關鍵參數。

裝飾器

上面登錄的例子中已經完成了登錄驗證的功能。實際應用中,很多頁面都需要登錄驗證,這就需要把驗證的功能獨立出來並且做成裝飾器。之後只要把其它處理函數裝飾上即可。

FBV的裝飾器

上面登錄例子中的welcome函數自帶了登錄驗證。現在welcome函數只需要完成顯示頁面的功能,驗證的功能交給裝飾器:

def auth(func):
    def inner(request, *args, **kwargs):
        # 裝飾的內容,驗證用戶是否已經登錄
        user = request.COOKIES.get(‘username‘)
        if not user:
            return redirect(‘/login/‘)
        # 裝飾的內容結束,下面調用執行被裝飾的函數
        return func(request, *args, **kwargs)
    return inner

@auth
def welcome(request):
    # 下面就是原來驗證的功能了,現在交給裝飾器
    # user = request.COOKIES.get(‘username‘)
    # if not user:
    #     return redirect(‘/login/‘)
    # 雖然裝飾器登錄驗證的時候已經獲取到user的值了,可以傳過來
    # 這裏不考慮裝飾器,自己獨立的完成處理函數的功能:獲取user,在頁面顯示出來
    user = request.COOKIES.get(‘username‘)
    return render(request, ‘welcome.html‘, {‘username‘: user})

CBV的裝飾器

CBV的裝飾器有2中情況。一種是只裝飾一個或部分方法,一種是裝飾整個類中的方法。裝飾器還是上面的裝飾器。
單獨裝飾一個方法

from django import views
from django.utils.decorators import method_decorator

class Order(views.View):

    @method_decorator(auth)
    def get(self, request):
        user = request.COOKIES.get(‘username‘)
        return render(request, ‘welcome.html‘, {‘username‘: user})

    def post(self, request):
        pass

裝飾類中的所有方法
這裏利用dispatch方法是在執行其他方法前執行。我們裝飾了dispatch方法,就阿是裝飾了其他方法

from django import views
from django.utils.decorators import method_decorator

class Order(views.View):

    @method_decorator(auth)
    def dispatch(self, request, *args, **kwargs):
        """簡單的繼承並重構這個方法,然後什麽都不改變,就為了加上裝飾器"""
        return super(Order, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        user = request.COOKIES.get(‘username‘)
        return render(request, ‘welcome.html‘, {‘username‘: user})

    def post(self, request):
        pass

裝飾整個類
裝飾的還是dispatch方法,把裝飾器寫在類上面

from django import views
from django.utils.decorators import method_decorator

@method_decorator(auth, name=‘dispatch‘)
class Order(views.View):

    def get(self, request):
        user = request.COOKIES.get(‘username‘)
        return render(request, ‘welcome.html‘, {‘username‘: user})

    def post(self, request):
        pass

Python自動化開發學習21-Django