表单(Forms)

Form 类提供了一个高级 API,用于快速构建 CRUD(创建、读取、更新和删除)表单,尤其是用于处理现有的数据库表。它可以根据所需字段列表和/或现有数据库表生成和处理表单。

有三种形式的表单:

添加数据的 CRUD 表单

@action('create_thing')
@action.uses('generic.html', db, flash)
def create_thing():
    form = Form(db.thing)
    if form.accepted:
        flash.set("record created")
        redirect(URL('other_page'))
    return locals()

更新数据的 CRUD 表单

@action('update_thing/<thing_id:int>')
@action.uses('generic.html', db, flash)
def update_thing(thing_id):
    form = Form(db.thing, thing_id)
    if form.accepted:
        flash.set("record updated")
        redirect(URL('other_page'))
    return locals()

非 CRUD 表单(与数据库无关联)

@action('some_form')
@action.uses('generic.html', flash)
def some_form():
    fields = [
        Field("name", requires=IS_NOT_EMPTY()),
        Field("color", required=IS_IN_SET(["red","blue","green"])),
    ]
    form = Form(fields)
    if form.accepted:
        flash.set("information recorded")
        redirect(URL('other_page'))
    return locals()

用于消息提示的 flash 的使用是可选的。 flash 是在脚手架应用程序中的 common.py 中定义的。它只是将消息存储在 cookie 中,以便在重定向后可以恢复和显示。这是在默认布局中完成的。

在本章中,从现在开始,我们假设有如下的数据模型和一个派生自脚手架程序的应用程序:

db.define_table(
    'thing',
    Field('name', requires=IS_NOT_EMPTY()),
    Field('color', requires=IS_IN_SET(['red','blue','green'])),
    Field('image', 'upload', download_url=lambda name: URL('download', name)),
)

Form 的构造函数

Form 的构造函数接受以下参数:

Form(self,
     table,
     record=None,
     readonly=False,
     deletable=True,
     formstyle=FormStyleDefault,
     dbio=True,
     keep_values=False,
     form_name=False,
     hidden=None,
     validation=None,
     csrf_session=None,
     csrf_protection=True,
     lifespan=None,
     signing_info=None,
     ):

参数解释:

  • table: 一个 DAL 数据表或是一个 DAL 字段的列表

  • record: 一个 DAL 数据库记录或是记录的 ID

  • readonly: 设置为 True 可生成只读的表单

  • deletable: 设置为 False 可禁用删除记录的功能

  • formstyle: 一个使用 helper 渲染表单的函数

    可以是 FormStyleDefault (默认值) 、 FormStyleBulmaFormStyleBootstrap4 或者 FormStyleBootstrap5

  • dbio: 设置为 False 可阻止向数据库写入数据

  • keep_values: 如果设置为 True ,那么页面将记住上次提交的表单中各项的值

  • form_name: 这个表单的可选名称

  • hidden: 已经添加给这个表单的要隐藏字段的字典。

  • validation: 一个可选的验证器,请见 验证函数

  • csrf_session: 如果为 None,则不添加 csrf 令牌。如果是 session,则添加并验证 CSRF 令牌

  • lifespan: 以秒为单位的 CSRF 令牌的有效期,用来限制表单有效性。

  • signing_info: 在 CSRF 令牌签名和验证期间不应被更改的信息

FormStyleDefault 是一个被每个表单默认使用的对象,它在 py4web.utils.form.FormStyleDefault 里被定义。你绝不应该改变它,但你可以对其进行复制后再将副本作为 formstyle 传递。

您可以通过更改 FormStyle 的 color 属性来更改名为 color 的字段的样式。

一个没有数据库的最小表单示例

让我们从一个最小的工作表单示例开始。创建一个名为 form_minimal 的新的最小应用程序:

# in controllers.py
from py4web impot action, redirect, URL, Field
from py4web.utils.form import Form
from pydal.validators import *

@action('index', method=['GET', 'POST'])
@action.uses('form_minimal.html')
def index():
    fields = [
        Field('name', requires=IS_NOT_EMPTY()),
        Field('color', requires=IS_IN_SET(['red','blue','green'])),
    ]
    form = Form(fields)
    if form.accepted:
        # Do something with form.vars['name'] and form.vars['color']
        redirect(URL('accepted'))
    if form.errors:
        # do something
        ...
    return dict(form=form)

@action("accepted")
def accepted():
    return "form_example accepted"

也需要在应用程序里创建一个文件,称其为 templates/form_minimal.html ,它仅仅包含下面的行:

[[extend 'layout.html']]
[[=form]]

然后重启 py4web ,再访问 http://127.0.0.1:8000/form_minimal - 你将看到那个 Form 页面:

_images/form1.png

注意:

  • Form 是一个包含在模块 py4web.utils.form 里面的类

  • 可以使用 表单验证器,比如 IS_NOT_EMPTY ,请参见后面的 表单验证 。它们来自模块 pydal.validators

  • 在有表单的 action 里面,同时使用 GETPOST 方法通常很重要

这个示例有意的没有使用数据库、模板,也没有使用会话管理。下面的例子将会使用。

基本的表单示例

在下一个基本示例中,我们从数据库生成一个添加数据的 DRUD 表单。创建一个名为 form_basic 的新的最小应用程序:

# in controllers.py
from py4web import action, redirect, URL, Field
from py4web.utils.form import Form
from pydal.validators import *
from .common import db

# controllers definition
@action("create_form", method=["GET", "POST"])
@action.uses("form_basic.html", db)
def create_form():
    form = Form(db.thing)
    rows = db(db.thing).select()
    return dict(form=form, rows=rows)

注意在顶部导入的两个简单的验证器,这是为了稍后它们被当作 requires 的参数使用。我们将在 表单验证 段落部分详细解释。

你还需要一个模板文件 templates/form_basic.html ,它包含下面示例代码:

[[extend "layout.html"]]

<h2 class="title">Form Basic example: My Things</h2>

[[=form]]

<h2 class="title">Rows</h2>

<ul>
[[for row in rows:]]
<li>[[=row.id]]: [[=row.name]] has color [[=row.color]]</li>
[[pass]]
</ul>

重启 py4web,再访问 http://127.0.0.1:8000/create_form :结果是一个页面,上面是一个输入表单,下面是以前添加的所有数据记录的列表。

_images/form2.png

这是一个简单的示例,你不能更改和不能删除已存在的记录。但如果你想尝试,可以使用 Dashboard 应用程序查看和更改数据库的全部内容。

通过将一个记录或者记录的 ID 当作表单的第二个参数,你能将一个添加数据的表单转换成更新数据的 CRUD 表单:

