一、自定义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格式错误'})