中間件 Middleware
中間件是 Django 請求/響應(yīng)處理的鉤子框架免糕。它是一個(gè)輕量級的、低級的“插件”系統(tǒng)痒给,用于全局改變 Django 的輸入或輸出说墨。
每個(gè)中間件組件負(fù)責(zé)做一些特定的功能骏全。例如苍柏,Django 包含一個(gè)中間件組件 AuthenticationMiddleware,它使用會(huì)話將用戶與請求關(guān)聯(lián)起來姜贡。
他的文檔解釋了中間件是如何工作的试吁,如何激活中間件,以及如何編寫自己的中間件楼咳。Django 具有一些內(nèi)置的中間件熄捍,你可以直接使用。它們被記錄在 built-in middleware reference 中母怜。
-
中間件類:
- 中間件類須繼承自
django.utils.deprecation.MiddlewareMixin
類 - 中間件類須實(shí)現(xiàn)下列五個(gè)方法中的一個(gè)或多個(gè):
def process_request(self, request):
執(zhí)行視圖之前被調(diào)用余耽,在每個(gè)請求上調(diào)用,返回None或HttpResponse對象def process_view(self, request, callback, callback_args, callback_kwargs):
調(diào)用視圖之前被調(diào)用苹熏,在每個(gè)請求上調(diào)用碟贾,返回None或HttpResponse對象def process_response(self, request, response):
所有響應(yīng)返回瀏覽器之前被調(diào)用,在每個(gè)請求上調(diào)用轨域,返回HttpResponse對象def process_exception(self, request, exception):
當(dāng)處理過程中拋出異常時(shí)調(diào)用袱耽,返回一個(gè)HttpResponse對象def process_template_response(self, request, response):
在視圖剛好執(zhí)行完畢之后被調(diào)用,在每個(gè)請求上調(diào)用干发,返回實(shí)現(xiàn)了render方法的響應(yīng)對象
- 注: 中間件中的大多數(shù)方法在返回None時(shí)表示忽略當(dāng)前操作進(jìn)入下一項(xiàng)事件朱巨,當(dāng)返回HttpResponese對象時(shí)表示此請求結(jié)果,直接返回給客戶端
- 中間件類須繼承自
編寫中間件類:
# file : middleware/mymiddleware.py
from django.http import HttpResponse, Http404
from django.utils.deprecation import MiddlewareMixin
class MyMiddleWare(MiddlewareMixin):
def process_request(self, request):
print("中間件方法 process_request 被調(diào)用")
def process_view(self, request, callback, callback_args, callback_kwargs):
print("中間件方法 process_view 被調(diào)用")
def process_response(self, request, response):
print("中間件方法 process_response 被調(diào)用")
return response
def process_exception(self, request, exception):
print("中間件方法 process_exception 被調(diào)用")
def process_template_response(self, request, response):
print("中間件方法 process_template_response 被調(diào)用")
return response
- 注冊中間件:
# file : settings.py
MIDDLEWARE = [
...
'middleware.mymiddleware.MyMiddleWare',
]
-
中間件的執(zhí)行過程
middleware.jpeg
- 練習(xí)
- 用中間件實(shí)現(xiàn)強(qiáng)制某個(gè)IP地址只能向/test 發(fā)送一次GET請求
- 提示:
- request.META['REMOTE_ADDR'] 可以得到遠(yuǎn)程客戶端的IP地址
- request.path_info 可以得到客戶端訪問的GET請求路由信息
- 答案:
from django.http import HttpResponse, Http404 from django.utils.deprecation import MiddlewareMixin import re class VisitLimit(MiddlewareMixin): '''此中間件限制一個(gè)IP地址對應(yīng)的訪問/user/login 的次數(shù)不能改過10次,超過后禁止使用''' visit_times = {} # 此字典用于記錄客戶端IP地址有訪問次數(shù) def process_request(self, request): ip_address = request.META['REMOTE_ADDR'] # 得到IP地址 if not re.match('^/test', request.path_info): return times = self.visit_times.get(ip_address, 0) print("IP:", ip_address, '已經(jīng)訪問過', times, '次!:', request.path_info) self.visit_times[ip_address] = times + 1 if times < 5: return return HttpResponse('你已經(jīng)訪問過' + str(times) + '次枉长,您被禁止了')
跨站請求偽造保護(hù) CSRF
- 跨站請求偽造攻擊
- 某些惡意網(wǎng)站上包含鏈接冀续、表單按鈕或者JavaScript琼讽,它們會(huì)利用登錄過的用戶在瀏覽器中的認(rèn)證信息試圖在你的網(wǎng)站上完成某些操作,這就是跨站請求偽造洪唐。
- CSRF
Cross-Site Request Forgey 跨 站點(diǎn) 請求 偽裝
- 說明:
- CSRF中間件和模板標(biāo)簽提供對跨站請求偽造簡單易用的防護(hù)跨琳。
- 作用:
- 不讓其它表單提交到此 Django 服務(wù)器
- 解決方案:
- 取消 csrf 驗(yàn)證(不推薦)
- 刪除 settings.py 中 MIDDLEWARE 中的
django.middleware.csrf.CsrfViewMiddleware
的中間件
- 刪除 settings.py 中 MIDDLEWARE 中的
- 開放驗(yàn)證
在視圖處理函數(shù)增加: @csrf_protect @csrf_protect def post_views(request): pass
- 通過驗(yàn)證
需要在表單中增加一個(gè)標(biāo)簽 {% csrf_token %}
- 練習(xí): 項(xiàng)目的注冊部分
- 創(chuàng)建一個(gè)數(shù)據(jù)庫 - FruitDay
- 創(chuàng)建實(shí)體類 - Users
- uphone - varchar(11)
- upwd - varchar(50)
- uemail - varchar(245)
- uname - varchar(20)
- isActive - tinyint 默認(rèn)值為1 (True)
- 完善注冊 - /register/
- 如果是get請求,則去往register.html
- 如果是post請求,則處理請求數(shù)據(jù)
將提交的數(shù)據(jù)保存回?cái)?shù)據(jù)庫
- 取消 csrf 驗(yàn)證(不推薦)
Django中的forms模塊
- 在Django中提供了 forms 模塊,用forms 模塊可以自動(dòng)生成form內(nèi)部的表單控件,同時(shí)在服務(wù)器端可以用對象的形式接收并操作客戶端表單元素,并能對表單的數(shù)據(jù)進(jìn)行服務(wù)器端驗(yàn)證
-
forms模塊的作用
- 通過 forms 模塊,允許將表單與class相結(jié)合桐罕,允許通過 class 生成表單
-
使用 forms 模塊的步驟
在應(yīng)用中創(chuàng)建 forms.py
-
導(dǎo)入 django 提供的 forms
- from django import forms
-
創(chuàng)建class,一個(gè)class會(huì)生成一個(gè)表單
- 定義表單類
class ClassName(forms.Form): ...
- 定義表單類
-
在 class 中創(chuàng)建類屬性
- 一個(gè)類屬性對應(yīng)到表單中是一個(gè)控件
利用Form 類型的對象自動(dòng)成表單內(nèi)容
讀取form表單并進(jìn)行驗(yàn)證數(shù)據(jù)
-
forms.Form 的語法
- 屬性 = forms.Field類型(參數(shù))
- 類型
class XXX(froms.Form): forms.CharField() : 文本框 <input type="text"> forms.ChoiceField() : 下拉選項(xiàng)框 <select> forms.DateField() : 日期框 <input type="date"> ... ...
- 參數(shù)
- label
- 控件前的文本
- widget
- 指定小部件
- initial
- 控件的初始值(主要針對文本框類型)
- required
- 是否為必填項(xiàng)脉让,值為(True/False)
- label
- form 表單示例
-
手動(dòng)實(shí)現(xiàn)Form 表單
<form action="/test_form1" method="post"> <div> <label for="id_input_text">請輸入內(nèi)容:</label> <input type="text" name="input_text" id="id_input_text" /> </div> <button type="submit">提交</button> </form>
-
Django Form 實(shí)現(xiàn) Form 表單
class MySearch(forms.Form): input_text = forms.CharField(label = '請輸入內(nèi)容')
-
-
在模板中解析form對象
-
方法
- 需要自定義 <form>
- 表單中的按鈕需要自定義
-
解析form對
在 視圖中創(chuàng)建form對象并發(fā)送到模板中解析. ex: form = XXXForm() return render(request,'xx.html',locals())
- 手動(dòng)解析
{% for field in form %}
field : 表示的是form對象中的每個(gè)屬性(控件)
{{field.label}} : 表示的是label參數(shù)值
{{field}} : 表示的就是控件
{% endfor %}
- 手動(dòng)解析
-
自動(dòng)解析
- {{form.as_p}}
將 form 中的每個(gè)屬性(控件/文本)都使用p標(biāo)記包裹起來再顯示
- {{form.as_ul}}
將 form 中的每個(gè)屬性(控件/文本)都使用li標(biāo)記包裹起來再顯示 注意:必須手動(dòng)提供ol 或 ul 標(biāo)記
- {{form.as_table}}
將 form 中的每個(gè)屬性(控件/文本)都使用tr標(biāo)記包裹起來再顯示 注意:必須手動(dòng)提供table標(biāo)記
- {{form.as_p}}
- 練習(xí):
- 創(chuàng)建一個(gè)注冊Form類 - RegisterForm
- username - 用戶名稱
- password - 用戶密碼(文本框)
- password2 - 重復(fù)用戶密碼(文本框)
- phonenumber - 用戶年齡(數(shù)字框)
- email - 電子郵箱
2.創(chuàng)建 register 路由 - get 請求 :
- 創(chuàng)建 RegisterForm 對象并發(fā)送到 模板register.html中顯示
- post 請求:
- 接收13-register.html 中的數(shù)據(jù)并輸出
- 創(chuàng)建一個(gè)注冊Form類 - RegisterForm
- 通過 forms 對象獲取表單數(shù)據(jù)
- 通過 forms.Form 子類的構(gòu)造器來接收 post 數(shù)據(jù)
- form = XXXForm(request.POST)
- 必須是 form 通過驗(yàn)證后,才能取值
- form.is_valid()
- 返回True:通過驗(yàn)證,可以取值
- 返回False:暫未通過驗(yàn)證,則不能取值
- form.is_valid()
- 通過 form.cleaned_data 字典的屬性接收數(shù)據(jù)
- form.cleaned_data : dict 類型
- 通過 forms.Form 子類的構(gòu)造器來接收 post 數(shù)據(jù)
-
-
Field 內(nèi)置小部件 - widget
- 什么是小部件
- 表示的是生成到網(wǎng)頁上的控件以及一些其他的html屬性
message=forms.CharField(widget=forms.Textarea) upwd=forms.CharField(widget=forms.PasswordInput)
- 常用的小部件類型
widget名稱 對應(yīng)和type類值 TextInput type='text' PasswordInput type='password' NumberInput type="number" EmailInput type="email" URLInput type="url" HiddenInput type="hidden" CheckboxInput type="checkbox" CheckboxSelectMultiple type="checkbox" RadioSelect type="radio" Textarea textarea標(biāo)記 Select select標(biāo)記 SelectMultiple select multiple 標(biāo)記
- 什么是小部件
-
小部件的使用
- 繼承自forms.Form
- 基本版
- 語法
屬性 = forms.CharField() #無預(yù)選值使用 text,password,email,url,textarea,checkbox 屬性 = forms.ChoiceField() #有預(yù)選值使用 checkbox,radio,select 屬性 = forms.CharField( label='xxx', widget=forms.小部件類型 )
- 示例:
upwd = forms.CharField( label='用戶密碼', widget=forms.PasswordInput ) message = forms.CharField( label='評論內(nèi)容', widget=forms.Textarea )
- 語法
- 基本版
- 繼承自forms.Form
Django之form表單驗(yàn)證
django form 提供表單和字段驗(yàn)證
當(dāng)在創(chuàng)建有不同的多個(gè)表單需要提交的網(wǎng)站時(shí),用表單驗(yàn)證比較方便驗(yàn)證的封裝
當(dāng)調(diào)用form.is_valid() 返回True表示當(dāng)前表單合法功炮,當(dāng)返回False說明表單驗(yàn)證出現(xiàn)問題
-
驗(yàn)證步驟:
- 先對form.XXXField() 參數(shù)值進(jìn)行驗(yàn)證溅潜,比如:min_length,max_length, validators=[...],如果不符合form.is_valid()返回False
- 對各自from.clean_zzz屬性名(self): 方法對相應(yīng)屬性進(jìn)行驗(yàn)證,如果驗(yàn)證失敗form.is_valid()返回False
- 調(diào)胳form.clean(self): 對表單的整體結(jié)構(gòu)進(jìn)行驗(yàn)證,如果驗(yàn)證失敗form.is_valid()返回False
- 以上驗(yàn)證都成功 form.is_valid()返回True
-
驗(yàn)證方法:
- validators = [驗(yàn)證函數(shù)1, 驗(yàn)證函數(shù)1]
- 驗(yàn)證函數(shù)驗(yàn)證失敗拋出forms.ValidationError
- 驗(yàn)證成功返回None
- def clean_xxx屬性(self):
- 驗(yàn)證失敗必須拋出forms.ValidationError
- 驗(yàn)證成功必須返回xxx屬性的值
- def clean(self):
- 驗(yàn)證失敗必須拋出forms.ValidationError
- 驗(yàn)證成功必須返回 self.cleaned_data
- validators = [驗(yàn)證函數(shù)1, 驗(yàn)證函數(shù)1]
文檔參見https://docs.djangoproject.com/en/1.11/ref/forms/validation/
驗(yàn)證示例
from django import forms
import re
mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
def mobile_validate(value):
if not mobile_re.match(value):
raise forms.ValidationError('手機(jī)號碼格式錯(cuò)誤')
class RegisterForm(forms.Form):
username = forms.CharField(label='用戶名')
password = forms.CharField(label='請輸入密碼', widget=forms.PasswordInput)
password2 = forms.CharField(label='再次輸入新密碼', widget=forms.PasswordInput)
mobile = forms.CharField(label='電話號碼', validators=[mobile_validate])
def clean(self):
pwd1 = self.cleaned_data['password']
pwd2 = self.cleaned_data['password2']
if pwd1 != pwd2:
raise forms.ValidationError('兩次密碼不一致!')
return self.cleaned_data # 必須返回cleaned_data
def clean_username(self):
username = self.cleaned_data['username']
if len(username) < 6:
raise forms.ValidationError("用戶名太短")
return username
- 練習(xí)薪伏,寫一個(gè)RegisterForm表單類型,要求如下四個(gè)屬性:
- username - 用戶名稱
- 用戶名只能包含[a-zA-Z_0_9]范圍內(nèi)的5~30個(gè)英文字符
- password - 用戶密碼(文本框)
- 任意字符滚澜,不能少于6個(gè)字符
- password2 - 重復(fù)用戶密碼(文本框)
- 任意字符,不能少于6個(gè)字符且必須與 password一致
- phonenumber - 用戶年齡(數(shù)字框)
- 必須符合
r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$'
正則表達(dá)式
- 必須符合
- username - 用戶名稱
分頁
- 分頁是指在web頁面有大量數(shù)據(jù)需要顯示時(shí)嫁怀,當(dāng)一頁的內(nèi)容太多不利于閱讀和不利于數(shù)據(jù)提取的情況下设捐,可以分為多頁進(jìn)行顯示。
- Django提供了一些類來幫助你管理分頁的數(shù)據(jù) — 也就是說塘淑,數(shù)據(jù)被分在不同頁面中萝招,并帶有“上一頁/下一頁”鏈接。
- 這些類位于django/core/paginator.py中存捺。
Paginator對象
-
對象的構(gòu)造方法
- Paginator(object_list, per_page)
- 參數(shù)
- object_list 對象列表
- per_page 每頁數(shù)據(jù)個(gè)數(shù)
- 返回值:
- 分頁對象
-
Paginator屬性
- count:對象總數(shù)
- num_pages:頁面總數(shù)
- page_range:從1開始的range對象, 用于記錄當(dāng)前面碼數(shù)
- per_page 每頁個(gè)數(shù)
-
Paginator方法
- Paginator.page(number)
- 參數(shù) number為頁碼信息(從1開始)
- 返回當(dāng)前number頁對應(yīng)的頁信息
- 如果提供的頁碼不存在槐沼,拋出InvalidPage異常
- Paginator.page(number)
-
Paginator異常exception
- InvalidPage:當(dāng)向page()傳入一個(gè)無效的頁碼時(shí)拋出
- PageNotAnInteger:當(dāng)向page()傳入一個(gè)不是整數(shù)的值時(shí)拋出
- EmptyPage:當(dāng)向page()提供一個(gè)有效值,但是那個(gè)頁面上沒有任何對象時(shí)拋出
Page對象
創(chuàng)建對象
Paginator對象的page()方法返回Page對象捌治,不需要手動(dòng)構(gòu)造-
Page對象屬性
- object_list:當(dāng)前頁上所有對象的列表
- number:當(dāng)前頁的序號岗钩,從1開始
- paginator:當(dāng)前page對象相關(guān)的Paginator對象
-
Page對象方法
- has_next():如果有下一頁返回True
- has_previous():如果有上一頁返回True
- has_other_pages():如果有上一頁或下一頁返回True
- next_page_number():返回下一頁的頁碼,如果下一頁不存在肖油,拋出InvalidPage異常
- previous_page_number():返回上一頁的頁碼兼吓,如果上一頁不存在,拋出InvalidPage異常
- len():返回當(dāng)前頁面對象的個(gè)數(shù)
-
說明:
- Page 對象是可迭代對象,可以用 for 語句來 訪問當(dāng)前頁面中的每個(gè)對象
參考文檔https://docs.djangoproject.com/en/1.11/topics/pagination/
- 分頁示例:
- 視圖函數(shù)
def book(request): bks = models.Book.objects.all() paginator = Paginator(bks, 10) print('當(dāng)前對象的總個(gè)數(shù)是:', paginator.count) print('當(dāng)前對象的面碼范圍是:', paginator.page_range) print('總頁數(shù)是:', paginator.num_pages) print('每頁最大個(gè)數(shù):', paginator.per_page) cur_page = request.GET.get('page', 1) # 得到默認(rèn)的當(dāng)前頁 page = paginator.page(cur_page) return render(request, 'bookstore/book.html', locals())
- 模板設(shè)計(jì)
<html> <head> <title>分頁顯示</title> </head> <body> {% for b in page %} <div>{{ b.title }}</div> {% endfor %} {# 分頁功能 #} {# 上一頁功能 #} {% if page.has_previous %} <a href="{% url "book" %}?page={{ page.previous_page_number }}">上一頁</a> {% else %} 上一頁 {% endif %} {% for p in paginator.page_range %} {% if p == page.number %} {{ p }} {% else %} <a href="{% url "book" %}?page={{ p }}">{{ p }}</a> {% endif %} {% endfor %} {#下一頁功能#} {% if page.has_next %} <a href="{% url "book" %}?page={{ page.next_page_number }}">上一頁</a> {% else %} 上一頁 {% endif %} 總頁數(shù): {{ page.len }} </body> </html>