# controllers definition
@action("update_form/<thing_id:int>", method=["GET", "POST"])
@action.uses("form_basic.html", db)
def update_form(thing_id):
    form = Form(db.thing, thing_id)
    rows = db(db.thing).select()
    return dict(form=form, rows=rows)

文件上传字典

我们可以对使用的模型和上传类型的文件进行小幅修改:

db.define_table(
    'thing',
    Field('name', requires=IS_NOT_EMPTY()),
    Field('color', requires=IS_IN_SET(['red','blue','green'])),
    Field('image', 'upload', download_url=lambda image: URL('download', image)),
)

文件上传字段非常特殊。使用它的标准方法(如在 _scaffold 应用程序中)是使用在 common.py 文件中定义的 UPLOAD_FOLDER 。但如果不指定,则将使用默认值的 your_app/upload 文件夹(如果需要,还将创建该文件夹)。 download_url 是一个回调函数,它给定图像名称,生成要下载的 URL 。 download url 在 common.py 中预定义。

我们可以修改 form_basic.html 以显示已上传的图像:

<h2 class="title">Form upload example: My Things</h2>

[[=form]]

<h2 class="title">Rows</h2>

<ul>
[[for row in rows:]]
<li>[[=row.id]]: [[=row.name]] has color [[=row.color]]
    <img src="[[=URL('download', row.image)]]" />
[[pass]]
</ul>

已上传的文件( “thing” 的图像)被存储在 UPLOAD_FOLDER 指定的文件夹中,原始文件名会被哈希处理。关于上传文件的其它内容能在 “Field” 的构造函数 段落部分中找到,包括一种将文件保存进数据库中的方法。

小部件

标准小部件

Py4web 在 Py4web.utility.form 库中提供了许多小部件。它们是简单的插件,允许您轻松指定表单中输入元素的类型及其一些属性。

这里是完整的列表

  • CheckboxWidget

  • DateTimeWidget

  • FileUploadWidget

  • ListWidget

  • PasswordWidget

  • RadioWidget

  • SelectWidget

  • TextareaWidget

这是一个改进的 “基本表单示例”,带有单选按钮小部件:

# in controllers.py
from py4web import action, redirect, URL, Field
from py4web.utils.form import Form, FormStyleDefault, RadioWidget
from pydal.validators import *
from .common import db

# controllers definition
@action("create_form", method=["GET", "POST"])
@action.uses("form_widgets.html", db)
def create_form():
    FormStyleDefault.widgets['color']=RadioWidget()
    form = Form(db.thing, formstyle=FormStyleDefault)
    rows = db(db.thing).select()
    return dict(form=form, rows=rows)

请注意与我们在本章开头看到的 “基本表单示例” 的不同之处:

  • 您需要从 py4web.utils.form 库导入小部件

  • 在表单定义之前,您可以使用下面的行定义 color 字段表单样式:

    FormStyleDefault.widgets['color']=RadioWidget()
    

结果与之前相同,但现在我们有了一个单选按钮小部件,而不是下拉菜单!

在表单中使用小部件非常简单,它们会让你对表单的各个部分有更多的控制权。

重要

当使用 py4web 时,使用 py4web 小部件,不要在 Field 对象中使用 pydal 小部件参数(请参阅 “Field” 的构造函数 )。

自定义小部件

您还可以通过克隆和修改现有样式来自定义小部件属性。让我们快速浏览一下,再次改进我们的 Superhero 示例:

# in controllers.py
from py4web import action, redirect, URL, Field
from py4web.utils.form import Form, FormStyleDefault, RadioWidget
from pydal.validators import *
from .common import db

# custom widget class definition
class MyCustomWidget:
    def make(self, field, value, error, title, placeholder, readonly=False):
        tablename = field._table if "_table" in dir(field) else "no_table"
        control = INPUT(
            _type="text",
            _id="%s_%s" % (tablename, field.name),
            _name=field.name,
            _value=value,
            _class="input",
            _placeholder=placeholder if placeholder and placeholder != "" else "..",
            _title=title,
            _style="font-size: x-large;color: red; background-color: black;",
        )
        return control

# controllers definition
@action("create_form", method=["GET", "POST"])
@action.uses("form_custom_widgets.html", db)
def create_form():
    MyStyle = FormStyleDefault.clone()
    MyStyle.classes = FormStyleDefault.classes
    MyStyle.widgets['name']=MyCustomWidget()
    MyStyle.widgets['color']=RadioWidget()

    form = Form(db.thing, deletable=False, formstyle=MyStyle)
    rows = db(db.thing).select()
    return dict(form=form, rows=rows)

结果与前面的类似,但现在我们有一个自定义输入字段,前景颜色为红色,背景颜色为黑色,

甚至单选按钮小部件也发生了变化,从红色变为蓝色。

高级的表单设计

表单结构操作

在 py4web 中,表单由 YATL helpers 呈现。这意味着在表单以 HTML 序列化之前,可以改变表单的树结构。以下是一个如何改变生成 HTML 结构的示例

db.define_table('paint', Field('color'))
form = Form(db.paint)
form.structure.find('[name=color]')[0]['_class'] = 'my-class'

请注意,在访问表单结构之前,表单不会形成 HTML 树。访问后,您可以使用 .find(...) 查找匹配的元素。 find 的参数是一个遵循 jQuery 过滤语法的字符串。在上述情况下,只有一个匹配的 [0] ,我们修改了该元素的 _class 属性。HTML 元素的属性名称前面必须加下划线。

自定义表单

自定义表单允许您精细控制表单的处理方式。在模板文件中,您可以在显示表单之前或提交数据之后执行特定指令,方法是在以下语句中插入代码:

[[=form.custom.begin ]]
[[=form.custom.submit ]]
[[=form.custom.end ]]

例如,可以使用自定义表单在表单中编辑记录时,不再显示 id 字段。

[[extend 'layout.html']]
[[=form.custom.begin ]]
    [[for field in DETAIL_FIELDS: ]]
        [[ if field not in ['id']: ]]
            <div class="select">
                [[=form.custom.widgets[field] ]]
            </div>
        [[pass]]
    [[pass]]
[[=form.custom.submit ]]
[[=form.custom.end ]]

注意:“custom” 只是一个约定,它可以是任何与已定义对象不冲突的名称。

警告

使用自定义表单时,如果您有一个不包含在表单中的可写字段,则在保存记录时会将其设置为 null。每当字段不被包含在自定义表单中时,都应将其设置为 field.writed=False,以确保该字段不被更新。

此外,自定义表单只为给定的字段创建元素,而不创建基于 css 框架可能需要的周围元素。例如,如果你使用 Bulma 作为 css 框架,你必须添加一个外部 DIV 才能让选择控件正确显示。

