Form详解
Form的功能
- 验证用户请求(form表单验证)
- 生成HTML标签(自动生成HTML标签)
- 保留上一次提交的数据(保留数据)
我们在设计form表单时,有许多字段需要填写,且需要判断用户填写的内容是否合法,此时,就需要对字段进行验证。试想,如果我们自己编程实现的话,需要针对用户填写的各种异常进行错误提示,此时我们需要花费大量精力去应付各种可能出现的错误,然而,Django的form为我们实现了快捷的方式,它相当于已经编写好了一个模板,只需要我们在模板的基础上进行定制。
Django中的Form表单模板是一个类,创建如下:
1、原始Form
views.py文件
from django import forms
# Create your views here.
class FM(forms.Form):
# 只关心处理自己定义的form表单数据,恶意攻击定义的数据不处理
user = forms.CharField()
pwd = forms.CharField() # 这里的变量名必须和html form里的name保持一致
email = forms.EmailField()
def fm(request):
if request.method == "GET":
return render(request, "fm.html")
elif request.method == "POST":
# 获取用户所有数据,每条数据请求的验证
# 成功 --> 获取所有的正确信息;失败 --> 显示错误信息
obj = FM(request.POST)
r1 = obj.is_valid()
if r1: # 返回的正确信息
print(obj.cleaned_data)
else: # 返回的错误信息
print(obj.errors)
print(obj.errors.as_json())
return HttpResponse('fm')
fm.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/fm/" method="POST">
{% csrf_token %}
<input type="text" name="user">
<input type="text" name="pwd">
<input type="text" name="email">
<input type="submit" value="提交" />
</form>
</body>
</html>
提交表单
错误输出
原始错误和json类型的错误
问题??
我们可以从上面看到错误信息,它是最简单的也是最基本的错误信息提示:为空的时候,提示字段需要填写!
此时有一个问题:我们是否可以自己定制错误信息呢?当然可以!Django的Form为我们量身定制了,而且非常简单。
2、自定义form错误信息
修改views.py
from django import forms
class FM(forms.Form):
# 只关心处理自己定义的form表单数据,恶意攻击定义的数据不处理
user = forms.CharField(error_messages={'required':'用户名不能为空'})
pwd = forms.CharField(
max_length=12,
min_length=6,
error_messages={'required':'密码不能为空','min_length':'密码长度不能小于6','max_length':'密码长度不能大于12'}
)
email = forms.EmailField(error_messages={'required':'用户名不能为空','invalid':'邮箱格式错误'})
def fm(request):
if request.method == "GET":
return render(request, "fm.html")
elif request.method == "POST":
obj = FM(request.POST)
r1 = obj.is_valid()
if r1: # 返回的正确信息
print(obj.cleaned_data)
else: # 返回的错误信息
# ErrorDict
# print(obj.errors['user'][0])
# print(obj.errors.as_json())
return render(request,'fm.html',{'obj':obj})
return render(request,'fm.html')
修改前端html文件
<form action="/fm/" method="POST">
{% csrf_token %}
<p><input type="text" name="user"> {{ obj.errors.user.0 }}</p>
<p><input type="text" name="pwd"> {{ obj.errors.pwd.0 }}</p>
<p><input type="text" name="email"> {{ obj.errors.email.0 }}</p>
<input type="submit" value="提交" />
</form>
错误提示
问题??
此时,都有对应的错误提示了,但是有一个比较严重的问题,就是上一次提交的内容一旦提交后,就消失了!
怎么解决?
3、保留上次提交的数据
修改fm.html文件
<form action="/fm/" method="POST">
{% csrf_token %}
<p>{{ obj.user }} {{ obj.errors.user.0 }}</p>
<p>{{ obj.pwd }} {{ obj.errors.pwd.0 }}</p>
<p>{{ obj.email }} {{ obj.errors.email.0 }}</p>
<input type="submit" value="提交" />
</form>
views.py文件修改:
def fm(request):
if request.method == "GET":
obj = FM()
return render(request, "fm.html",{'obj':obj})
elif request.method == "POST":
obj = FM(request.POST)
r1 = obj.is_valid()
if r1: # 返回的正确信息
print(obj.cleaned_data)
else: # 返回的错误信息
# ErrorDict
# print(obj.errors['user'][0])
# print(obj.errors.as_json())
return render(request,'fm.html',{'obj':obj})
return HttpResponse('提交成功!')
提交成功后:
4、更简洁的html标签生成
- obj.as_p
- obj.as_ul
- obj.as_table
注意,字段的lable可以定制:通过label参数
user = forms.CharField(error_messages={'required':'用户名不能为空'},label='用户名')
fm.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/fm/" method="POST">
{% csrf_token %}
{{ obj.as_p }}
<input type="submit" value="提交" />
</form>
</body>
</html>
效果如下:
5、插件widget
form类里面的字段,只有一个功能,就是验证客户端发过来的数据。生成html的功能做不了。
但是怎么生成的html标签呢,在charfield里面有个插件,插件生成的。在其源码里做了html字符串的拼接返回。
from django import forms
from django.forms import widgets # 插件在这里面
class FM(forms.Form):
# 字段本身只做验证
user = forms.CharField( # 修改html标签,并指定样式##############
error_messages={'required':'用户名不能为空'},
widget=widgets.Textarea(attrs={'class':'c1'}), # 页面再看就是textarea了
label="用户名"
)
pwd = forms.CharField(
max_length=12,
min_length=6,
error_messages={'required':'密码不能为空','min_length':'密码长度不能小于6','max_length':'密码长度不能大于12'},
widget=widgets.PasswordInput # 密码密文显示,如果自定义样式也可加上(attrs……)
)
email = forms.EmailField(error_messages={'required':'用户名不能为空','invalid':'邮箱格式错误'})
字段都在from django.forms import fields
里面,所以上面的forms可以改用fields
email = fields.EmailField()
前端<p>{{ obj.user.label }}{{ obj.user }} {{ obj.errors.user.0 }}</p>
插件里面input、checkbox、select、redio等全部都有。
6、常用插件
# 单radio,值为字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
# 单radio,值为字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.RadioSelect
# )
# 单select,值为字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
# 单select,值为字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.Select
# )
# 多选select,值为列表
# user = fields.MultipleChoiceField(
# choices=((1,'上海'),(2,'北京'),),
# initial=[1,],
# widget=widgets.SelectMultiple
# )
# 单checkbox
# user = fields.CharField(
# widget=widgets.CheckboxInput()
# )
# 多选checkbox,值为列表
# user = fields.MultipleChoiceField(
# initial=[2, ],
# choices=((1, '上海'), (2, '北京'),),
# widget=widgets.CheckboxSelectMultiple
# )
7、内置插件
TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
8、form 内置字段
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
* show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
* validators=[], 自定义验证规则
localize=False, 是否支持本地化,使用本地时间
disabled=False, 是否可以编辑
label_suffix=None Label内容后缀
################# 下面的通过自己写正则表达式也能实现 #################
CharField(Field)
max_length=None, 最大长度
min_length=None, 最小长度
strip=True 是否移除用户输入空白
IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值
FloatField(IntegerField)
...
DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 总长度
decimal_places=None, 小数位长度
BaseTemporalField(Field)
input_formats=None 时间格式化
DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
DurationField(Field) 时间间隔:%d %H:%M:%S.%f
...
RegexField(CharField)
regex, 自定制正则表达式
max_length=None, 最大长度
min_length=None, 最小长度
error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}
EmailField(CharField)
...
FileField(Field)
allow_empty_file=False 是否允许空文件
ImageField(FileField)
...
注:需要PIL模块,pip3 install Pillow
以上两个字典使用时,需要注意两点:
- form表单中 enctype="multipart/form-data"
- view函数中 obj = MyForm(request.POST, request.FILES)
URLField(Field)
...
BooleanField(Field)
...
NullBooleanField(BooleanField)
...
ChoiceField(Field)
...
choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 插件,默认select插件
label=None, Label内容
initial=None, 初始值
help_text='', 帮助提示
ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查询数据库中的数据
empty_label="---------", # 默认空显示内容
to_field_name=None, # HTML中value的值对应的字段
limit_choices_to=None # ModelForm中对queryset二次筛选
ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField
TypedChoiceField(ChoiceField)
coerce = lambda val: val 对选中的值进行一次转换
empty_value= '' 空值的默认值
MultipleChoiceField(ChoiceField)
...
TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 对选中的每一个值进行一次转换
empty_value= '' 空值的默认值
ComboField(Field)
fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
MultiValueField(Field)
PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
SplitDateTimeField(MultiValueField)
input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
required=True,
widget=None,
label=None,
initial=None,
help_text=''
GenericIPAddressField
protocol='both', both,ipv4,ipv6支持的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
SlugField(CharField) 数字,字母,下划线,减号(连字符)
...
UUIDField(CharField) uuid类型
...
9、初始化操作
在Web应用程序中开发编写功能时,时常用到获取数据库中的数据并将值初始化在HTML中的标签上。
def fm(request):
if request.method == "GET":
dic = {
"user":'r1',
"pwd":'123456',
"email":'aera@11.com',
"city":1,
"city2":[1,2],
}
obj = FM(initial=dic) # 初始化
return render(request, "fm.html",{'obj':obj})
10、widget动态更新数据
models.py文件
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class UserType(models.Model):
caption = models.CharField(max_length=32)
def __str__(self):
return self.caption
forms.py文件
#coding:utf-8
from django import forms
from django.forms import fields
from app01 import models
from django.core.exceptions import ValidationError
class UserInfoForm(forms.Form):
username = fields.CharField(max_length=32)
email = fields.EmailField(max_length=32)
user_type = fields.ChoiceField(
choices=models.UserType.objects.values_list('id','caption')
)
views.py文件
def index(request):
if request.method == 'GET':
obj = UserInfoForm()
new_obj = models.User.objects.all()
return render(request,'index.html',{'obj':obj,'new_obj':new_obj})
elif request.method == 'POST':
obj = UserInfoForm(request.POST)
if obj.is_valid():
rt_dic = obj.cleaned_data
username = rt_dic.get('username')
email = rt_dic.get('email')
user_type_id = rt_dic.get('user_type')
ut = models.UserType.objects.filter(id=user_type_id).first()
models.User.objects.create(username=username,email=email,user_type=ut)
return render(request,'index.html',{'obj':obj})
问题??
上面有个问题,就是数据库添加数据后,django需要重启网页才能看到新加的数据。为什么会这样呢?
forms里面,定义的类,类里的username、email、user_type都是静态字段(类变量),这些都是属于类的。
在__init__里面写的属于对象。
而启动Django的时候,类变量一次性都加在到内存了。
上面views.py里的obj = UserInfoForm()生成了一个对象,会执行类里的__init__方法,会把类里的字段封装到obj.fields里面(里面有username、email、user_type三个字段),所以:
我们可以考虑在form加载的时候进行数据初始化,也就是在forms.py的__init__()里面做文章:
# 下面的操作是让数据在网页上实时更新:每次刷新时,必先执行父类的初始化函数,再设定下拉列表框选项。
def __init__(self, *args, **kwargs):
super(UserInfoForm,self).__init__(*args, **kwargs)
self.fields['user_type'].choices = models.UserType.objects.values_list('id','caption')
通过widgets还有另外两种方法进行更新:
#coding:utf-8
from django import forms
from django.forms import fields,widgets,ModelChoiceField
from app01 import models
from django.core.exceptions import ValidationError
class UserInfoForm(forms.Form):
username = fields.CharField(max_length=32)
email = fields.EmailField(max_length=32)
#和user_type2类似,只是参数改变了方式
user_type = fields.ChoiceField(
choices=[],
widget=widgets.Select
)
user_type2 = fields.ChoiceField(
widget=widgets.Select(choices=[])
)
#ModelChoiceField参数
"""
ModelChoiceField(ChoiceField)
django.forms.models.ModelChoiceField
queryset, # 查询数据库中的数据
empty_label="---------", # 默认空显示内容
to_field_name=None, # HTML中value的值对应的字段(html源码中value不同)
limit_choices_to=None # ModelForm中对queryset二次筛选
"""
user_type3 = ModelChoiceField(
queryset=models.UserType.objects.all()
)
# 下面的操作是让数据在网页上实时更新:每次刷新时,必先执行父类的初始化函数,再设定下拉列表框选项。
def __init__(self, *args, **kwargs):
super(UserInfoForm,self).__init__(*args, **kwargs)
self.fields['user_type'].choices = models.UserType.objects.values_list('id','caption')
self.fields['user_type2'].widget.choices = models.UserType.objects.values_list('id', 'caption')
实现效果如下:
当然,在models.py文件里面还需要新增__str__()方法:
class UserType(models.Model):
caption = models.CharField(max_length=32)
def __str__(self):
return self.caption
11、内置钩子
初始化操作
Form里面加一个字典数据,生成显示默认值
def index(request):
from cmdb.forms import UserInfoForm
obj = UserInfoForm({'username':'xq','email':'xq@qq.com','user_type':'1'}) # 默认值
return render(request, 'index.html', {'obj':obj})
效果如下:
数据验证
obj.is_valid()#做简单的数据验证,查看源码可以知道,这个方法有2个功能:一个是判断是否有字段为空,一个是判断是否有错误信息。
def is_valid(self):
"""
Returns True if the form has no errors. Otherwise, False. If errors are
being ignored, returns False.
"""
return self.is_bound and not self.errors
self.is_bound = data is not None or files is not None
问题??虽然我们可以通过is_valid()方法进行简单的验证,但是错误提示和错误信息我们都无法定制。
怎么解决呢?form提供了接口:
#清洗单个字段
def clean_username(self): # value = self.cleaned_data['username'] if value == 'root': return value else: raise ValidationError('你不是超级用户...')
#清洗钩子,可以自定义 def clean(self): username = self.cleaned_data.get('username') email = self.cleaned_data.get('email') if models.User.objects.filter(username=username, email=email).count() == 1: raise ValidationError('用户名和邮箱联合唯一索引重复!!!') return self.cleaned_data #清洗钩子,可以自定义 def _post_clean(self): print 'aaa %s' %self.cleaned_data username = self.cleaned_data['username'] email = self.cleaned_data['email'] if models.User.objects.filter(username=username,email=email).count()==1: self.add_error("__all__", ValidationError('用户名和邮箱联合唯一索引重复!!!'))
在form验证里面的顺序:
is_valid()===》self.errors()和self.is_bound()===>self.full_clean()===》self._clean_fields()===>self._clean_form()===》self._post_clean()
form验证,
- 先第一个字段正则表达式判断,执行字段钩子函数clean_fields();
- 第二个字段正则,第二个的钩子;
- 所有字段完成后,执行clean()钩子;
- clean执行完后,执行
_post_clean()
钩子
12、序列化错误信息
不管提交数据是浏览器提交、还是curl方式,还是ajax提交,只要是request.POST
都可以做验证。
res['error'] = obj.errors.as_json() # 转为json格式
<script src="/static/jquery-1.12.4.js"></script>
<script>
// 页面框架加载完自动执行
$('#submit').click(function(){
$.ajax({
url:'/login.html',
type:'POST',
data:$('#fm').serialize(),
success:function(arg){
console.log(arg);
arg = JSON.parse(arg); // 转为字典
console.log(arg);
},
error: function(){
}
})
})
</script>
上面的方式可以实现,只是前端需要反解两次,不太好,下面优化一下。
- as_json : 生成json格式
- as_data : 生成dict数据。
from django import forms
from django.forms import widgets, fields
class LoginForm(forms.Form):
username = fields.CharField()
password = fields.CharField(
max_length=64,
min_length=12
)
# 序列化,转换为指定数据类型
import json
from django.core.exceptions import ValidationError
class JsonCustomEncoder(json.JSONEncoder):
def default(self, field):
if isinstance(field, ValidationError):
return {'code':field.code, 'messages': field.messages}
else:
return json.JSONEncoder.default(self, field)
def login(request):
res = {'status':True, 'error':None, 'data': None}
if request.method == "GET":
return render(request,"login.html")
elif request.method == "POST":
obj = LoginForm(request.POST)
if obj.is_valid():
print(obj.cleand_data)
else:
# print(obj.errors, type(obj.errors))
# res['error'] = obj.errors.as_json() # 转为json格式
print(type(obj.errors.as_data()))
for k,v in obj.errors.as_data().items():
print(k,v) # 这里v是ValidationError类型,不能序列化
res['error'] = obj.errors.as_data()
result = json.dumps(res, cls=JsonCustomEncoder)
return HttpResponse(json.dumps(result))
13、序列化操作
Json.dumps
用来做序列化,但是只能序列化一些简单的基本数据类型。对于无法序列化的数据类型,只能自定制了。
ErrorDict自定义JSONEncoder
由于json.dumps时无法处理datetime日期,所以可以通过自定义处理器来做扩展,如:
import json
from datetime import date
from datetime import datetime
class JsonCustomEncoder(json.JSONEncoder):
def default(self, field):
if isinstance(field, datetime):
return o.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(field, date):
return o.strftime('%Y-%m-%d') # 转成字符串类型
else:
return json.JSONEncoder.default(self, field)
v = {'k':'123', "k1":datetime.datetime.now()}
ds = json.dumps(v, cls=JsonCustomEncoder)
QuerySet 第一种序列化方式
上面自己写实现也可以,不过Django提供了方法做序列化
from django.core import serializers
v = models.tb.objects.all()
data = serializers.serialize("json", v)
QuerySet 第二种序列化方式
import json
from datetime import date
from datetime import datetime
class JsonCustomEncoder(json.JSONEncoder):
def default(self, field):
if isinstance(field, datetime):
return field.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(field, date):
return field.strftime('%Y-%m-%d')
else:
return json.JSONEncoder.default(self, field)
v = models.tb.objects.values('id','name','ctime')
v = list(v) # 把(类似列表的queryset类型)转换为列表
v = json.dumps(v,cls=JsonCustomEncoder) # 这里cls只对ctime操作。
# 如果没有ctime这个类型,只写上边的三句就可以实现
14、Form总结
我们在程序中使用Form表单非常多,Django中的Form类除了具备form表单功能外,它最强大的是它的数据验证功能,让编程人员省去了很多事情,而且它的数据验证非常灵活。
a、Form数据字段验证过程:
首先,我们可以使用is_valid()方法,对表单字段进行简单数据验证(是否为空或有errors),其中errors会调用full_clean(),【空字段或简单错误】
紧接着,full_clean()函数里面会有每个字段的正则,clean_field()然后会调用_clean_form(),它里面会有clean()钩子,【清洗字段、清洗整个表单,可灵活自定义】
最后会调用_post_clean()钩子。【清洗提交数据,可灵活自定义】
b、Form数据返回值:
is_valid()进行数据验证,然后通过cleaned_data获取数据字典,errors获取错误字典。
c、Form数据实时更新:
在form类初始化时,把对象进行实时拉取最新数据,保证数据为最新。
super(UserInfoForm,self).__init__(*args, **kwargs)
self.fields['user_type'].choices = models.UserType.objects.values_list('id','caption')
self.fields['user_type2'].widget.choices = models.UserType.objects.values_list('id', 'caption')
d、序列化数据