Python自動化開發學習21-Django
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個點(...)
使用內置函數不是重點,重點是可以自定義函數
自定義函數
要自定義函數,按照下面的步驟操作:
- 在APP下,創建templatetags目錄,目錄名字很重要不能錯。
- 創建任意 .py 文件,這裏文件名隨意,比如:myfun.py。
- 文件裏創建一個template.Library()對象,名字是register。這裏的對象名字必須是register。
- 然後寫自己的函數,但是都用@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"
- 頁面文件中加載你的文件{% load myfun %}。放在頂部就好了。只要在你使用前加載加可以,不一定要在上面。如果有extends(
{% extends ‘master.html‘ %}
),放在extends的下面。 - 使用你的函數
{% 函數名 參數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的語法
獲取Cookierequest.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