Form组件

在之前编写的项目中,表单这一类内容,都由我们手工编写.然后将数据提交到后端,后端程序从请求中对每个数据进行验证.
从抽象的过程来看,表单的每一个提交的数据,都有一种默认的类型,对应着数据库中的某种字段,又都对应着一些默认的HTTP代码.比如一组多选,在后端通过getlist取得的是一个列表.在页面上展示的默认代码是一组多选checkbox.

Django的Form组件是这样一个对象:根据表单要提交的数据,自动生成HTML代码,然后将对应的模板标签放置在页面上,后端能够操作这个对象,根据取回来的数据预先校验然后返回校验结果,则建立表单和校验的对应关系和编写代码的工作量会减少很多.

Django 1.11的官方文档对于Form组件,提供了 在Django中使用FormForm API 清单 两部分文档.

Form组件初步使用

通过一个之前经常使用的登陆用户名的表单来进行简单的Form操作:

<form action="/add_name/" method="post">
    <label for="your_name">Your name: </label>
    <input id="your_name" type="text" name="your_name" value="{{ current_name }}">
    <input type="submit" value="OK">
</form>

这个表单很简单,就是一个输入用户名并且提交的表单.可以看到几个结构化的特点:form标签规定了提交的URL和请求类型.label标签和input标签成对出现,input标签由于是输入用户名,类型是text,还有值的设置.一个表单还一定会有一个功能是submit的元素,这里是input标签作为submit元素.

现在这里的所有代码,都不在通过HTML编码,而是采取在后端生成一个Form对象,用Form对象生成的HTML代码来覆盖这个位置的代码,同时通过Form对象的操作来验证数据:
在views.py或者其他地方,编写如下代码:

from django import forms

class NameForm(forms.Form):
    your_name = forms.CharField(label='Your name', max_length=100)

Form对象必须继承forms.Form类,有点像ORM的表类一样.然后在其中定义了一个属性,看起来也很像ORM定义表字段,这个叫your_name的属性对应的是一个CharField类,然后设置了具体属性label="Your_name",max_length=100.
即使此时还不了解Form类,我们也可以猜测到,label属性对应的值就是HTML页面里label属性的text内容.max_length则是验证条件之一,即最长长度是100个字符.

猜测其实是正确的.除此之外,每个Form对象有一个is_valid()方法,当调用这个方法的时候,如果所有填入的内容都是有效的,则这个方法会返回True,然后会将表单内所有的数据放到Form对象的cleaned_data 属性中.

接下来,需要先继续编写完后端代码,将这段东西传递到页面上,然后再修改模板看一下效果.

def get_name(request):
    if request.method == 'POST':
        form = NameForm(request.POST)
        if form.is_valid():
            return HttpResponse('thanks')

    else:
        form = NameForm()
    return render(request, 'index.html', {'form': form})

视图的逻辑很简单,如果是POST请求,就通过刚才建立的类,把POST请求传进去,利用POST请求附带的所有数据实例化一个Form类,然后对这个类调用验证方法来验证,如果通过验证,就返回成功的消息.如果是GET请求,就返回空白表单供填写.

最后是编写模板,由于我们使用了表单对象,因此就将模板修改成:

<form action="/add_name/" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Submit" />
</form>

最后运行Django站点,会发现{{ form }}的部分在实际的页面中显示如下:

<label for="id_your_name">Your name:</label>
<input type="text" name="your_name" maxlength="100" required="" id="id_your_name">

在实际的输入框中,会发现也只能输入最长100个字符.实际上Form对象做的事情就是生成了对应的HTML代码然后在后台进行了控制.对于这个例子来说,由于控制了输入长度和不能为空,所以不会出现is_valid()为False的情况.如果print(form.clean_data),则可以看到传入的输入框name和对应的值组成的字典.

Form组件的组成内容

可以看到,通过一个Form对象,就可以将页面上的表单元素和后端通过这个对象连接起来,取数和验证都变得更加方便,实际上form对象的方法和属性还有很多,可以单独控制页面元素并且显示出错信息.
首先来看一下如何初始化Form对象.

从前边例子可以看出来,Form的类定义有点类似ORM的类.Form对象里也有各种Field,也就是各种表单元素.Django 1.11 的Fields 官方文档.

Form对象的写法示例:

class LoginForm(forms.Form):
    ...
    pwd = forms.CharField(
        min_length=6,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
    )

可以看到,每一个属性都要对应forms.fields.XXX的某一类fields,然后其中有参数 field arguments.参数主要是用来控制验证条件,参数中还有一个特殊的参数是widget,用于控制HTML代码生成.

Fields

先来看一下常用的fields:

