跳转到主要内容

简约表单库。

项目描述

repoze.formapi 提供了一个表单库,该库与HTML表单集成,而不是将它们抽象化。

它提供了一个小框架,帮助您完成整个渲染表单、提供默认值、验证和执行表单操作的过程。

表单字段使用Python基本类型定义,这些类型将嵌套数据结构映射为整数、字符串、浮点数或这些类型的元组的端点。将它们与更复杂对象桥接是应用程序的责任。

简介

这个库帮助您解析、验证反序列化表单输入以及执行表单操作。

起点是定义描述您数据的数据结构。例如,一个动作字符串和一组数字(适用于允许用户选择一些项目并执行操作的表单)

>>> fields = {
...     'action': unicode,
...     'items': [int]
... }

让我们应用以下输入序列

>>> params = [
...      ('action', 'submit'),
...      ('items', 1),
...      ('items', 2),
...      ('security-token', '...')
... ]

请注意,'security-token' 是一个提供的参数示例,但未在字段中定义。这不是一个错误。

现在,要将上述参数解析到由我们的字段定义指定的数据结构中,我们使用 parse 函数(请注意,在本文的其余部分,我们假设已导入符号)。

>>> from repoze.formapi import parse
>>> data, errors = parse(params, fields)

在处理此调用的逻辑中,您通常会检查 errors 值是否为真(表示有错误)或假(表示没有错误)。

>>> bool(errors)
False

让我们看看数据

>>> data
{'action': u'submit', 'items': [1, 2]}

类型

在前一个例子中,我们使用了Python的内置数据类型:intunicode

然而,您可以使用任何可调用的数据类型。请注意,如果可调用引发了一个KeyError,则它将被简单地忽略。

让我们尝试一个例子

>>> data, errors = parse((('foo', 'bar'), ('baz', 'boo'), ('baz', 'qux')), {
...     'foo': str.upper,
...     'baz': {'boo': 'yes'}.__getitem__,
... })

我们可以在数据中期望什么?

参数'foo'将被转换为大写,第一个'baz'解析为'yes',而其他引发了一个KeyError并被忽略。

>>> data
{'foo': 'BAR', 'baz': 'yes'}

如果您想忽略一个参数,只需引发一个KeyError即可。

表单

该库提供了一个处理表单的抽象。

要创建一个表单,您需要继承Form类,并在fields属性中定义表单字段定义。

>>> class TapeForm(Form):
...     """Casette tape form."""
...
...     fields = {
...         'artist': unicode,
...         'title': unicode,
...         'asin': str,
...         'year': int,
...         'playtime': float
...     }

如果没有起始值或default值,则可以使用不带参数的实例化表单

>>> form = TapeForm()

表单数据可通过data属性获取。由于我们没有传递请求,因此没有可用的数据。

>>> form.data['artist'] is None
True

这并不很有趣。让我们传递一个输入参数。

>>> params = (('title', u'Motorcity Detroit USA Live'),)
>>> form = TapeForm(params=params)

这将设置'title'字段。

输入是一个有效的Unicode字符串,并且没有验证错误。

>>> form.validate()
True

让我们确认数据是否可用。

>>> form.data['title']
u'Motorcity Detroit USA Live'

该库还支持传递一个具有params属性的request参数。

>>> request = Request(params=params)

来自WebOb(一个提供面向对象接口的HTTP协议的流行包)的Request类是兼容的。

>>> form = TapeForm(request=request)

我们通常希望使用默认值初始化表单。为此,我们传递一个字典对象。

>>> data = {
...    'artist': u'Bachman-Turner Overdrive',
...    'title': u'Four Wheel Drive',
...    'asin': 'B000001FL8',
...    'year': 1975,
...    'playtime': 33.53
... }
>>> form = TapeForm(data)

值在表单数据对象中可用。

>>> form.data['title']
u'Four Wheel Drive'

然而,如果我们从之前的例子中传递请求,我们会看到请求中的值在查询传递的字典对象之前被使用。

>>> form = TapeForm(data, request=request)
>>> form.data['title']
u'Motorcity Detroit USA Live'

此时提供的data字典保持不变

