forms组件
forms组件#
我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来.
与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确... 如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.
Django form组件就实现了上面所述的功能, forms组件的主要功能如下:
- 渲染页面标签
- 校验字段
- 保留用户的输入并渲染错误信息
在用到forms组件的之前首先需要定义forms组件, 然后才能使用.
接下来都以用户注册作为示例.
Form定义#
要定义Form组件, 需要与模型表一一对应, 下面是模型表的定义
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
email = models.EmailField()
接下来就要对模型表用户的这些字段与Form类进行映射. 首先可以在app目录下创建一个my_form的py文件. 然后就可以在里面定义我们需要校验或渲染的Form类了.
class RegForm(forms.Form):
# 接下来的定义需要与模型表的字段类型一一对应
username = forms.CharField(
max_length=15, # 用户名最大长度为15
min_length=3, # 用户名的最小长度为3
label='用户名', # 渲染出在页面上的标签的名字
)
password = forms.CharField(
max_length=15, # 密码最大长度为15
min_length=3, # 密码的最小长度为3
label='密码', # 渲染出在页面上的标签的名字
)
re_password = forms.CharField(
max_length=15, # 密码最大长度为15
min_length=3, # 密码的最小长度为3
label='确认密码', # 渲染出在页面上的标签的名字
)
email = forms.EmailField(
label='邮箱'
)
定义完了之后我们接下来就要用到forms组件的第一个功能, 标签的快速渲染. 下面是Django项目的基本搭建.
url:
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^reg', views.reg, name='reg'),
]
views.py
def reg(request):
form = RegForm()
return render(request, 'reg.html', {'form': form})
渲染标签#
forms组件的渲染标签比较强大, 主要有三种方式
第一种:#
直接将所有信息都一起渲染出来.
reg.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<title>Title</title>
</head>
<body>
<div class="container">
<div class="row">
{#第一种渲染方式#}
<div class="col-md-offset-2 col-md-8">
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<hr>
{{ form.as_table }}
<hr>
{{ form.as_ul }}
<hr>
<input type="submit" value="提交" class="btn btn-success">
</form>
</div>
</div>
</div>
</body>
</html>
这种方式, Django的封装程度太高了, 我们完全不能够自定义样式, 所以它只能用来前期的页面快速搭建以及做测试使用. 最后的渲染结果巨丑无比... 实际基本不考虑它.
第二种#
单独渲染每一个标签
reg.html
...
<form action="" method="post">
{% csrf_token %}
{{ form.username }}
{{ form.password }}
<input type="submit" value="提交" class="btn btn-success">
</form>
...
当我们使用form对象.字段名字的时候, 可以发现这样只渲染出单独的一个input框. 检查还能发现form组件渲染完毕后的一些规律.
添加属性
根据上面这个规律, 可以自己添加label标签, 并且由于只是单单显示一个input框, 我们的自由度就高了许多, 可以在Python代码层面上控制input
样式了. 接下来需要修改RegForm
的定义了, 添加上应用bootstrap
样式的属性. 这需要为每个字段属性上添加一个widget关键参数, 里面就可以为input
框自由添加属性了, 也包括class
属性.
class RegForm(forms.Form):
# 接下来的定义需要与模型表的字段类型一一对应
username = forms.CharField(
max_length=15, # 用户名最大长度为15
min_length=3, # 用户名的最小长度为3
label='用户名', # 渲染出在页面上的标签的名字
widget=forms.TextInput(attrs={'class': 'form-control'})
)
password = forms.CharField(
max_length=15, # 密码最大长度为15
min_length=3, # 密码的最小长度为3
label='密码', # 渲染出在页面上的标签的名字
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
)
re_password = forms.CharField(
max_length=15, # 密码最大长度为15
min_length=3, # 密码的最小长度为3
label='确认密码', # 渲染出在页面上的标签的名字
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
)
email = forms.EmailField(
label='邮箱',
widget=forms.EmailInput(attrs={'class': 'form-control'})
)
reg.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<title>Title</title>
</head>
<body>
<div class="container">
<div class="row">
{#第一种渲染方式#}
<div class="col-md-offset-3 col-md-6">
<h2 class="text-center">注册页面</h2>
<hr>
<form action="" method="post">
{% csrf_token %}
<div class="form-group">
<label for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
{{ form.username }}
</div>
<div class="form-group">
<label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
{{ form.password }}
</div>
<div class="form-group">
<label for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
{{ form.email }}
</div>
<div class="form-group">
<input type="submit" value="提交" class="btn btn-success">
</div>
</form>
</div>
</div>
</div>
</body>
</html>
最后的效果如下所示:
第三种#
上面这种方式也有一个不好的地方, 就是当字段多了以后, 每一个都需要添加类似的样式, 就比较麻烦, 这时候就需要用到第三种方式, 遍历form对象来渲染出标签. 只需要对form对象作出以下小的变化, 就可以达到同样的效果
<form action="" method="post">
{% csrf_token %}
{% for foo in form %}
<div class="form-group">
<label for="{{ foo.id_for_label }}">{{ foo.label }}</label>
{{ foo }}
</div>
{% endfor %}
<div class="form-group">
<input type="submit" value="提交" class="btn btn-success">
</div>
</form>
批量添加样式#
之前定义form组件的时候, 也发现了所有的输入框使用的都是相同的样式, 这时候如果还是一个个添加, 明显可以有更方便的方式, 这就可以通过重写form类的init
方法来实现.
class RegForm(forms.Form):
# 接下来的定义需要与模型表的字段类型一一对应
username = forms.CharField(
max_length=15, # 用户名最大长度为15
min_length=3, # 用户名的最小长度为3
label='用户名', # 渲染出在页面上的标签的名字
)
password = forms.CharField(
max_length=15, # 密码最大长度为15
min_length=3, # 密码的最小长度为3
label='密码', # 渲染出在页面上的标签的名字
)
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({
'class': 'form-control'
})
校验字段#
渲染完标签之后, 接下来也是form组件最强大的地方, 就是校验字段了, form组件的校验方式分为2步, 第一步需要通过字段类型自身定义的校验, 例如最大最小长度等等, 第二步则是需要通过钩子函数的检验才算是干净的数据(cleaned_data)
.
字段的校验#
之前写的视图函数里面不含任何校验信息的逻辑, 接下来在视图函数中把注册功能的逻辑补全.
def reg(request):
form = RegForm()
if request.method == 'POST':
# form类可以接受一个data参数, 用这个参数可以给form做初始化工作.
form = RegForm(request.POST)
# 必须调用这一步, 来对前端传来的数据进行校验
if form.is_valid():
# 获取校验通过的data, 注意cleaned_data只有在调用了is_valid之后才有这个属性
data = form.cleaned_data
print(data)
# 由于多了一个确认密码的字段, 需要将re_password弹出
data.pop('re_password')
# 创建对象
models.User.objects.create(**data)
else:
# 未通过的数据会保存到errors里面 返回结果是一个字典, 键是字段名字, 值是错误提示信息的列表
for error in form.errors.items():
print(error)
return render(request, 'reg.html', {'form': form})
接下来就是做测验了. 不过做测验之前需要把前端在浏览器端的字段校验给关闭, 因为在input框中填写字段的约束条件, 在前端会做一个初步的校验.
就如上面所示, 关闭校验只需要在form表单中加入novalidate
参数即可
<form action="" method="post" novalidate>
...
</form>
结果#
当在浏览器输入以上信息时, 打印的错误信息为
('username', ['Ensure this value has at least 3 characters (it has 2).'])
('password', ['This field is required.'])
('re_password', ['This field is required.'])
('email', ['Enter a valid email address.'])
输入正确的信息时, cleaned_data能够正确把干净的数据返回.
{'username': 'test', 'password': '123', 're_password': '123', 'email': '14@11.com'}
中文错误信息提示#
写到这里, 也发现了上面的错误信息提示是英文的, 需要在form类定义中添加中文的错误信息.
username = forms.CharField(
max_length=15, # 用户名最大长度为15
min_length=3, # 用户名的最小长度为3
label='用户名', # 渲染出在页面上的标签的名字
error_messages={
'max_length': '用户名最大长度为15',
'min_length': '用户名最小长度为3',
'required': '用户名不能为空'
}
)
所有字段只需要像上面这样添加一个error_messages关键字参数, 就可以实现将错误提示改为中文了.
# 返回结果
('username', ['用户名最小长度为3'])
想要知道所有字段都支持更改哪些错误提示信息, 可以参考官方文档的form组件部分.
钩子函数#
上面只靠类型检查明显不符合一些比较复杂的校验, 例如用户名不能包含敏感词, 两次密码需要一致, 这都需要进行校验.
局部钩子函数
局部钩子函数是用来单独校验某一个字段的, 以下面这个校验用户名为例.
def clean_username(self):
username = self.cleaned_data.get('username')
if username.startswith('sb'):
self.add_error('username', '用户名包含敏感词汇')
if models.User.objects.filter(username=username).exists():
self.add_error('username', '用户名已存在')
# 这里必须返回用户名, 不然cleaned_data不能获取到username的值
return username
有了钩子函数, 一些更加复杂的校验工作就可以进行了. 这里当校验不通过的时候, 我们可以主动将报错的字段名字和报错的提示信息添加到errors这个错误字典里面, 最后会显示出来.
全局钩子函数#
def clean(self):
password = self.cleaned_data.get('password')
re_password = self.cleaned_data.get('re_password')
if not re_password == password:
self.add_error('re_password', '两次密码不一致')
# 这里也返回cleaned_data即可
return self.cleaned_data
全局钩子函数一般使用来校验多个字段之间的关系的.
有了这两个钩子函数, 注册表单的逻辑就完善了. 现在可以正确的校验密码的错误问题了.
到了这里, 数据也校验通过了, 还有form组件的一个功能错误信息的展示没有体现, 接下来就是错误信息的展示.
错误展示#
错误提示的信息的添加非常的容易, 因为我们清楚的知道, 错误信息是保存到errors这样一个字典中, 并且还是以套着字典的形式, 那么就可以直接通过模板语法取出来.
最后前端模板那只需要加上一个span
标签渲染即可
...
{% for foo in form %}
<div class="form-group">
<label for="{{ foo.id_for_label }}">{{ foo.label }}</label>
{{ foo }}
<!-- 加上这样一句就可以实现错误的信息渲染了 -->
<span class="pull-right" style="color: red">{{ foo.errors.0 }}</span>
</div>
{% endfor %}
...
最终的展示效果就像这样