form组件
一、form组件简介
1、form在组件的两大功能:
- 数据重置
- 校验规则
2、form组件于传统form表单对比
- 当我们用传统的form表单提交时会刷新页面,如果这个我们表单中的某项填错了,刷新后我们正确的选项也没有了.
- 传统的form表单需要我们自己亲自校验每一项,其工作量太大
-
form组件前端自动生成表单元素。
-
form组件可自动验证表单内容信息。
- form组件可保留用户上次输入的信息。
但是form表单的输出不包含submit
按钮,和表单的<form>
标签。 你必须自己提供。
二、form组件生成方法(三种)
1、方法一
<form action="/login/" method="post" novalidate>
{% csrf_token %}
{{ form_obj.as_p }} #{{ form.as_p }}将它们渲染在<p>
标签中
{{ form_obj.as_table }} #{{ form.as_table }}
以表格的形式将它们渲染在<tr>
标签中
{{ form_obj.as_ul }} #{{ form.as_ul }将它们渲染在<li>
标签中
<p><input type="submit" value="提交"></p>
</form>
But:你必须自己提供<ul>
或 <table>
元素。
方式一示例
#HTML
<form action="/login/" method="post" novalidate> #novalidate不对输入进行验证的表单
{% csrf_token %}
{{ form_obj.as_p }}
<p><input type="submit" value="提交"></p>
</form>
#views
from . import models
from django import forms
from django.forms import widgets
from django.core.exceptions import NON_FIELD_ERRORS,ValidationError
class LoginForm(forms.Form):
user = forms.CharField(max_length=12,min_length=5,
label="用户名",
help_text="6~16个字符,区分大小写",
error_messages={"required":"不能为空",
"min_length":"最小长度为5",})
pwd = forms.CharField(
help_text= "6~16个字符,区分大小写",
error_messages={
"invalid":"格式错误"
},
widget = widgets.PasswordInput(attrs={"class":"active"})
)
def login(request):
if request.method == "POST":
form_obj = LoginForm(request.POST)
if form_obj.is_valid():
# 数据全部合格
#取出合格数据
return HttpResponse("success")
else:
# 最少存在一个字段的错误
#取出错误信息
return render(request, "login.html", {"form_obj": form_obj}) #存放错误信息
form_obj = LoginForm()
return render(request,"login.html",{"form_obj":form_obj})
2、方法二(在任意标签中渲染)
<form action="/login/" method="post" novalidate> {# novalidate不对输入进行验证的表单
{% csrf_token %}
<div>
<label for="user">用户名:</label>
{{ form_obj.user }}
<span>{{ form_obj.errors.user.0 }}</span>
</div>
<div>
<label for="pwd">密码:</label>
{{ form_obj.pwd }}<span>{{ form_obj.errors.pwd.0 }}</span>
</div>
<p><input type="submit" value="提交"><span>{{ ret.0 }}</span></p>
</form>
3、方法三(for循环生成)
<form action="/login/" method="post" novalidate>
{% csrf_token %}
{% for field in form_obj %}
<div>
<lable>{{ field.label }}</lable>
{{ field }}
</div>
{% endfor %}
三、表单验证的model加form方法
调取数据库进行验证:在models.py文件中创建一个类,在数据库中生成
创建的一个类:
生成的数据库:
views及forms代码:
form组件代码可建立一个form.py文件,然后在views中引入
#form代码
class RegForm(forms.Form):
user = forms.CharField(min_length=5,max_length=10,
error_messages={"required": "不能为空",
"min_length": "最小长度为5",
})
pwd = forms.CharField(
help_text="6~16个字符,区分大小写",
widget=widgets.PasswordInput(attrs={"class": "active"}),
error_messages={"required": "不能为空",
})
repwd = forms.CharField(
help_text="6~16个字符,区分大小写",
widget=widgets.PasswordInput(attrs={"class": "active"}),
error_messages={"required": "不能为空",
}
)
email = forms.EmailField(
error_messages={"required": "不能为空",
"invalid": "格式错误"
},
)
tel = forms.CharField(
error_messages={"required": "不能为空",
}
)
def clean_user(self):
val = self.cleaned_data.get("user")
if val.isdigit():
raise ValidationError("不能为数字")
else:
ret = UserInfo.objects.filter(name=val)
if not ret:
return val
else:
raise ValidationError("该用户已注册")
def clean_tel(self):
val = self.cleaned_data.get("tel")
import re #引入正则
ret = re.search("^1[3578]\d{9}$",val)
if ret:
return val
else:
raise ValidationError("手机号码格式")
def clean(self):
repwd = self.cleaned_data.get("repwd")
pwd = self.cleaned_data.get("pwd")
if repwd == pwd:
return self.cleaned_data
else:
raise ValidationError("两次密码不一致")
views代码
def reg(request):
if request.method == "POST":
reg_form = RegForm(request.POST)
if reg_form.is_valid():
name = reg_form.cleaned_data.get("user")
pwd = reg_form.cleaned_data.get("pwd")
UserInfo.objects.create(name=name,pwd=pwd)
return redirect("/login/")
else:
all_error = reg_form.errors.get("__all__")
return render(request, "register.html", {"reg_form": reg_form,"all_error":all_error})
reg_form = RegForm()
return render(request,"register.html",{"reg_form":reg_form})
#html代码
<h1>注册页面</h1>
<form action="/reg/" method="post" novalidate>
{% csrf_token %}
{% for field in reg_form %}
<div>
<lable>{{ field.label }}</lable>
{{ field }}
<span>{{ field.errors.0 }}</span>
{% if field.label == "Repwd" %} {#field.label == "Repwd" Repwd是页面渲染后的名称#}
<span>{{ all_error.0 }}</span>
{% endif %}
</div>
{% endfor %}
<p><input style="margin-left: 100px" type="submit" value="注册">
</form>
四、form组件中常用字段及插件
Django中form的创建涉及到一些字段及插件
字段用于对用户请求数据的验证,插件用于自动生成HTML
1.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类型
...
# 注:UUID是根据MAC以及当前时间等创建的不重复的随机字符串
>>> import uuid
# make a UUID based on the host ID and current time
>>> uuid.uuid1() # doctest: +SKIP
UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')
# make a UUID using an MD5 hash of a namespace UUID and a name
>>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org')
UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')
# make a random UUID
>>> uuid.uuid4() # doctest: +SKIP
UUID('16fd2706-8baf-433b-82eb-8c7fada847da')
# make a UUID using a SHA-1 hash of a namespace UUID and a name
>>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')
# make a UUID from a string of hex digits (braces and hyphens ignored)
>>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}')
# convert a UUID to a string of hex digits in standard form
>>> str(x)
'00010203-0405-0607-0809-0a0b0c0d0e0f'
# get the raw 16 bytes of the UUID
>>> x.bytes
b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'
# make a UUID from a 16-byte string
>>> uuid.UUID(bytes=x.bytes)
UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')
2.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
------------------
常用选择插件
# 单radio,值为字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
# 单radio,值为字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.RadioSelect
# )
# 单select,值为字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
# 单select,值为字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.Select
# )
# 多选select,值为列表
# user = fields.MultipleChoiceField(
# choices=((1,'上海'),(2,'北京'),),
# initial=[1,],
# widget=widgets.SelectMultiple
# )
# 单checkbox
# user = fields.CharField(
# widget=widgets.CheckboxInput()
# )
# 多选checkbox,值为列表
# user = fields.MultipleChoiceField(
# initial=[2, ],
# choices=((1, '上海'), (2, '北京'),),
# widget=widgets.CheckboxSelectMultiple
# )
# 在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于静态字段×××获取的值
# 无法实时更新×××,那么需要自定义构造方法从而达到此目的。
方法一.
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.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.Select
)
def __init__(self, *args, **kwargs):
super(MyForm,self).__init__(*args, **kwargs)
# self.fields['user'].widget.choices = ((1, '上海'), (2, '北京'),)
# 或
self.fields['user'].widget.choices = models.Classes.objects.all().value_list('id','caption')
方法二.
# 使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现
from django import forms
from django.forms import fields
from django.forms import widgets
from django.forms import models as form_model
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
class FInfo(forms.Form):
authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())
# authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())
五、form组件的校验
form组件自定义校验规则:
自定义校验规则中也会使用到局部钩子 跟 全局钩子 前边小例子中有涉及
方式一:
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
form diango.
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]
局部钩子跟全局钩子案例:
from django import forms #调用forms模块
from django.forms import widgets #调用widgets模块,用来对form组件的参数配置。
from django.core.exceptions import ValidationError #调用 ValidationError 模块。用来手动触发raise错误。
from fileupdate.models import * #载入必要的数据库列表。
class FormReg(forms.Form):
name = forms.CharField(min_length=4, widget=widgets.TextInput(attrs={'class': 'form-control '}), label='姓名',
error_messages={'required': '*不能为空', })
pwd = forms.CharField(min_length=4, widget=widgets.PasswordInput(attrs={'class': 'form-control'}), label='密码')
r_pwd = forms.CharField(min_length=4, widget=widgets.PasswordInput(attrs={'class': 'form-control'}),
label='确认密码')
email = forms.EmailField(widget=widgets.EmailInput(attrs={'class': 'form-control'}), label='邮箱')
tel = forms.CharField(max_length=13, label='电话', widget=widgets.TextInput(attrs={'class': 'form-control'}))
##字段的校验,通过对widget的属性设置,可定义INPUT标签的type类型,以及标签的其他属性。通过对label设置,可以自定义form渲染时的标签名,
##另外,通过对error_messages属性设置,可对验证信息进行自定义。注意:字典中错误信息的key值是固定的
def clean_name(self): #局部钩子 注意:名字必须为clean_%s ,这是根据源码来设置的。
#其原理是,当字段校验完毕后,再进行查找是否有以clean_开头的函数名,如果有,就调用该函数,
#运行我们自定义的函数,如果满足条件就返回当前被校验字段的内容。否则手动触发ValidationError错误,源码中会捕获并将值返回。
val = self.cleaned_data.get('name') #通过cleaned_data获得对应字段的'干净数据'
user_obj = User.objects.filter(name=val).first() #与对应的数据库中字段相比较,并获得一个字段对象
if not user_obj: #对字段进行判断,如果为空(数据库中没有对应的名字),那么返回这个校验值。
return val
else:
raise ValidationError('名字存在') #如果存在,那么手动触发异常(异常名为ValidationError),并设置自定义内容。
def clean(self): #全局钩子 注意:名字必须为clean,这是根据源码来设置的。
#其原理是对校验完毕的字段,再进行字段间的校验。当字段校验完毕,查找是否有clean的函数,如果有就运行该
#函数,其功能是对所有校验的字段进行校验比对。如果满足条件,就将cleaned_data返回(这与源码相匹配)
#如果不满足就手动触发ValidationError错误。
pwd = self.cleaned_data.get('pwd')
r_pwd = self.cleaned_data.get('r_pwd')
if pwd and r_pwd: #如果两个字段中一个为空值那么就不用再进行校验。直接返回cleaned_data,通过校验功能返回错误信息。
if pwd == r_pwd:
return self.cleaned_data
else:
raise ValidationError('两次密码不一致!')
else:
return self.cleaned_data
def reg(request):
if request.method == 'POST': #如果是一次POST提交,那么进行校验。
formreg = FormReg(request.POST) #对提交的信息实例化。
if formreg.is_valid(): #通过is_valid()方法进行判断,(注意:当执行这个函数时,将对所有字段进行校验,运行局部钩子和全局钩子)
return HttpResponse('OK')
else:
print('cleaneddata', formreg.cleaned_data)
print('errordata', formreg.errors)
error = formreg.errors.get('__all__') #当设置了全局钩子时,要设置一个变量来获得全局钩子返回的错误信息。
#这是由于,全局钩子的错误在form对象的errors中,当clean()方法抛出异常时,源码会自动捕获,并将错误
#存储在errors字典中,其中键名'__all__'就是全局钩子的变量。
return render(request, 'reg.html', locals())
formreg = FormReg() #当为get请求时,实例化一个空的对象,通过这个空的实例化对象可以渲染前段,自动生成form表单。
return render(request,'reg.html', locals())