你也可以更有创意地在模板中使用 HTML,而不是使用小部件:

[[extend 'layout.html']]

[[for field, error form.errors.items:]]
<div class="error">Field [[=field]] [[=error]]</div>
[[pass]]

[[=form.custom.begin ]]

<div class="select">
     <input name="name" value="form.vars.get('name', '')"/>
</div>
<div class="select">
[[for color in ['red', 'blue', 'green']:]]
     <label>[[=color]]</label>
     <input name="color" type="radio" value="[[=color]]"
                [[if form.vars.get('color') == color:]]checked[[pass]]
     />
[[pass]]
</div>
<input type="submit" value="Click me"/>
[[=form.custom.end ]]

sidecar 参数

sidecar 是与提交按钮一起被注入到表单中的东西。

例如,您可以使用以下代码在表单中注入一个简单的 click me 按钮:

form.param.sidecar = DIV(BUTTON("click me", _onclick="alert('doh!')"))

特别是,这通常用于添加 py4web 不提供的 Cancel 按钮:

attrs = {
"_onclick": "window.history.back(); return false;",
"_class": "button is-default",
}
form.param.sidecar.append(BUTTON("Cancel", **attrs))

表单验证

验证器是用于验证输入字段(包括从数据库表生成的表单)的类。它们通常使用表定义中的 Field 对象的 requires 属性分配,如 DAL 章节中 “Field” 的构造函数 段落所示。此外,您可以使用高级验证器来创建小部件,如下拉菜单、单选按钮,甚至从其他表中查找。最后但同样重要的是,您甚至可以显式定义验证函数。

这个示例简单地说明了如何为表定义中的字段添加一个验证器,

db.define_table(
    'person',
    Field('name',requires=IS_NOT_EMPTY(),
    Field('job')
)

验证器经常以这种等效语法显式地写在表定义之外:

db.define_table(
    'person',
    Field('name'),
    Field('job')
)
db.person.name.requires = IS_NOT_EMPTY()

一个字段可以只有一个单独的验证器,也可以有多个验证器的列表。

db.person.name.requires = [
    IS_NOT_EMPTY(),
    IS_NOT_IN_DB(db, 'person.name')]

请注意,只能与 list: 类型字段一起使用的验证器是:

  • IS_IN_DB(..., multiple=True)

  • IS_IN_SET(..., multiple=True)

  • IS_NOT_EMPTY()

  • IS_LIST_OF_EMAILS()

  • IS_LIST_OF(...)

最后一个可用于将任何验证器应用于列表中的单个项目。 multiple=(1, 1000) 需要选择 1 到 1000 个项目。这强制选择至少一个选项。

内置验证器的构造器有一个 error_message 参数:

IS_NOT_EMPTY(error_message='cannot be empty!')

注意, error_message 通常是构造器的第一个可选参数,而且一般不用参数名称。因此,以下语法是等效的: IS_NOT_EMPTY('cannot be empty!')

如果你想像上一章中解释的那样使用国际化,你需要定义自己的消息,并将验证器消息包装在 T 运算符中:

IS_NOT_EMPTY(error_message=T('cannot be empty!'))

IS_NOT_EMPTY('cannot be empty!')

这是一个基于数据库表的验证器示例:

db.person.name.requires = IS_NOT_EMPTY(error_message=T('fill this!'))

这里,我们使用了翻译运算符 T 来实现国际化。注意,默认情况下,error messages 是不被翻译的,除非你明确地使用 T 对其进行了定义

还可以为字段显式调用验证器:

db.person.name.validate(value)

如果 value 有效,验证器会返回一个元组 (value, error),并且 errorNone .

直接使用 Python ,你也能很容易地测试大部分验证器。例如:

>>> from pydal.validators import *
>>> IS_ALPHANUMERIC()('test')
('test', None)
>>> IS_ALPHANUMERIC()('test!')
('test!', 'Enter only letters, numbers, and underscore')
>>> IS_ALPHANUMERIC('this is not alphanumeric')('test!')
('test!', 'this is not alphanumeric')
>>> IS_ALPHANUMERIC(error_message='this is not alphanumeric')('test!')
('test!', 'this is not alphanumeric')

提示

在 python 的源代码中, DAL 验证器有完整的文档。你自己就能很容易地查看验证器的所有详细信息:

from pydal import validators
dir(validators) # get the list of all validators
help(validators.IS_URL) # get specific help for the IS_URL validator

文本格式的验证器

IS_ALPHANUMERIC

此验证器检查字段的值是否仅包含 a-z、 A-Z、 0-9 和下划线中的字符。

requires = IS_ALPHANUMERIC(error_message='must be alphanumeric!')

IS_LOWER

此验证器不返回错误信息,它只是把值转换为小写。

requires = IS_LOWER()

IS_UPPER

此验证器不返回错误信息,它只是把值转换为大写。

requires = IS_UPPER()

IS_EMAIL

它检查字段值是否看起来像电子邮件地址。它不会尝试发送电子邮件进行确认。

requires = IS_EMAIL(error_message='invalid email!')

IS_MATCH

此验证器将值与一个正则表达式进行匹配,如果失败则返回错误信息。这里是一个用来验证美国邮政编码的示例:

requires = IS_MATCH('^\d{5}(-\d{4})?$',
    error_message='not a zip code')

这是一个用来验证 IPv4 地址的示例(注意:IS_IPV4 验证器会更适合):

requires = IS_MATCH('^\d{1,3}(\.\d{1,3}){3}$',
        error_message='not an IP address')

这是一个用来验证美国电话号码的示例:

requires = IS_MATCH('^1?((-)\d{3}-?|\(\d{3}\))\d{3}-?\d{4}$',
        error_message='not a phone number')

要更多地了解 Python 正则表达式,请参考 Python 的官方在线文档。

"IS_MATCH 有一个可选参数 strict ,其值默认为 False。如果被设置为 True ,验证器仅按整个字符串(从头到尾)进行匹配。

>>> IS_MATCH('ab', strict=False)('abc')
('abc', None)
>>> IS_MATCH('ab', strict=True)('abc')
('abc', 'Invalid expression')

IS_MATCH 还有一个可选参数 search ,其值默认为 False。如果被设置为 True ,验证器将使用正则方法 search 代替 match 方法去验证字符串。

IS_MATCH('...', extract=True) 过滤并仅提取第一个匹配的子字符串,而不是原始值。

IS_LENGTH

检查字段值的长度是否在给的范围内。适用于 text 和 file 。

它的参数是

  • maxsize: 允许的最大长度/大小(默认为 255)

  • minsize: 允许的最小长度/大小

示例:检查文本字符串是否少于 15 个字符:

>>> IS_LENGTH(15)('example string')
('example string', None)
>>> IS_LENGTH(15)('example long string')
('example long string', 'Enter from 0 to 15 characters')
>>> IS_LENGTH(15)('33')
('33', None)
>>> IS_LENGTH(15)(33)
('33', None)

检查上传的文件大小是否在 1KB 到 1MB 之间:

INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024))