>>> data['title']
u'Four Wheel Drive'

我们需要调用save方法以将更改提交到提供的字典。

>>> form.data.save()
>>> data['title']
u'Motorcity Detroit USA Live'

额外的验证

对于更复杂的需求,可以创建验证方法。这些额外的验证器可以使用validator装饰器连接。

>>> class CDForm(Form):
...     fields = {
...         'artist': unicode,
...         'title': unicode,
...         'asin': str,
...         'year': int,
...         'genre': str,
...         'playtime': float}
...
...     @validator
...     def check_genre(self):
...         if self.data['genre'] != 'rock':
...             yield 'Genre is invalid'

验证器可以查看所有可用的数据。这使得创建需要检查多个字段的验证器变得容易。

>>> form = CDForm()
>>> form.validate()
False

errors属性包含我们的错误消息。

>>> form.errors[0]
'Genre is invalid'

错误也可以分配给特定字段。为此,验证器可以为自己注册一个特定字段。

>>> class CDForm(Form):
...
...     fields = {
...         'artist': unicode,
...         'title': unicode,
...         'asin': str,
...         'year': int,
...         'genre': str,
...         'playtime': float}
...
...     @validator('genre')
...     def check_genre(self):
...         if self.data['genre'] != 'rock':
...             yield 'Genre is invalid'

当此表单经过验证时,它将为特定字段提供错误。

>>> form = CDForm()
>>> form.validate()
False
>>> 'genre' in form.errors
True
>>> form.errors['genre'][0]
'Genre is invalid'

表单上下文

我们可以将表单的上下文设置为一个对象。

>>> class Tape:
...    title = u'Motorcity Detroit USA Live'
...    asin = 'B000001FL8'
...    year = 1975
>>> tape = Tape()

表单数据将从上下文中提取默认值。

>>> form = TapeForm(context=tape)
>>> form.data['title']
u'Motorcity Detroit USA Live'

请求参数优先于上下文。在下面的示例中,我们使用平凡的输入提交表单。

>>> request = Request(params=(('asin', u''), ('year', u'')))
>>> form = TapeForm(context=tape, request=request)

此表单输入是有效的;尽管year是一个整数字段,但平凡的输入是有效的,并将被分配一个值为None

>>> form.validate()
True

asin输入被转换为字符串(从Unicode)。

>>> form.data['asin']
''

year输入是平凡的。它不是一个必填字段,因此值为None(被视为非输入)。

>>> form.data['year'] is None
True

必填字段

我们使用required方法标记字段为必填。

让我们继续上面的例子。如果我们使字段必填,则输入不再验证。

>>> TapeForm.fields['year'] = required(int, u"Required field" )
>>> TapeForm.fields['asin'] = required(str)

表单输入不再有效。

>>> form = TapeForm(context=tape, request=request)
>>> form.validate()
False
>>> form.data['year'] is None
True

错误消息也是可用的

>>> form.errors['year'][0]
'Required field'

现在让我们尝试一个有效的输入

>>> request = Request(params=(('asin', u'B000001FL8'), ('year', u'1978')))
>>> form = TapeForm(context=tape, request=request)

我们预计所需的字段都将被转换并正确输入。

>>> form.validate()
True
>>> form.data['asin']
'B000001FL8'
>>> form.data['year']
1978

表单提交

如果没有设置表单前缀,请求将默认应用。然而,大多数应用程序将想要设置表单前缀并要求显式表单提交。

如果前缀作为参数提交,表单将提交“默认动作”。

>>> request = Request(params=(
...    ('tape_form', ''),
...    ('title', u'Motorcity Detroit USA Live')))
>>> form = TapeForm(request=request, prefix='tape_form')
>>> form.data['title']
u'Motorcity Detroit USA Live'

如预期的那样,如果我们提交带有不同前缀的表单,请求将不会应用。

>>> form = TapeForm(request=request, prefix='other_form')
>>> form.data['title'] is None
True

我们还可以在表单类本身上定义表单动作。

>>> class TapeAddForm(TapeForm):
...     """An add-form for a casette tape."""
...
...     @action
...     def handle_add(self, data):
...         print "add"
...
...     @action("add_and_edit")
...     def handle_add_and_edit(self, data):
...         print "add_and_edit"

