form组件
一 form介绍
在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且包起来
于此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息
Django form组件就实现了上面所述的功能。
总结,form组件的功能主要如下:
- 生成页面可用HTML标签
- 对用户提交的数据进行校验
- 保留上次输入的内容
- 注意:前端校验可以没有,后端校验必须有
二 forms校验字段功能
创建appform文件
# appform.py
from django import forms
# 写一个类,类里写要校验的字段
class MyForm(forms.Form):
# 校验这个字段,最大长度是32,最小是3
name = forms.CharField(max_length=32, min_length=3)
email = forms.EmailField(label='邮箱') # 默认必须是邮箱格式 渲染模板的前缀名
age = forms.IntegerField(max_value=200, min_value=0, label='年龄')
定义视图函数
from app10.appform import MyForm
def register(request):
# 数据可以使前端传来的,也可以是自己后台数据
# 现在有以下数据
data = {'name': 'arther', 'email': '11111@qq.com', 'age': 990}
# 校验数据是否合法
# 实例化得到form对象,把要校验的数据传入
form = MyForm(data)
# 校验数据: 返回布尔类型
if form.is_valid():
print('校验通过')
print(form.cleaned_data) # 打印通过校验的数据类型,所以不一定是原数据
else:
print(form.cleaned_data)
print('校验失败')
# 哪个字段失败了? 失败的原因是什么
print(form.errors)
print(type(form.errors))
print(form.errors.as_json())
print(form.errors.as_data())
return HttpResponse('ok')
# form.errors 打印的是标签,内含错误原因
'''
<ul class="errorlist"><li>age<ul class="errorlist"><li>Ensure this value is less than or equal to 200.</li></ul></li></ul>
'''
# form.errors.as_json() 打印json内含错误字段对应的错误原因以及限制参数
'''
{"age": [{"message": "Ensure this value is less than or equal to 200.", "code": "max_value"}]}
# 错误信息
{'age': [ValidationError(['Ensure this value is less than or equal to 200.'])]}
'''
三 forms渲染模板功能
视图层
def register(request):
if request.method == 'GET':
form = MyForm()
return render(request, 'register.html', {'form': form})
elif request.method == 'POST':
# 校验数据: 返回布尔类型
form = MyForm(request.POST)
if form.is_valid():
print('校验通过')
print(form.cleaned_data) # 打印通过校验的数据类型,所以不一定是原数据
else:
print(form.cleaned_data)
print('校验失败')
# 哪个字段失败了? 失败的原因是什么
print(form.errors)
print(form.errors.as_json())
print(form.errors.as_data())
return HttpResponse('ok')
模板层
方式一:纯手写
<body>
<div class="row">
<div class="col-md-6 col-md-offset-3 " style="margin-top: 100px">
<h3>手动创建模板</h3>
<form action="" method="post" class="input-group input-group-lg">
<p>用户名:<input type="text" name="name"></p>
<p>密码:<input type="password" name="pwd"></p>
<p>再输入密码:<input type="password" name="re_pwd"></p>
<p>邮箱:<input type="email" name="email"></p>
<p>年龄:<input type="text" name="age"></p>
<p><input type="submit" value="提交"></p>
</form>
</div>
</div>
</body>
方式二:半自动1
<body>
<div class="row">
<div class="col-md-6 col-md-offset-3 " style="margin-top: 100px">
<h3>手动创建模板</h3>
<form action="" method="post" class="input-group input-group-lg">
<p>用户名:{{ form.name }}</p>
<p>邮箱:{{ form.email }}</p>
<p>年龄:{{ form.age }}</p>
<p><input type="submit" value="提交"></p>
</form>
</div>
</div>
</body>
方式三:半自动2(使用的最多)
<div class="row">
<div class="col-md-6 col-md-offset-3 " style="margin-top: 100px">
<h3>手动创建模板</h3>
<form action="" method="post" class="input-group input-group-lg">
<p>{{ form.name.label }}:{{ form.name }}</p>
<p>{{ form.email.label }}:{{ form.email }}</p>
<p>{{ form.age.label}}:{{ form.age }}</p>
<p><input type="submit" value="提交"></p>
</form>
</div>
</div>
方式四:全自动(了解)
<form action="" method="post">
{# {{ form.as_ul }}#} 套在ul列表标签里
{{ form.as_p }} 套在p段落标签里
{# <table>#}
{# {{ form.as_table }}#}套在table表格标签里面
{# </table>#}
<p><input type="submit" value="提交"></p>
</form>
四 forms渲染错误信息
1.form对象.errors 字典 包含各个字段以及对应的错误信息
2.name对象.errors 该字段的错误信息
## 视图函数
def register(request):
if request.method == 'GET':
form = MyForm()
return render(request, 'register.html', {'form': form})
elif request.method == 'POST':
# 校验数据: 返回布尔类型
form = MyForm(request.POST)
if form.is_valid():
print('校验通过')
print(form.cleaned_data) # 打印通过校验的数据类型,所以不一定是原数据
return HttpResponse('ok')
else:
print(form.cleaned_data)
print('校验失败')
# 哪个字段失败了? 失败的原因是什么
print(form.errors)
return render(request, 'register.html', {'form': form})
## 模板
<form action="" method="post" novalidate> # 前端不提示信息,直接由后端判定返回信息
{% for foo in form %}
<div class="form-group">
<label for="">{{ foo.label }}</label>
{{ foo }}
<span class="text-danger pull-right">{{ foo.errors }}</span>
</div>
{% endfor %}
<div class="text-center">
<input type="submit" value="提交" class="btn btn-danger">
</div>
</form>
五 forms组件参数配置
from django import forms
from django.forms import widgets
# 定制模板中的显示样式,及配置类
# 错误信息中文显示
error_messages = {'min_length': '太短了小伙子'}
class MyForm(forms.Form):
# 校验这个字段,最大长度是32,最小是3
name = forms.CharField(max_length=9, min_length=3, label='用户名',
widget=widgets.TextInput(attrs={'class': 'form-control'}),
error_messages={'max_length': '用户名少于9位', 'min_length': '用户名至少大于3位'})
password = forms.CharField(max_length=9, min_length=3, label='密码',
widget=widgets.TextInput(attrs={'class': 'form-control'}),
error_messages={'max_length': '密码要少于9位', 'min_length': '密码至少大于3位'})
re_password = forms.CharField(max_length=9, min_length=3, label='密码',
widget=widgets.TextInput(attrs={'class': 'form-control'}),
error_messages={'max_length': '密码要少于9位', 'min_length': '密码至少大于3位'})
email = forms.EmailField(label='邮箱',
widget=widgets.TextInput(attrs={'class': 'form-control'})) # 默认必须是邮箱格式 渲染模板的前缀名
age = forms.IntegerField(max_value=200, min_value=0, label='年龄',
widget=widgets.TextInput(attrs={'class': 'form-control'}),
error_messages={'max_value': '要小于200岁', 'min_value': '要大于0岁'})
date = forms.DateField(label='日期', widget=widgets.DateInput(attrs={'class': 'form-control'}))
text = forms.CharField(label='个人简介', widget=widgets.Textarea(attrs={'class': 'form-control'}))
# 默认required=True 输入不可为空
# 映射出来的相应标签
'''
<p><input type="text" name="name" class="form-control" maxlength="9" minlength="3" required id="id_name"></p>
'''
六 局部钩子和全局钩子
局部钩子的使用
-
在定义Form类中写 clean_字段名
-
取出字段的真正值,name=self.cleaned_data.get('name'),注意:此时的name是已经经过校验的数据,所以局部钩子的校验是在正常校验之后
-
判断自己的规则,如果判断失败,抛出ValidationError,需要导入模块
(from django.core.exceptions import ValidationError)
-
如果通过,return name ,是该字段重回cleaned_data中
# 局部钩子
def clean_name(self):
# 先进行校验,再进行局部钩子,此时拿的是已经通过校验的name
name = self.cleaned_data.get('name')
import re
res = re.findall('.*?(傻|操|爷)', name)
if res:
raise ValidationError(f'具有敏感词汇{res[0]}')
else:
return name
'''
第一个字段校验---->第一个字段的局部校验(从cleaned_data取该字段)
第二个字段校验---->第二个字段的局部校验(从cleaned_data取该字段)
:如果局部校验中cleaned_data取出pwd校验与re_pwd关系,但是re_pwd此时没有完成字段校验
:从cleaned_data取不出来,所以局部钩子不能校验两个字段关系的合法性
:
:
所以字段以及其字段的局部校验都完毕
全局校验(字段与字段关系的合法校验)
'''
全局钩子的使用
全局钩子校验的是两个字段的关系的合法性,如注册密码与确认输入的密码是否一致。所以是从全局出发,不是指单单一个字段的校验。所以直接在views.py中,用form对象取得所有字段的错误信息,然后再获得全局钩子中发生的错误
error = form.errors.get('__all__') # 获取全局钩子的错误信息
# 全局钩子
def clean(self):
password = self.cleaned_data.get('password')
re_password = self.cleaned_data.get('re_password')
if password == re_password:
return self.cleaned_data
else:
raise ValidationError('两次密码不一致')
七 form组件校验源码
1 读的入口是:
form.is_valid()--->self.errors(BaseForm类)---》self.full_clean()(BaseForm类)--》
-self._clean_fields(局部数据校验)和self._clean_form(全局数据校验)
2 self._clean_fields(BaseForm类)
for name, field in self.fields.items():
try:
# 字段自己的校验(最大值,最小值,是不是邮箱格式)
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name): # 反射判断有没有clean_字段名
value = getattr(self, 'clean_%s' % name)()
# 如果有,反射得到form对象的该方法然后直接调用
self.cleaned_data[name] = value # = return 字段名
# 调用后的产生结果放入cleaned_data中,所以在局部钩子中需要返回校验成功的 字段名
except ValidationError as e:
self.add_error(name, e)
# 在字段校验和局部钩子校验中产生的错误添加进来
3 self._clean_form(BaseForm类) # 全局钩子
try:
cleaned_data = self.clean() # self.clean执行的是自己类的clean方法
except ValidationError as e:
self.add_error(None, e)
面向切面编程(AOP OOP:面向对象编程)