一、form组件基本使用
Django组件有很多,它类似功能模块
1.1、form组件主要功能
- 校验数据
- 渲染HTML代码
- 展示提示信息
那么为什么数据校验要在后端而不是前端JS完成?数据校验前端可有可无啊,我随时可以右键浏览器-检查-直接改掉前端代码来避过校验!
1.2、基本使用
要用form组件,需要在views里要导入forms模块,写个类定义校验规则,该类必须继承forms.Form。
from django import forms # 必须导入此模块
# Create your views here.
class MyForm(forms.Form):
username = forms.CharField(min_length=6, max_length=12) # 用户名最少6位,最大12位。
password = forms.CharField(min_length=8, max_length=16) # 密码最少8位,最大16位。
email = forms.EmailField() # 必须符合邮箱的xxxx@xxx.com格式
1.3、测试环境准备
测试环境的准备,可以用之前的test.py方式自己拷贝代码准备;还可以用pycharm提供的Python console;如下所示:
1.4、校验数据
from study import views # 先导入模块
form_obj = views.MyForm({'username': 'sanxi', 'password':'sanxi666'}) # 以字典形式传入
form_obj.is_valid() # 校验数据是否合法,只要有一个不合法则全部不合法。
False
form_obj.cleaned_data # 查看校验通过的数据
{'password': 'sanxi666'}
form_obj.errors # 查看校验不通过的数据及其原因
{'username': ['Ensure this value has at least 6 characters (it has 5).'], 'email': ['This field is required.']}
form_obj.errors
{'email': ['This field is required.']}
# 校验数据仅仅校验类中所定义的字段,多传不影响而是会被忽略掉。
form_obj = views.MyForm({'username': 'sanxi666', 'password':'sanxi666', 'email':'haha@123.com', 'haha':666})
form_obj.is_valid()
True
form_obj = views.MyForm({'username': 'sanxi666', 'password':'sanxi666', 'haha':666})
form_obj.is_valid() # 可以多传但不能少传,默认情况下所有字段必须传值!
False
form_obj.errors
{'email': ['This field is required.']}
1.5、渲染HTML代码
前面说到了如何校验数据,那么如何将校验结果展示给用户呢?其实from组件会自动帮你渲染获取用户输入的标签,但不会渲染提交按钮!如下所示:
label属性默认展示的是类中定义的字段名且首字母大写,如需自定义则在字段中添加label属性即可!
<body>
<form action="" method="post">
{% for form_info in form_obj %}
<p>{{ form_info.label }}:{{ form_info }}</p>
{% endfor %}
<input type="submit" value="提交">
</form>
</body>
哎!我没有设置这段文本啊,这是怎么回事???其实这是浏览器自动渲染的,不是我们设置的,浏览器识别到后端的校验结果后自动渲染,这个渲染用户可以直接通过右键浏览器-检查,直接修改前端代码来规避,所以说前端的校验并不严谨可靠!
1.6、展示提示信息
需要先获取用户数据再校验,校验数据需要构造成字典的格式传入才行,而request.POST可以看成是一个字典,所以直接将POST传给forms对象即可!
前面提到浏览器会自动渲染结果,但是前端的校验弱不禁风,为了更好地展示效果,我们先在form表单里添加novalidate来关闭前端校验。
views代码
def form_test(request):
form_obj = MyForm()
if request.method == 'POST':
form_obj = MyForm(request.POST) # 重新赋值覆盖,get和post请求变量名必须一致,因此用重新赋值的方式。
if form_obj.is_valid(): # 校验是否合法
return HttpResponse('数据库已更新') # 合法则进行的下一步操作(多数为操作数据库)
return render(request, 'form_test.html', locals()) # 无论如何都要返回
HTML代码
<body>
<form action="" method="post" novalidate>
{% for form_info in form_obj %}
<p>{{ form_info.label }}:{{ form_info }}</p>
<span style="color: red">{{ form_info.errors.0 }}</span>
{% endfor %}
<input type="submit" value="提交">
</form>
</body>
结果
注意
1、views中get和post请求传给HTML页面的变量名必须一致!!!
2、form组件校验到数据不合法后会保留原数据,让用户基于此进行修改,更为人性化。
1.7、定制错误信息
在上面展示提示信息的示例中,错误信息时英文的,我们是在中国,当然要给用户展示全中文啦!所以错误信息我们也应该个性化,定制指定的文本内容!出来吧!error_message!
views代码
class MyForm(forms.Form):
username = forms.CharField(min_length=6, max_length=12, error_messages={ # error_messages必须传入字典格式的数据
'min_length': '用户名最少6位!',
'max_length': '用户名最多12位!',
'required': '用户名不能为空!'
})
password = forms.CharField(min_length=8, max_length=16, error_messages={
'min_length': '密码最少8位!',
'max_length': '密码最多16位!',
'required': '密码不能为空!'
})
email = forms.EmailField(error_messages={'invalid': '邮箱格式不正确!', 'required': '邮箱不能为空!'})
结果
二、forms钩子函数HOOK
有Linux基础的朋友肯定不会陌生钩子函数HOOK,比如在iptables的运用!
2.1、钩子函数是什么?
钩子嘛,就是把东西勾过来;然后用函数去处理勾过来的物事,再放回去。说稍微难懂些就是在特定的节点自动触发完成某些响应操作!
2.2、为什么要用它?
不懂历史也应该看过古装片吧!古时城门都是分好几道的,一道一道地盘查来往民众是否可疑,一层比一层严格!校验数据也是如此,必须要严禁!
而在form组件中有两类钩子:
- 局部钩子:需要给某个字段增加校验规则的时候可以使用。
- 全局钩子:需要给多个字段增加校验规则时可以使用。
2.3、使用案例
校验用户名中不能含有黄色;校验密码和确认密码是否一致
views代码
class MyForm(forms.Form):
username = forms.CharField(min_length=6, max_length=12, error_messages={
'min_length': '用户名最少6位!',
'max_length': '用户名最多12位!',
'required': '用户名不能为空!'
})
password = forms.CharField(min_length=8, max_length=16, error_messages={
'min_length': '密码最少8位!',
'max_length': '密码最多16位!',
'required': '密码不能为空!'
})
confirm_password = forms.CharField(min_length=8, max_length=16)
email = forms.EmailField(error_messages={'invalid': '邮箱格式不正确!', 'required': '邮箱不能为空!'})
def clean_username(self): # 局部钩子,通常以clean开头,后面跟上指定字段。
username = self.cleaned_data.get('username') # 把数据勾出来
if 'huangse' in username: # 匹配
self.add_error('username', '查水表警告!') # 如匹配,则为对应字段添加错误信息
return username # 把勾出来的变量放回去
def clean(self): # 全局钩子,建议就这么写。
password = self.cleaned_data.get('password')
confirm_passwd = self.cleaned_data.get('confirm_password')
if not password == confirm_passwd:
self.add_error('confirm_password', '两次输入密码不一致,请重新输入!')
return self.cleaned_data # 直接返回去
三、form补充知识点
3.1、属性补充
3.1.1、label属性
在本文1.5节时我们曾提过,默认展示的名称是字段名且首字母大写,而label属性则可以定制该字段叫什么名字。
class MyForm(forms.Form):
username = forms.CharField(min_length=6, max_length=12, label='用户名',
error_messages={
'min_length': '用户名最少6位!',
'max_length': '用户名最多12位!',
'required': '用户名不能为空!'
})
password = forms.CharField(min_length=8, max_length=16, label='密码',
error_messages={
'min_length': '密码最少8位!',
'max_length': '密码最多16位!',
'required': '密码不能为空!'
})
confirm_password = forms.CharField(min_length=8, max_length=16, label='确认密码')
email = forms.EmailField(label='邮箱', error_messages={'invalid': '邮箱格式不正确!', 'required': '邮箱不能为空!'})
3.1.2、initial属性
控制各字段默认值
class MyForm(forms.Form):
username = forms.CharField(min_length=6, max_length=12, label='用户名', initial='sanxi',
error_messages={
'min_length': '用户名最少6位!',
'max_length': '用户名最多12位!',
'required': '用户名不能为空!'
})
password = forms.CharField(min_length=8, max_length=16, label='密码', initial='sanxi666',
error_messages={
'min_length': '密码最少8位!',
'max_length': '密码最多16位!',
'required': '密码不能为空!'
})
3.1.3、required属性
控制字段是否必填
class MyForm(forms.Form):
username = forms.CharField(min_length=6, max_length=12, label='用户名', required=False,
error_messages={
'min_length': '用户名最少6位!',
'max_length': '用户名最多12位!',
'required': '用户名不能为空!'
})
password = forms.CharField(min_length=8, max_length=16, label='密码',
error_messages={
'min_length': '密码最少8位!',
'max_length': '密码最多16位!',
'required': '密码不能为空!'
})
3.1.4、正则表达式
针对字段的校验多种:
- 最简单的min max
- 正则validator
- 钩子函数
正则演示,必须先导入RegexValidator模块,然后在需要校验的字段里添加validators属性:
from django.core.validators import RegexValidator # 必须先导入此模块
class MyForm(forms.Form):
username = forms.CharField(min_length=6, max_length=12, label='用户名', required=False,
# 固定语法,RegexValidator里面第一个参数为正则表达式,第二个参数为不匹配时的提示信息
validators=[RegexValidator(r'^[a-zA-Z]+[a-zA-Z0-9]$', '只能是字母开头,非符号结尾')],
error_messages={
'min_length': '用户名最少6位!',
'max_length': '用户名最多12位!',
'required': '用户名不能为空!'
})
3.2、其它字段补充
form组件还有很多内置字段,来介绍几个。
3.2.1、修改标签样式
如何给不同类型的input修改样式?多个属性值用空格隔开!
class MyForm(forms.Form):
username = forms.CharField(min_length=6, max_length=12, label='用户名', required=False,
widget=forms.widgets.TextInput(attrs={'class': 'form-control'}))
password = forms.CharField(min_length=8, max_length=16, label='密码',
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}))
confirm_password = forms.CharField(min_length=8, max_length=16, label='确认密码',
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}),),
email = forms.EmailField(label='邮箱', error_messages={'invalid': '邮箱格式不正确!', 'required': '邮箱不能为空!'},
widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}))
还是很丑。。。还是直接复制粘贴bootstrap样式好过。
3.2.2、其它类型渲染
单选都是choice,多选都是multiplechoice,widget里点什么就是什么标签
3.3、Django内置字段集合
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
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类型
3.4、forms组件源码
切入点:form_obj.is_valid(),精髓就在full_clean了。