Django框架之form组件详解
form组件:
前戏:
# 我们先来实现一个注册功能来引出什么是form组件
需求:获取用户名和密码:
利用form表单提交数据
在后端判断用户名和密码是否符合一定的格式条件
格式条件:
用户名不能有特殊符号
密码不能少于六位...
# 如果不符合条件需要将提示信息展示到前端页面
这要怎么实现呢? 我们来尝试一下:
视图层:views.py
def ab_form(request):
back_dic = {'username':'','password':''} # 定义一个空的字典(用来前端处理GET请求数据)
if request.method == 'POST': # 判断post请求
username = request.POST.get('username') # 取到用户输入的username
password = request.POST.get('password') # 取到用户输入的password
if '傻逼' in username: # 判断是否存在'傻逼'不合法字符
back_dic['username'] = '用户名不合法' # 如果不合法则把提示信息添加到字典中
if len(password) < 6: # 判断密码是否小于6
back_dic['password'] = '密码不能少于六位数'
return render(request,'ab_form.html',locals()) # 返回到前端页面
前端页面
<form action="" method="post"> // 记得修改method属性
<p>username:
<input type="text" name="username">
<span style="color: darkred">
{{ back_dic.username }} 、
// 无论是post请求还是get请求,页面都能够获取到字典 只不过:
// 如果提交的是GET请求这个span标签是没有数据的(字典是空的)
// 如果提交的是post请求才可能有数据
</span>
</p>
<p>password: <input type="password" name="password">
<span style="color: darkred">
{{ back_dic.password }} // 取出后端传的数据(同上)
</span>
</p>
<input type="submit" class="btn btn-success">
</form>
总结:
1.手动书写前端获取用户数据的html代码 # 渲染html代码
2.后端对用户数据进行校验 # 校验数据
3.对不符合要求的数据进行前端提示 # 展示提示信息
补充:
# 数据的校验必须在后端执行,不能在前端利用js直接完成:
数据校验前端可有可无
但是后端必须要有!
因为前端的校验是弱不禁风的 他人可以直接修改,或者利用爬虫程序绕过前端页面直接朝后端提交数据
# 举例:
购物网站
选取了货物之后 会计算一个价格发送给后端进行结算,如果后端不做价格的校验,用户通过部分手段修改了价格,修改了1元,后端在不校验的话,那么这样可想而知...
# 正规的编写方式
实际是获取到用户选择的所有商品的主键值
然后在后端通过主键值查询出所有商品的价格 再次计算一遍
如果跟前端一致 那么完成支付如果不一致直接拒绝
forms组件
# forms组件
能够完成的事情
1.渲染html代码
2.校验数据
3.展示提示信息
基本使用:
from django import forms # 导入forms组件
# 定义一个类继承forms.Form
class MyForm(forms.Form):
# username字符串类型最小三位最长8位
username = forms.CharField(min_length=3,max_length=8)
# password字符串类型最小三位,最长12位
password = forms.CharField(min_length=3,max_length=12)
# email字符必须符合邮箱格式:xxx@xx.com
email = forms.EmailField()
测试环境:
# pycharm为我们提供了一个测试django的测试环境:
在这个里面就可以选择性的测试Django里面任意一个py文件
位置:pycharm下方 >> Python Console
# 但是提示功能不是很全:需要自己写(不提示不要认为是错了)
校验数据关键字:
# 测试环境下:
from app01 import views
form_obj = views.MyForm({'username':'gary','password':123456,'email':'qwer'})
# 给类传一个字典的形式,那么它会按照类里面写的这些字段去校验这些数据。
# 那么如何查看这些数据是否合法呢?
is_valid()
# 判断是否合法:返回结果为布尔值
# 示例:
form_obj.is_valid()
返回结果:False
# 注意:该方法只有在所有数据全部合法的情况下才会返回True
cleaned_data
# 查看所有校验通过的数据
# 示例:
form_obj.cleaned_data
返回结果:{'username': 'gary', 'password': '123456'}
errors
# 查看所有不符合校验规则以及不符合的原因
# 示例:
form_obj.errors
返回结果:{'email': ['Enter a valid email address.']}
验证多传数据是否进行校验
form_obj = views.MyForm({'username':'gary','password':'123','email':'123@qq.com','hobby':'study'}) # 多传一个字段
form_obj.is_valid() # 判断是否符合校验规则
True
# 结论:校验数据只校验类中定义过的字段,多传不影响,多传的数据字段直接忽略
验证少传数据是否进行校验
form_obj = views.MyForm({'username':'gary','password':'123'})
form_obj.is_valid()
False
form_obj.errors
{'email': ['This field is required.']}
总结:
也就意味着:校验数据的时候,默认情况下数据可以多传但是不能少传
forms组件渲染标签
准备工作:
def index(request):
form_obj = MyForm() # 将上述定义的类实例化先要生成一个空对象
return render(request,'index.html',locals()) # 直接将该空对象传递给html页面
第一种渲染方式:
<h3>第一种渲染方式:</h3>
<br>
<!-- 关键字:as_p -->
{{ form_obj.as_p }}
<!-- 关键字:as_table -->
{{ form_obj.as_table }}
<!-- 关键字:as_ul -->
{{ form_obj.as_ul }}
第一种渲染方式:代码书写极少,封装程度太高 不便于后续的扩展 一般情况下只在本地测试使用
第二种渲染方式:
<h3>第二种渲染方式:</h3>
<br>
<p>{{ form_obj.username.label }}{{ form_obj.username }}</p>
<p>{{ form_obj.password.label }}</p>
<p>{{ form_obj.email }}</p>
第二种渲染方式:可扩展性很强 但是需要书写的代码太多 一般情况下不用
第三种渲染方式:
<h3>第三种渲染方式:</h3>
{% for form in form_obj %}
<p>{{ form.label }}:{{ form }}</p>
{% endfor %}
第三种渲染方式(推荐使用):使用for循环的方式,代码书写简单 并且扩展性也高
总结:
1、
需要在后端类名()实例化生成一个空对象
2、
forms组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox)
不能帮你渲染提交按钮
3、
label属性默认展示的是类中定义的字段首字母大写的形式
也可以自己修改 直接给字段对象加label属性即可
4、
# 推荐使用第三种渲染方式渲染标签
forms组件展示提示信息
html页面
<form action="" method="post">
{% for form in form_obj %}
<p>
{{ form.label }}:{{ form }}
<span style="color: red">{{ form.errors }}</span>
</p>
{% endfor %}
<input type="submit" class="btn btn-info">
</form>
def index(request):
form_obj = MyForm()
if request.method == 'POST':
# 获取用户数据并校验
# request.POST可以看成就是一个字典
form_obj = MyForm(request.POST)
# 判断数据是否合法
if form_obj.is_valid():
# 如果合法 操作数据库存储数据
return HttpResponse('OK')
else:
# 不合法怎样将错误信息展示到前端页面呢
pass
return render(request,'index.html',locals())
# 前端浏览器会帮我们校验数据,但是前端的校验是弱不禁风的(如果在'检查'位置修改了前端的代码块他就可以校验通过)
# 如何取消这种浏览器的校验呢
<form action="" method="post" novalidate>
# 只需要在form表单中添加novalidate属性即可
# 我们看到这个提示信息是ul>li的形式
# 因为:errors的结果是一个列表的形式,浏览器针对列表的形式会自动生成一个ul > li标签
# 解决:
<span style="color: red">{{ form.errors.0 }}</span>
# 需要在错误信息后(.)一个0
# 这样就相当于拿到索引[0]的错误信息就不会是一个列表的形式,这样浏览器就不会自动产生ul标签,只会拿到普通文本
现象:
# 我们看到在我们提交数据的时候页面刷新并没有将页面的数据清空:
# 那么这是怎么实现的呢?
# 这样数据会一直保留到数据彻底合法。
提示信息自定义:
# 现在我们的错误提示信息都是forms组件自动帮我们生成的错误信息,并且是英文的,我们就来解决这个问题:
# 参数 :error_messages={约束条件:不成立条件的提示信息}
eg:
username = forms.CharField(min_length=3,max_length=8,label='用户名',
error_messages={'min_length':'用户名不能少于3位',
'max_length':'用户名不能大于8位',
'required':'用户名不能为空'
}
)
email = forms.EmailField(label='邮箱',
error_messages={
'invalid':'邮箱格式不正确', # 邮箱格式需要这个参数
'required': '邮箱不能为空'
}
)
# 效果:
总结:
1.novalidate:取消浏览器校验数据提示信息
2.form.errors.0 :取消自动生成ul标签拿到纯文本
3.必备的条件 get请求和post传给html页面对象变量名必须一样
4.forms组件当你的数据不合法的情况下 会保存你上次的数据 让你基于之前的结果进行修改
更加的人性化
5.error_messages={参数名:参数值} # 自定义错误提示信息
钩子函数(HOOK)
# 在特定的节点自动触发,完成相应的操作
# 钩子函数在forms组件中就类似于第二道管卡,能够让我们自定义校验规则
# 在forms组件中有两类钩子
1.局部钩子
当你需要给某个字段添加校验规则的时候可以使用局部钩子
2.全局钩子
当你需要给多个字段添加校验规则的时候可以使用全局钩子
# 书写位置:
在forms组件类里书写方法即可
示例:
# 1.校验用户名中不能含有sb
# 判断:只需要校验username字段 (局部钩子)
# 钩子函数(局部钩子)
# 关键字:clean_局部字段名
def clean_username(self):
username = self.cleaned_data.get('username') # 数据通过了forms组件条件的数据都会存储在cleaned_data里,所以我们拿出这里面(过了第一道关卡)的数据,进行添加第二道关卡
if 'sb' in username: # 如果数据的用户名含有'sb'
# 提示前端展示错误信息(关键字:add_error)
self.add_error('username','不能骂人呀~~')
# 将钩子函数沟取出来的数据再放回去
return username
# 2.校验密码和确认密码两次密码是否一致
# 判断:需要校验password和confirm_password两个字段 (全局钩子)
# 全局钩子
# 关键字:clean
def clean(self): # 定义全局钩子
# 拿到通过forms组件的两次密码
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if password != confirm_password: # 如果两次密码不一致
# 告诉前端展示错误信息
self.add_error('confirm_password','两次密码不一致')
# 将钩子函数够出来的数据返回
return self.cleaned_data
整体代码:
点击查看代码
from django import forms
class MyForm(forms.Form ):
# username字符串类型最小三位最长8位
username = forms.CharField(min_length=3,max_length=8,label='用户名',
error_messages={'min_length':'用户名不能少于3位',
'max_length':'用户名不能大于8位',
'required':'用户名不能为空'
}
)
# password字符串类型最小三位,最长12位
password = forms.CharField(min_length=3,max_length=12,label='密码',
error_messages={'min_length': '密码不能少于3位',
'max_length': '密码不能大于8位',
'required': '密码不能为空'
}
)
confirm_password = forms.CharField(min_length=3,max_length=12,label='确认密码',
error_messages={'min_length': '密码不能少于3位',
'max_length': '密码不能大于8位',
'required': '密码不能为空'
})
# email字符必须符合邮箱格式:xxx@xx.com
email = forms.EmailField(label='邮箱',
error_messages={
'invalid':'邮箱格式不正确',
'required': '邮箱不能为空'
}
)
# 钩子函数(局部钩子)
def clean_username(self):
username = self.cleaned_data.get('username')
if 'sb' in username:
# 提示前端展示错误信息
self.add_error('username','不能骂人呀~~')
# 将钩子函数沟取出来的数据再放回去
return username
# 全局钩子
def clean(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if password != confirm_password:
self.add_error('confirm_password','两次密码不一致')
# 将钩子函数狗出来的数据返回
return self.cleaned_data
forms组件其他参数及补充知识点
forms组件字段里常见参数
label # 字段名
error_messages # 自定义报错信息
initial # 默认值
initial
initial # 默认值
required
required # 控制字段是否必填
required=False # 字段可以为空
样式渲染
# 我们发现我们现在写的标签是没有样式的,因为标签不是我们写的,是forms组件帮助我们完成的,那么我们是不能在前端页面添加样式的。
# 那么这样我们如何给标签添加样式呢?
# 针对不同类型的input如何修改呢?
text password redio date checkbox file
# forms组件默认渲染的都是text类型
控制input标签的type属性数据格式
# 关键字:widgef
widgef = forms.widgefs.TextInput()
widgef = forms.widgets.PasswordInput()
widgef = forms.widgets.CheckboxInput()
widgef = forms.widgets.EmailInput()
widgef = forms.widgets.FileInput()
widgef = forms.widgets.TextInput()
添加样式:
# 关键字:attrs={'属性名':'属性值'}
# 多个属性值的话 直接空格隔开即可
字段正则校验:
# 第一道关卡里面还支持正则校验
from django.core.validators import RegexValidator
phone = forms.CharField(
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'), # 第一个正则约束
RegexValidator(r'^159[0-9]+$', '数字必须以159开头') # 第二个正则约束
]
)
# 只有第一个正则通过才走第二个正则
其他类型渲染:
# radio
gender = forms.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3, # 默认是3
widget=forms.widgets.RadioSelect()
)
# select
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
# 多选
hobby1 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
# 单选checkbox
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
# 多选checkbox
hobby2 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
# 总结:
1.widgets.什么就是什么标签类型
2.只要是选择框都是 :ChoiceField
多选框都是:MultipleChoiceField