# Form使用

在编写Web应用中,经常要使用到的就是和用户的交互,在传统的HTML开发中,一般是使用 Form来进行的,它通过一组Form相关的界面元素,提供各种信息的录入。在ajax的处理过 程中,Form元素也经常被使用。除了前端的展示外,真正的交互是需要后台参与的,它包 括上传数据的解析和处理,然后可能会与数据库进行交互,并返回相应的结果。在Uliweb 中,提供了Form类来进行相关的处理,它的主要功能有:

1. 自动生成前端的展示代码,允许支持自定义布局 1. 对上传后的数据进行校验,如果正确则返回转換后的数据,如果出错,返回出错信息

## Form的定义

在使用Form时,我们做的第一件事就是定义一个Form类,定义之后,我们会创建它的实例, 然后使用这个实例来展示或进行数据的校验处理。一个简单的Form类定义代码如下:

``` from uliweb.form import *

class F(Form):
title = StringField(label=’中文:’, required=True, help_string=’Title help string’) content = TextField(label=’Content:’) password = PasswordField(label=’Password:’) age = IntField(label=’Age:’) birthday = DateField(label=’Birthday’) id = HiddenField() tag = ListField(label=’Tag:’) public = BooleanField(label=’Public:’) format = SelectField(label=’Format:’, choices=[(‘rst’, ‘reStructureText’), (‘text’, ‘Plain Text’)], default=’rst’) radio = RadioSelectField(label=’Radio:’, choices=[(‘rst’, ‘reStructureText’), (‘text’, ‘Plain Text’)], default=’rst’) file = FileField(label=’file’)

```

上面的代码定义了一个Form类,里面有很多的字段,类似于Model的定义。在uliewb/form/uliform.py 中定义了许多Form字段类,分别代表不同类型的字段。所有的字段都继承自 BaseField 类,对 于BaseField类的详细说明见下面。

除直接使用类的方式定义Form之外,还可以通过 make_form 函数来动态定义。

## BaseField

``` class BaseField(object):

default_build = Text field_css_class = ‘field’ default_validators = [] default_datatype = None creation_counter = 0

def __init__(self, label=’‘, default=None, required=False, validators=None,
name=’‘, html_attrs=None, help_string=’‘, build=None, datatype=None, multiple=False, idtype=None, static=False, **kwargs):

```

## Form的布局

Form本身只是用来定义用来接受用户输入的数据项,当我们需要将Form转为HTML代码展示在 页面上时,我们可以使用Layout类进行处理。一个Layout类用来处理Form的展示。Layout是基类, 真正使用的是它的子类。在Uliweb中已经缺省实现了若干Layout,分别为:

Layout (基类) CSSLayout (使用Div来布局) TableLayout (使用Table来布局) BootstrapLayout (基于Bootstrap, 以div来布局) BootstrapTableLayout (基于Bootstrap,以Table来布局) QueryLayout (查询条件使用的Layout)

如果你使用Bootstrap作为前端可以考虑使用BoostrapLayout或BootstrapTableLayout。因为 Layout的处理要结合CSS,所以当不满足你的需要时,可以考虑自已来写Layout类。

### 如何使用某个Layout

在定义Form时,通过设置Form类的 layout_class 来指明用哪个Layout类。目前缺省是使用 BootstrapLayout。你可以通过:

from uliweb.form import Form Form.layout_class = NewLayout

来统一修改所有Form的缺省Layout类,也可以只针对某个Form的子类来修改 layout_class 属性。如:

class MyForm(Form):
layout_class = NewLayout

