Django forms组件
Django forms
Django
中的forms
组件能够为前端提交的数据进行验证,同时也能生成HTML
代码,在全栈开发中非常的方便。
但是如果你的项目是前后端分离的,那么就没必要使用forms
的渲染HTML
代码功能了。
学习forms
组件将分为三步:
1.如何对数据进行验证
2.如何渲染
HTML
代码3.如何将错误信息反馈给页面
数据验证
forms
提供的数据验证非常强大,你可以自定义你的数据验证格式,也可以使用内置的很多校验方法。
测试环境
还是先准备好测试环境,在APP
下的tests
中进行如下设置:
from django.test import TestCase
# Create your tests here.
import os
import sys
sys.path.append(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Djangoforms.settings")
import django
django.setup()
验证类
forms
组件应该放在一个单独的文件中,我在APP
下命建立了一个dataforms.py
文件,并在里面书写了forms
验证类。
from django import forms
class F1(forms.Form):
username = forms.CharField(max_length=16, min_length=6, label="用户名")
password = forms.CharField(max_length=16, min_length=6, label="密码")
email = forms.EmailField(label="邮箱") # label是渲染HTML时使用的名字
大概意思非常明了:
1.
username
为字符类型,最长为16(字符),最短为6(字符)。2.
password
为字符类型,最长为16(字符),最短为6(字符)。3.
校验数据
校验数据时,使用dict
进行传入并且实例化F1
类。
使用is_valid()
查看是否全部验证通过。
使用cleand_data
获取验证成功的字段。
使用errors
获取验证失败的字段与错误信息。(错误信息是HTML
形式,这个是往页面上渲染用的)
1.实例化
F1
类时,传入参数必须与字段一一对应。可以多传但是绝对不能少传,除非字段限制设置为可以为空。
from dataforms import F1
data = {
"username": "云崖先生",
"password": "12345678",
"email": "c2323182108@gmail.com",
}
verify_data = F1(data) # 注意!这里是实例化出了F1的对象
if verify_data.is_valid():
print("全部验证通过...")
print(verify_data.cleaned_data) # 获取验证通过的字段与数据
else:
print("某个字段数据没能通过验证...")
print(verify_data.errors)
# <ul class="errorlist"><li>username<ul class="errorlist"><li>Ensure this value has at least 6 characters (it has 4).</li></ul></li></ul>
那么校验数据就说到这里。
渲染页面
forms
组件可以自动生成HTML
代码,并且做了make_safe()
处理,因此可以直接在页面上进行显示。
视图函数
还是使用F1
进行验证,书写一个视图函数。这里面有1个细节点。
from django.shortcuts import render
from django.shortcuts import redirect
from app01 import dataforms
def test(request):
verify_obj = dataforms.F1() # 注意!这里是传了一个空对象
if request.method == "POST":
verify_obj = dataforms.F1(request.POST) # request.POST 本身就是一个字典 注意!这里又实例化出了一个对象
if verify_obj.is_valid():
pass # 写入数据库的操作,然后执行redirect跳转
return redirect("http://www.google.com")
else:
print(verify_obj.errors)
return render(request,"test.html",locals()) # 无论如何都会返回这个页面
自动生成
自动生成的方式有好几种,推荐使用手动的方式。
其实每一个
forms
里面的字段都是一个实例化对象,而填入的参数则是其中的属性。我们可以通过.
给他拿出来。
以下是自动生成的方式。
<body>
{{verify_obj.as_ul}} <!-- ul套li -->
{{verify_obj.as_p}} <!-- p标签 -->
{{verify_obj.as_table}} <!-- table -->
</body>
推荐使用手动生成的方式。
<body>
<!-- 循环验证对象的字段,label即为其名字 -->
<!-- form表单上使用novalidate属性将会禁止浏览器的检测 -->
<!-- 如果input外边又label标签,可位for属性设置filed.auto_id 这将自动关联到input的id -->
<form action="" method="POST" novalidate>
{% for field in verify_obj %}
<p> {{field.label}} : {{field}} {{field.errors.0}}</p>
{% endfor %}
<button type="submit">提交信息</button>
</form>
</body>
错误信息
我们可以在页面上对错误信息进行渲染。
经过上面视图函数的两次对F1
的实例化,使我们可以做到在刷新页面的情况下保留原本输入的数据。
渲染信息
由于某个字段的验证条件可能有多种,所以错误信息默认是以<ul>
套着<li>
出现的。
所以我们使用{{field.errors.0}}
拿出第一条信息即可。
<body>
<!-- 循环验证对象的字段,label即为其名字 -->
<!-- form表单上使用novalidate属性将会禁止浏览器的检测 -->
<form action="" method="POST" novalidate>
{% for field in verify_obj %}
<p> {{field.label}} : {{field}} {{field.errors.0}}</p>
{% endfor %}
<button type="submit">提交信息</button>
</form>
</body>
自定错误
可以看到,默认的它的错误信息是英文的。
这个时候可以自定制错误,针对不同的规则定制不同的错误。
class F1(forms.Form):
username = forms.CharField(max_length=16, min_length=6, label="用户名",
error_messages={
"max_length":"用户名不能超过16位",
"min_length":"用户名不能小于6位",
"required":"不能为空",
"invalid":"格式出错", # 代表全部错误
}
)
password = forms.CharField(max_length=16, min_length=6, label="密码",
error_messages={
"max_length":"密码不能超过16位",
"min_length":"密码不能小于6位",
"required":"不能为空",
}
)
email = forms.EmailField(label="邮箱",
error_messages={
"required":"不能为空",
"invalid":"必须是邮箱格式",
}
)
样式插件
默认的字段样式都不太好看,我们可以利用widget
来使用一些自带的插件。
内置插件
使用内置插件前要先进行导入:
from django.forms import widgets
插件名称 | 对应input |
---|---|
TextInput | input type="text" |
PasswordInput | input type="password" |
HiddenInput | input type="hidden" |
NumberInput | input type="number" |
EmailInput | input type="email" |
URLInput | input type="url" |
Textarea | textarea |
DateInput | input type="data" |
DateTimeInput | input type="datetime" |
TimeInput | 只获取时分秒 |
CheckboxInput | input type="checkbox" |
CheckboxSelectMultiple | 多选按钮 |
Select | select框 |
NullBooleanSelect | select框 三个选项 0 1 null |
SelectMultiple | 可多选的select框 |
RadioSelect | input type="radion" |
FileInput | 可以查看当前目录下的所有文件。作用不大 |
ClearableFileInput | 同上 |
MultipleHiddenInput | 作用不大 |
SplitDateTimeWidget | 作用不大 |
SplitHiddenDateTimeWidget | 作用不大 |
SelectDateWidget | 作用不大 |
常用插件
以下例举一些常用的插件。
# 单radio,值为字符串
# user = forms.CharField(
# initial=2, # initial 默认选择的值
# widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
# 单radio,值为字符串
# user = forms.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.RadioSelect
# )
# 单select,值为字符串
# user = forms.CharField(
# initial=2,
# widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
# 单select,值为字符串
# user = forms.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.Select
# )
# 多选select,值为列表
# user = forms.MultipleChoiceField(
# choices=((1,'上海'),(2,'北京'),),
# initial=[1,],
# widget=widgets.SelectMultiple
# )
# 单checkbox
# user = forms.CharField(
# widget=widgets.CheckboxInput()
# )
# 多选checkbox,值为列表
# user = forms.MultipleChoiceField(
# initial=[2, ],
# choices=((1, '上海'), (2, '北京'),),
# widget=widgets.CheckboxSelectMultiple
# )
插件样式
如果使用了widget
插件,我们可以利用attrs
跟上一个字典,来定制插件的样式。
注意:不同的类之间,用空格进行分割。
from django import forms
from django.forms import widgets
class F1(forms.Form):
username = forms.CharField(max_length=16, min_length=6, label="用户名",
error_messages={
"max_length":"用户名不能超过16位",
"min_length":"用户名不能小于6位",
"required":"不能为空",
"invalid":"格式出错", # 代表全部错误
},
widget=widgets.TextInput(attrs={"class":"form-control","style":"color:red;font-size:16px;"}),
)
password = forms.CharField(max_length=16, min_length=6, label="密码",
error_messages={
"max_length":"密码不能超过16位",
"min_length":"密码不能小于6位",
"required":"不能为空",
},
widget=widgets.PasswordInput(attrs={"class":"form-control"}),
)
email = forms.EmailField(label="邮箱",
error_messages={
"required":"不能为空",
"invalid":"必须是邮箱格式",
},
widget=widgets.EmailInput(attrs={"class":"form-control"})
)
内置字段
介绍完插件后,还需要介绍一下内置字段。
内置字段的作用是定义验证规则,而插件则是对生成的标签样式做美化功能的。
注意不要被相同的字段名搞混淆了。
通用参数
参数名 | 描述 |
---|---|
required | 是否允许为空,默认False |
label | 生成Label标签或显示内容 |
initial | 初始值,常用于SELECT或RADION以及CHECKBOX中 |
help_text | 帮助信息(在标签旁边显示),没啥用 |
error_messages | 自定义错误信息 |
validators | 这是一个列表,可以存放多个正则用于验证 |
localize | 是否支持本地化,对于时间类型的可以选择为True |
disabled | 是否可以编辑,默认是True |
label_suffix | Lable的后缀,没什么用,主要是自动生成HTML代码是才能用得上 |
show_hidden_initial | 这个比较有用,一个隐藏的input框,可用于存放用户的id信息 |
widget | HTML插件 |
字段大全
注意区分,这里的字段是forms.字段名
,不是插件。
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) 必须是URL类型
...
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类型
钩子函数
局部钩子
在forms
验证类中,可以定义局部钩子。
可以作为二次验证。
局部钩子的名字为clean_字段名
。
注意,当局部钩子函数处理完数据后,应该将数据
return
回去。
# 针对username字段的提交数据进行二次验证
def clean_username(self):
username = self.cleaned_data.get("username")
prohibit = {"蛤蟆","维尼熊","跳跳虎"}
for name in prohibit:
if name in username:
self.add_error("username","含有违禁词汇!") # 新增一个页面错误信息
return username
全局钩子
全局钩子可以拿到所有字段提交的数据信息,因此非常适合用来做数据库验证,验证提交的数据信息是否存在于数据库中。
当然你如果是一个注册页面,可以用来验证两次密码是否输入一致。
全局钩子的名称为clean
数据库结构:
from django.db import models
class User(models.Model):
username = models.CharField(max_length=16)
password = models.CharField(max_length=16)
email = models.EmailField(max_length=32)
def __str__(self):
return self.username
全局钩子函数:
def clean(self):
res = models.User.objects.filter(**self.cleaned_data).exists()
if not res:
self.ALLERROR = "数据库中不存在该数据" # 自定义一个实例属性
raise ValidationError(self.ALLERROR) # 先导入该异常,再添加上自定义的异常信息
return self.cleaned_data # 无论如何都进行返回
抛出异常
抛出异常分为字段异常和全局异常。
如我们再局部钩子中发现某个字段数据有一些问题,则使用字段异常即可。
self.add_error("username","含有违禁词汇!") # 新增一个页面错误信息
同时前端获取的时候可以直接获取到,因为0
就是这个异常。
<p> {{verify_obj.username.label}} : {{verify_obj.username}} {{verify_obj.username.errors.0}}</p>
如果抛出的是全局异常,那么页面上就需要另外的显示手段了,我这里直接把它做成一个实例属性就可以进行取出。
from django.forms import ValidationError # 先导入全局异常
self.ALLERROR = "数据库中不存在该数据" # 自定义一个实例属性
raise ValidationError(self.ALLERROR) # 触发异常
前端获取的话就可以直接用这个对象常量进行获取。
{{verify_obj.ALLERROR}}
全部代码
urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^test/', views.test,name="test"),
]
app01/views.py
from django.shortcuts import render
from django.shortcuts import redirect
from app01 import dataforms
def test(request):
verify_obj = dataforms.F1() # 注意!这里是传了一个空对象
if request.method == "POST":
verify_obj = dataforms.F1(request.POST) # request.POST 本身就是一个字典 注意!这里又实例化出了一个对象
if verify_obj.is_valid():
pass # 写入数据库的操作,然后执行redirect跳转
return redirect("http://www.google.com")
else:
print(verify_obj.errors)
return render(request,"test.html",locals())
app01/dataforms.py
from django import forms
from django.forms import widgets
from django.forms import ValidationError
from app01 import models
class F1(forms.Form):
username = forms.CharField(max_length=16, min_length=6, label="用户名",
error_messages={
"max_length":"用户名不能超过16位",
"min_length":"用户名不能小于6位",
"required":"不能为空",
"invalid":"格式出错", # 代表全部错误
},
widget=widgets.TextInput(attrs={"class":"form-control","style":"color:red;font-size:16px;"}),
)
password = forms.CharField(max_length=16, min_length=6, label="密码",
error_messages={
"max_length":"密码不能超过16位",
"min_length":"密码不能小于6位",
"required":"不能为空",
},
widget=widgets.PasswordInput(attrs={"class":"form-control"}),
)
email = forms.EmailField(label="邮箱",
error_messages={
"required":"不能为空",
"invalid":"必须是邮箱格式",
},
widget=widgets.EmailInput(attrs={"class":"form-control"})
)
def clean_username(self):
username = self.cleaned_data.get("username")
prohibit = {"蛤蟆","维尼熊","跳跳虎"}
for name in prohibit:
if name in username:
self.add_error("username","含有违禁词汇!") # 新增一个页面错误信息
return username
def clean(self):
res = models.User.objects.filter(**self.cleaned_data).exists()
if not res:
self.ALLERROR = "数据库中不存在该数据" # 自定义一个实例属性
raise ValidationError(self.ALLERROR) # 先导入该异常
return self.cleaned_data # 无论如何都进行返回
app01/models.py
from django.db import models
# Create your models here.
class User(models.Model):
username = models.CharField(max_length=16)
password = models.CharField(max_length=16)
email = models.EmailField(max_length=32)
def __str__(self):
return self.username
templates/test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href='https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css' rel='stylesheet'>
<script src='https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js'></script>
<script src='https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js'></script>
</head>
<body>
<!-- 循环验证对象的字段,label即为其名字 -->
<!-- form表单上使用novalidate属性将会禁止浏览器的检测 -->
<form action="" method="POST" class="form" novalidate>
{% for field in verify_obj %}
<p> {{field.label}} : {{field}} {{field.errors.0}}</p>
{% endfor %}
{{verify_obj.ALLERROR}}
<button type="submit">提交信息</button>
</form>
</body>
</html>
正则验证
forms
组件允许自定义正则验证。
如果你的业务需求很***钻,则可尝试使用正则验证。
方式一
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开头')],
) # 可以写多个正则,一个一个对字段数据进行验证
方式二
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'邮箱'}))
方式三:自定义方法
from django import forms
from django.forms import fields
from django.forms import widgets
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
class FInfo(forms.Form):
username = fields.CharField(max_length=5,
validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.', 'invalid')], )
email = fields.EmailField()
def clean_username(self):
"""
Form中字段中定义的格式匹配完之后,执行此方法进行验证
:return:
"""
value = self.cleaned_data['username']
if "666" in value:
raise ValidationError('666已经被玩烂了...', 'invalid')
return value
方式四:同时生成多个标签进行验证
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
############## 自定义字段 ##############
class PhoneField(fields.MultiValueField):
def __init__(self, *args, **kwargs):
# Define one message for all fields.
error_messages = {
'incomplete': 'Enter a country calling code and a phone number.',
}
# Or define a different message for each field.
f = (
fields.CharField(
error_messages={'incomplete': 'Enter a country calling code.'},
validators=[
RegexValidator(r'^[0-9]+$', 'Enter a valid country calling code.'),
],
),
fields.CharField(
error_messages={'incomplete': 'Enter a phone number.'},
validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid phone number.')],
),
fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.')],
required=False,
),
)
super(PhoneField, self).__init__(error_messages=error_messages, fields=f, require_all_fields=False, *args,
**kwargs)
def compress(self, data_list):
"""
当用户验证都通过后,该值返回给用户
:param data_list:
:return:
"""
return data_list
############## 自定义插件 ##############
class SplitPhoneWidget(widgets.MultiWidget):
def __init__(self):
ws = (
widgets.TextInput(),
widgets.TextInput(),
widgets.TextInput(),
)
super(SplitPhoneWidget, self).__init__(ws)
def decompress(self, value):
"""
处理初始值,当初始值initial不是列表时,调用该方法
:param value:
:return:
"""
if value:
return value.split(',')
return [None, None, None]
实时更新
如果我们页面上展示的一个select
框中的数据,是从数据库实时获取的,那么是没办法做到数据新插入一条消息页面也会立即显示出来这样的效果的。
定义字段为类属性,只会加载一次。
每次实例化对象用到的字段数据,都是用的类属性,故不能实时与数据库更新。
我们把它做成实例属性即可。
from django import forms
from django.forms import widgets
from app01 import models
class F1(forms.Form):
user_id = forms.CharField(
label="选择用户",
)
def __init__(self,*args,**kwargs):
super(F1,self).__init__(*args,**kwargs)
self.fields["user_id"].widget = widgets.Select(
choices=(models.User.objects.values_list("id","username"))
)
# 做成实例,每次进入视图函数时都会触发实例对象,故此能动态获取数据库中的数据
# values_list [(1,"user1"),(2,"user2")]