forms组件最大的作用,就是做数据校验。
普通做法,一个一个写校验规则,没有解耦。校验规则,都在视图函数里面。
网页校验
修改urls.py,增加路径addbook
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', views.index),
path('addbook/', views.addbook),
]
修改views.py,增加addbook视图函数,完整代码如下:
from django.shortcuts import render,HttpResponse # Create your views here.
from django import forms # 必须导入模块
class UserForm(forms.Form): # 必须继承Form
#限制数据为字符串,最小长度4,最大长度12
name = forms.CharField(min_length=4,max_length=12)
age = forms.IntegerField() # 限制为数字
#限制长度为11位
tel = forms.CharField(min_length=11,max_length=11) def index(request):
return render(request,"index.html") def addbook(request):
if request.method == "POST":
# 将post数据传给UserForm
form = UserForm(request.POST)
if form.is_valid(): # 验证数据
print("success")
else:
print("fail")
return HttpResponse("ok")
return render(request,"addbook.html")
templates新增addbook.html
做表单校验的时候,一定要注意,表单的name和class的属性必须一一对应
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>添加用户</h3>
<form action="" method="post">
{% csrf_token %}
<lable>姓名</lable><input type="text" name="name"/><br/>
<lable>年龄</lable><input type="text" name="age"/><br/>
<lable>邮箱</lable><input type="text" name="email"/><br/>
<lable>手机号码</lable><input type="text" name="tel"/>
<br/>
<input type="submit">
</form>
</body>
</html>
网页访问添加页面,输出信息
提交之后,效果如下:
Pycharm控制台输出:success
空表单直接提交
Pycharm控制台输出:fail
initial 初始值,input框里面的初始值。
error_messages 重写错误信息
form django import forms
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
class RegForm(forms.Form): name = forms.Charfield(
initail = 'xxx'
label = '用户名',
min_length = 8,
required = False,
widget = forms.TextInput(attrs={'class':'form-control'})#可以写PasswordInput
#widget=forms.widgets.RadioSelect()单选Select() 多选SelectMultiple()
error_message= {
'min_length':'最小长度为8',
}
#复杂校验
1.直接加自定义验证规则或者写局部钩子
user = fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
)
) def clean_tel(self):#局部钩子
val = self.cleaned_data.get("tel")
if len(val) == 11: # 判断长度
return val
else:
raise ValidationError("手机号码必须11位") def clean_name(self):
# self.cleaned_data.get('name') # 校验成功 返回当前字段值
# 不成功 抛出异常
raise ValidationError() def clean(self):
# 校验成功 返回所有的值
# 不成功 抛出异常
self.add_error('字段','错误信息')
raise ValidationError() def clean(self): # 全局钩子
pwd = self.cleaned_data.get("pwd")
r_pwd = self.cleaned_data.get("r_pwd")
if pwd and r_pwd and pwd != r_pwd: # 判断2次密码不为空,并且2次密码不相等
raise ValidationError("两次密码不一致")
else:
return self.cleaned_data # 这句是固定写法,不能变动
form.is_valid() 它做了2件事情:
1.将数据传给form
2.将验证数据拆分到2个容器中
self.cleaned_data= {} 表示干净的数据
self.error = {} 表示验证不通过的数据
self表示UserForm类的实例对象
as_p是一个特殊的属性,常见的有:
-
{{ form.as_table }}
以表格的形式将它们渲染在<tr>
标签中 -
{{ form.as_p }}
将它们渲染在<p>
标签中 -
{{ form.as_ul }}
将它们渲染在<li>
标签中
Widgets
Widget 是Django 对HTML 输入元素的表示。Widget 负责渲染HTML和提取GET/POST 字典中的数据。
如果你想让某个Widget 实例与其它Widget 看上去不一样,你需要在Widget 对象实例化并赋值给一个表单字段时指定额外的属性(以及可能需要在你的CSS 文件中添加一些规则)
注意:当input属性为password时,是不会渲染的!除此之外,其他的表单元素,是可以渲染的!
显示错误信息
field.errors 表示错误列表。因为是列表,会有很多错误信息
field.errors.0 表示接收第一个错误信息。一般取第一个即可!
局部钩子与全局钩子
上面提到的校验规则是forms组件自带的。 它做了一些简单的校验功能,比如判断字符串,纯数字,邮箱等等。
比如要求用户名,必须包含字母和数字。年龄必须要大于18岁,手机号码要以136,137开头...
这些需求,默认的校验规则是做不到的。
def clean_name(self):
val = self.cleaned_data.get("name") # 获取输入的用户名 if not val.isdigit(): # 判断不是数字类型
return val
else:
raise ValidationError("用户名不能是纯数字")
注意:
clean_name 这个名字是有含义的,不能随便定义。name表示UserForm类的属性。clean表示校验
val 表示用户输入的用户名
val.isdigit() 表示判断输入的是否为数字,必须return 一个值
raise 表示主动报错,必须接ValidationError。
局部钩子只能校验一个字段,那么2个字段校验呢?比如密码和确认密码必须一致,这个时候,需要使用全局钩子
如何定义全局钩子呢?查看源代码
is_valid()-->self.errors-->self.full_clean()-->self._clean_form()-->self.clean()
Django内置字段
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
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类型
...
Django内置插件
TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
实例案例
urls.py
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^index/', views.index),
url(r'^adduser/', views.adduser),
]
form.py
from django import forms # 必须导入模块
from django.forms import widgets
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError #用于密码验证 class UserForm(forms.Form): # 必须继承Form
# 定义变量,专门给text类型的输入框添加class
wid = widgets.TextInput(attrs={"class": "form-control"})
# 定义字典,错误信息显示中文
# 限制数据为字符串,最小长度4,最大长度12
error_hints = {"required": "该字段不能为空", "invalid": "格式错误!"}
name = forms.CharField(min_length=4, max_length=12, label="姓名", widget=wid, error_messages=error_hints)
# 密码字段
pwd = forms.CharField(label="密码", widget=widgets.PasswordInput(attrs={"class": "form-control"}),
error_messages=error_hints) r_pwd = forms.CharField(label="确认密码", widget=widgets.PasswordInput(attrs={"class": "form-control"}),
error_messages=error_hints)
# 限制为数字
age = forms.IntegerField(label="年龄", widget=wid, error_messages=error_hints)
# 限制为邮箱格式
email = forms.EmailField(label="邮箱", widget=widgets.EmailInput(attrs={"class": "form-control"}),
error_messages=error_hints)
# 限制长度为11位
tel = forms.CharField(max_length=11, label="手机号码", widget=wid, error_messages=error_hints) def clean_name(self): # 校验name值
val = self.cleaned_data.get("name") # 获取输入的用户名 if not val.isdigit(): # 判断数字类型
return val
else:
raise ValidationError("用户名不能是纯数字") def clean_tel(self):
val = self.cleaned_data.get("tel")
if len(val) == 11: # 判断长度
return val
else:
raise ValidationError("手机号码必须11位") def clean_age(self):
val = self.cleaned_data.get("age")
if int(val) > 18: # input输入的值为字符串,必须转换为int
return val
else:
raise ValidationError("年龄必须满18岁以上!") def clean(self): # 全局钩子
pwd = self.cleaned_data.get("pwd")
r_pwd = self.cleaned_data.get("r_pwd")
if pwd and r_pwd and pwd != r_pwd: # 判断2次密码不为空,并且2次密码不相等
raise ValidationError("两次密码不一致")
else:
return self.cleaned_data # 这句是固定写法,不能变动
views.py
from django.shortcuts import render, HttpResponse
from app01.form import UserForm # 导入UserForm类 # Create your views here.
def index(request):
return render(request, "index.html") def adduser(request):
if request.method == "POST":
# 将post数据传给UserForm
form = UserForm(request.POST)
print(request.POST)
if form.is_valid(): # 验证数据 1.将数据传给form 2.将验证数据拆分到2个容器中
print("###success###")
print(form.cleaned_data) # 所有干净的字段以及对应的值
# ErrorDict : {"校验错误的字段":["错误信息",]}
print(form.errors)#表示验证不通过的数据
print(type(form.errors)) # 打印类型
return HttpResponse("添加成功")
else:
print("###fail###")
# print(form.cleaned_data)
print(form.errors)
# # 获取email错误信息,返回一个错误列表,可以切片
# print(form.errors.get("email"))
# # 获取第一个错误信息
# print(form.errors.get("email")[0])
g_error = form.errors.get("__all__") # 接收全局钩子错误信息
if g_error: # 判断有错误信息的情况下
g_error = g_error[0] # 取第一个错误信息 # 将form和g_error变量传给adduser.html
return render(request, "adduser.html", {"form": form, "g_error": g_error}) else: # 默认是get请求(地址栏输入访问时)
form = UserForm() # 没有表单数据的form
return render(request, "adduser.html", {"form": form})
adduser.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
<style>
.error {
color: red;
}
</style>
</head>
<body> <div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-2">
<h3>添加用户</h3><br/>
<form action="" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="">{{ field.label }}</label>
{{ field }} <span class="error pull-right">{{ field.errors.0 }}</span>
{% if field.label == "确认密码" %}
<span class="error pull-right">{{ g_error|default_if_none:"" }}</span>
{% endif %}
</div>
{% endfor %}
<br/>
<input type="submit" class="btn btn-success btn-sm">
</form>
</div>
</div>
</div> </body>
</html>
点击提交什么都不输入的情况下
校验
ModelForm
form与model的终极结合。
class BookForm(forms.ModelForm): class Meta:
model = models.Book
fields = "__all__"
labels = {
"title": "书名",
"price": "价格"
}
widgets = {
"password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
}
class Meta:下常用参数:
model = models.Student # 对应的Model中的类
fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段
exclude = None # 排除的字段
labels = None # 提示信息
help_texts = None # 帮助提示信息
widgets = None # 自定义插件
error_messages = None # 自定义错误信息