django—forms组件
1、forms组件的作用
1、生成页面可用的html标签
2、对用户提交的数据进行校验,并返回校验结果
3、保留上次输入内容
2、如何使用forms组件
2.1 使用前提:定义类
2.1.1 基本结构
# views.py
from django import forms
class MyForm(forms.Form):
'''
1、使用类似于orm的表定义,但在此处,一个字段即为一个form表单的标签,简称form类标签
2、通过字段参数可以控制form类标签的值,如最长位数
3、通过特殊字段类型,控制特定输入规范,如email字段控制了input输入框必须为邮箱格式,如123@qq.com
'''
username = forms.CharField(max_langth=8,min_length=3)
password = forms.CharField(max_langth=8,min_length=3)
email = forms.EmailField()
2.1.2 更改标签属性\样式
forms标签默认为input文本输入框,可用参数widget改变标签属性
1、Input前缀相当于前端中input标签的type属性,首字母大写
2、以attrs={key:value}的方式设置标签属性
3、常用的标签类型有:
PasswordInput:密文
CheckboxInput:单选按钮
CheckboxSelectMultiple:多选按钮
RadioSelect:单选框
Select:下拉单选框
SelectMultiple:下拉多选框
class MyForm(forms.Form):
username = forms.CharField(
max_lenget=8,
min_length=3,
widget=forms.widgets.TextInput(attrs={"class": "form-control"}) # 文本输入框,默认
widget=forms.widgets.PasswordInput(attrs={"class": "form-control"}) # 密文输入框
)
2.2 校验数据
校验数据,即获取前端的输入,并根据类中定义该标签时的参数进行校验,根据校验结果处理响应
2.2.1 数据处理流程
1、后端实例化一个空的对象form_obj,交给前端渲染成form表单
2、前端form表单提交数据,数据格式是字典
3、数据字典传给类,实例化生成一个对象,与1中的空对象同名form_obj
4、根据对象form_obj的校验结果返回响应数据
代码:
# 定义类MyForm
# 定义视图函数index
def index(request):
form_obj = MyForm()
if request.method == 'POST':
data = request.POST
form_obj = MyForm(data)
if form_obj.is_valid():
return HttpResponse('数据全部正确')
return render(request,'index.html',local())
2.2.2 数据校验逻辑
1、通过对象绑定方法is_valid
判断数据校验结果是否通过
2、只有所有数据校验通过的情况下,is_valid
才为True
3、内部的数据处理逻辑:
-
从类MyForm定义的字段中,依次去数据字典中比对
-
若字典中存在同名的key,那么按照字段的参数条件去校验数据字典中key对应的value,不存在就将错误描述加入到form_obj.errors字典中
-
比对之后,若value符合要求,则加入到form_obj.cleaned_data字典中,若不符合要求,则将错误描述加入到form_obj.errors字典中
-
比对完所有的类MyForm定义的字段后,结束比对,因此,数据字典多余的数据不会影响校验结果
-
form_obj.errors中,每一个value值都是一个列表,因为每一个字段都可能有多个约束条件
源码分析:
is_valid()
is_bound()
errors
数据校验
2.2.3 常用内置字段及其参数
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类型
Django Form内置字段
2.2.4 常用的几个校验参数
class MyForm(forms.Form):
name = forms.CharField(
lable='给input标签添加一个lable标签,并设置文本内容,默认为字段名'),
initial='input框默认值',
required=True, # 默认为True,控制标签是否可以为空
error_messages={
'本字段已经有的参数名':'校验不通过时的信息描述',
'required':'不能为空'
}
2.2.4 内置校验器
from django.core.validators import RegexValidator
from django import forms
class MyForm(forms.Form):
user = forms.CharField(
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
)
2.2.5 钩子函数
局部校验
在MyForm中定义clean_字段名()方法,实现对特定字段的校验
from django.core.exceptions import ValidationError
class MyForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三",
error_messages={
"required": "不能为空",
"invalid": "格式错误",
"min_length": "用户名最短8位"
},
widget=forms.widgets.TextInput(attrs={"class": "form-control"})
)
...
# 定义局部钩子,用来校验username字段
def clean_username(self):
value = self.cleaned_data.get("username")
if "666" in value:
raise ValidationError("光喊666是不行的")
else:
return value
全局校验
在MyForm中定义clean()方法,实现对字段的全局校验
class MyForm(forms.Form):
...
password = forms.CharField(
min_length=6,
label="密码",
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
)
re_password = forms.CharField(
min_length=6,
label="确认密码",
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
)
...
# 定义全局的钩子,用来校验密码和确认密码字段是否相同
def clean(self):
password_value = self.cleaned_data.get('password')
re_password_value = self.cleaned_data.get('re_password')
if password_value == re_password_value:
return self.cleaned_data
else:
self.add_error('re_password', '两次密码不一致')
raise ValidationError('两次密码不一致')
2.3 渲染标签
1、forms组件只会渲染获取用户输入的标签,不会渲染提交按钮
2、forms组件应该在form标签内,需要自己写
3、三种渲染方式如下,推荐使用第三种
<p>forms组件渲染标签方式1:封装程度态高 不推荐使用 但是可以用在本地测试</p>
{{ form_obj.as_p }} <!--自动渲染所有input框 -->
{{ form_obj.as_ul }}
{{ form_obj.as_table }}
<p>forms组件渲染标签方式2:不推荐使用 写起来太烦了</p>
{{ form_obj.username.label }}{{ form_obj.username }}
{{ form_obj.username.label }}{{ form_obj.password }}
{{ form_obj.username.label }}{{ form_obj.email }}
<p>forms组件渲染标签方式3:推荐使用 </p>
{% for form in form_obj %}
<p>{{ form.label }}{{ form }}</p> <!--form 等价于你方式2中的对象点字段名-->
{% endfor %}
2.4 展示信息
以校验登陆为例:
# views.py
class MyForm(forms.Form):
username = forms.CharField(max_length=8, min_length=3, required=True, label='用户名:', error_messages={
'max_length': '最长8位数',
'min_length': '最短3位数',
'required': '不能为空'
}, widget=forms.widgets.TextInput({'class': 'form-control'}))
password = forms.CharField(max_length=8, min_length=3, required=True, label='密码:', error_messages={
'max_length': '最长8位数',
'min_length': '最短3位数',
'required': '不能为空'
}, widget=forms.widgets.PasswordInput({'class': 'form-control'}))
confir_password = forms.CharField(max_length=8, min_length=3, required=True, label='确认密码:', error_messages={
'max_length': '最长8位数',
'min_length': '最短3位数',
'required': '不能为空'
}, widget=forms.widgets.PasswordInput({'class': 'form-control'}))
email = forms.EmailField(label='邮箱:')
def clean_username(self):
value = self.cleaned_data.get('username')
if '666' in value:
raise ValidationError('6什么6,坐下!')
else:
return value
def clean(self):
password_value = self.cleaned_data.get('password')
confir_password_value = self.cleaned_data.get('confir_password')
if password_value != confir_password_value:
self.add_error('confir_password', '密码不一致哟')
raise ValidationError('密码不一致哟')
else:
return self.cleaned_data
def index1(request):
form_obj = MyForm()
if request.method == 'POST':
data = request.POST
form_obj = MyForm(data)
if form_obj.is_valid():
return HttpResponse('数据全部正确')
return render(request, 'index.html', locals())
<!--index.html-->
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<form action="" method="post" novalidate>
{% for forms in form_obj %}
<p>
{{ forms.label }}{{ forms }}
<span>{{ forms.errors.0 }}</span>
</p> <!--form 等价于你方式2中的对象点字段名-->
{% endfor %}
<input type="submit">
</form>
</div>
</div>
</div>