除了文件类型的字段,它检查字段值的长度(字符个数)。在是文件的情况下,值是一个 cgi.FieldStorage 对象,因此他检查文件中数据的长度,这是人们直观预期的行为。

IS_URL

如果以下任何一项为真,则不是 URL 字符串:

  • 字符串为 空 或者 None

  • 字符串中使用了 URL 不允许的字符。

  • 该字符串违反了任何 HTTP 语法规则

  • 指定了 URL 架构,但不是 'http' 或 'https'

  • 如果指定了主机名,但顶层域名不存在

(这些规则基于 RFC 2616)

此函数仅按 URL 语法进行检查。它不检查 URL 是否指向了一个真正的文件,或

  • 字符串为 空 或者 None

  • 字符串中使用了 URL 不允许的字符。

  • 指定的 URL 架构无效

这些规则基于 RFC 2396

允许的架构列表可以使用 allowd_schemes 参数进行自定义。如果从列表中排除 None,则缩写 URL(缺少 “http” 等架构)将被视为无效。

默认的预置架构可以使用 prepend_scheme 参数进行自定义。如果将 repend_scheme 设置为 None,则预先添加架构操作将被禁用。仍将接受需要预先添加架构才能解析的 URL,但不会修改返回值。

IS_URL 与 RFC 3490 中指定的国际化域名(IDN)标准兼容。因此,URL 可以是常规字符串或 unicode 字符串。如果 URL 的域组件(例如 google.ca)包含非美国 ASCII 字母,则域将转换为 Punycode(在 RFC 3492 中定义)。IS_URL 超出了标准,允许非美国 ASCII 字符出现在 URL 的路径和查询组件中。这些非美国 ASCII 字符将被编码。例如,空格将被编码为 “%20”。十六进制代码为 0x4e86 的 unicode 字符将变为 “%4e%86” 。

示例:

requires = IS_URL())
requires = IS_URL(mode='generic')
requires = IS_URL(allowed_schemes=['https'])
requires = IS_URL(prepend_scheme='https')
requires = IS_URL(mode='generic',
                allowed_schemes=['ftps', 'https'],
                prepend_scheme='https')

IS_SAFE

requires = IS_SAFE(error_message='Unsafe Content')
requires = IS_SAFE(mode="sanitize")
requires = IS_SAFE(sanitizer=lambda text: str(XML(text, sanitize=True)))

此验证器用于应包含 HTML 且可能包含无效标签(脚本、成员、对象、iframe)的文本字段。它的工作原理是尝试对内容进行净化(去除或编码不安全的内容),并提供错误(mode="error")或用净化后的内容替换内容(mode="sanitize")。您可以指定错误消息、模式,并提供自己的净化函数。

IS_SLUG

requires = IS_SLUG(maxlen=80, check=False, error_message='must be slug')

如果 check 设置为 True ,则检查验证值是否符合 slug 规则(只允许字母、数字和不重复的破折号)。

如果 check 设置为 False (默认),它把输入值转换成符合 slug 规则的内容。

IS_JSON

requires = IS_JSON(error_message='Invalid json', native_json=False)

此验证器检查字段值是否为 JSON 格式。

如果 native_json 设置为 False (默认值),则将输入值转换为序列化值,否则输入值保持不变。

日期和时间的验证器

IS_TIME

此验证器检查字段值是否是符合指定格式的时间

requires = IS_TIME(error_message='must be HH:MM:SS!')

IS_DATE

此验证器检查字段值是否是符合指定格式的日期。建议指定使用翻译操作符 T 的格式,这样可以在不同区域环境下支持不同的格式。

requires = IS_DATE(format=T('%Y-%m-%d'),
    error_message='must be YYYY-MM-DD!')

IS_DATETIME 验证器部分,可以查看 % 指令的完整描述。

IS_DATETIME

此验证器检查字段值是否是符合指定格式的日期时间。建议指定使用翻译操作符 T 的格式,这样可以在不同区域环境下支持不同的格式。

requires = IS_DATETIME(format=T('%Y-%m-%d %H:%M:%S'),
                   error_message='must be YYYY-MM-DD HH:MM:SS!')

以下符号可用于定义格式的字符串(展示了符号、其含义和示例字符串):

%Y  year with century (e.g. '1963')
%y  year without century (e.g. '63')
%d  day of the month (e.g. '28')
%m  month (e.g '08')
%b  abbreviated month name (e.g.'Aug')
%B  full month name (e.g. 'August')
%H  hour (24-hour clock, e.g. '14')
%I  hour (12-hour clock, e.g. '02')
%p  either 'AM' or 'PM'
%M  minute (e.g. '30')
%S  second (e.g. '59')

IS_DATE_IN_RANGE

其作用和前面的验证器类似,但需在指定的范围内:

requires = IS_DATE_IN_RANGE(format=T('%Y-%m-%d'),
                minimum=datetime.date(2008, 1, 1),
                maximum=datetime.date(2009, 12, 31),
                error_message='must be YYYY-MM-DD!')

IS_DATETIME 验证器部分,可以查看 % 指令的完整描述。

IS_DATETIME_IN_RANGE

其作用和前面的验证器类似,但需在指定的范围内:

requires = IS_DATETIME_IN_RANGE(format=T('%Y-%m-%d %H:%M:%S'),
                    minimum=datetime.datetime(2008, 1, 1, 10, 30),
                    maximum=datetime.datetime(2009, 12, 31, 11, 45),
                    error_message='must be YYYY-MM-DD HH:MM::SS!')

IS_DATETIME 验证器部分,可以查看 % 指令的完整描述。

范围、集合和相等的验证器

IS_EQUAL_TO

检查被验证的值是否等于已给定的值(可以是一个变量)

requires = IS_EQUAL_TO(request.vars.password,
                    error_message='passwords do not match')

IS_NOT_EMPTY

这个验证器检查字段值的内容是否不是 None 、空字符串或者空列表。一个字符串类型的值会在调用 .strip() 之后才被检查。

requires = IS_NOT_EMPTY(error_message='cannot be empty!')

您可以提供一个正则表达式来匹配空字符串。

requires = IS_NOT_EMPTY(error_message='Enter a value', empty_regex='NULL(?i)')

IS_NULL_OR

已弃用,如下所述的 IS_EMPTY_OR 的别名。

IS_EMPTY_OR