第一个动作是“默认动作”;如果我们提交之前设置的请求,这个动作将被读取以提交。

>>> form = TapeAddForm(request=request, prefix='tape_form')
>>> form.actions
[<Action name="" submitted="True">,
 <Action name="add_and_edit" submitted="False">]

提交的动作在 action 参数中可用。

>>> form.action
<Action name="" submitted="True">

要调用提交动作的表单处理程序,我们调用表单的调用方法。

>>> form()
add

要调用命名表单动作,请求中必须有一个参数,它是前缀和表单动作名称的拼接。接受的分隔字符是‘。’(点)、‘_’(下划线)和‘-’(连字符)。

>>> request = Request(params=(
...    ('tape_form-add_and_edit', ''),
...    ('title', u'Motorcity Detroit USA Live'),))
>>> form = TapeAddForm(request=request, prefix='tape_form')
>>> form.actions
[<Action name="" submitted="False">,
 <Action name="add_and_edit" submitted="True">]
>>> form()
add_and_edit

数据代理

我们可以通过使用代理对象将上下文对象绑定到数据对象。这种技术可用于创建编辑或添加表单。

为了说明这一点,让我们定义一个内容对象。为了简单起见,我们将硬编码默认值。

>>> class Tape(object):
...    artist = u'Bachman-Turner Overdrive'
...    title = u'Four Wheel Drive'
...    asin = 'B000001FL8'
...    year = 1975
...    playtime = 33.53

现在我们可以为这个类的实例创建一个数据代理。

>>> tape = Tape()
>>> proxy = Proxy(tape)

在不进行进一步干预的情况下,这个数据对象充当内容对象上读取和写入属性的代理。

>>> proxy.title
u'Four Wheel Drive'
>>> proxy.title = u'Motorcity Detroit USA Live'
>>> tape.title
u'Motorcity Detroit USA Live'

如果我们想对这个过程有更多的控制,我们可以子类化并定义描述符。

以下示例定义了 title 属性的定制行为;值被转换为大写。

>>> class TapeProxy(Proxy):
...     def get_title(self):
...         return self.title
...
...     def set_title(self, value):
...         self.title = value.upper()
...
...     title = property(get_title, set_title)
>>> proxy = TapeProxy(tape)

如果我们读取和写入这个代理对象的 title 属性,将使用自定义的获取器和设置器函数。

>>> proxy.title = u'Motorcity Detroit USA Live'

如代理所预期的那样,更改实际上是对底层内容对象进行的。

>>> tape.title
u'MOTORCITY DETROIT USA LIVE'

保存表单数据

在实例化表单时,您可以使用代理对象而不是 data。这绑定数据对象到代理,但也允许我们在代理对象上保存表单数据。

>>> form = TapeForm(proxy, request=request)
>>> form.data['title'] = u'Four Wheel Drive'

赋值的行为逻辑。

>>> form.data['title']
u'Four Wheel Drive'

然而,如果我们调用 save 动作,更改将对代理对象生效。

>>> form.data.save()
>>> tape.title
u'FOUR WHEEL DRIVE'

变更日志

0.6.1 (2012-12-10)

  • 增加了对字段定义可调用功能的支持,以引发 KeyError 异常,从而忽略输入。

0.6 (2012-12-10)

  • marshalling 模块重命名为 parser

  • 修复了整数即使不是必需的也会导致错误的问题。

0.5.4 (2012-12-04)

  • 向marshaller添加了 keys 方法,以便对象可以作为映射使用。

0.5.3 (2012-11-23)

  • 修复了当相应数据有多个条目时,单个条目字段定义会错误地序列化的问题。

0.5.2 (2012-11-19)

  • 修复了 required 问题,其中marshaller不会正确地将值转换为提供的值类型。