字段名称 默认插件 错误键 解释
BooleanField CheckboxInput required 所有field的子类默认都设置了required=True.而布尔字段没有选中意味着False,会触发requird错误.因此在布尔字段上要特别设置required=False
CharField TextInput required, max_length, min_length 得到一个unicode字符串,如果设置最大和最小长度,在HTML内就会验证.如果不设置最大和最小,任何输入都可以通过验证.还有参数strip,默认为True,表示去除输入前后空格.
ChoiceField Select required, invalid_choice 用于单选的字段,更改默认的widget时候必须注意搭配,choices的参数必须是可迭代的序列,每一个元素是一个2个元素的元组,第一个元素是值,第二个是显示的内容.
DateField DateInput required, invalid 返回一个Python的 datetime.date 对象,HTML表现形式是一个日期输入框.可以用input_formats字符串格式化参数指定具体样式
DateTimeField DateTimeInput required, invalid 与DateField类似.也有input_formats字符串格式化参数
DecimalField NumberInput或TextInput required, invalid, max_value, min_value, max_digits, max_decimal_places, max_whole_digits 十进制浮点数字段,返回Python的decimal对象,可选参数是最大值,最小值,最大位数,最大小数位数
DurationField TextInput required, invalid 返回一个Python timedelta对象,表示间隔.
EmailField EmailInput required, invalid 返回unicode字符串的邮件地址.可选参数是 max_length min_length.
FileField ClearableFileInput required, invalid, missing, empty, max_length 上传文件.返回一个Uploadfile对象,包含文件名和文件内容.两个可选参数max_length和allow_empty_file.上传文件的时候还需要对form元素进行设置.
FilePathField Select required, invalid, max_value, min_value 选择文件上传,有一个必须参数path来指定想要开始选择的目录.具体看这里
FloatField NumberInput或TextInput required, invalid 可选参数为max_value 和 min_value,控制最大和最小值.
ImageField ClearableFileInput required, invalid, missing, empty, invalid_image 与上传文件类似,但使用ImageField需要安装pillow库.
IntegerField NumberInput或TextInput required, invalid, max_value, min_value 可选参数是max_value 和 min_value
MultipleChoiceField SelectMultiple required, invalid_choice, invalid_list 使用choices属性传入选择项.用于多选.更改默认对应的widget时候注意搭配

Fields arguments

再来看一下Core field arguments 核心字段属性,这些Core field arguments是建立Form对象里的fields时一定要包含的属性.除此之外,上边的fields表格也列出了某些field可以额外添加的参数=属性.

属性名 解释
Field.required 默认设置为True,表示一定要输入内容,None或者空字符串都会引发错误.
Field.label 用于生成HTML代码对应该输入元素的label标签的text内容.
Field.label_suffix 用于覆盖整个表单级别的label_suffix,就是给label的text部分加上后缀
Field.initial 设置初始化的值,也就是设置标签的value属性.注意,不同的field,initial需要被设置成对应的对象,比如时间字段就必须用datetime系列对象赋值给initial属性
Field.widget 设置对应的widget类,用于控制具体的HTML代码
Field.help_text 在HTML中显示帮助文本信息
Field.error_messages 用于覆盖默认的错误信息,需要采用error_messages={‘required’: ‘Please enter your name’}类似的方法来传入,前边的键就是错误键的名称,值是自定义的错误信息.
Field.validators 选择针对该字段的验证器,验证器的详细看这里
Field.localize 和本地化有关,控制结果的本地化输出.
Field.disabled 设置表单元素的属性是否为disabled
Field.has_changed() 检测元素的值是否从initial值发生了变化,返回布尔类型.

Widgets 插件

最后一部分在建立form对象时候要了解的是widget,官方文档的原话是: A widget is Django’s representation of an HTML input element.也就是说一个插件就对应着一段HTML代码.
通过fields可以知道要拿到哪一种数据类型,通过fields arguments 可以得到验证相关的条件,widget则是最后一步,即将字段的逻辑通过HTML展示出来.同时widget也有各种属性可以设置,用于更好的控制具体HTML代码.

所有的widget类都继承自 Widget 和 MultiWidget 两个类,其中Widget有attrs属性,用来设置HTML标签的各种属性,常用的是设置css类从而应用样式.

内建的Widget类
类名 解释
TextInput 输入类型是text,渲染的时候按照<input type=”text” …>渲染
NumberInput 输入类型是number,渲染的时候是number类型的input标签
EmailInput 渲染的时候是email类型的input标签
URLInput URL类型的input标签
PasswordInput password类型的input标签,可以带一个额外属性是render_value,表示验证失败之后填写在密码框内的值,默认是False即保留原来的值
HiddenInput 类型是hidden 的input标签
DateInput 类型是text的input标签,可以使用额外参数format来控制格式化
DateTimeInput 类型是text的input标签,同样有format属性
TimeInput 类型是text的input标签,同样有format属性
Textarea 渲染为textarea标签
CheckboxInput 渲染为checkbox对象,有一个调用方法是check_test,检查是否应该选中这个值
Select 渲染为select及内嵌的option标签.有choices属性用于设置各个选项
SelectMultiple 多选,渲染为<select multiple=”multiple”>
RadioSelect 渲染成一个ul,每个li内部包含一个radio类型的input,模板内的标签使用方法比较多,具体看这里
CheckboxSelectMultiple 渲染成一个ul,每个li内包含一个类型是checkbox的input标签
FileInput 渲染成<input type=”file” …>