有时,需要在一个有其他要求的字段上允许空值。例如,一个要求是日期的字段值也可以为空。 IS_EMPTY_OR 验证器允许这样做:

requires = IS_EMPTY_OR(IS_DATE())

一个空值是 None,或者是一个空字符串,还可以是一个空列表。字符串类型的值在调用 .strip() 之后才被检查。

您可以为 empty_regex 参数提供一个正则表达式,用于将空字符串的匹配(类似 IS_NOT_empty 验证器)。

还可以指定一个用于 “被视为空” 的值。

requires = IS_EMPTY_OR(IS_ALPHANUMERIC(), null='anonymous')

IS_EXPR

此验证器允许您通过可调用函数来表达一般条件,该函数接受一个值进行验证并返回错误消息,或返回 None 以接受输入值。

requires = IS_EXPR(lambda v: T('not divisible by 3') if int(v) % 3 else None)

注意 ,如果您不像上面一样处理,返回的消息将不会被翻译。

为了向后兼容,条件可以表示为包含变量值逻辑表达式的字符串。如果表达式的计算结果为 True ,字段值将通过验证。

requires = IS_EXPR('int(value) % 3 == 0',
               error_message='not divisible by 3')

应该首先确保值是一个整数,以避免发生异常。

requires = [IS_INT_IN_RANGE(0, None),
            IS_EXPR(lambda v: T('not divisible by 3') if v % 3 else None)]

IS_DECIMAL_IN_RANGE

INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10, dot="."))

它将输入转换为 Python 十进制。如果十进制不在指定的包含范围内,则生成错误。使用Python 十进制算法进行比较。

最小和最大限制可以是 None,那分别表示没有下限或上限,

dot 参数是可选的,允许您将用于分隔小数的符号国际化。

IS_FLOAT_IN_RANGE

检查字段值是否为一定范围内的浮点数,在以下示例中为 0 <= value <= 100

requires = IS_FLOAT_IN_RANGE(0, 100, dot=".",
                            error_message='negative or too large!')

dot 参数是可选的,允许您将用于分隔小数的符号国际化。

IS_INT_IN_RANGE

检查字段值是否为一定范围内的整数,

在以下示例中为 0 <= value < 100

requires = IS_INT_IN_RANGE(0, 100,
                        error_message='negative or too large!')

IS_IN_SET

此验证器将自动将表单字段设置为选项字段(即,使用下拉菜单)。

IS_IN_SET 检查字段值是否在集合中:

requires = IS_IN_SET(['a', 'b', 'c'], zero=T('choose one'),
             error_message='must be a or b or c')

zero 参数是可选的,它决定了默认选择的选项的文本, IS_IN_SET 验证器本身不接受该选项。如果你不想默认 “选择一个” 选项,请设置为 zero=None

只要 IS_IN_SET 是列表中的第一个,该集合的元素就可以与数字验证器组合。这样做将强制后面的验证器转换为数字类型。因此, S_IN_SET 后面可以跟 IS_INT_IN_RANGE (将值转换为 INT)或 IS_FLOAT_IN_RANGE (将值转化为 FLOAT)。

requires = [ IS_IN_SET([2, 3, 5, 7], error_message='must be prime and less than 10'),
            IS_INT_IN_RANGE(0, None) ]

复选框验证

要强制填写表格复选框(例如接受条款和条件),请使用以下命令:

requires=IS_IN_SET(['ON'])

在 IS_IN_SET 中使用字典和元组

您还可以使用字典或元组列表使下拉列表更具描述性:

# Dictionary example:
requires = IS_IN_SET({'A':'Apple', 'B':'Banana', 'C':'Cherry'}, zero=None)

# List of tuples example:
requires = IS_IN_SET([('A', 'Apple'), ('B', 'Banana'), ('C', 'Cherry')])

排序选项

要在下拉列表中按标签的字母顺序排列选项,请在 IS_IN_SET 中使用 sort 参数。

IS_IN_SET([('H', 'Hulk'), ('S', 'Superman'), ('B', 'Batman')], sort=True)

IS_IN_SET 和标记

IS_IN_SET 验证器具有可选属性 multiple=False 。如果设置为 True,则可以在一个字段中存储多个值。字段的类型应为 list:integerlist:string ,如 list:<type> 和 contains 中所述。这里讨论了一个明确的标记示例。我们强烈建议使用 jQuery multiselect 插件来呈现多个值的字段。

请注意 ,当 multiple=True 时, IS_IN_SET 将接受零个或多个值,即当没有选择任何值时,它将接受该字段。 multiple 也可以是 (a, b) 形式的元组,其中 ab 分别是可以选择的最小和(不包括)最大项目数。

复杂性和安全性的验证器

IS_STRONG

强制对字段执行复杂性要求(通常用于密码字段)。

示例:

requires = IS_STRONG(min=10, special=2, upper=2)

参数:

  • min 是值的长度的最小值

  • special 是需要的特殊字符的最小数目,默认的特殊字符是 ^!!@#$%^&*()_+-=?<>,.:;{}[]| (你可以使用 specials = '...' 对其进行自定义)

  • upper 是值中大写字符的最小数目

其它可使用的参数是:

  • invalid 用于禁止使用的字符的列表,默认是 invalid=' "'

  • max 是值的长度的最大值

  • lower 是值中小写字符的最小数目

  • number 是数字字符的最小数目

显然,您可以像其他验证器一样提供 error_message ,尽管 IS_STRONG 足够聪明,可以提供一条清晰的消息来描述验证失败。

您可以使用的一个特殊参数是 entropy ,它是要接受的值(一个数字)的复杂性的最小值,请尝试:

>>> IS_STRONG(entropy=100.0)('hello')
('hello', Entropy (24.53) less than required (100.0))

请注意 ,如果参数 entropy 没有给出,则 IS_STRONG 隐式设置以下默认值:min = 8, upper = 1, lower = 1, number = 1, special = 1,否则都设置为None。

CRYPT

这也是一个过滤器。它对输入执行安全散列,用于防止密码以明文形式传递到数据库

requires = CRYPT()

默认情况下,CRYPT 使用 1000 次迭代的 pbkdf2 算法结合 SHA512 来生成 20 字节长的哈希值。旧版本的 web2py 使用 md5 或 HMAC+SHA512 ,具体取决于是否指定了密钥。

如果指定了密钥( key 参数),CRYPT 将使用 HMAC 算法。密钥可能包含一个前缀,用于确定与 HMAC一起使用的算法,例如 SHA512 :

requires = CRYPT(key='sha512:thisisthekey')

这是推荐的语法。密钥必须是与所使用的数据库关联的唯一字符串。钥匙永远不能改变。如果你丢失了密钥,之前散列的值将变得无用。默认情况下,CRYPT 使用随机的 salt,因此每个结果都不同。要使用恒定的 salt 值,请指定其值:

