Form使用¶
在编写Web应用中,经常要使用到的就是和用户的交互,在传统的HTML开发中,一般是使用 Form来进行的,它通过一组Form相关的界面元素,提供各种信息的录入。在ajax的处理过 程中,Form元素也经常被使用。除了前端的展示外,真正的交互是需要后台参与的,它包 括上传数据的解析和处理,然后可能会与数据库进行交互,并返回相应的结果。在Uliweb 中,提供了Form类来进行相关的处理,它的主要功能有:
- 自动生成前端的展示代码,允许支持自定义布局
- 对上传后的数据进行校验,如果正确则返回转換后的数据,如果出错,返回出错信息
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说明¶
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_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 项目中新写了几个新的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的校验的定义有多种形式:
- 在Form类上,通过rules类属性来定义
- 在Form类上编写validate_fieldname或form_validate函数来校验,第一种是只校验某个字段,第二种 是校验整个Form
- 在定义字段时,传入validators或rules
- 在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 | |
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 |