0.5.1 (2012-11-16)

  • 从序列化代码中移除了类型检查。

    if not error and not isinstance(value, data_type):
        error = True

    此更改是为了支持“架构类型”等。

    import re
    
    match_email = re.compile(
        r"^(\w&.%#$&'\*+-/=?^_`{}|~]+!)*[\w&.%#$&'"
        r"\*+-/=?^_`{}|~]+@(([0-9a-z]([0-9a-z-]*[0-9a-z])"
        r"?\.)+[a-z]{2,6}|([0-9]{1,3}\.){3}[0-9]{1,3})$",
        re.IGNORECASE).match
    
    class email(unicode):
        def __new__(cls, string):
          if match_email(string):
              return string
    
          raise ValueError(u"Not a valid e-mail address.")

0.5.0 (2010-05-28)

  • 修复了 Errors 类,以使用更隐晦的 __getattr__ 覆盖;这修复了查找 __class__ 属性时失败的问题。[malthe]

  • 为字段类型添加了 required 装饰器;当我们使用此装饰器包装类型时,未验证的输入(即使是空字符串这样的简单输入)都会以原值给出。与默认的(可选的)字段进行比较,其中空字符串会被解释为非输入(除非它是字符串类型或接受空字符串的另一种类型)。[malthe]

  • 为 Error 类添加了 __contains__/has_key 支持。[wichert]

0.4.2 (2009-09-14)

  • 添加了 any 内置回退。[malthe]

0.4.1 (2009-07-31)

  • 添加了 defaultdict 模拟类。[malthe]

0.4 (2009-07-29)

  • 如果有选定的操作,它将在表单实例的 action 属性中可用。[malthe]

  • 即使表单输入未通过验证,输入也会作为原值出现在 data 字典中。[malthe, rnix]

  • 现在可以直接将请求参数作为 params 关键字参数提供。[malthe]

  • 为错误字典添加了 get 方法。[malthe]

  • 现在可以比较错误,以查看它们是否相同。这使得在没有 doctest 的情况下进行测试变得更容易。[malthe]

  • 错误对象的真值测试现在仅检查自身或其子错误中的消息。这避免了在访问不存在的键后错误对象说它是真的(从而创建一个新的错误对象)。[jvloothuis]

  • 将错误的基础类从 unicode 更改为 object。这使得它在打印等操作时表现得像预期的那样。[jvloothuis]

0.3.2 (2009-03-09)

  • 移除了对 'zope.interface' 的未使用依赖项。[jvloothuis]

0.3.1 (2008-10-27)

  • 使 prefix 能够作为类属性设置。[malthe]

  • 修复了操作参数不会设置的问题。[malthe]

0.3 (2008-10-27)

  • 当请求中没有参数可用时,不再应用序列化的数据。[malthe]

  • errors-对象子类从 defaultdict 更改为 unicode,并实现了自定义的字符串表示函数,该函数将错误消息连接起来。[malthe]

0.2 (2008-10-19)

  • 创建/改进了自定义验证的能力。[jvloothuis]

  • 添加了对表单操作的支援。[malthe]

  • 重新设计了序列化代码以处理动态字典键。[malthe]

  • 实现了表单数据对象,该对象简化了从应用请求到提交对上下文对象的更改的表单处理流程。[malthe]

  • 将转换器集成到 form 模块中。[seletz]

  • 添加了 ValidationErrors 对象,如果发生验证错误则评估为 True。[seletz]

  • 添加了 unicode 转换器。[seletz]

  • 添加了 converter 模块以支持将请求参数转换为表单。[seletz]

  • 添加了逻辑以允许从模板中注册和获取模板 API 组件。[malthe]

  • 将皮肤模板视图改为类,并添加了最小的接口 ISkinTemplate 以访问模板路径 [seletz]

  • 修复了未正确拆卸测试的bug [seletz]

  • 修复了在检查模板存在时 INewRequest 事件处理器会调用模板的bug [seletz]

0.1 (2008-09-25)

  • 初始发布 [malthe]

  • 添加了对动态注册模板的支持,如果它们被添加到已注册的模板目录中 [seletz]

项目详细信息


下载文件

下载适用于您平台的文件。如果您不确定该选择哪个,请了解有关安装包的更多信息。

源代码分发

repoze.formapi-0.6.1.tar.gz (37.3 kB 查看哈希值)

源代码

支持