一、自定義Form的原理
1.1 各種form表單驗證比較
只有python提供了form表單驗證,其他的都沒有提供。django提供的功能還不夠強大。最強大的是微軟的ASP.NET!我們可以自己寫一個來實現。
1.2 【不推薦】通過request.post獲取用戶輸入內容
用戶輸入表單==>用戶輸入的檢測
1)我們之前是創建一個form類
2)用戶提交表單,我們通過request.post拿到數據,然后封裝到Form(數據)里面
3)obj.is_valid方法,來檢查用戶輸入的內容,跟Form()定義的,是否匹配。
問題:
request.post 獲取用戶輸入的內容,它知道用戶輸入了幾個嗎?
1.3 【推薦】通過自定義Form類,通過類對象來獲取
1.3.1如何獲取類的所有靜態字段:
所以,通過request.post取數據不好,所以我們可以用form類。
如:
Form類:
u = xxxx
p = xxxx
我們先創建一個Form對象:
obj = Form()
for i in Form類中的所有東西:
問題:
Form類:這些都是靜態字段,靜態字段屬於類。
如何獲取一個類的所有靜態字段?
'''
這些靜態屬性是屬於類的
這就是通過打印類的字典,來獲取類的靜態屬性
'''
class Foo(object):
p=123
u=456
print Foo.__dict__
# 如果要循環就是循環類的所有東西:
# for k,v in Foo類的所有東西:
'''
打印結果:
{'__module__': '__main__', 'p': 123, 'u': 456, '__dict__':
<attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
'''
1.3.2 通過類對象來獲取所有實例屬性
'''
如果把屬性寫到初始化里,我們要找這些屬性的話,就要找類的對象的字典了(這些屬性屬於類的實例)
'''
class Foo2(object):
def __init__(self):
self.u='wang'
self.p=123
# 如果要循環就是循環類的對象所有東西:
# for k,v in Foo2對象所有東西:
obj=Foo2()
print obj.__dict__
# 打印結果
# {'p': 123, 'u': wang}
# for k,v in 對象的所有東西:
1.3.3 自定義form驗證原理(類對象獲取對象屬性)
'''
我們先定義一個類,類里面有多少字段,是我們程序員控制的。
我們可以根據Foo類來生成頁面的標簽!
用戶提交數據的時候,我們先創建Foo類對象,然后再循環對象里所有的東西。
'''
#這里的obj為類的實例化對象
for k,v in obj.__dict__.iteritems():
print k,v # k代表屬性名:如'p', v代表值:如:123
# request.POST[k] # 如果循環,來獲取request.POST[k],其實就是獲取用戶輸入的數據
# 如果用戶輸入的是alex,request.POST[k]就等於alex
# 所以,我們通過循環自己的form來去前端取什么數據。是以我們在Form定義的項目為主,跟前端寫多少沒關系。
二、自定義Form實例1
2.1 獲取用戶輸入的值
就是創建一個web程序,只要請求一進來,訪問/index,其實就是訪問MainHandler類的get方法或者post方法
目錄結構如下:
運行:form_framework.py
瀏覽器訪問:http://localhost:8888/index
這時,就執行了MainHandler類的get方法,就是渲染index.html頁面
class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): host = self.get_argument('host') # 在Tornado里,獲得用戶的輸入,都是用get_argument print host
重新運行:form_framework.py
然后輸入:hostname內容,然后提交
此時python后台就打印:wang
2.2 自定義form類,打印對象的k,v
既然能獲取單個屬性,我們就可以循環form類對象來獲取對象的所有屬性(用戶輸入數據)
我們定義一個Form類,然后定義屬性名(注意,跟form表單的name名要對應)
然后循環
class Form(object): def __init__(self): self.host = None self.ip = None self.port = None self.phone = None class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): # host = self.get_argument('host') # 在Tornado里,獲得用戶的輸入,都是用get_argument # print host obj=Form() for k,v in obj.__dict__.iteritems(): print k,v
現在后台打印:
ip None
host None
port None
phone None
我們可以通過自定義的form來獲取用戶提交的數據,如果index.html,多了一個地址欄,
<p>address: <input type="text" name="address" /> </p>
但是form類里沒定義,我們也不管它。
也就是我們只收集form類定義的對象屬性
加上:self.get_argument(k)
def post(self, *args, **kwargs): # host = self.get_argument('host') # 在Tornado里,獲得用戶的輸入,都是用get_argument # print host obj=Form() for k,v in obj.__dict__.iteritems(): print k,v,self.get_argument(k) # 對象的k和值,用戶輸入的值(通過跟form表單里的name名對應)
此時再從瀏覽器輸入表單內容,提交
python后台打印結果:
ip None 10.0.0.1
host None wang
port None 22
phone None 123456
沒有address,因為我們不管它。這樣就做到了,我們在Form類里寫了哪些字段,就取用戶輸入的哪些數據。
2.3 用正則來驗證用戶輸入數據
我們為啥要寫Form類呢?因為要做驗證!
k是字段,v是form對象的值,self.get_argument(k)是用戶輸入的值。
我們把v改成正則表達式,我們用正則來驗證用戶輸入的值,如果驗證成功,就代表合法的,不成功就不合法。
1)我們先改寫Form類,把值改成正則表達式:
class Form(object): def __init__(self): self.host = "(.*)" self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" self.port = '(\d+)' self.phone = '^1[3|4|5|8][0-9]\d{8}$'
然后我們,通過正則驗證,如果循環過程,有一個驗證不成功,flag=False,打印錯誤:
def post(self, *args, **kwargs): # 在請求的時候,默認是驗證成功的 flag= True # host = self.get_argument('host') # 在Tornado里,獲得用戶的輸入,都是用get_argument # print host obj=Form() for k,v in obj.__dict__.iteritems(): # k,對象的字典 # v,對象中字段對應的值,正則 # self.get_argument(k),用戶輸入的值(通過跟form表單里的name名對應) # 我們用正則來驗證用戶輸入的值,如果驗證成功,就代表合法的,不成功就不合法。 # print k,v,self.get_argument(k) import re if re.match(v,self.get_argument(k)): # 如果符合,則返回一個對象,如果不符合就返回一個None pass else: flag = False # 在循環的過程中,一旦有一個不滿足,就是false了。 if flag: self.write('ok') else: self.write('error')
重啟:form_framework.py
然后在表單輸入錯誤的格式,提交后就返回error:
輸入格式錯誤,返回:error
2.4 把驗證寫到form類里
因為必須從Form類里獲取正則表達式,再做驗證,所以,我們直接把驗證寫到Form類里,把MainHandler類的post只做主函數就行了。
1)首先把:or k,v in obj.__dict__.iteritems(): 移動到Form里,
這里的obj,就是Form的實例化對象,在Form里就是指self
class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): # 在請求的時候,默認是驗證成功的 flag= True obj=Form() #for k,v in obj.__dict__.iteritems(): #移動到Form類里 import re if re.match(v,self.get_argument(k)): pass else: flag = False # 在循環的過程中,一旦有一個不滿足,就是false了。 if flag: self.write('ok') else: self.write('error') class Form(object): def __init__(self): self.host = "(.*)" self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" self.port = '(\d+)' self.phone = '^1[3|4|5|8][0-9]\d{8}$' def is_valid(self): for k,v in self.__dict__.iteritems(): # 移動到這里,obj改成self import re
2)傳代碼:if re.match(v,self.get_argument(k)):
看上面代碼
if re.match(v,self.get_argument(k)):
post函數里的self,指的是MainHandler這個類的對象,
我們把is_valid函數傳個參數,request,
在is_valid函數里改寫代碼為:if re.match(v,request.get_argument(k)):
其實這里的request就是:MainHandler對象,
is_valid函數在MainHandler里被調用,把自己的對象self傳進去。
3)把flag=True,分別定義在兩個函數里
改寫后的代碼如下:
class Form(object): def __init__(self): self.host = "(.*)" self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" self.port = '(\d+)' self.phone = '^1[3|4|5|8][0-9]\d{8}$' def is_valid(self): # 在請求的時候,默認是驗證成功的 flag= True for k,v in self.__dict__.iteritems(): import re import re if re.match(v,self.get_argument(k)): # 如果符合,則返回一個對象,如果不符合就返回一個None pass else: flag = False # 在循環的過程中,一旦有一個不滿足,就是false了。 class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): # host = self.get_argument('host') # 在Tornado里,獲得用戶的輸入,都是用get_argument # print host flag= True obj=Form() obj.is_valid(self) # 把自己的類對象self傳到MainHandler里。 if flag: self.write('ok') else: self.write('error')
三、Form類優化
3.1 把is_valid放到基類里
在django里,每一個表單就是一個form,例如LoginForm,AssetForm。。。
在你創建很多form的時候,但是is_valid都一樣。你需要在每個Form類里都寫一遍嗎?
不需要,你可以用基類。
class BaseForm(object): def is_valid(self): # 在請求的時候,默認是驗證成功的 flag= True for k,v in self.__dict__.iteritems(): import re import re if re.match(v,self.get_argument(k)): # 如果符合,則返回一個對象,如果不符合就返回一個None pass else: flag = False # 在循環的過程中,一旦有一個不滿足,就是false了。 class Form(BaseForm): def __init__(self): self.host = "(.*)" self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" self.port = '(\d+)' self.phone = '^1[3|4|5|8][0-9]\d{8}$' class LoginForm(object): def __init__(self): self.name= "(.*)"
3.2 正則表達式分門別類
如果多個表單有同一個正則,例如ip地址,就會重復寫同樣正則了。
我們可以針對每個類型的驗證,單獨寫正則表達式類。
調用的時候,調用這個字段的正則類的對象就行了。這樣就提高重用性了。
class ValidateIpv4(object): ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') class ValidatePhone(object): phone_re = re.compile(r'^1[3|4|5|8][0-9]\d{8}$') class Form(BaseForm): def __init__(self): self.host = "(.*)" self.ip = ValidateIpv4.ipv4_re self.port = '(\d+)' self.phone = ValidatePhone.phone_re
3.3 驗證后返回成功或失敗信息
在django里,驗證無論成功或者失敗,都返回信息
對於IP來說,如果IP錯誤的話,也會有IP格式錯誤的提示。
在django中是這么做的:
from django import forms import re from django.core.exceptions import ValidationError def validate_ipv4(value): # ip地址 驗證例如:10.1.6.10 ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') if not ipv4_re.match(value): # 這里做了驗證,如果不成功,則報錯 raise ValidationError('IP段格式錯誤.') # ValidationError是django的方法 class AddressPoolForm(forms.Form): getway = forms.CharField(validators=[validate_ipv4, ], # 驗證ip地址 error_messages={'required': u'網關不能為空', 'invalid': u'網關格式錯誤'}, widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': '網關'}))
那在自定義的Form里怎么做呢?
那在自定義的Form里怎么做呢?
required的值是傳進來的:
required:True 表示:字段必須要填,並且驗證
required:False 表示:字段可填可不填,不做驗證
假如,required是False,則不做驗證,直接返回值。
如果required=True:則判斷:如果沒有自定義'required'和'valid'錯誤信息,則用默認的(Field提供)
如果驗證成功,則返回正則匹配的內容。
import tornado.ioloop import tornado.web import re class Field(object): def __init__(self, error_msg_dict, required): self.id_valid = False self.value = None self.error = None self.name = None self.error_msg = error_msg_dict self.required = required def match(self, name, value): self.name = name if not self.required: # 假如,required是False,則不做驗證,直接返回值。 self.id_valid = True self.value = value else: if not value: # 假如用戶沒傳值,則給出require報錯信息: if self.error_msg.get('required', None): # 先看有沒有自定義的'required'錯誤信息,有就用自定義的,沒有則用默認的。 self.error = self.error_msg['required'] else: self.error = "%s is required" % name else: # 如果用戶傳來值,則做驗證,如果驗證成功,則返回匹配結果,沒成功,則返回自定義(優先級高)或默認信息 ret = re.match(self.REGULAR, value) if ret: self.id_valid = True # 是正則驗證成功的標識 self.value = ret.group() else: # 先看有沒有自定義的'valid'錯誤信息,有就用自定義的,沒有則用默認的。 if self.error_msg.get('valid', None): self.error = self.error_msg['valid'] else: self.error = "%s is invalid" % name # 如果required=True:則判斷:如果有自定義了'required'和'valid'錯誤,則用自定義的 class IPField(Field): REGULAR = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" def __init__(self, error_msg_dict=None, required=True): error_msg = {} # {'required': 'IP不能為空', 'valid': 'IP格式錯誤'} if error_msg_dict: error_msg.update(error_msg_dict) # 繼承基類的__init__方法,同時,把參數(error_msg,required)傳給基類__init__ super(IPField, self).__init__(error_msg_dict=error_msg, required=required)
說明:
class IPField(Field):里的: # 外面傳參:error_msg_dict 可以定制化錯誤信息!這里required默認值是True def __init__(self, error_msg_dict=None, required=True): error_msg = {} # {'required': 'IP不能為空', 'valid': 'IP格式錯誤'} if error_msg_dict: error_msg.update(error_msg_dict) # 把定制化的錯誤信息來更新error_msg這個字典(也就是定制化錯誤優先級最高!)
# 這時候調用IPField,就可以隨意傳自定義的錯誤信息了!required=True是默認值,可以不用傳
class Form(BaseForm): def __init__(self): # required=True是默認值,可以不用傳 self.ip = IPField(error_msg_dict={'required': '我的IP不能為空', 'valid': '我的IP格式錯誤'})