一句惯、Cookie
- Cookie的介紹
1.Cookie的由來
大家都知道HTTP協(xié)議是無狀態(tài)的雨膨。
無狀態(tài)的意思是每次請求都是獨立的淹冰,它的執(zhí)行情況和結(jié)果與前面的請求和之后的請求都無直接關(guān)系购啄,它不會受前面的請求響應情況直接影響,也不會直接影響后面的請求響應情況肪获。
一句有意思的話來描述就是人生只如初見,對服務器來說柒傻,每次的請求都是全新的孝赫。
狀態(tài)可以理解為客戶端和服務器在某次會話中產(chǎn)生的數(shù)據(jù),那無狀態(tài)的就以為這些數(shù)據(jù)不會被保留红符。會話中產(chǎn)生的數(shù)據(jù)又是我們需要保存的青柄,也就是說要“保持狀態(tài)”。因此Cookie就是在這樣一個場景下誕生预侯。
2.什么是Cookie
Cookie具體指的是一段小信息致开,它是服務器發(fā)送出來存儲在瀏覽器上的一組組鍵值對,下次訪問服務器時瀏覽器會自動攜帶這些鍵值對萎馅,以便服務器提取有用信息双戳。
3.Cookie的原理
cookie的工作原理是:由服務器產(chǎn)生內(nèi)容,瀏覽器收到請求后保存在本地糜芳;當瀏覽器再次訪問時飒货,瀏覽器會自動帶上Cookie,這樣服務器就能通過Cookie的內(nèi)容來判斷這個是“誰”了峭竣。
4.查看Cookie
我們使用Chrome瀏覽器塘辅,打開開發(fā)者工具。
- Django中操作Cookie
1. 獲取Cookie
request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
參數(shù):
- default: 默認值
- salt: 加密鹽
- max_age: 后臺控制過期時間
2. 設(shè)置Cookie
rep = HttpResponse(...)
rep = render(request, ...)
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密鹽', max_age=None, ...)
return rep
參數(shù):
- key, 鍵
- value='', 值
- max_age=None, 超時時間
- expires=None, 超時時間(IE requires expires, so set it if hasn't been already.)
- path='/', Cookie生效的路徑皆撩,/ 表示根路徑扣墩,特殊的:根路徑的cookie可以被任何url的頁面訪問
- domain=None, Cookie生效的域名
- secure=False, https傳輸
- httponly=False 只能http協(xié)議傳輸,無法被JavaScript獲取(不是絕對呻惕,底層抓包可以獲取到也可以被覆蓋)
3. 刪除Cookie
def logout(request):
rep = redirect("/login/")
rep.delete_cookie("user") # 刪除用戶瀏覽器上之前設(shè)置的usercookie值
return rep
示例盘榨,使用cookie驗證登錄:
#這個是用來裝飾需要校驗的頁面
def check_login(func):
@wraps(func) #這里用wraps裝飾方法是python內(nèi)置的,目的是為了保持被裝飾方法原有的調(diào)用不變
def inner(request, *args, **kwargs):
next_url = request.get_full_path()
if request.get_signed_cookie("login", salt="SSS", default=None) == "yes":
# 已經(jīng)登錄的用戶...
return func(request, *args, **kwargs)
else:
# 沒有登錄的用戶蟆融,跳轉(zhuǎn)剛到登錄頁面
return redirect("/login/?next={}".format(next_url))
return inner
def login(request):
if request.method == "POST":
username = request.POST.get("username")
passwd = request.POST.get("password")
if username == "xxx" and passwd == "dashabi":
next_url = request.GET.get("next")
if next_url and next_url != "/logout/":
response = redirect(next_url)
else:
response = redirect("/class_list/")
response.set_signed_cookie("login", "yes", salt="SSS")
return response
return render(request, "login.html")
@check_login
def logout(request):
rep = redirect("/login/")
rep.delete_cookie("login") # 刪除用戶瀏覽器上之前設(shè)置的cookie值
return rep
@check_login
def index(request):
return render(request, "index.html")
二草巡、Session
- Session的介紹
Cookie雖然在一定程度上解決了“保持狀態(tài)”的需求,但是由于Cookie本身最大支持4096字節(jié)型酥,以及Cookie本身保存在客戶端山憨,可能被攔截或竊取,因此就需要有一種新的東西弥喉,它能支持更多的字節(jié)郁竟,并且他保存在服務器,有較高的安全性由境。這就是Session棚亩。
問題來了,基于HTTP協(xié)議的無狀態(tài)特征虏杰,服務器根本就不知道訪問者是“誰”讥蟆。那么上述的Cookie就起到橋接的作用。
我們可以給每個客戶端的Cookie分配一個唯一的id纺阔,這樣用戶在訪問時瘸彤,通過Cookie,服務器就知道來的人是“誰”笛钝。然后我們再根據(jù)不同的Cookie的id质况,在服務器上保存一段時間的私密資料,如“賬號密碼”等等玻靡。
總結(jié)而言:Cookie彌補了HTTP無狀態(tài)的不足结榄,讓服務器知道來的人是“誰”;但是Cookie以文本的形式保存在本地囤捻,自身安全性較差臼朗;所以我們就通過Cookie識別不同的用戶,對應的在Session里保存私密的信息以及超過4096字節(jié)的文本最蕾。
另外依溯,上述所說的Cookie和Session其實是共通性的東西,不限于語言和框架瘟则。
- Django中操作Session
1. 基操
request.session['k1'] # 獲取k1的值黎炉,不存在則報錯
request.session.get('k1',None) # 獲取k1的值,不存在則返回None
request.session['k1'] = 123 # 不存在則創(chuàng)建醋拧,存在則更新
request.session.setdefault('k1',123) # 不存在則創(chuàng)建默認值慷嗜,存在則不作操作
del request.session['k1'] # 刪除k1
鍵淀弹,值,鍵值對操作
request.session.keys() # 提取所有鍵
request.session.values() # 提取所有值
request.session.iterkeys() # 迭代鍵
request.session.itervalues() # 迭代值
request.session.iteritems() # 迭代鍵值
request.session.session_key # 用來獲取session字符串
request.session.clear_expired() # 清除所有已超過自身設(shè)定超時時間的session
request.session.exists('session_key') # 檢查session字串在數(shù)據(jù)庫中是否存在
request.session.delete('session_key') # 刪除當前用戶的所有session數(shù)據(jù)
request.session.clear() # 清除用戶的所有session數(shù)據(jù)庆械,用于注銷
# 刪除當前的會話數(shù)據(jù)并刪除會話的Cookie薇溃。
request.session.flush()
這用于確保前面的會話數(shù)據(jù)不可以再次被用戶的瀏覽器訪問
例如,django.contrib.auth.logout() 函數(shù)中就會調(diào)用它缭乘。
request.session.set_expiry(value): 設(shè)置session超時時間沐序,默認2周
# 如果value是個整數(shù),session會在value秒后失效,
# 如果value是個datatime或timedelta堕绩,session會在這個時間后失效
# 如果value是0策幼,用戶關(guān)閉游覽器session會失效
# 如果value是None,session會依賴全局session失效策略
2. Session流程解析
3. Django中的Session配置
Django中默認支持Session,其內(nèi)部提供了5種類型的Session供開發(fā)者使用奴紧。
1. 數(shù)據(jù)庫Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默認)
2. 緩存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
SESSION_CACHE_ALIAS = 'default'
# 使用的緩存別名(默認內(nèi)存緩存特姐,也可以是memcache),此處別名依賴緩存的設(shè)置
3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = None
# 緩存文件路徑黍氮,如果為None唐含,則使用tempfile模塊獲取一個臨時地址tempfile.gettempdir()
4. 緩存+數(shù)據(jù)庫
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎
5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎
其他公用設(shè)置項:
SESSION_COOKIE_NAME = "sessionid"
# Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串(默認)
SESSION_COOKIE_PATH = "/"
# Session的cookie保存的路徑(默認)
SESSION_COOKIE_DOMAIN = None
# Session的cookie保存的域名(默認)
SESSION_COOKIE_SECURE = False
# 是否Https傳輸cookie(默認)
SESSION_COOKIE_HTTPONLY = True
# 是否Session的cookie只支持http傳輸(默認)
SESSION_COOKIE_AGE = 1209600
# Session的cookie失效日期(2周)(默認)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
# 是否關(guān)閉瀏覽器使得Session過期(默認)
SESSION_SAVE_EVERY_REQUEST = False
# 是否每次請求都保存Session沫浆,默認修改之后才保存(默認)
4. Session版登陸驗證
from functools import wraps
def check_login(func):
@wraps(func)
def inner(request, *args, **kwargs):
next_url = request.get_full_path()
if request.session.get("user"):
return func(request, *args, **kwargs)
else:
return redirect("/login/?next={}".format(next_url))
return inner
def login(request):
if request.method == "POST":
user = request.POST.get("user")
pwd = request.POST.get("pwd")
if user == "alex" and pwd == "alex1234":
# 設(shè)置session
request.session["user"] = user
# 獲取跳到登陸頁面之前的URL
next_url = request.GET.get("next")
# 如果有捷枯,就跳轉(zhuǎn)回登陸之前的URL
if next_url:
return redirect(next_url)
# 否則默認跳轉(zhuǎn)到index頁面
else:
return redirect("/index/")
return render(request, "login.html")
@check_login
def logout(request):
# 刪除所有當前請求相關(guān)的session
request.session.delete()
return redirect("/login/")
@check_login
def index(request):
current_user = request.session.get("user", None)
return render(request, "index.html", {"user": current_user})
5. 補充:CBV加session驗證裝飾器
CBV實現(xiàn)的登錄視圖
class LoginView(View):
def get(self, request):
"""
處理GET請求
"""
return render(request, 'login.html')
def post(self, request):
"""
處理POST請求
"""
user = request.POST.get('user')
pwd = request.POST.get('pwd')
if user == 'alex' and pwd == "alex1234":
next_url = request.GET.get("next")
# 生成隨機字符串
# 寫瀏覽器cookie -> session_id: 隨機字符串
# 寫到服務端session:
# {
# "隨機字符串": {'user':'alex'}
# }
request.session['user'] = user
if next_url:
return redirect(next_url)
else:
return redirect('/index/')
return render(request, 'login.html')
要在CBV視圖中使用我們上面的check_login裝飾器,有以下三種方式:
from django.utils.decorators import method_decorator
1. 加在CBV視圖的get或post方法上
from django.utils.decorators import method_decorator
class HomeView(View):
def dispatch(self, request, *args, **kwargs):
return super(HomeView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return render(request, "home.html")
@method_decorator(check_login)
def post(self, request):
print("Home View POST method...")
return redirect("/index/")
2. 加在dispatch方法上
因為CBV中首先執(zhí)行的就是dispatch方法件缸,所以這么寫相當于給get和post方法都加上了登錄校驗铜靶。
from django.utils.decorators import method_decorator
class HomeView(View):
@method_decorator(check_login)
def dispatch(self, request, *args, **kwargs):
return super(HomeView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return render(request, "home.html")
def post(self, request):
print("Home View POST method...")
return redirect("/index/")
3. 直接加在視圖類上,但method_decorator必須傳 name 關(guān)鍵字參數(shù)
如果get方法和post方法都需要登錄校驗的話就寫兩個裝飾器他炊。
from django.utils.decorators import method_decorator
@method_decorator(check_login, name="get")
@method_decorator(check_login, name="post")
class HomeView(View):
def dispatch(self, request, *args, **kwargs):
return super(HomeView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return render(request, "home.html")
def post(self, request):
print("Home View POST method...")
return redirect("/index/")
CSRF Token相關(guān)裝飾器在CBV只能加到dispatch方法上,或者加在視圖類上然后name參數(shù)指定為dispatch方法已艰。
備注:
- csrf_protect痊末,為當前函數(shù)強制設(shè)置防跨站請求偽造功能,即便settings中沒有設(shè)置全局中間件哩掺。
- csrf_exempt凿叠,取消當前函數(shù)防跨站請求偽造功能,即便settings中設(shè)置了全局中間件嚼吞。
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
class HomeView(View):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(HomeView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return render(request, "home.html")
def post(self, request):
print("Home View POST method...")
return redirect("/index/")
或者:
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt, name='dispatch')
class HomeView(View):
def dispatch(self, request, *args, **kwargs):
return super(HomeView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return render(request, "home.html")
def post(self, request):
print("Home View POST method...")
return redirect("/index/")
三盒件、分頁
當數(shù)據(jù)庫中數(shù)據(jù)有很多,我們通常會在前端頁面做分頁展示舱禽。
分頁的數(shù)據(jù)可以在前端頁面實現(xiàn)炒刁,也可以在后端實現(xiàn)分頁。
后端實現(xiàn)分頁的原理就是每次只請求一頁數(shù)據(jù)誊稚。
準備工作
我們使用腳本批量創(chuàng)建一些測試數(shù)據(jù)(將下面的代碼保存到bulk_create.py文件中放到Django項目的根目錄翔始,直接執(zhí)行即可罗心。):
import os
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
import django
django.setup()
from app01 import models
bulk_obj = (models.Publisher(name='沙河第{}出版社'.format(i)) for i in range(300))
models.Publisher.objects.bulk_create(bulk_obj)
1. 自定義分頁
無封裝,直接調(diào)用版本:
def publisher_list(request):
# 從URL中取當前訪問的頁碼數(shù)
try:
current_page = int(request.GET.get('page'))
except Exception as e:
# 取不到或者頁碼數(shù)不是數(shù)字都默認展示第1頁
current_page = 1
# 總數(shù)據(jù)量
total_count = models.Publisher.objects.count()
# 定義每頁顯示多少條數(shù)據(jù)
per_page = 10
# 計算出總頁碼數(shù)
total_page, more = divmod(total_count, per_page)
if more:
total_page += 1
# 定義頁面上最多顯示多少頁碼(為了左右對稱城瞎,一般設(shè)為奇數(shù))
max_show = 11
half_show = max_show // 2
# 計算一下頁面顯示的頁碼范圍
if total_page <= max_show: # 總頁碼數(shù)小于最大顯示頁碼數(shù)
page_start = 1
page_end = total_page
elif current_page + half_show >= total_page: # 右邊越界
page_end = total_page
page_start = total_page - max_show
elif current_page - half_show <= 1: # 左邊越界
page_start = 1
page_end = max_show
else: # 正常頁碼區(qū)間
page_start = current_page - half_show
page_end = current_page + half_show
# 數(shù)據(jù)索引起始位置
data_start = (current_page-1) * per_page
data_end = current_page * per_page
publisher_list = models.Publisher.objects.all()[data_start:data_end]
# 生成頁面上顯示的頁碼
page_html_list = []
page_html_list.append('<nav aria-label="Page navigation"><ul class="pagination">')
# 加首頁
first_li = '<li><a href="/publisher_list/?page=1">首頁</a></li>'
page_html_list.append(first_li)
# 加上一頁
if current_page == 1:
prev_li = '<li><a href="#"><span aria-hidden="true">«</span></a></li>'
else:
prev_li = '<li><a href="/publisher_list/?page={}"><span aria-hidden="true">«</span></a></li>'.format(current_page - 1)
page_html_list.append(prev_li)
for i in range(page_start, page_end + 1):
if i == current_page:
li_tag = '<li class="active"><a href="/publisher_list/?page={0}">{0}</a></li>'.format(i)
else:
li_tag = '<li><a href="/publisher_list/?page={0}">{0}</a></li>'.format(i)
page_html_list.append(li_tag)
# 加下一頁
if current_page == total_page:
next_li = '<li><a href="#"><span aria-hidden="true">»</span></a></li>'
else:
next_li = '<li><a href="/publisher_list/?page={}"><span aria-hidden="true">»</span></a></li>'.format(current_page + 1)
page_html_list.append(next_li)
# 加尾頁
page_end_li = '<li><a href="/publisher_list/?page={}">尾頁</a></li>'.format(total_page)
page_html_list.append(page_end_li)
page_html_list.append('</ul></nav>')
page_html = "".join(page_html_list)
return render(request, "publisher_list.html", {"publisher_list": publisher_list, "page_html": page_html})
封裝保存版:
class Pagination(object):
"""自定義分頁(Bootstrap版)"""
def __init__(self, current_page, total_count, base_url, per_page=10, max_show=11):
"""
:param current_page: 當前請求的頁碼
:param total_count: 總數(shù)據(jù)量
:param base_url: 請求的URL
:param per_page: 每頁顯示的數(shù)據(jù)量渤闷,默認值為10
:param max_show: 頁面上最多顯示多少個頁碼,默認值為11
"""
try:
self.current_page = int(current_page)
except Exception as e:
# 取不到或者頁碼數(shù)不是數(shù)字都默認展示第1頁
self.current_page = 1
# 定義每頁顯示多少條數(shù)據(jù)
self.per_page = per_page
# 計算出總頁碼數(shù)
total_page, more = divmod(total_count, per_page)
if more:
total_page += 1
self.total_page = total_page
# 定義頁面上最多顯示多少頁碼(為了左右對稱脖镀,一般設(shè)為奇數(shù))
self.max_show = max_show
self.half_show = max_show // 2
self.base_url = base_url
@property
def start(self):
return (self.current_page-1) * self.per_page
@property
def end(self):
return self.current_page * self.per_page
def page_html(self):
# 計算一下頁面顯示的頁碼范圍
if self.total_page <= self.max_show: # 總頁碼數(shù)小于最大顯示頁碼數(shù)
page_start = 1
page_end = self.total_page
elif self.current_page + self.half_show >= self.total_page: # 右邊越界
page_end = self.total_page
page_start = self.total_page - self.max_show
elif self.current_page - self.half_show <= 1: # 左邊越界
page_start = 1
page_end = self.max_show
else: # 正常頁碼區(qū)間
page_start = self.current_page - self.half_show
page_end = self.current_page + self.half_show
# 生成頁面上顯示的頁碼
page_html_list = []
page_html_list.append('<nav aria-label="Page navigation"><ul class="pagination">')
# 加首頁
first_li = '<li><a href="{}?page=1">首頁</a></li>'.format(self.base_url)
page_html_list.append(first_li)
# 加上一頁
if self.current_page == 1:
prev_li = '<li><a href="#"><span aria-hidden="true">«</span></a></li>'
else:
prev_li = '<li><a href="{}?page={}"><span aria-hidden="true">«</span></a></li>'.format(
self.base_url, self.current_page - 1)
page_html_list.append(prev_li)
for i in range(page_start, page_end + 1):
if i == self.current_page:
li_tag = '<li class="active"><a href="{0}?page={1}">{1}</a></li>'.format(self.base_url, i)
else:
li_tag = '<li><a href="{0}?page={1}">{1}</a></li>'.format(self.base_url, i)
page_html_list.append(li_tag)
# 加下一頁
if self.current_page == self.total_page:
next_li = '<li><a href="#"><span aria-hidden="true">»</span></a></li>'
else:
next_li = '<li><a href="{}?page={}"><span aria-hidden="true">»</span></a></li>'.format(
self.base_url, self.current_page + 1)
page_html_list.append(next_li)
# 加尾頁
page_end_li = '<li><a href="{}?page={}">尾頁</a></li>'.format(self.base_url, self.total_page)
page_html_list.append(page_end_li)
page_html_list.append('</ul></nav>')
return "".join(page_html_list)
封裝保存版調(diào)用示例:
def publisher_list(request):
# 從URL中取當前訪問的頁碼數(shù)
current_page = int(request.GET.get('page'))
# 比len(models.Publisher.objects.all())更高效
total_count = models.Publisher.objects.count()
page_obj = Pagination(current_page, total_count, request.path_info)
data = models.Publisher.objects.all()[page_obj.start:page_obj.end]
page_html = page_obj.page_html()
return render(request, "publisher_list.html", {"publisher_list": data, "page_html": page_html})
封裝保存版html部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>書籍列表</title>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<table class="table table-bordered">
<thead>
<tr>
<th>序號</th>
<th>id</th>
<th>書名</th>
</tr>
</thead>
<tbody>
{% for book in books %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ book.id }}</td>
<td>{{ book.title }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<nav aria-label="Page navigation">
<ul class="pagination">
{{ page_html|safe }}
</ul>
</nav>
</div>
</body>
</html>
2. Django內(nèi)置分頁
view部分
from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
L = []
for i in range(999):
L.append(i)
def index(request):
current_page = request.GET.get('p')
paginator = Paginator(L, 10)
# per_page: 每頁顯示條目數(shù)量
# count: 數(shù)據(jù)總個數(shù)
# num_pages:總頁數(shù)
# page_range:總頁數(shù)的索引范圍飒箭,如: (1,10),(1,200)
# page: page對象
try:
posts = paginator.page(current_page)
# has_next 是否有下一頁
# next_page_number 下一頁頁碼
# has_previous 是否有上一頁
# previous_page_number 上一頁頁碼
# object_list 分頁之后的數(shù)據(jù)列表
# number 當前頁
# paginator paginator對象
except PageNotAnInteger:
posts = paginator.page(1)
except EmptyPage:
posts = paginator.page(paginator.num_pages)
return render(request, 'index.html', {'posts': posts})
html部分
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<ul>
{% for item in posts %}
<li>{{ item }}</li>
{% endfor %}
</ul>
<div class="pagination">
<span class="step-links">
{% if posts.has_previous %}
<a href="?p={{ posts.previous_page_number }}">Previous</a>
{% endif %}
<span class="current">
Page {{ posts.number }} of {{ posts.paginator.num_pages }}.
</span>
{% if posts.has_next %}
<a href="?p={{ posts.next_page_number }}">Next</a>
{% endif %}
</span>
</div>
</body>
</html>