FORM 后端 API

其他次要的可以看官方文档.至此已经学习完了所有建立Form类所需要知道的内容.

下一步是要看Form对象如何使用,使用的地方分为两种,一种是在后端使用,先来看看后端的API:

Form API
属性或方法名 解释
Form.is_bound 如果没有任何数据传入而新建Form对象,这是一个没有绑定的Form对象,如果传入了数据比如request.POST,这就是一个绑定了一个具体表单的数据,这个方法返回Form对象是否是一个绑定的对象
Form.clean() 使用clean方法意味着调用is_valid()方法然后返回一个布尔值
Form.errors 返回错误键与错误内容的字典.调用该属性和is_valid()方法都会触发对Form对象的校验.
Form.errors.as_data() 错误键不变,值变成原始的错误对象
Form.errors.as_json(escape_html=False) 将错误序列化为JSON对象,可加上 escape_html=True进行转义以便直接在HTML内使用
Form.initial 用字典的形式设置初始值,如果Form对象通过initial属性和字段的initial属性都设置了初始值,以Form对象的优先.
Form.get_initial_for_field(field, field_name) 取得初始值,按照先取Form.initial,再取fields.initial的顺序,如果初始值需要求值也会被求值.
Form.has_changed() 整个表单的初始值是否改变,需要先设置Form的initial属性,然后调用该方法即可查看是否改变.
Form.changed_data 返回一个列表,包含所有与初始值有变化的字段名称.
Form.fields 直接用对象的字段变量名就可以访问该字段.之后再用field的那些arguments就可以访问字段的各种属性
Form.cleaned_data 当is_valid()或其他触发验证的动作实行后,如果通过了验证,则所有的数据会被包含在这个属性对应的一个字典里.而且所有的数据都被整理过,比如从前边可以知道,时间类型默认对应的widget是text类型,但是在cleaned_data中,时间类型的数据会被整理成datetime类型.其他的数据类型可以参考field部分的表格.
Form.as_p() 按照P段落渲染,将所有的表单元素包裹在P标签内.改变的是直接print(Form对象)的结果.
Form.as_ul() 将每一个表单元素放进一个ul的li元素中,影响print结果
Form.as_table() 包裹在tr th标签里,但是table元素需要页面来提供,一般不采用该方法.
Form.label_suffix 这个属性的内容会在渲染的时候追加到所有的label 的text内容之后.
Form.use_required_attribute 这个属性被设置成True的时候,所有必须填写的表单元素标签内都会带有required 的HTML 5 属性.

在模板内使用Form对象

最后一部分,就是看看Form对象如何在HTML中展示. 表单的关键,是展示提示,输入框以及错误信息.
有两种展示方式:

自动渲染
自动渲染就是一次性将整个form按照某种形式渲染出来,不单独操作表单的各个元素.

  • {{ form.as_table }}
  • {{ form.as_p }}
  • {{ form.as_ul }}

如果在对象内不做任何设置,那么元素的id会被自动设置成id_属性名.这种方法可自定义的部分较少,需要后期慢慢配样式.一般采用第二种方法.

手动渲染
手动渲染就是将传入模板的form对象的各个字段和错误信息取出,自行编写.
其基础是 {{ form.name_of_field }} 表示渲染表单中的一个输入元素.
{{ form.name_of_field.label }}表示该字段对应的label标签.
{{ form.name_of_field.errors }}表示经过验证后的该字段对应的错误消息.由于错误信息只会同时有一个,所以一般用{{ form.name_of_field.errors.0 }}取出错误信息.

看一下模板内的操作列表:

Form对象在模板内的操作
tag名称 解释
{{ field.label }} 字段的label属性的内容,就是一个字符串
{{ field.label_tag }} 一个完整的label标签,推荐使用该tag与field搭配
{{ field.id_for_label }} 这个字段使用的id
{{ field.value }} 字段的值,提交表单之后会动态根据当前值改变
{{ field.html_name }} html的name属性值
{{ field.help_text }} 帮助信息
{{ field.errors }} 当前字段的错误信息,如果验证通过则不会有错误信息.可以对其迭代取出所有错误或者用.0取第一个错误内容
{{ field.is_hidden }} 判断当前字段是否是隐藏的
{{ form.hidden_fields }} 这里注意之前的是field的属性,这里是form的属性,表示表单内的全部hidden字段,可以迭代取出具体字段
{{ form.visible_fields }} 这个是所有的可视字段.