requires = CRYPT(salt='mysaltvalue')

或者,不使用 salt:

requires = CRYPT(salt=False)

CRYPT 验证器对其输入进行哈希运算,这使得它有点特别。如果需要在散列之前验证密码字段,可以在验证器列表中使用 CRYPT,但必须确保它是列表中的最后一个,以便最后调用它。例如:

requires = [IS_STRONG(), CRYPT(key='sha512:thisisthekey')]

CRYPT 还接受一个 min_length 参数,其默认值为 0

生成的哈希采用 alg$salt$hash 的形式,其中 alg 是使用的哈希算法, salt 是 salt 字符串(可以为空), hash 是算法的输出。因此,哈希是自我识别的,例如,在不使之前的哈希无效的情况下,允许更改算法。然而,密钥必须保持不变。

特殊类型的验证器

IS_LIST_OF

此验证器可帮助您确保列表类型值的长度限制,为此,请使用其 minimum` `、 ``maximumerror_message 参数,例如:

requires = IS_LIST_OF(minimum=2)

列表值可能来自包含多个同名字段的表单或多选框。请注意,此验证器会自动将非列表值转换为单值列表:

>>> IS_LIST_OF()('hello')
(['hello'], None)

如果 IS_LIST_OF 的第一个参数是另一个验证器,那么它会将另一个验证器应用于列表的每个元素。一个典型的用法是验证 list: 类型字段,例如:

Field('emails', 'list:string', requires=IS_LIST_OF(IS_EMAIL()), ...)

IS_LIST_OF_EMAILS

此验证器被专门设计以用于以下字段:

Field('emails', 'list:string',
      widget=SQLFORM.widgets.text.widget,
    requires=IS_LIST_OF_EMAILS(),
    represent=lambda v, r:
        XML(', '.join([A(x, _href='mailto:'+x).xml() for x in (v or [])]))
    )

