一、前戏
需求:写一个注册功能
- 获取用户名和密码,利用form表单提交数据
- 在后端判断用户名和密码是否符合一定的条件
- 用户名不能含有三国演义
- 密码不少于三位
1、前端
<form action="" method="post">
<p>username:
<input type="text" name="username" >
<span style="color: red">{{ back_dic.username }}</span>
</p>
<p>password:
<input type="password" name="password">
<span style="color: red">{{ back_dic.password }}</span>
</p>
<input type="submit" class="btn btn-info">
</form>
2、后端
from django.shortcuts import render
def ab_form(request):
back_dic = {'username': '', 'password': ''}
"""
无论是post请求还是get请求,页面都能获取到字典
只不过get请求来的时候,字典都是空的,而post请求来之后,字典可能有值
"""
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if '三国演义' in username:
back_dic['username'] = '不符合党的团结主义思想'
if len(password) < 3:
back_dic['password'] = '男人不能太短'
return render(request, 'ab_form.html', locals())
3、路由
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('ab_form/',views.register),
]
4、用到的知识点
- 手动书写前端获取用户数据的html页码 渲染html代码
- 后端对用户数据进行校验 校验数据
- 对不符合要求的数据进行前端提示 展示提示信息
而forms组件可以直接完成上述的所有事情
二、Forms组件校验数据
1、引入
首先为什么数据校验要在后端执行,而不是前端在JS完成?
- 数据校验前端可有可无
- 但是后端必须有!
原因:
- 前端的校验存在实时被修改的风险,可以在前端直接修改输入的数据
- 利用爬虫程序可以绕过前端页面直接向后端提交数据
例如购物网站
- 在前端计算完总价后,直接提交给后端
- 后端如果不做校验就会产生极大的风险安全问题
正确的做法是
- 在后端查出所有商品的必要信息,再次计算一遍
- 前段与后端都要进行校验,保证数据的安全性
2、编写表单类
我们可以通过Django提供的Form类来自用生成上面的表单,不再需要手动在HTML中编写。
(1)创建文件
首先,在你当前app内新建一个forms.py
文件(这个套路是Django的惯用手法,就像views.py
,models.py
等等)
(2)书写form表单类
# 导入模块
from django import forms
# 定义一个类继承forms.Form
class MyForm(forms.Form):
# username字符串类型最小3位最大8位
username = forms.CharField(min_length=3,max_length=8)
# password字符串类型最小3位最大8位
password = forms.CharField(min_length=3,max_length=8)
# email字段必须符合邮箱格式 xxx@xx.com
email = forms.EmailField()
(3)注意事项
- 提前导入forms模块
- 所有的表单类都要继承forms.Form类
- 每个表单字段都有自己的字段类型比如CharField,它们分别对应一种HTML语言中的
<form>
元素中的表单元素。这一点和Django模型系统的设计非常相似。 - 例子中的label用于设置说明标签
max_length
限制最大长度为8。它同时起到两个作用,一是在浏览器页面限制用户输入不可超过8个字符,二是在后端服务器验证用户输入的长度不可超过8。
警告:由于浏览器页面是可以被篡改、伪造、禁用、跳过的,所有的HTML手段的数据验证只能防止意外不能防止恶意行为,是没有安全保证的,破坏分子完全可以跳过浏览器的防御手段伪造发送请求!所以,在服务器后端,必须将前端当做“裸机”来对待,再次进行完全彻底的数据验证和安全防护!
3、校验数据
(1)测试的方式
① 测试脚本
当你只是想要测试Django中的某一个py文件内容,那么你可以不用书写前后端交互的形式,而是直接写一个测试脚本即可
这内容其实就是最外部 manage.py 文件中的上面几句话
脚本代码无论是写在应用下的 tests.py文件还是自己新建文件,将内容写在新文件中,都会生效
from django.test import TestCase
# Create your tests here.
import os
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django05.settings')
import django
django.setup()
# 在下面书写我们需要测试的代码
# 即所有的测试代码都必须等待环境准备完毕之后才能书写
② pycharm下的python console
下面的代码都是在pycharm下的python console进行测试的
(2)数据准备
from app01 import models
from app01 import views
# 1.将待校验的数据组织称字典的形式传入即可
form_obj = views.MyForm({'username':'xiao','password':'123','email':'123'})
(3)数据是否合法(is_valid)
- 注意该方法只有在所有的数据都合法的时候才会返回True
form_obj.is_valid()
# False
(4)查看所有符合校验通过的数据(cleaned_data)
form_obj.cleaned_data
# {'username': 'xiao', 'password': '123'}
(5)查看所有不符合校验规则以及不符合的原因(errors)
form_obj.errors
# {'email': ['Enter a valid email address.']}
(6)校验数据只校验类中出现的新字段
- 多传不影响,多传的字段直接忽略
form_obj = views.MyForm({'username':'xiao','password':'123','email':'123@qq.com','hobby':'study'})
form_obj.is_valid()
# True
(7)校验数据默认情况下类里面所有的字段
- 默认情况下类里面所有的字段都必须要传值
form_obj = views.MyForm({'username':'xiao','password':'123'})
form_obj.is_valid()
# False
(8)小结
默认情况下,校验数据可以多传但是不能少传
三、Forms组件渲染标签
form组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox)
1、后端数据准备
- views.py
def index(request):
# 1.先产生一个空对象
form_obj = MyForm()
# 2.直接将该空对象传递给html页面
return render(request, 'index.html', locals())
- urls.py
# forms 组件渲染html标签
path('index/',views.index),
2、前端渲染方式
(1)第一种渲染方式
代码书写极少,封装成都太高,不便于后续的扩展,一般情况系只在本地测试使用
{{ form_obj.as_p }}
{{ form_obj.as_ul }}
{{ form_obj.as_table }}
页面样式
- as_p
- as_ul
- as_table
综上所述,第一种渲染方式会将定义的字段的首字母大写显示外加一个input框,只是渲染的样式不同
浏览器会自动添加p标签 > label标签 > input标签
(2)第二种渲染方式( {{ form_obj }} )
可扩展性很强,但是需要书写的代码太多,一般情况下不用
<p>{{ form_obj.username.label }}:{{ form_obj.username }}</p>
<p>{{ form_obj.password.label }}:{{ form_obj.password }}</p>
<p>{{ form_obj.email.label }}:{{ form_obj.email }}</p>
页面样式
如果在后端给字段添加label参数的话则显示参数的值,
.字段名.label显示额是字段首字母大写
只点.字段名的话显示input框
(3)第三种渲染方式( for )
代码精简,并且扩展性也高,推荐使用
{% for form in form_obj %}
<p>
{{ form.label }}:{{ form }}
</p>
{% endfor %}
页面样式
使用for循环的形式可循环对象并使用第二种渲染方式进行渲染
(4)补充
label属性默认展示的是类中定义的字段首字母大写的形式,也可以自己修改,直接给字段对象加label属性即可
username = forms.CharField(min_length=3, max_length=8, label='用户名')
3、小结
- 需要在后端类名()实例化生成一个空对象
- forms组件只会自动帮你渲染获取用户输入的标签( input select radio checkbox ) ,不能帮你渲染提交按钮
- label属性默认展示的是类中定义的字段首字母大写的形式,也可以自己修改 直接给字段对象加label属性即可
- 推荐使用第三种渲染方式渲染标签
四、展示错误信息
1、form组件自带展示错误信息
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>
views.py
def index(request):
# 1.先产生一个空对象
form_obj = MyForm()
if request.method == 'POST':
# 获取用户数据并校验
# request.POST可以看成就是一个字典
form_obj = MyForm(request.POST)
# 判断数据是否合法
if form_obj.is_valid():
# 如果合法 操作数据库存储数据
return HttpResponse('OK')
else:
# 不合法怎样将错误信息展示到前端页面呢
pass
# 2.直接将该空对象传递给html页面
return render(request,'index.html',locals())
前端浏览器会帮我们校验数据,但是前端的校验是弱不禁风的(如果在'检查'位置修改了前端的代码块他就可以校验通过)
2、取消浏览器自带的展示错误信息
浏览器会自动帮你校验数据,但是前端的校验弱不禁风,如何让浏览器不做校验?
<form action="" method="post" novalidate>
novalidate:无需验证
我们看到这个提示信息是ul>li的形式
因为:errors的结果是一个列表的形式,浏览器针对列表的形式会自动生成一个ul > li标签
解决:
<span style="color: red">{{ form.errors.0 }}</span>
需要在错误信息后(.)一个0
这样就相当于拿到索引[0]的错误信息就不会是一个列表的形式,这样浏览器就不会自动产生ul标签,只会拿到普通文本
3、有个奇怪现象
那我们在提交完错误数据的时候页面刷新并没有将页面的数据清空,这个效果怎么实现呢?
因为在定义对象变量名的时候最开始的对象名和post方法里面的对象名是一样的;第一次在提交GET请求的时候这是是没有数据的,只会渲染字段名和input标签;提交完post请求后,那么此时对象就携带了数据,那么前端页面才会有可能显示错误信息,因为有数据才会去类下面校验;
那么在刷新的时候此时的form_obj还是携带数据的,所以数据就不会自动清除,这样就优化了有些网页在提交后,有报错的地方就需要重新输入所有的数据。
所以注意:
get请求和post请求传给html页面对象变量名必须一样,这样的话,form组件当你的数据不合法的情况下,会保存你上次的数据,让你基于之前的结果进行修改。
4、自定义错误信息展示
针对错误的提示信息还可以自己自定制
比如:
- username字符串类型最小3位最大8位
- password字符串类型最小3位最大8位
- email字段必须符合邮箱格式 xxx@xx.com
class MyForm(forms.Form):
# username字符串类型最小3位最大8位
username = forms.CharField(min_length=3, max_length=8, label='用户名',
error_messages={
'min_length': '用户名最少3位',
'max_length': '用户名最大8位',
'required': '用户名不能为空'
})
# password字符串类型最小3位最大8位
password = forms.CharField(min_length=3, max_length=8, label='密码',
error_messages={
'min_length': '密码最少3位',
'max_length': '密码最大8位',
'required': '密码不能为空'
})
# email字段必须符合邮箱格式 xxx@xx.com
email = forms.EmailField(label='邮箱',
error_messages={
'invalid': '邮箱格式不正确',
'required': '用户名不能为空'
})
5、补充
(1){{ field }}
中非常有用的属性
- 下表是
{{ field }}
中非常有用的属性,这些都是Django内置的模板语言给我们提供的方便:
属性 | 说明 |
---|---|
{{ field.label }} |
字段对应的label信息 |
{{ field.label_tag }} |
自动生成字段的label标签,注意与{{ field.label }} 的区别。 |
{{ field.id_for_label }} |
自定义字段标签的id |
{{ field.value }} |
当前字段的值,比如一个Email字段的值someone@example.com |
{{ field.html_name }} |
指定字段生成的input标签中name属性的值 |
{{ field.help_text }} |
字段的帮助信息 |
{{ field.errors }} |
包含错误信息的元素 |
{{ field.is_hidden }} |
用于判断当前字段是否为隐藏的字段,如果是,返回True |
{{ field.field }} |
返回字段的参数列表。例如{{ char_field.field.max_length }} |
(2)不可见字段
- 很多时候,我们的表单中会有一些隐藏的不可见的字段,比如honeypot。
- 我们需要让它在任何时候都仿佛不存在一般,比如有错误的时候,如果你在页面上显示了不可见字段的错误信息,那么用户会很迷惑,这是哪来的呢?
- 所以,通常我们是不显示不可见字段的错误信息的。
- Django提供了两种独立的方法,用于循环那些不可见的和可见的字段,
hidden_fields()
和visible_fields()
。 - 这里,我们可以稍微修改一下前面的例子:
{# 循环那些不可见的字段 #}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{# 循环可见的字段 #}
{% for field in form.visible_fields %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
五、forms组件钩子函数(HOOK)
1、什么是钩子函数
钩子函数是在系统在进行消息的传递处理时候利用钩子机制截取消息,可以对一些特定的消息进行处理。
像一些系统的开发功能可以基于消息处理机制,对消息进行初始化、动态创建分配空间,然后转发消息,对各个模块的消息进行实时监听,而钩子函数就是监听消息在传递到制定的模块窗口时候的操作,如果消息没有满足条件,钩子函数就可以把它进行处理然后让它无法最后成功达到目的地。如果满足了条件,就可以继续往后面传递。
在特定的节点自动触发完成响应操作,钩子函数在form罪案中就类似于第二道关卡,能够让我们自定义校验规则
2、forms组件里的钩子函数
在form组件中有两类钩子
(1)局部钩子
- 当你需要给单个字段增加校验规则的时候可以使用
- 在自定义的 forms 类中添加类方法即可
(2)全局钩子
- 当你需要给多个字段增加校验规则的时候可以使用
- 在自定义的 forms 类中添加类方法即可
3、案例
(1)校验用户名中不能含有666
只需要校验username字段,使用局部钩子
# 定义form类
class MyForm(forms.Form):
# username : 字符串类型 最小三位,最大八位
username = forms.CharField(max_length=8, min_length=3, label="用户名",
error_messages={
"max_length": "最大八位",
"min_length": "最小三位",
"required": "必填字段",
})
# # password : 字符串类型 最小三位,最大八位 : 字符串类型 最小三位,最大八位
password = forms.CharField(max_length=8, min_length=3, label="密码",
error_messages={
"max_length": "最大八位",
"min_length": "最小三位",
"required": "必填字段",
})
# confirm_password : 字符串类型 最小三位,最大八位 : 字符串类型 最小三位,最大八位
confirm_password = forms.CharField(max_length=8, min_length=3, label="确认密码",
error_messages={
"max_length": "最大八位",
"min_length": "最小三位",
"required": "必填字段",
})
# email : 必须符合邮箱格式 xxx@xx.com
email = forms.EmailField(label="邮箱",
error_messages={
"invalid": "格式不正确",
"required": "必填字段",
})
# 钩子函数
# 局部钩子
def clean_username(self):
# 获取到用户名
username = self.cleaned_data.get("username")
if "666" in username:
# 提示给前端错误信息
self.add_error("username", "用户名不能包含敏感词")
# 将钩子勾出来的数据再放回到原来的逻辑中
return username
(2)校验密码和确认密码是否一致
password confirm两个字段,使用全局钩子
# 定义form类
class MyForm(forms.Form):
# username : 字符串类型 最小三位,最大八位
username = forms.CharField(max_length=8, min_length=3, label="用户名",
error_messages={
"max_length": "最大八位",
"min_length": "最小三位",
"required": "必填字段",
})
# # password : 字符串类型 最小三位,最大八位 : 字符串类型 最小三位,最大八位
password = forms.CharField(max_length=8, min_length=3, label="密码",
error_messages={
"max_length": "最大八位",
"min_length": "最小三位",
"required": "必填字段",
})
# confirm_password : 字符串类型 最小三位,最大八位 : 字符串类型 最小三位,最大八位
confirm_password = forms.CharField(max_length=8, min_length=3, label="确认密码",
error_messages={
"max_length": "最大八位",
"min_length": "最小三位",
"required": "必填字段",
})
# email : 必须符合邮箱格式 xxx@xx.com
email = forms.EmailField(label="邮箱",
error_messages={
"invalid": "格式不正确",
"required": "必填字段",
})
# 钩子函数
# 局部钩子
def clean_username(self):
# 获取到用户名
username = self.cleaned_data.get("username")
if "666" 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
六、form组件其它参数及补充知识点
1、引入
- 创建Form类时主要涉及到 【字段】 和 【插件】
- 字段用于对用户请求数据的验证
- 插件用于自动生成HTML;
2、Field.clean(value)
- 注意:这里说的是字段Field的clearn方法,不是表单Form的clean方法。
- 虽然表单字段的Field类主要使用在Form类中,但也可以直接实例化它们来使用,以便更好地了解它们是如何工作的。
- 每个Field的实例都有一个clean()方法,它接受一个参数,然后返回“清洁的”数据或者抛出一个
django.forms.ValidationError
异常:
from django import forms
f = forms.EmailField()
f.clean('foo@example.com')
# 'foo@example.com'
f.clean('invalid email address')
# Traceback (most recent call last):
# ...
# ValidationError: ['Enter a valid email address.']
- 这个clean方法经常被我们用来在开发或测试过程中对某个字段的数据进行验证和测试。
3、核心字段参数
(1)required
- 设置表单项是否为必填项,如果未填写必填项,提交表单时将触发验证错误。
(2)initial
- 为HTML页面中表单元素定义初始值。
- 也就是input元素的value参数的值,如下所示:
from django import forms
class CommentForm(forms.Form):
name = forms.CharField(initial='Your name')
url = forms.URLField(initial='http://')
comment = forms.CharField()
f = CommentForm(auto_id=False)
print(f)
# <tr><th>Name:</th><td><input type="text" name="name" value="Your name" required /></td></tr>
# <tr><th>Url:</th><td><input type="url" name="url" value="http://" required /></td></tr>
# <tr><th>Comment:</th><td><input type="text" name="comment" required /></td></tr>
- 你可能会问为什么不在渲染表单的时候传递一个包含初始化值的字典给它,不是更方便?
- 因为如果这么做,你将触发表单的验证过程,此时输出的HTML页面将包含验证中产生的错误,如下所示:
from django import forms
class CommentForm(forms.Form):
name = forms.CharField()
url = forms.URLField()
comment = forms.CharField()
default_data = {'name': 'Your name', 'url': 'http://'}
f = CommentForm(default_data, auto_id=False)
print(f)
# <tr><th>Name:</th><td><input type="text" name="name" value="Your name" required /></td></tr>
# <tr><th>Url:</th><td><ul class="errorlist"><li>Enter a valid URL.</li></ul><input type="url" name="url" value="http://" required /></td></tr>
# <tr><th>Comment:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="comment" required /></td></tr>
- 这就是为什么initial参数只用在未绑定的表单上。
- 还要注意,如果提交表单时某个字段的值没有填写,initial的值不会作为“默认”的数据。initial值只用于原始表单的显示
(3)label
- label参数用来给字段添加‘人类友好’的提示信息。
- 如果没有设置这个参数,那么就用字段的首字母大写名字。
from django import forms
class CommentForm(forms.Form):
name = forms.CharField(label='Your name')
url = forms.URLField(label='Your website', required=False)
comment = forms.CharField()
f = CommentForm(auto_id=False)
print(f)
# <tr><th>Your name:</th><td><input type="text" name="name" required /></td></tr>
# <tr><th>Your website:</th><td><input type="url" name="url" /></td></tr>
# <tr><th>Comment:</th><td><input type="text" name="comment" required /></td></tr>
(4) label_suffix
- Django默认为上面的label参数后面加个冒号后缀,如果想自定义,可以使用
label_suffix
参数。 - 比如下面的例子用“?”代替了冒号:
from django import forms
class ContactForm(forms.Form):
age = forms.IntegerField()
nationality = forms.CharField()
captcha_answer = forms.IntegerField(label='2 + 2', label_suffix=' =')
f = ContactForm(label_suffix='?')
print(f.as_p())
# <p><label for="id_age">Age?</label> <input id="id_age" name="age" type="number" required /></p>
# <p><label for="id_nationality">Nationality?</label> <input id="id_nationality" name="nationality" type="text" required /></p>
# <p><label for="id_captcha_answer">2 + 2 =</label> <input id="id_captcha_answer" name="captcha_answer" type="number" required /></p>
(5)help_text
- 该参数用于设置字段的辅助描述文本。
from django import forms
class HelpTextContactForm(forms.Form):
subject = forms.CharField(max_length=100, help_text='100 characters max.')
message = forms.CharField()
sender = forms.EmailField(help_text='A valid email address, please.')
cc_myself = forms.BooleanField(required=False)
f = HelpTextContactForm(auto_id=False)
print(f.as_table())
# <tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" required /><br /><span class="helptext">100 characters max.</span></td></tr>
# <tr><th>Message:</th><td><input type="text" name="message" required /></td></tr>
# <tr><th>Sender:</th><td><input type="email" name="sender" required /><br />A valid email address, please.</td></tr>
# <tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself" /></td></tr>
print(f.as_ul())
# <li>Subject: <input type="text" name="subject" maxlength="100" required /> <span class="helptext">100 characters max.</span></li>
# <li>Message: <input type="text" name="message" required /></li>
# <li>Sender: <input type="email" name="sender" required /> A valid email address, please.</li>
# <li>Cc myself: <input type="checkbox" name="cc_myself" /></li>
print(f.as_p())
# <p>Subject: <input type="text" name="subject" maxlength="100" required /> <span class="helptext">100 characters max.</span></p>
# <p>Message: <input type="text" name="message" required /></p>
# <p>Sender: <input type="email" name="sender" required /> A valid email address, please.</p>
# <p>Cc myself: <input type="checkbox" name="cc_myself" /></p>
(6)error_messages
- 该参数允许你覆盖字段引发异常时的默认信息。
- 传递的是一个字典,其键为你想覆盖的错误信息。
- 例如,下面是默认的错误信息:
from django import forms
generic = forms.CharField()
generic.clean('')
# Traceback (most recent call last):
# ...
# ValidationError: ['This field is required.']
- 而下面是自定义的错误信息:
from django import forms
name = forms.CharField(error_messages={'required': 'Please enter your name'})
name.clean('')
# Traceback (most recent call last):
# ...
# ValidationError: ['Please enter your name']
- 可以指定多种类型的键,不仅仅针对‘requeired’错误,参考下面的内容。
(7)validators
-
指定一个列表,其中包含了为字段进行验证的函数。
-
也就是说,如果你自定义了验证方法,不用Django内置的验证功能,那么要通过这个参数,将字段和自定义的验证方法链接起来。
-
参考前面的章节,学习如何编写验证器。
(8)localize
- localize参数帮助实现表单数据输入的本地化。
(9)disabled
- 设置有该属性的字段在前端页面中将显示为不可编辑状态。
- 该参数接收布尔值,当设置为True时,使用HTML的disabled属性禁用表单域,以使用户无法编辑该字段。
- 即使非法篡改了前端页面的属性,向服务器提交了该字段的值,也将依然被忽略。
(10)has_changed()方法
- 和Form类似,每个Field也有一个has_changed()方法(注意这不是参数,是方法),用于判断字段的值是否发生了改变。
4、widget小插件
- 最重要的参数之一,指定渲染Widget时使用的widget类,也就是这个form字段在HTML页面中是显示为文本输入框?密码输入框?单选按钮?多选框?还是别的....
(1)password输入框
class LoginForm(forms.Form):
...
pwd = forms.CharField(
min_length=6,
label="密码",
widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
)
(2)radioSelect选择框
- 单radio值为字符串
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三",
error_messages={
"required": "不能为空",
"invalid": "格式错误",
"min_length": "用户名最短8位"
}
)
pwd = forms.CharField(min_length=6, label="密码")
gender = forms.fields.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
(3)Select单选框
class LoginForm(forms.Form):
...
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
(14)Select多选框
class LoginForm(forms.Form):
...
hobby = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
(5)checkbox单选框
class LoginForm(forms.Form):
...
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
(6)checkbox多选框
class LoginForm(forms.Form):
...
hobby = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
5、choice字段注意事项
- 在使用选择标签时
- 需要注意choices的选项可以配置从数据库中获取
- 但是由于是静态字段
- 获取的值无法实时更新
- 需要重写构造方法从而实现choice实时更新。
(1)方式一
from django.forms import Form
from django.forms import widgets
from django.forms import fields
class MyForm(Form):
user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.Select
)
def __init__(self, *args, **kwargs):
super(MyForm,self).__init__(*args, **kwargs)
# self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
# 或
self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
(3)方式二
from django import forms
from django.forms import fields
from django.forms import models as form_model
class FInfo(forms.Form):
authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多选
# authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 单选
6、自定义字段校验
(1)RegexValidator验证器
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
class MyForm(Form):
user = fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
)
(2)自定义验证函数
import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
# 自定义验证规则
def mobile_validate(value):
mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
if not mobile_re.match(value):
raise ValidationError('手机号码格式错误')
class PublishForm(Form):
title = fields.CharField(max_length=20,
min_length=5,
error_messages={'required': '标题不能为空',
'min_length': '标题最少为5个字符',
'max_length': '标题最多为20个字符'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': '标题5-20个字符'}))
# 使用自定义验证规则
phone = fields.CharField(validators=[mobile_validate, ],
error_messages={'required': '手机不能为空'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': u'手机号码'}))
email = fields.EmailField(required=False,
error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'},
widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
7、Django中的form所有内置字段
总览
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类型
概述
(1)BooleanField
- 默认的Widget:CheckboxInput
- 空值:False
- 规范化为:Python的True或者False
- 可用的错误信息键:required
(2)CharField
- 默认的Widget:TextInput
- 空值:与empty_value给出的任何值。
- 规范化为:一个Unicode 对象。
- 验证
max_length
或min_length
,如果设置了这两个参数。 否则,所有的输入都是合法的。 - 可用的错误信息键:min_length, max_length, required
有四个可选参数:
- max_length,min_length:设置字符串的最大和最小长度。
- strip:如果True(默认),去除输入的前导和尾随空格。
- empty_value:用来表示“空”的值。 默认为空字符串。
(3)ChoiceField
- 默认的Widget:Select
- 空值:''(一个空字符串)
- 规范化为:一个Unicode 对象。
- 验证给定的值是否在选项列表中。
- 可用的错误信息键:required, invalid_choice
参数choices:
用来作为该字段选项的一个二元组组成的可迭代对象(例如,列表或元组)或者一个可调用对象。
格式与用于和ORM模型字段的choices参数相同。
(4)TypedChoiceField
像ChoiceField一样,只是还有两个额外的参数:coerce和empty_value。
- 默认的Widget:Select
- 空值:empty_value参数设置的值。
- 规范化为:coerce参数类型的值。
- 验证给定的值在选项列表中存在并且可以被强制转换。
- 可用的错误信息的键:required, invalid_choice
(5)DateField
- 默认的Widget:DateInput
- 空值:None
- 规范化为:datetime.date对象。
- 验证给出的值是一个datetime.date、datetime.datetime 或指定日期格式的字符串。
- 错误信息的键:required, invalid
接收一个可选的参数:input_formats。
一个格式的列表,用于转换字符串为datetime.date对象。
如果没有提供input_formats,默认的输入格式为:
['%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y', # '10/25/2006'
'%m/%d/%y'] # '10/25/06'
另外,如果你在设置中指定USE_L10N=False
,以下的格式也将包含在默认的输入格式中:
['%b %d %Y', # 'Oct 25 2006'
'%b %d, %Y', # 'Oct 25, 2006'
'%d %b %Y', # '25 Oct 2006'
'%d %b, %Y', # '25 Oct, 2006'
'%B %d %Y', # 'October 25 2006'
'%B %d, %Y', # 'October 25, 2006'
'%d %B %Y', # '25 October 2006'
'%d %B, %Y'] # '25 October, 2006'
(6)DateTimeField
- 默认的Widget:DateTimeInput
- 空值:None
- 规范化为:Python的datetime.datetime对象。
- 验证给出的值是一个datetime.datetime、datetime.date或指定日期格式的字符串。
- 错误信息的键:required, invalid
接收一个可选的参数:input_formats
如果没有提供input_formats,默认的输入格式为:
['%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
'%m/%d/%Y %H:%M', # '10/25/2006 14:30'
'%m/%d/%Y', # '10/25/2006'
'%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
'%m/%d/%y %H:%M', # '10/25/06 14:30'
'%m/%d/%y'] # '10/25/06'
(7)DecimalField
- 默认的Widget:当Field.localize是False时为NumberInput,否则为TextInput。
- 空值:None
- 规范化为:Python decimal对象。
- 验证给定的值为一个十进制数。 忽略前导和尾随的空白。
- 错误信息的键:
max_whole_digits
,max_digits
,max_decimal_places
,max_value
, invalid, required,min_value
接收四个可选的参数:
max_value,min_value:允许的值的范围,需要赋值decimal.Decimal对象,不能直接给个整数类型。
max_digits:值允许的最大位数(小数点之前和之后的数字总共的位数,前导的零将被删除)。
decimal_places:允许的最大小数位。
(8)DurationField
- 默认的Widget:TextInput
- 空值:None
- 规范化为:Python timedelta。
- 验证给出的值是一个字符串,而且可以转换为timedelta对象。
- 错误信息的键:required, invalid.
(9)EmailField
- 默认的Widget:EmailInput
- 空值:''(一个空字符串)
- 规范化为:Unicode 对象。
- 使用正则表达式验证给出的值是一个合法的邮件地址。
- 错误信息的键:required, invalid
两个可选的参数用于验证,max_length 和min_length。
(10)FileField
- 默认的Widget:ClearableFileInput
- 空值:None
- 规范化为:一个UploadedFile对象,它封装文件内容和文件名到一个对象内。
- 验证非空的文件数据已经绑定到表单。
- 错误信息的键:missing, invalid, required, empty, max_length
具有两个可选的参数用于验证:max_length 和 allow_empty_file。
(11)FilePathField
- 默认的Widget:Select
- 空值:None
- 规范化为:Unicode 对象。
- 验证选择的选项在选项列表中存在。
- 错误信息的键:required, invalid_choice
这个字段允许从一个特定的目录选择文件。 它有五个额外的参数,其中的path是必须的:
path:要列出的目录的绝对路径。 这个目录必须存在。
recursive:如果为False(默认值),只用直接位于path下的文件或目录作为选项。如果为True,将递归访问这个目录,其内所有的子目录和文件都将作为选项。
match:正则表达模式;只有具有与此表达式匹配的文件名称才被允许作为选项。
allow_files
:可选。默认为True。表示是否应该包含指定位置的文件。它和allow_folders
必须有一个为True。
allow_folders
可选。默认为False。表示是否应该包含指定位置的目录。
(12)FloatField
- 默认的Widget:当Field.localize是False时为NumberInput,否则为TextInput。
- 空值:None
- 规范化为:Float 对象。
- 验证给定的值是一个浮点数。
- 错误信息的键:max_value, invalid, required, min_value
接收两个可选的参数用于验证,max_value和min_value,控制允许的值的范围。
(13)ImageField
- 默认的Widget:ClearableFileInput
- 空值:None
- 规范化为:一个UploadedFile 象,它封装文件内容和文件名为一个单独的对象。
- 验证文件数据已绑定到表单,并且该文件是Pillow可以解析的图像格式。
- 错误信息的键:missing, invalid, required, empty, invalid_image
使用ImageField需要安装Pillow(pip install pillow)。如果在上传图片时遇到图像损坏错误,通常意味着使用了Pillow不支持的格式。
>>> from PIL import Image
>>> from django import forms
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> class ImageForm(forms.Form):
... img = forms.ImageField()
>>> file_data = {'img': SimpleUploadedFile('test.png', <file data>)}
>>> form = ImageForm({}, file_data)
# Pillow closes the underlying file descriptor.
>>> form.is_valid()
True
>>> image_field = form.cleaned_data['img']
>>> image_field.image
<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=191x287 at 0x7F5985045C18>
>>> image_field.image.width
191
>>> image_field.image.height
287
>>> image_field.image.format
'PNG'
>>> image_field.image.getdata()
# Raises AttributeError: 'NoneType' object has no attribute 'seek'.
>>> image = Image.open(image_field)
>>> image.getdata()
<ImagingCore object at 0x7f5984f874b0>
(14)IntegerField
- 默认的Widget:当Field.localize是False时为NumberInput,否则为TextInput。
- 空值:None
- 规范化为:Python 整数或长整数。
- 验证给定值是一个整数。 允许前导和尾随空格,类似Python的int()函数。
- 错误信息的键:max_value, invalid, required, min_value
两个可选参数:max_value和min_value,控制允许的值的范围。
(15)JSONField
Django3.1新增。
接收JSON编码的字段。
- 默认的Widget:Textarea
- 空值:None
- 规范化为:一个JSON对象。
- 验证给定值是否合法的JSON。
- 错误信息的键:required, invalid
- 可接受两个参数:encoder和decoder,编码器和解码器
(16)GenericIPAddressField
包含IPv4或IPv6地址的字段。
- 默认的Widget:TextInput
- 空值:''(一个空字符串)
- 规范化为:一个Unicode对象。
- 验证给定值是有效的IP地址。
- 错误信息的键:required, invalid
有两个可选参数:protocol和unpack_ipv4
(17)MultipleChoiceField
- 默认的Widget:SelectMultiple
- 空值:[](一个空列表)
- 规范化为:一个Unicode 对象列表。
- 验证给定值列表中的每个值都存在于选择列表中。
- 错误信息的键:invalid_list, invalid_choice, required
(18)TypedMultipleChoiceField
类似MultipleChoiceField,除了需要两个额外的参数,coerce和empty_value。
- 默认的Widget:SelectMultiple
- 空值:empty_value
- 规范化为:coerce参数提供的类型值列表。
- 验证给定值存在于选项列表中并且可以强制。
- 错误信息的键:required, invalid_choice
(19)NullBooleanField
- 默认的Widget:NullBooleanSelect
- 空值:None
- 规范化为:Python None, False 或True 值。
- 不验证任何内容(即,它从不引发ValidationError)。
(20)RegexField
- 默认的Widget:TextInput
- 空值:''(一个空字符串)
- 规范化为:一个Unicode 对象。
- 验证给定值与某个正则表达式匹配。
- 错误信息的键:required, invalid
需要一个必需的参数:regex,需要匹配的正则表达式。
还可以接收max_length,min_length和strip参数,类似CharField。
(21)SlugField
- 默认的Widget:TextInput
- 空值:''(一个空字符串)
- 规范化为:一个Unicode 对象。
- 验证给定的字符串只包括字母、数字、下划线及连字符。
- 错误信息的键:required, invalid
此字段用于在表单中表示模型的SlugField。
(22)TimeField
- 默认的Widget:TextInput
- 空值:None
- 规范化为:一个Python 的datetime.time 对象。
- 验证给定值是datetime.time或以特定时间格式格式化的字符串。
- 错误信息的键:required, invalid
接收一个可选的参数:input_formats,用于尝试将字符串转换为有效的datetime.time对象的格式列表。
如果没有提供input_formats,默认的输入格式为:
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
(23)URLField
- 默认的Widget:URLInput
- 空值:''(一个空字符串)
- 规范化为:一个Unicode 对象。
- 验证给定值是个有效的URL。
- 错误信息的键:required, invalid
可选参数:max_length和min_length
(24)UUIDField
- 默认的Widget:TextInput
- 空值:''(一个空字符串)
- 规范化为:UUID对象。
- 错误信息的键:required, invalid
(25) ComboField
- 默认的Widget:TextInput
- 空值:''(一个空字符串)
- 规范化为:Unicode 对象。
- 根据指定为ComboField的参数的每个字段验证给定值。
- 错误信息的键:required, invalid
接收一个额外的必选参数:fields,用于验证字段值的字段列表(按提供它们的顺序)。
>>> from django.forms import ComboField
>>> f = ComboField(fields=[CharField(max_length=20), EmailField()])
>>> f.clean('test@example.com')
'test@example.com'
>>> f.clean('longemailaddress@example.com')
Traceback (most recent call last):
...
ValidationError: ['Ensure this value has at most 20 characters (it has 28).']
(26)MultiValueField
- 默认的Widget:TextInput
- 空值:''(一个空字符串)
- 规范化为:子类的compress方法返回的类型。
- 根据指定为MultiValueField的参数的每个字段验证给定值。
- 错误信息的键:incomplete, invalid, required
(27)SplitDateTimeField
- 默认的Widget:SplitDateTimeWidget
- 空值:None
- 规范化为:Python datetime.datetime 对象。
- 验证给定的值是datetime.datetime或以特定日期时间格式格式化的字符串。
- 错误信息的键:invalid_date, invalid, required, invalid_time
创建自定义字段
- 如果内置的Field真的不能满足你的需求,还可以自定义Field。
- 只需要创建一个
django.forms.Field
的子类,并实现clean()和__init__()
构造方法。__init__()
构造方法需要接收前面提过的那些核心参数,比如widget、required,、label、help_text、initial。 - 还可以通过覆盖
get_bound_field()
方法来自定义访问字段的方式。
七、forms组件源码
"""
切入点
form_obj.is_valid()
"""
def is_valid(self):
"""Return True if the form has no errors, or False otherwise."""
return self.is_bound and not self.errors
# 如果is_valid要返回True的话,那么self.is_bound要为True,self.errors要为False
self.is_bound = data is not None or files is not None
# 只要你传值了,它就是True
@property # 报错提示
def errors(self):
"""Return an ErrorDict for the data provided for the form."""
if self._errors is None:
self.full_clean()
return self._errors
def full_clean(self): # forms组件功能基本都出自这个方法
self._clean_fields() # 校验数据 + 局部钩子
self._clean_form() # 全局钩子
self._post_clean()
1、切入点
切入点
form_obj.is_valid()
def is_valid(self):
"""Return True if the form has no errors, or False otherwise."""
return self.is_bound and not self.errors
- 如果
is_valid
要想返回True- 那么
self.is_bound
要为True self.errors
要为 False
- 那么
2、self.is_bound
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
self.is_bound = data is not None or files is not None
- data 是我们传入的数据
- 只要传入数据有值
- 那么
self.is_bound
一定是True
3、self.errors
@property
def errors(self):
"""Return an ErrorDict for the data provided for the form."""
if self._errors is None:
self.full_clean()
return self._errors
- forms组件所有的功能基本都出自这个方法
def full_clean(self):
"""
Clean all of self.data and populate self._errors and self.cleaned_data.
"""
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
self._clean_fields() # 校验字段
self._clean_form()
self._post_clean()
(1)self._clean_fields() 校验字段 + 局部钩子
def _clean_fields(self):
for name, field in self.fields.items(): # 循环获取字段对象
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
# 获取字段对应的值
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value # 将合法的字段添加到字典里
if hasattr(self, 'clean_%s' % name): # 利用反射获取局部钩子函数
value = getattr(self, 'clean_%s' % name)() # 局部钩子需要有返回值
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e) # 添加提示信息
循环获取字段对象
-
局部钩子报错也可以使用
ValidationError
主动抛出异常- 较为繁琐,一般不用
(2)_clean_form 全局钩子
def _clean_form(self):
try:
cleaned_data = self.clean() # 调用父类的clean方法或者自定义的clean方法
except ValidationError as e:
self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
(3)_post_clean
def _post_clean(self):
"""
An internal hook for performing additional cleaning after form cleaning
is complete. Used for model validation in model forms.
"""
pass
标签:None,form,self,Django,forms,length,组件,label
From: https://www.cnblogs.com/xiao01/p/18122601