在0.5版本之后,layout_class可以是一个字串符,而不是类本身。因此在使用之前需要先将布局类进行 配置,具体配置参见 [Layout类的配置](#layout_setup)

### Layout说明

Layout是基类,它定义了实际使用的Layout的基本属性和要实现的方法。在实际中,你不应该 直接使用它。

``` class Layout(object):

form_class = ‘’

def __init__(self, form, layout=None, **kwargs):
self.form = form self.layout = layout self.kwargs = kwargs self.init()
def init(self):
pass
def html(self):
return ‘n’.join([x for x in [self.begin(), self.hiddens(), self.body(), self.buttons_line(), self.end()] if x])
def __str__(self):
return self.html()
def get_widget_name(self, f):
return f.build.__name__
def is_hidden(self, f):
return f.type_name == ‘hidden’ or f.hidden
def begin(self):
if not self.form.html_attrs[‘class’] and self.form_class:
self.form.html_attrs[‘class’] = self.form_class

return self.form.form_begin

def hiddens(self):

s = [] for name, obj in self.form.fields_list:

f = getattr(self.form, name) if self.is_hidden(obj):

s.append(str(f))

return ‘’.join(s)

def body(self):
return ‘’
def end(self):
return self.form.form_end
def _buttons_line(self, buttons):
return ‘ ‘.join([str(x) for x in buttons])
def buttons_line(self):
return str(self._buttons_line(self.form.get_buttons()))
def buttons(self):
return ‘ ‘.join([str(x) for x in self.form.get_buttons()])

```

## Layout 类配置 {#layout_setup}

如果在设置 layout_class 时希望使用字符串的形式,需要在settings.ini中配置:

[FORM_LAYOUT_CLASSES] bs3v = ‘#{appname}.form_helper.Bootstrap3VLayout’ bs3h = ‘#{appname}.form_helper.Bootstrap3HLayout’ bs3t = ‘#{appname}.form_helper.Bootstrap3TLayout’

上面的示例是设置了三个 Layout 类,其中 #{appname} 表示替换为当前的appname。

配置好之后,就可以直接使用字符串的名字了,如:

class MyForm(Form):
layout_class = ‘bs3v’

## 关于 Bootstrap3 的布局扩展

Uliweb/form/layout.py 中支持的Bootstrap的布局还是基于2.X版本的,但是因为Bootstrap3 的版本差异比较大,所以不能满足要求。因此在 [uliweb_peafowl](https://github.com/uliwebext/uliweb_peafowl) 项目中新写了几个新的Layout布局类,专门用于Bootstrap 3版本。所以有需要,可以参考并且uliweb_peafowl项目。

## get_form (0.1.5)

可以方便替换contrib中对于Form的定义,也可以替换一些不同模块下的form,同时也可以增加一些复用。 使用get_form,首先需要向apps/settings.ini中的INSTALLED_APPS中添加’uliweb.contrib.form’, 安装完毕后,在配置文件的FUNCTIONS就引用了get_form这个函数

因此如果想要使用get_form,可以采用下面的方式

``` from uliweb.core.SimpleFrame import functions

Form = functions.get_form(‘form_name’) … ```

## make_form 动态创建Form (0.5)

为了方便实现配置化,uliweb提供了动态生成Form的若干种办法,其中可以通过定义简单的数据结构来动态创建一个Form,甚至 包括Layout信息的定义。简单的示例如下:

from uliweb.form import make_form

f = {
‘fields’:[
{‘name’:’username’, ‘type’:’str’, ‘label’:u’用户名’, ‘placeholder’:u’用户名’}, {‘name’:’password’, ‘type’:’password’, ‘label’:u’密码’, ‘placeholder’:u’密码’}, {‘name’:’remember_me’, ‘type’:’bool’, ‘label’:u’记住我’},

], ‘layout_class’:’bs3h’, ‘layout’:{

‘rows’:[
‘username’, ‘password’, {‘name’:’remember_me’, ‘inline’:True, ‘label’:’‘},

], ‘buttons’:[u’<button type=”submit” class=”btn btn-primary”>提交</button>’, ‘<a href=”#”>忘记密码</a>’]

}

}

form_cls = make_form(**f) form = form_cls()

以上的代码将创建一个登录Form。动态创建Form类可以使用 make_form 函数,它可以使用的参数主要有:

fields –
用来定义Form的字段,目前支持的所有字段类型可以参见下面的字段类型的详细描述。
layout_class –
用来指定要使用的Layout类,可以是字段串形式。
layout –
具体的Layout信息。不同的Layout类可能使用不同的Layout信息,详细要看Layout类的相关说明。
base_class –
Form的基类。如果提供,新的Form类将是指定基类的子类。主要是考虑动态定义的Form的校验处理,通过 配置只完成了界面相关的定制,通过基类实现用代码来解决其它的一些不方便配置的功能。
get_form_field –
根据字段名,动态返回想要的字段类型。这是对于某些在运行时才可以确定字段的情况下使用的。它是一个 回调函数,形式为: def func(name, field_info) ,其中 name 是字段名,field_info 是对应的dict信息。
name –
返回的Form类的名字。如果不提供则缺省为: MakeForm_
rules –
用来定义Form的校验规则,包括前端及后端。后端则会转化为相应的validator的形式,前端校验则需要 自行编写相应的前端校验代码。

### 常用字段类型

## Form的校验处理 (0.5 Update)

Form的校验的定义有多种形式:

  1. 在Form类上,通过rules类属性来定义
  2. 在Form类上编写validate_fieldname或form_validate函数来校验,第一种是只校验某个字段,第二种 是校验整个Form
  3. 在定义字段时,传入validators或rules
  4. 在make_form时传入rules参数

关于 rules 的处理方式是在 0.5 版本以后才有的。

其中validator是用于后端校验,而rules可以是前端或后端或者两者都要校验。但是要注意的是,因为无法决定 用户使用什么样的前端,所以在这里只是一个定义,并不能真正进行校验,用户需要根据前端校验的规则来自己生成 相应的校验处理代码。所以在使用rules时,后端校验的规则将转为validator函数。而前端校验规则可以通过 Form.front_rules 来获取,它的表示形式为:

{‘rules’:{
‘fieldname’:{
‘rule1’:xxx,

}

},

‘messages’:{
‘fieldname’:{
‘rule1’:xxx,

}

}

}

单个的rule是一个dict数据结构,形式为:

{
‘required’:(True, ‘This field is needed!’), ‘email:front’:True,

}

其中,key为规则名,值可以是tuple, list或单值。如果是tuple或list,则第一个元素是规则所需要的值, 第二个是出错时的错误描述。如果是单值,则使用缺省出错信息。如果规则名后面无 : 则表示前后通用。否则 可以通过定义 :end:front 说明是后端或前端校验使用。

对于设置在Form上或传入 make_form 函数的rules参数,定义格式为:

{
‘fieldname’: <单个rule>规则, …

}

对于 required 既可以在 rules 中定义,也可以在定义字段时,设置 required=True 参数来设置, 以实现对以前版本的兼容。

### 校验类 (Validator)

Uliweb预定义了一些校验类,可以在uliweb.form.validators中找到以 TEST_ 开头的类.校验类的基类 是 Validator,所有自定义的校验类都需要从这个类进行派生.

``` class Validator(object):

default_message = _(‘There is an error!’)

def __init__(self, args=None, message=None, extra=None, next=None, field=None):
self.message = message or self.default_message self.extra = extra or {} self.args = args self.next = next self.result = None self.field = field self.init()
def get_message(self):
if isinstance(self.message, LazyString):
message = unicode(self.message)
else:
message = self.message

return message % self.extra

def validate(self, data, all_data=None):
return True
def init(self):
if self.field:
self.extra[‘label’] = self.field.label
def __call__(self, data, all_data=None):

self.result = data if not self.validate(data, all_data):

return self.get_message()
if self.next:
return self.next(self.result)

```

default_message –
用来定义缺省的提示信息.提示信息中可能会有一些占位符,因此要和 __init__ 中的 extra 参数进行对应.
__init__ –

初始化函数.

args –
用来定义传入的参数,不同的校验类可以传入不同的参数. args 的具体类型由校验类自行定义.
message –
校验提示信息.如果不提供,则缺省使用 default_message.目前message中可以有占位符,支持 %{}. 需要使用关键字占位符.
extra –
用于提供与消息占位符相匹配的参数.校验类一般会根据args自动分析,但也可以直接提供进行覆盖.类型为 dict.
next –
表示是否有后续的校验类.用它可以实现多个校验的串接处理.
field –
对应的输入字段类.校验类可以用它获取字段中的一些信息,如 label,放在extra中.
validate –
校验处理. data 为当前待校验的字段值. all_data 为所有待校验的数据.
get_message –
消息获取函数.
init –
初始化函数
__call__ –
供form调用使用

### 自定义校验类

首先从 Validator 类派生,然后根据需要覆盖 default_message, __init__ , init, validate 函数.

## 规则映射

目前针对后端校验,Uliweb定义了一些预置的规则映射,详情如下:

|规则名|校验类|仅后台| |-----|—–|----| |required|TEST_NOT_EMPTY| | |email|TEST_EMAIL| | |url |TEST_URL| | |equalTo |TEST_EQUALTO| | |in |TEST_IN | * | |image |TEST_IMAGE | * | |minlength |TEST_MINLENGTH | | |maxlength |TEST_MAXLENGTH | | |rangelength |TEST_RANGELENGTH | | |min |TEST_MIN | | |max |TEST_MAX | | |range |TEST_RANGE | | |date |TEST_DATE | | |datetime |TEST_DATETIME | * | |time |TEST_TIME | * | |number |TEST_NUMBER | | |digits |TEST_DIGITS | |