请注意,由于 widget 的自定义,此字段将被呈现为 SQLFORM 中的文本区域(请参阅下一节 [[Widgets #Widgets]] )。这允许您在单个输入字段中插入和编辑多封电子邮件(非常像普通邮件客户端程序),用 ,; 或 空白(空格、换行符或制表符)分隔每个电子邮件地址。因此,现在我们需要一个能够对单个值输入进行操作的验证器,以及一种将验证后的值拆分为 DAL 下一步处理的列表的方法。这是由 requires 验证器完成的。作为 filter_in 的替代方法,您可以将以下函数传递给表单 acceptsprocessvalidate 方法的 onvalidation 参数:

def emails_onvalidation(form):
    form.vars.emails = IS_LIST_OF_EMAILS.split_emails.findall(form.vars.emails)

represent 参数(第6行和第7行)的作用是在 HTML 页面中呈现记录时,为每个电子邮件地址添加一个 “mailto:...” 链接。

ANY_OF

此验证器接受其它验证器组成的列表,如果列表中的任何验证器接受值,则它接受该值(即,它对给定的验证器的行为类似于逻辑 OR )。

requires = ANY_OF([IS_ALPHANUMERIC(), IS_EMAIL()])

当没有验证器接受值时(验证失败),你会得到最后一个验证器的错误消息(列表中的最后一个),您可以像往常一样自定义错误消息:

>>> ANY_OF([IS_ALPHANUMERIC(), IS_EMAIL()])('@ab.co')
('@ab.co', 'Enter a valid email address')
>>> ANY_OF([IS_ALPHANUMERIC(), IS_EMAIL()],
...        error_message='Enter login or email')('@ab.co')
('@ab.co', 'Enter login or email')

IS_IMAGE

此验证器检查通过文件输入上传的文件是否以所选图像格式之一保存,并且尺寸(宽度和高度)是否在给定范围内。

它不会检查最大文件大小(使用 IS_LENGTH )。如果没有上传数据,则返回验证失败。它支持 BMP、GIF、JPEG、PNG 等文件格式,并且不需要 Python 图像库。

代码部分取自 ref.``source1``:cite (?)

它接受以下参数: - extensions:iterable 包含允许的小写图像文件扩展名; - maxsize:iterable 包含图像的最大宽度和高度; - minsize:iterate 包含图像的最小宽度和高度

使用 (-1, -1) 作为最小尺寸来避免图像尺寸检查。

这里有些示例:- 检查已上传的文件是否为任何支持的图像格式:

requires = IS_IMAGE()
  • 检查已上传的文件扩展名是否是 JPEG 或 PNG :

requires = IS_IMAGE(extensions=('jpeg', 'png'))
  • 检查已上传的文件扩展名是否是 PNG ,且大小最大为 200x200 像素:

requires = IS_IMAGE(extensions=('png'), maxsize=(200, 200))

注意:在显示包含 requires=IS_IMAGE()``的表的编辑表单时,将不会出现 ``delete 复选框,因为删除文件会导致验证失败。要显示 delete 复选框,请使用以下验证:

requires = IS_EMPTY_OR(IS_IMAGE())

IS_FILE

检查通过文件输入上传的文件的名称和扩展名是否符合给定条件。

不以任何方式确保文件类型。如果没有上传数据,则返回验证失败。

它的参数是

  • filename: 字符串/编译的正则表达式,或有效文件名的字符串/正则表达式列表

  • extension: 字符串/编译后的正则表达式,或有效扩展名的字符串/正则表达式列表

  • lastdot: 应将哪个点用作文件名/扩展名分隔符:True 表示最后一个点(例如,“file.tar.gz” 将被拆分为 “file.tar”+“gz” ),而 False 表示第一个点(例如,“file.tar.gz“ 将被拆分为 ”file”+“tar.gz” )。

  • case: 0 表示保留原有的大小写;1 表示将字符串转换为小写(默认);2 表示将字符串转换为大写。

如果没有点,将对空字符串进行扩展名检查,并对整个值进行文件名检查。

示例:检查文件是否有 pdf 扩展名(不区分大小写):

INPUT(_type='file', _name='name',
        requires=IS_FILE(extension='pdf'))

检查文件名是否为 'thumbnail' ,同时有 jpg 或 png 扩展名(不区分大小写)

INPUT(_type='file', _name='name',
        requires=IS_FILE(filename='thumbnail',
        extension=['jpg', 'png']))

检查文件是否有 tar.gz 扩展名,同时文件名开头是 backup:

INPUT(_type='file', _name='name',
        requires=IS_FILE(filename=re.compile('backup.*'),
        extension='tar.gz', lastdot=False))

检查文件是否没有扩展名,并且文件名与 README 匹配(区分大小写):

INPUT(_type='file', _name='name',
    requires=IS_FILE(filename='README',
    extension='', case=0)

IS_UPLOAD_FILENAME

这是检查文件的旧实现,包括向后的兼容性。对于新应用程序,请使用 IS_FILE()

此验证器检查通过文件输入上传的文件的名称和扩展名是否符合给定的条件。

它不会以任何方式确保文件类型。如果没有上传数据,则返回验证失败。

它的参数是

  • filename: 文件名(点之前的部分)正则表达式

  • extension: 扩展名(点之后的部分)正则表达式

  • lastdot: 应将哪个点用作文件名/扩展名分隔符:True 表示最后一个点(例如,“file.tar.gz” 将被拆分为 “file.tar”+“gz” ),而 False 表示第一个点(例如,“file.tar.gz“ 将被拆分为 ”file”+“tar.gz” )。

  • case: 0 表示保留原有的大小写;1 表示将字符串转换为小写(默认);2 表示将字符串转换为大写。

如果没有点,将对空字符串进行扩展名检查,并对整个值进行文件名检查。

示例:

检查文件是否有 pdf 扩展名(不区分大小写):

requires = IS_UPLOAD_FILENAME(extension='pdf')

检查文件是否有 tar.gz 扩展名,同时文件名开头是 backup:

requires = IS_UPLOAD_FILENAME(filename='backup.*', extension='tar.gz', lastdot=False)

检查文件是否没有扩展名,并且文件名与 README 匹配(区分大小写):

requires = IS_UPLOAD_FILENAME(filename='^README$', extension='^$', case=0)

IS_IPV4

此验证器以十进制形式检查字段的值是否为版本 4 的 IP 地址。可以设置以强制地址在特定范围内。

IPv4 正则表达式取自 regexlibIS_IPV4 构造函数的签名如下:

IS_IPV4(minip='0.0.0.0', maxip='255.255.255.255', invert=False,
        is_localhost=None, is_private=None, is_automatic=None,
        error_message='Enter valid IPv4 address')

参数解释:

  • minip 是允许的最低/小地址

  • maxip 是允许的最高/大地址

  • invert 是一个反转允许地址范围的标志,即如果设置为 True,则只允许来自给定范围之外的地址;请注意,不会匹配范围边界。

你传递的 IP 地址,可以是一个字符串(例如 “192.168.0.1” ),或是一个有 4 个整数构成的列表或元组(例如 [192, 168, 0, 1])。

要检查多个地址范围,请向 minipmaxip 传递一个边界地址列表或元组,例如只允许 “192.168.20.10” 和 “192.168.20.19” 之间或 “192.168.30.100” 和 “168.30.199” 之间的地址,请使用:

requires = IS_IPV4(minip=('192.168.20.10', '192.168.30.100'),
                maxip=('192.168.20.19', '192.168.30.199'))

请注意,只配置了一个同时设置了下限和上限的范围,也就是说,配置的范围的数量由传递给 minipmaxip 的可迭代对象中的较短者决定。

参数 is_localhostis_privateis_automatic 接受以下值:

  • None :忽略

  • True : 强制

  • False : 禁止

可选参数的含义是:

  • is_localhost: 匹配本机地址 (127.0.0.1)

  • is_private: 匹配在 172.16.0.0 - 172.31.255.255 或 192.168.0.0 - 192.168.255.255 范围内的地址

  • is_automatic: 匹配 169.254.0.0 - 169.254.255.255 范围内的地址

示例:

检查 IPv4 地址是否有效:

requires = IS_IPV4()

检查私有网络 IPv4 地址是否有效:

requires = IS_IPV4(minip='192.168.0.1', maxip='192.168.255.255')

IS_IPV6

此验证器检查字段的值是否为版本 6 的 IP 地址。

IS_IPV6 构造函数的签名如下:

IS_IPV6(is_private=None,
        is_link_local=None,
        is_reserved=None,
        is_multicast=None,
        is_routeable=None,
        is_6to4=None,
        is_teredo=None,
        subnets=None,
        error_message='Enter valid IPv6 address')

参数 is_privateis_link_localis_reservedis_multicastis_routeableis_6to4is_teredo 接受以下值:

  • None :忽略

  • True : 强制

  • False :禁用该选项,对 is_routeable 无效

可选参数的含义是:

  • is_private: 匹配为专用网络分配的地址

  • is_link_local: 匹配为 link local 保留的地址(即在 fe80::/10 范围内),这也是一个专用网络(也可用上面的 is_private 匹配)

  • is_reserved: 匹配一个地址,否则 IETF 被保留

  • is_multicast: 匹配为多播使用保留的地址(即在 ff00::/8 范围内)

  • is_6to4: 匹配一个似乎包含 6to4 嵌入式地址的地址(即在 2002::/16 范围内)

  • is_teredo: 匹配 teredo 地址(即在 2001::/32 范围内)

强制 is_routeable (设置为 True)是全部禁止(设置为 False) is_privateis_reservedis_multicast 的快捷方式。

使用 subnets 参数传递子网或子网列表以检查地址成员资格,这样地址必须是要验证的子网成员。

示例:

检查是否为有效的 IPv6 地址:

requires = IS_IPV6()

检查是否为有效的专用网络 IPv6 地址:

requires = IS_IPV6(is_link_local=True)

检查是否为子网中的有效 IPv6 地址:

requires = IS_IPV6(subnets='fb00::/8')

IS_IPADDRESS

此验证器检查字段的值是否是 IP 地址(版本 4 或版本 6 )。可以设置为强制地址在特定范围内。使用适当的 IS_IPV4IS_IPV6 验证器进行检查。

IS_IPADDRESS 构造函数的签名如下:

IS_IPADDRESS(minip='0.0.0.0', maxip='255.255.255.255', invert=False,
            is_localhost=None, is_private=None, is_automatic=None,
            is_ipv4=None,
            is_link_local=None, is_reserved=None, is_multicast=None,
            is_routeable=None, is_6to4=None, is_teredo=None,
            subnets=None, is_ipv6=None,
            error_message='Enter valid IP address')

对于 IS_IPV4IS_IPV6 验证器,唯一添加的参数是:

  • is_ipv4, 设置为 True 强制为版本 4 ,或设置为 False 禁止版本 4

  • is_ipv6, 设置为 True 强制为版本 6 ,或设置为 False 禁止版本 6

有关其他参数的含义,请参阅 IS_IPV4 和 IS_IPV6 验证器。

示例:

检查是否为有效的 IP 地址(IPv4 和 IPv6 ):

requires = IS_IPADDRESS()

检查是否为有效的 IP 地址(仅限 IPv6):

requires = IS_IPADDRESS(is_ipv6=True)

其它的验证器

CLEANUP

这是一个过滤器。它从不失败。默认情况下,它只会删除十进制 ASCII 码不在列表 [10,13,32-127] 中的所有字符。它总是对值执行基本的清除(即删除开始和结尾的空白字符)。

requires = CLEANUP()

您可以传递一个正则表达式来决定必须删除的内容,例如清除所有非数字字符,请使用:

>>> CLEANUP('[^\\d]')('Hello 123 world 456')
('123456', None)

数据库验证器

IS_NOT_IN_DB

用法简介:IS_NOT_IN_DB(db|set, 'table.field')

考虑下面的示例:

db.define_table('person', Field('name'))
db.person.name.requires = IS_NOT_IN_DB(db, 'person.name')

它要求当您插入一个新人的数据时,他/她的名字不在数据库 dbperson.name 字段中。

可以使用一个集合替代 db

与所有其他验证器一样,此要求是在表单处理级别执行的,而不是在数据库级别。这意味着有可能性很小的事情发生,如果两个访问者试图同时插入具有相同 person.name 的记录,则很可能会导致冲突,并且两条记录都被接受。因此,更安全的做法是通知数据库该字段应具有唯一值:

db.define_table('person', Field('name', unique=True))
db.person.name.requires = IS_NOT_IN_DB(db, 'person.name')

现在,如果发生冲突的情况,数据库将引发 OperationalError ,两个插入中的一个将被拒绝。

IS_NOT_IN_DB 的第一个参数可以是数据库连接或 Set。在后一种情况下,将只检查 set 定义的集合。

IS_NOT_IN_DB()` 的完整的参数列表如下:

IS_NOT_IN_DB(dbset, field, error_message='value already in database or empty',
            allowed_override=[], ignore_common_filters=True)

例如,以下代码不允许在 10 天内注册两个同名人员:

import datetime
now = datetime.datetime.today()
db.define_table('person',
    Field('name'),
    Field('registration_stamp', 'datetime', default=now))
recent = db(db.person.registration_stamp > now-datetime.timedelta(10))
db.person.name.requires = IS_NOT_IN_DB(recent, 'person.name')

IS_IN_DB

用法简介:IS_IN_DB(db|set, 'table.value_field', '%(representing_field)s', zero='choose one') ,这里的第三个和第四个参数是可选的。

如果字段类型是 list,那么可以使用 multiple= ,其默认值为 False。可以设置其为 True 或 一个元组 (min, max) 来严格限制选择的数量。因此, multiple=(1, 10) 要求,必须有 1 至 10 个已选择的项目。

其他可选参数将在下面讨论。

考虑以下示例中的表格和要求:

db.define_table('person', Field('name', unique=True))
db.define_table('dog', Field('name'), Field('owner', db.person))
db.dog.owner.requires = IS_IN_DB(db, 'person.id', '%(name)s',
                                zero=T('choose one'))

IS_IN_DB 的要求也可以写成使用 Set 来替代 DB

db.dog.owner.requires = IS_IN_DB(db(db.person.id > 10), 'person.id', '%(name)s',
                                zero=T('choose one'))

它在 dog 表的 INSERT/UPDATE/DELETE 表单级别强制执行。此示例要求 dog.owner 是数据库 dbperson.id 字段中的有效 id。由于这个验证器, dog.owner 字段在页面被呈现为下拉列表。验证器的第三个参数是一个描述下拉列表中元素的字符串,它被传递给验证器的 label 参数。在示例中,您希望看到人员的 %(name)s 而不是人员的 %(id)s%(...)s 将被每条记录括号中的字段值替换。 label 的其他可接受值是 Field 实例(例如,您可以使用 db.person.name 而不是 “%(name)s” ),甚至是一个可调用对象,它接受一行并返回选项的描述。

zero 选项的工作方式与 IS_IN_SET 验证器非常相似。

IS_IN_DB 接受的其他可选参数有: orderbygroupbydistinctcacheleft ;这些将传递给数据库查询语句(请参阅DAL章节中的 their description )。

请注意groupbydistinctleft 不适用于 Google App Engine。

要按字母顺序对下拉列表中列出的选项进行排序,可以将 sort 参数设置为 True (排序不区分大小写),当没有可行或实用的排序方式时,这可能会很有用。

验证器的第一个参数可以是数据库连接或一个 DAL 数据集,如 IS_NOT_IN_DB 。例如,当希望限制下拉列表中的记录时,这可能很有用。在这个例子中,我们在控制器中使用``IS_IN_DB`` 来动态限制每次调用控制器时的记录:

def index():
    (...)
    query = (db.table.field == 'xyz') # in practice 'xyz' would be a variable
    db.table.field.requires = IS_IN_DB(db(query), ...)
    form = Form(...)
    if form.process().accepted: ...
    (...)

如果您希望验证字段,但不希望出现下拉列表,则必须将验证器放入列表中。

db.dog.owner.requires = [IS_IN_DB(db, 'person.id', '%(name)s')]

有时您需要下拉列表(因此您不想使用上面的列表语法),但您想使用其他验证器。为此,IS_IN_DB 验证器需要一个额外的 _and 参数,如果值需要通过 IS_IN_DB 验证,该参数可以指向其他要添加的验证器的列表。例如,要验证数据库中不在某个子集中的所有狗主人:

subset = db(db.person.id > 100)
db.dog.owner.requires = IS_IN_DB(db, 'person.id', '%(name)s',
                                _and=IS_NOT_IN_DB(subset, 'person.id'))

IS_IN_DB 和标记

IS_IN_DB 验证器具有可选属性 multiple=False。如果设置为 True ,则可以在一个字段中存储多个值。此字段的类型应为 list:reference ,如 list:<type> 和 contains 中所述,里面讨论了一个明确的标记示例。在创建和更新表单中会自动处理多个引用,但它们对 DAL 是透明的。我们强烈建议使用 jQuery 的 multiselect 插件来呈现多个字段。

验证函数

为了显式定义验证函数,我们向参数 validation 传递一个函数,该函数采把表单作为参数并返回一个字典,将字段的名称映射到错误信息中。如果字典非空,则错误将显示给用户,并且不会发生数据库读写操作。

这里是一个示例:

from py4web import Field
from py4web.utils.form import Form, FormStyleBulma
from pydal.validators import IS_INT_IN_RANGE

def custom_check(form):
    if not 'name' in form.errors and len(form.vars['name']) < 4
        form.errors['name'] = T("too short")

@action('form_example', method=['GET', 'POST'])
@action.uses('form_example.html', session)
def form_example():
    form = Form(db.thing, validation=custom_check)
    if form.accepted:
        redirect(URL('index'))
    return dict(form=form)