至此已经学完了Form对象的基础,目前已经可以建立各种类型的表单并且验证来得到信息,同时在页面上展示错误信息.
所以,对于已经学过的内容来说,如果想要快速建立一个表单,就可以采取给form对象的字段指定一些css类和id的方法,然后通过bootstrap来设置样式.在模板中通过手工渲染,分开每一个部件方便展示和修改.
对于表单来说,其实还有一部分比较重要的内容,那就是前边fields arguments中提到的验证器.对于一些特殊需求,采取何种规则验证Form是一个需要特别控制的,并不能完全交给默认的验证机制.

validators参数和自定义验证器

Form对象的建立和HTML展示都已经学习完毕,现在最重要的一个功能就是如何验证.刚刚的Fields arguments有一个参数叫validators,这个参数指的就是验证器.验证器有很多种类,不同的验证器对应不同的验证规范,还可以自定义验证器.
验证器的工作实际上是对应form arguments的,比如指定了max_length,就意味着这个字段带了一个max_length的验证器.validators则用来指定自定义的验证器,通常是正则验证器.自定义的验证器额外附加在所有的验证器上.当数据过来的时候,所有的验证器都要通过,这个字段才算没有错误.

自定义验证器
自定义验证器有两种方式,一种是自行编写一个符合验证标准的函数,然后必须返回一个Django的ValidationError对象及错误信息,一种是用内置的验证器对象生成新的验证实例,然后传给validators函数.

方法一:

# 自定义一个函数,参数是传入的值,然后验证之后返回一个ValidationError.
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('手机号码格式错误')

然后在某个字段内,传入自定义的验证器给validators参数:

phone = fields.CharField(validators=[mobile_validate, ],
                            error_messages={'required': '手机不能为空'},
                            widget=widgets.TextInput(attrs={'class': "form-control",
                                                          'placeholder': u'手机号码'}))

从传参数的方法可以看出来,是一个验证器列表,说明可以传入多个自定义的验证器.

方法二:

# 用内置的验证器生成新的实例对象,然后传给validators参数.
from django.core.validators import RegexValidator
class MyForm(Form):
    user = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
    )

所有的内置验证器都在django.core.validators这个模块内,自定义验证器既可以编写函数也可以生成内置验证器的实例对象:

RegexValidator(regex=None, message=None, code=None, inverse_match=None, flags=0)

正则验证器,一般使用前两个参数,传入正则表达式和错误信息,如果验证器未验证通过,就返回错误信息

  • regex 是正则表达式
  • message 是错误信息
  • code是供 ValidationError 使用的错误代码,默认是invalid,一般无需改动
  • 匹配模式,默认是False
  • 用于regex是一个编译过的正则表达式,一般不用

EmailValidator(message=None, code=None, whitelist=None)

邮件验证器,EmailField默认使用该验证器

  • message是错误信息
  • code是错误代码
  • whitelist是白名单,即只有在whitelist参数里的邮件地址才能通过验证

URLValidator(schemes=None, regex=None, message=None, code=None)

URL验证器,URL相关field默认使用该验证器

  • schemes指URL方案,如果不指定,默认是[‘http’, ‘https’, ‘ftp’, ‘ftps’]也就是只能通过HTTP和FTP协议的URL
  • regex是正则表达式
  • message是错误信息
  • code是错误代码

validate_email

一个 EmailValidator 的不带任何参数的实例

validate_slug

一个正则表达式验证器的实例,仅能验证字母,数字,减号和下划线的组合

validate_unicode_slug

一个正则表达式验证器的实例,仅能验证UNICODE的字母,数字,减号和下划线的组合

validate_ipv4_address

一个正则表达式验证器的实例,验证合法的ipv4地址

validate_ipv6_address

这是一个用了django.utils.ipv6 模块的ipv6地址的验证器

validate_ipv46_address

实际上是同时使用了前边两个验证器的实例

validate_comma_separated_integer_list

一个正则表达式验证器的实例,验证逗号分割的数字

int_list_validator(sep=’, ‘, message=None, code=’invalid’, allow_negative=False)

正则表达式验证器实例,用于检测字符串是否由一串整数被sep参数的分隔符分割后组成

  • sep表示分隔符
  • message为错误信息
  • allow_negative表示是否允许负整数

MaxValueValidator(max_value, message=None)

最大值验证器,max_value参数默认使用该验证器

MinValueValidator(min_value, message=None)

最小值验证器

MaxLengthValidator(max_length, message=None)

最大长度验证器,

MinLengthValidator(min_length, message=None

最小长度验证器

DecimalValidator(max_digits, decimal_places)

Decimal类型验证器

  • max_digits 是总的最长位数
  • decimal_places 是小数的位数

还有两个文件验证器可以看这里