跳转到主要内容

plone.z3cform 是一个库,它允许在 Zope 和 CMF 中使用 z3c.form。

项目描述

plone.z3cform 是一个库,它允许在 Zope 中使用 z3c.form。它仅依赖于 Zope 和 z3c.form。

对于 Plone 集成,还有一个 plone.app.z3cform,可以安装以使默认表单模板更加 Plone 化。该软件包将其作为依赖项拉入。

除了纯互操作性支持外,还实现了 Zope 2 应用程序中一些有用的模式。

安装

要使用此软件包,只需将其作为您使用表单的软件包的依赖项安装,通过 setup.py 中的 install_requires 行。然后通过 ZCML 加载其配置。

<include package="plone.z3cform" />

独立表单

如果您使用的是 Zope 2.12 或更高版本,z3c.form 表单将几乎能够直接使用。然而,仍然存在两个障碍。

  • 标准的文件上传数据转换器在 Zope 2 中无法使用,因此使用文件小部件的字段(如 zope.schema.Bytes)将无法正确工作。

  • z3c.form 期望请求值被发布者解码为 Unicode 字符串,但在 Zope 2 中并未发生这种情况。

为了解决第一个问题,本包提供了一种覆盖标准数据转换适配器的方法(直接在 zope.schema.Bytes 类上注册,以覆盖默认值,该默认值是为更通用的 IBytes 接口注册的)。为了解决第二个问题,它对来自 z3c.formBaseFormGroupFormupdate() 方法应用了猴子补丁,以与 Zope 3 风格的发布者保持一致的方式进行必要的解码。

注意:如果您在自己的表单中覆盖了 update(),则必须调用基类版本或请求上的函数 plone.z3cform.z2.processInputs() 在使用请求中的任何值之前。例如

from plone.z3cform.z2 import processInputs
from z3c.form import form

...

class MyForm(form.Form):

    ...

    def update(self):
        processInputs(self.request)
        ...

除此之外,您可以使用标准的 z3c.form 习惯用法创建表单。例如

from zope.interface import Interface
from zope import schema
from z3c.form import form, button

class IMyFormSchema(Interface):
    field1 = schema.TextLine(title=u"A field")
    field2 = schema.Int(title=u"Another field")

class MyForm(form.Form):
    fields = field.Fields(IMyformSchema)

    @button.buttonAndHandler(u'Submit')
    def handleApply(self, action):
        data, errors = self.extractData()
        # do something

您可以使用标准的 <browser:page /> 指令将此注册为 ZCML 中的视图

<browser:page
    for="*"
    name="my-form"
    class=".forms.MyForm"
    permission="zope2.View"
    />

将使用默认模板来渲染表单。如果您想关联一个自定义模板,您应该通过设置 template 类变量来做到这一点,而不是使用 ZCML 指令的 template 属性

from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile

class MyForm(form.Form):
    fields = field.Fields(IMyformSchema)
    template = ViewPageTemplateFile('mytemplate.pt')

    @button.buttonAndHandler(u'Submit')
    def handleApply(self, action):
        data, errors = self.extractData()
        # do something

有关标准表单宏的更多详细信息,请参阅以下内容。

请注意,为了渲染任何标准小部件,您还需要确保请求被标记为 z3c.form.interfaces.IFormLayer,这与 z3c.form 中的常规做法一致。如果您在 Plone 中安装了 plone.app.z3cform,那么这已经为您完成,但在其他场景中,您需要以 Zope 浏览器层通常应用的方式完成此操作。

布局表单包装器

在 Zope 2.12 之前的版本中,z3c.form 实例不能直接注册为视图,因为它们不支持 Zope 2 安全性(通过获取机制)。虽然可能可以通过自定义混合来实现对此的支持,但首选的方法是使用包装视图,它将表单的渲染与页面布局分开。

即使在 Zope 的较新版本中,也有几个其他原因可能需要使用包装视图

  • 为了支持早期版本的 Zope 和 Zope 2.12+

  • 为了在多个视图或视图中重用相同的表单

  • 为了使用 z3c.form 的 IPageTemplate 适配器查找语义,为整体页面布局提供不同的默认模板或覆盖模板,同时保留(或独立定制)表单的默认布局。

当使用包装视图时,您不需要确保请求被标记为 IFormLayer,因为它在包装视图的渲染过程中自动应用。

创建包装视图的最简单方法是通过调用 wrap_form() 函数

from zope.interface import Interface
from zope import schema
from z3c.form import form, button

from plone.z3cform import layout

class IMyFormSchema(Interface):
    field1 = schema.TextLine(title=u"A field")
    field2 = schema.Int(title=u"Another field")

class MyForm(form.Form):
    fields = field.Fields(IMyformSchema)

    @button.buttonAndHandler(u'Submit')
    def handleApply(self, action):
        data, errors = self.extractData()
        # do something

MyFormView = layout.wrap_form(MyForm)

您现在可以将(生成的)MyFormView 类注册为浏览器视图

<browser:page
    for="*"
    name="my-form"
    class=".forms.MyFormView"
    permission="zope2.View"
    />

如果您想要更多控制,您可以手动定义包装类。您应该从默认版本派生以获得正确的语义。以下代码与上面的 wrap_form() 调用等效

class MyFormView(layout.FormWrapper):
    form = MyForm

然后您可以向类中添加其他方法,使用自定义页面模板等。

默认的 FormWrapper 类公开了一些方法和属性

  • 调用update()来准备请求,然后更新包装表单。

  • 调用render()来渲染包装视图。如果已设置模板(通常通过<browser:page />指令的template属性),它将在这里渲染。否则,通过适配视图(self)和请求到zope.pagetemplate.interfaces.IPageTemplate来查找默认页面模板,就像z3c.form对其视图所做的那样。本包提供了默认模板(并在plone.app.z3cform中进行定制以实现标准的Plone外观和感觉)。

  • form是一个类变量,指向上面设置的表单类。

  • form_instance是一个实例变量,一旦初始化视图,就会将其设置为当前表单实例。

当在包装视图中渲染表单时,表单实例会临时标记为plone.z3cform.interfaces.IWrappedForm(除非表单是子表单),以允许自定义适配器注册。具体来说,这是为了确保渲染的“独立”表单应用了全页模板,而在包装中渲染的表单则使用仅渲染表单元素的模板。

默认模板和宏

本包提供了一些标准模板。这些都是注册为从(view, request)IPageTemplate的适配器,正如z3c.form中的惯例。因此,可以使用适配器覆盖来定制这些默认值,例如针对特定的浏览器层。如果您想覆盖所有表单的标准渲染,这很有用。如果您只想为特定表单或包装视图提供自定义模板,可以直接在表单或视图中指定模板,如上所示。

  • templates/layout.pt是布局包装视图的默认模板。它使用CMFDefault的main_template并填充header槽。

  • templates/wrappedform.pt是包装表单的默认模板。它渲染来自@@ploneform-macros视图的titlelessform宏。

  • templates/subform.pt是子表单的默认模板。它使用来自@@ploneform-macros视图的宏来渲染标题、顶部/底部内容(逐字)以及子表单的字段和操作(但不渲染<form />标签本身)。

  • templates/form.pt是独立表单的默认模板。它使用由Five提供的宏context/@@standard_macros/page(通常委托给CMF的main_template)来渲染一个表单,其中表单标签是页面标题。

如前所述,此包还注册了一个视图@@ploneform-macros,其中包含一组宏,可用于构建具有标准布局、错误消息显示等的表单。它包含以下宏

  • form是一个全页表单,包括标签(输出为<h3 />)、描述以及titlelessform的所有元素。它定义了两个槽:title包含标签,description包含描述。

  • titlelessform 包含顶部的 status 表单、<form /> 元素以及 fieldsactions 宏的内容。它还定义了四个插槽:formtop 位于 <form> 标签内部;formbottom 位于 </form> 标签内部;fields 包含 fields 宏;actions 包含 actions 宏。

  • fields 遍历表单中的所有小部件,并使用 field 宏的内容来渲染每个小部件。它还定义了一个插槽,field,其中包含 field 宏。

  • field 渲染单个字段。它期望在 TAL 范围内定义变量 widget,指代一个 z3c.form 小部件实例。如果存在字段验证错误,它将输出错误信息,一个标签,一个标记来说明该字段是否为必填项,字段描述以及小部件本身(通常只是一个 <input /> 元素)。

  • actions 渲染表单上的所有操作(按钮)。这通常会导致一排 <input type="submit" ... /> 元素。

因此,要使用 titlelessform 宏,你可以在自定义表单模板中添加如下内容

<metal:use use-macro="context/@@ploneform-macros/titlelessform" />

请注意,上述所有模板都由 plone.app.z3cform 定制,以使用标准的 Plone 标记(但仍保留相同的宏),因此如果你使用该包来配置此包,你应该在那里寻找特定的 Plone 版本。

模板工厂

如果你想要提供一个 IPageTemplate 适配器来自定义用于包装视图、表单或子表单的默认页面模板,此包提供辅助类来创建适配器工厂。你应该使用这些类而不是 z3c.form.form.FormTemplateFactory 以及(可能)z3c.form.widget.WidgetTemplateFactory 来获取具有 Zope 2 语义的页面模板。这些工厂也了解 Chameleon,如果你安装了 five.pt

最常用的工厂是 plone.z3cform.templates.ZopeTwoFormTemplateFactory,它可以用来渲染包装视图或独立表单。

要渲染包装表单,你可以使用 plone.z3cform.templates.FormTemplateFactory,它更接近默认的 z3c.form 版本,但增加了对 Chameleon 的支持。

要渲染小部件,z3c.form 的默认 WidgetTemplateFactory 就足够了,但如果你需要任何原因的 Zope 2 语义,你可以使用 plone.z3cform.templates.ZopeTwoWidgetTemplateFactory

以下是一个示例,这是此包的默认注册项

import z3c.form.interfaces
import plone.z3cform.interfaces

from plone.z3cform.templates import ZopeTwoFormTemplateFactory
from plone.z3cform.templates import FormTemplateFactory

path = lambda p: os.path.join(os.path.dirname(plone.z3cform.__file__), 'templates', p)

layout_factory = ZopeTwoFormTemplateFactory(path('layout.pt'),
    form=plone.z3cform.interfaces.IFormWrapper
)

wrapped_form_factory = FormTemplateFactory(path('wrappedform.pt'),
        form=plone.z3cform.interfaces.IWrappedForm,
    )

# Default templates for the standalone form use case

standalone_form_factory = ZopeTwoFormTemplateFactory(path('form.pt'),
        form=z3c.form.interfaces.IForm
    )

subform_factory = FormTemplateFactory(path('subform.pt'),
        form=z3c.form.interfaces.ISubForm
    )

它们注册在 ZCML 中如下

<!-- Form templates for wrapped layout use case -->
<adapter factory=".templates.layout_factory" />
<adapter factory=".templates.wrapped_form_factory" />

<!-- Form templates for standalone form use case -->
<adapter factory=".templates.standalone_form_factory" />
<adapter factory=".templates.subform_factory" />

小部件遍历器

有时,能够在 小部件 上注册一个视图并能够遍历到该视图很有用,例如在背景 AJAX 请求期间。一个实现此功能的部件示例请参见 plone.formwidget.autocomplete

本包提供了一个用于该目的的++widget++命名空间遍历适配器。它可以在表单包装视图或表单本身(对于独立表单)中进行查找。因此,如果您有一个名为@@my-form的表单视图,其中有一个名为myfield的字段,您可以使用以下方式遍历到该视图的控件:

http://example.com/@@my-form/++widget++myfield

控件可能位于表单本身上,或在一个组(fieldset)中。如果它在多个组中存在,则将使用找到的第一个。

上面的示例将产生控件,但它可能不可发布。因此,通常会在控件本身上注册视图并使用该视图。在这种情况下,视图中的self.context是控件实例。此类视图可以使用以下方式查找:

http://example.com/@@my-form/++widget++myfield/@@some-view

关于安全性的注意事项

在Zope 2.12及以后版本中,安全机制了解__parent__指针。因此,上述示例中的@@some-view的遍历和授权对于标准控件将正常工作。在Zope的早期版本中,您需要将获取功能混合到您的控件中(这排除了使用任何标准z3c.form控件)。例如:

from Acquisition import Explicit
from z3c.form.widget import Widget

class MyWidget(Widget, Explicit):
    ...

不幸的是,在Zope 2.12中,除非您还混合了获取功能到在控件上注册的视图(如上述@@some-view),否则这将在遍历过程中引起一些问题。具体来说,当发布者尝试将视图包装在控件中时,您将收到一个错误。

为了与Zope 2.12+和早期版本保持兼容,您有两个选项

  • 确保将获取功能混合到控件上的视图

  • 确保控件继承自Explicit,但不提供IAcquirer接口。这会让发布者依赖于Zope 2.12中的__parent__指针。

要实现后者,您可以使用implementsOnly(),例如:

from zope.interface import implementsOnly
from Acquisition import Explicit
from z3c.form.widget import Widget

...

class MyWidget(Widget, Explicit):
    implementsOnly(IMyWidget) # or just IWdget from z3c.form
    ...

字段集和表单扩展器

plone.z3cform.fieldsets包提供了对z3c.form组(fieldset)和其他修改的支持,这些修改通过“扩展器”适配器进行。其想法是第三方组件可以修改表单中的字段以及它们的分组和排序方式。

这种支持依赖于一个混合类,该类本身是z3c.form的GroupForm的子类。

>>> from plone.z3cform.fieldsets import group, extensible

要使用它,您必须将其作为第一个基类混合到另一个表单中。

>>> from zope.annotation import IAttributeAnnotatable
>>> from z3c.form import form, field, tests, group
>>> from zope.interface import Interface
>>> from zope.interface import implementer
>>> from zope import schema

>>> class ITest(Interface):
...     title = schema.TextLine(title=u"Title")

>>> @implementer(ITest, IAttributeAnnotatable)
... class Test(object):
...     # avoid needing an acl_users for this test in Zope 2.10
...     __allow_access_to_unprotected_subobjects__ = 1
...
...     title = u""
...     def getPhysicalRoot(self): # needed for template to acquire REQUEST in Zope 2.10
...         return self

>>> class TestForm(extensible.ExtensibleForm, form.Form):
...     fields = field.Fields(ITest)

在这里,注意基类的顺序。还要注意我们直接在表单上使用一组普通字段。这被称为默认字段集。

此表单应按原样工作,即我们可以更新它。首先我们需要伪造一个请求

>>> from plone.z3cform.tests import TestRequest

>>> request = TestRequest()
>>> request.other = {}
>>> context = Test()
>>> context.REQUEST = request

>>> form = TestForm(context, request)
>>> form.update()
>>> list(form.fields.keys())
['title']

现在让我们注册一个适配器,添加两个新字段 - 一个作为默认字段集的第一个字段,另一个在新的组中。为此,我们只需要注册一个命名的多适配器。但是,我们可以使用便利的基类来简化对表单字段的操作。

>>> from plone.z3cform.fieldsets.interfaces import IFormExtender
>>> from zope.component import adapter
>>> from zope.component import provideAdapter

>>> class IExtraBehavior(Interface):
...     foo = schema.TextLine(title=u"Foo")
...     bar = schema.TextLine(title=u"Bar")
...     baz = schema.TextLine(title=u"Baz")
...     fub = schema.TextLine(title=u"Fub")
...     qux = schema.TextLine(title=u"Qux")

一个合理的实现是使用注解来存储这些数据。

>>> from zope.annotation import factory
>>> from zope.annotation.attribute import AttributeAnnotations
>>> from persistent import Persistent
>>> @implementer(IExtraBehavior)
... @adapter(Test)
... class ExtraBehavior(Persistent):
...
...
...     foo = u""
...     bar = u""
...     baz = u""
...     fub = u""
...     qux = u""

>>> ExtraBehavior = factory(ExtraBehavior)
>>> provideAdapter(ExtraBehavior)
>>> provideAdapter(AttributeAnnotations)

现在我们可以编写扩展器。基类为我们提供了一些辅助方法来添加、删除和移动字段。在这里,我们进行了一些不必要的工作,只是为了练习这些方法。

>>> @adapter(Test, TestRequest, TestForm) # context, request, form
... class ExtraBehaviorExtender(extensible.FormExtender):
...
...     def __init__(self, context, request, form):
...         self.context = context
...         self.request = request
...         self.form = form
...
...     def update(self):
...         # Add all fields from an interface
...         self.add(IExtraBehavior, prefix="extra")
...
...         # Remove the fub field
...         self.remove('fub', prefix="extra")
...
...         all_fields = field.Fields(IExtraBehavior, prefix="extra")
...
...         # Insert fub again, this time at the top
...         self.add(all_fields.select("fub", prefix="extra"), index=0)
...
...         # Move 'baz' above 'fub'
...         self.move('baz', before='fub', prefix='extra', relative_prefix='extra')
...
...         # Move 'foo' after 'bar' - here we specify prefix manually
...         self.move('foo', after='extra.bar', prefix='extra')
...
...         # Remove 'bar' and re-insert into a new group
...         self.remove('bar', prefix='extra')
...         self.add(all_fields.select('bar', prefix='extra'), group='Second')
...
...         # Move 'baz' after 'bar'. This means it also moves group.
...         self.move('extra.baz', after='extra.bar')
...
...         # Remove 'qux' and re-insert into 'Second' group,
...         # then move it before 'baz'
...         self.remove('qux', prefix='extra')
...         self.add(all_fields.select('qux', prefix='extra'), group='Second')
...         self.move('qux', before='baz', prefix='extra', relative_prefix='extra')

>>> provideAdapter(factory=ExtraBehaviorExtender, name=u"test.extender")

有了这些,让我们再次更新表单

>>> form = TestForm(context, request)
>>> form.update()

此时,我们应该有一组默认字段,它们代表适配器中设置的字段

>>> list(form.fields.keys())
['extra.fub', 'title', 'extra.foo']

我们还应该有一个由组工厂创建的组

>>> form.groups # doctest: +ELLIPSIS
(<plone.z3cform.fieldsets.group.Group object at ...>,)

请注意,创建的组是标准z3c.form组的子类型,它支持单独的标签和描述以及规范名称

>>> isinstance(form.groups[0], group.Group)
True

这个段落应包含适配器提供的分组字段

>>> list(form.groups[0].fields.keys())
['extra.bar', 'extra.qux', 'extra.baz']

CRUD(创建、读取、更新和删除)表单

此模块提供了一个抽象基类来创建CRUD表单。默认情况下,此类表单提供多个对象的表格视图,其属性可以就地编辑。

有关详细信息,请参阅ICrudForm接口。

>>> from plone.z3cform.crud import crud

设置

>>> from plone.z3cform.tests import setup_defaults
>>> setup_defaults()

一个简单的表单

首先,让我们定义一个接口和一个类来玩一玩

>>> from zope import interface, schema
>>> class IPerson(interface.Interface) :
...     name = schema.TextLine()
...     age = schema.Int()
>>> @interface.implementer(IPerson)
... class Person(object):
...     def __init__(self, name=None, age=None):
...         self.name, self.age = name, age
...     def __repr__(self):
...         return "<Person with name=%r, age=%r>" % (self.name, self.age)

为此测试,我们以我们的人的名字作为我们存储中的键

>>> storage = {'Peter': Person(u'Peter', 16),
...            'Martha': Person(u'Martha', 32)}

我们的简单表单看起来像这样

>>> class MyForm(crud.CrudForm):
...     update_schema = IPerson
...
...     def get_items(self):
...         return sorted(storage.items(), key=lambda x: x[1].name)
...
...     def add(self, data):
...         person = Person(**data)
...         storage[str(person.name)] = person
...         return person
...
...     def remove(self, id_item):
...         id, item = id_item
...         del storage[id]

这是我们渲染包含所有项目组合编辑添加表单所需的所有内容

>>> class FakeContext(object):
...     def absolute_url(self):
...         return 'http://nohost/context'
>>> fake_context = FakeContext()
>>> from plone.z3cform.tests import TestRequest
>>> print(MyForm(fake_context, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...Martha...Peter...</div>

使用我们的表单编辑条目

在我们开始编辑对象之前,让我们记录表单为我们触发的所有事件

>>> from zope.lifecycleevent.interfaces import IObjectModifiedEvent
>>> from plone.z3cform.tests import create_eventlog
>>> log = create_eventlog(IObjectModifiedEvent)
>>> request = TestRequest()
>>> request.form['crud-edit.Martha.widgets.select-empty-marker'] = u'1'
>>> request.form['crud-edit.Peter.widgets.select-empty-marker'] = u'1'
>>> request.form['crud-edit.Martha.widgets.name'] = u'Martha'
>>> request.form['crud-edit.Martha.widgets.age'] = u'55'
>>> request.form['crud-edit.Peter.widgets.name'] = u'Franz'
>>> request.form['crud-edit.Peter.widgets.age'] = u'16'
>>> request.form['crud-edit.form.buttons.edit'] = u'Apply changes'
>>> html = MyForm(fake_context, request)()
>>> "Successfully updated" in html
True

应该已经触发了两个修改事件

>>> event1, event2 = log.pop(), log.pop()
>>> storage['Peter'] in (event1.object, event2.object)
True
>>> storage['Martha'] in (event1.object, event2.object)
True
>>> log
[]

如果我们没有做任何更改,我们会收到一条消息说我们没有做任何更改

>>> html = MyForm(fake_context, request)()
>>> "No changes made" in html
True
>>> log
[]

现在,既然我们已将Peter更名为Franz,Franz也使用“Franz”作为存储中的id,不是很好吗?

>>> storage['Peter']
<Person with name='Franz', age=16>

我们可以覆盖CrudForm的before_update方法,以便在更改人的姓名时执行重命名

>>> class MyRenamingForm(MyForm):
...     def before_update(self, item, data):
...         if data['name'] != item.name:
...             del storage[item.name]
...             storage[str(data['name'])] = item

让我们将Martha更名为Maria。这将给她在我们的存储中另一个键

>>> request.form['crud-edit.Martha.widgets.name'] = u'Maria'
>>> html = MyRenamingForm(fake_context, request)()
>>> "Successfully updated" in html
True
>>> log.pop().object == storage['Maria']
True
>>> log
[]
>>> sorted(storage.keys())
['Maria', 'Peter']

接下来,我们将提交表单进行编辑,但我们将不做任何更改。相反,我们将选择一次。由于我们点击了“应用更改”按钮,所以这不应该做任何事情

>>> request.form['crud-edit.Maria.widgets.name'] = u'Maria'
>>> request.form['crud-edit.Maria.widgets.age'] = u'55'
>>> request.form['crud-edit.Maria.widgets.select'] = [u'selected']
>>> html = MyRenamingForm(fake_context, request)()
>>> "No changes" in html
True
>>> log
[]

如果我们有更改并且点击了复选框会怎样呢?

>>> request.form['crud-edit.Maria.widgets.age'] = u'50'
>>> html = MyRenamingForm(fake_context, request)()
>>> "Successfully updated" in html
True
>>> log.pop().object == storage['Maria']
True
>>> log
[]

如果我们省略了名称,我们将收到一个错误

>>> request.form['crud-edit.Maria.widgets.name'] = u''
>>> html = MyRenamingForm(fake_context, request)()
>>> "There were some errors" in html
True
>>> "Required input is missing" in html
True

我们期望在Maria的标题单元格中有一个错误消息

>>> checkbox_pos = html.index('crud-edit.Maria.widgets.select-empty-marker')
>>> "Required input is missing" in html[checkbox_pos:]
True

使用我们的表单删除条目

我们可以通过选择要删除的项目并点击“删除”按钮来删除一个条目

>>> request = TestRequest()
>>> request.form['crud-edit.Peter.widgets.select'] = ['selected']
>>> request.form['crud-edit.form.buttons.delete'] = u'Delete'
>>> html = MyForm(fake_context, request)()
>>> "Successfully deleted items" in html
True
>>> 'Franz' in html
False
>>> storage
{'Maria': <Person with name='Maria', age=50>}

使用我们的表单添加条目

>>> from zope.lifecycleevent.interfaces import IObjectCreatedEvent
>>> from plone.z3cform.tests import create_eventlog
>>> log = create_eventlog(IObjectCreatedEvent)
>>> request = TestRequest()
>>> request.form['crud-add.form.widgets.name'] = u'Daniel'
>>> request.form['crud-add.form.widgets.age'] = u'28'
>>> request.form['crud-add.form.buttons.add'] = u'Add'
>>> html = MyForm(fake_context, request)()
>>> "Item added successfully" in html
True

添加的项目应立即显示

>>> "Daniel" in html
True
>>> storage['Daniel']
<Person with name='Daniel', age=28>
>>> log.pop().object == storage['Daniel']
True
>>> log
[]

如果我们尝试两次添加“Daniel”,我们的当前添加表单实现将简单地覆盖数据

>>> save_daniel = storage['Daniel']
>>> html = MyForm(fake_context, request)()
>>> "Item added successfully" in html
True
>>> save_daniel is storage['Daniel']
False
>>> log.pop().object is storage['Daniel']
True

让我们实现一个防止这种情况的类

>>> class MyCarefulForm(MyForm):
...     def add(self, data):
...         name = data['name']
...         if name not in storage:
...             return super(MyCarefulForm, self).add(data)
...         else:
...             raise schema.ValidationError(
...                 u"There's already an item with the name '%s'" % name)
>>> save_daniel = storage['Daniel']
>>> html = MyCarefulForm(fake_context, request)()
>>> "Item added successfully" in html
False
>>> "There's already an item with the name 'Daniel'" in html
True
>>> save_daniel is storage['Daniel']
True
>>> len(log) == 0
True

在视图模式下渲染一些字段

我们可以在我们的表单中实现一个view_schema属性,然后它将被用于在表单的表中查看信息。假设我们只想在表中查看我们的人的姓名

>>> from z3c.form import field
>>> class MyAdvancedForm(MyForm):
...     update_schema = field.Fields(IPerson).select('age')
...     view_schema = field.Fields(IPerson).select('name')
...     add_schema = IPerson
>>> print(MyAdvancedForm(fake_context, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...Daniel...Maria...</div>

我们仍然可以编辑我们的人的年龄

>>> request = TestRequest()
>>> request.form['crud-edit.Maria.widgets.age'] = u'40'
>>> request.form['crud-edit.Daniel.widgets.age'] = u'35'
>>> request.form['crud-edit.form.buttons.edit'] = u'Apply Changes'
>>> html = MyAdvancedForm(fake_context, request)()
>>> "Successfully updated" in html
True
>>> storage['Maria'].age
40
>>> storage['Daniel'].age
35

我们仍然可以使用姓名和年龄添加一个人

>>> request = TestRequest()
>>> request.form['crud-add.form.widgets.name'] = u'Thomas'
>>> request.form['crud-add.form.widgets.age'] = u'28'
>>> request.form['crud-add.form.buttons.add'] = u'Add'
>>> html = MyAdvancedForm(fake_context, request)()
>>> "Item added successfully" in html
True
>>> len(storage)
3
>>> storage['Thomas']
<Person with name='Thomas', age=28>

我们的表单也可以包含到我们项目的链接

>>> class MyAdvancedLinkingForm(MyAdvancedForm):
...     def link(self, item, field):
...         if field == 'name':
...             return 'http://en.wikipedia.org/wiki/%s' % item.name
>>> print(MyAdvancedLinkingForm(fake_context, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Daniel"...
...<a href="http://en.wikipedia.org/wiki/Maria"...
...<a href="http://en.wikipedia.org/wiki/Thomas"...
</div>

如果我们想同时使用名称进行链接和编辑,我们只需将标题字段包含两次

>>> class MyAdvancedLinkingForm(MyAdvancedLinkingForm):
...     update_schema = IPerson
...     view_schema = field.Fields(IPerson).select('name')
...     add_schema = IPerson
>>> print(MyAdvancedLinkingForm(fake_context, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Thomas"...Thomas...</a>...
</div>

现在我们可以更改Thomas的姓名,并立即在维基百科链接中看到更改

>>> request = TestRequest()
>>> for name in 'Daniel', 'Maria', 'Thomas':
...     request.form['crud-edit.%s.widgets.name' % name] = storage[name].name
...     request.form['crud-edit.%s.widgets.age' % name] = storage[name].age
>>> request.form['crud-edit.Thomas.widgets.name'] = u'Dracula'
>>> request.form['crud-edit.form.buttons.edit'] = u'Apply Changes'
>>> print(MyAdvancedLinkingForm(fake_context, request)()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Dracula"...Dracula...</a>...
</div>
>>> storage['Thomas'].name = u'Thomas'

不渲染一部分

如果我们想使我们的表单只显示一部分,即只显示添加或编辑表单,我们的CrudForm可以实现editform_factoryaddform_factory以覆盖一个或两个表单。将其中一个设置为crud.NullForm将使它们消失

>>> class OnlyEditForm(MyForm):
...     addform_factory = crud.NullForm
>>> html = OnlyEditForm(fake_context, TestRequest())()
>>> 'Edit' in html, 'Add' in html
(True, False)
>>> class OnlyAddForm(MyForm):
...     editform_factory = crud.NullForm
>>> html = OnlyAddForm(fake_context, TestRequest())()
>>> 'Edit' in html, 'Add' in html
(False, True)

仅在视图模式下渲染,并定义自己的操作

有时你想展示一个项目的列表,可能仅在查看模式下,并让用户选择一个或多个项目来对它们执行某些操作。我们将在此提供一个实现此功能的简单示例。

我们可以简单地省略update_schema类属性(它默认为None)。此外,我们还需要用我们的自定义版本覆盖ediform_factory,该版本提供除“编辑”和“删除”之外的按钮

>>> from pprint import pprint
>>> from z3c.form import button
>>> class MyEditForm(crud.EditForm):
...     @button.buttonAndHandler(u'Capitalize', name='capitalize')
...     def handle_capitalize(self, action):
...         self.status = u"Please select items to capitalize first."
...         selected = self.selected_items()
...         if selected:
...             self.status = u"Capitalized items"
...             for id, item in selected:
...                 item.name = item.name.upper()
>>> class MyCustomForm(crud.CrudForm):
...     view_schema = IPerson
...     editform_factory = MyEditForm
...     addform_factory = crud.NullForm     # We don't want an add part.
...
...     def get_items(self):
...         return sorted(storage.items(), key=lambda x: x[1].name)
>>> request = TestRequest()
>>> html = MyCustomForm(fake_context, TestRequest())()
>>> "Delete" in html, "Apply changes" in html, "Capitalize" in html
(False, False, True)
>>> pprint(storage)
{'Daniel': <Person with name='Daniel', age=35>,
 'Maria': <Person with name='Maria', age=40>,
 'Thomas': <Person with name='Thomas', age=28>}
>>> request.form['crud-edit.Thomas.widgets.select'] = ['selected']
>>> request.form['crud-edit.form.buttons.capitalize'] = u'Capitalize'
>>> html = MyCustomForm(fake_context, request)()
>>> "Capitalized items" in html
True
>>> pprint(storage)
{'Daniel': <Person with name='Daniel', age=35>,
 'Maria': <Person with name='Maria', age=40>,
 'Thomas': <Person with name='THOMAS', age=28>}

我们不能使用其他任何按钮

>>> del request.form['crud-edit.form.buttons.capitalize']
>>> request.form['crud-edit.form.buttons.delete'] = u'Delete'
>>> html = MyCustomForm(fake_context, request)()
>>> "Successfully deleted items" in html
False
>>> 'Thomas' in storage
True

自定义子表单

EditForm类允许你指定一个editsubform_factory,这是一个从EditSubForm继承的类。这允许你覆盖crud-row.pt页面模板并自定义字段的样式。

>>> import zope.schema
>>> class MyCustomEditSubForm(crud.EditSubForm):
...
...     def _select_field(self):
...         """I want to customize the field that it comes with..."""
...         select_field = field.Field(
...         zope.schema.TextLine(__name__='select',
...                              required=False,
...                              title=u'select'))
...         return select_field
>>> class MyCustomEditForm(MyEditForm):
...     editsubform_factory = MyCustomEditSubForm
>>> class MyCustomFormWithCustomSubForm(MyCustomForm):
...     editform_factory = MyCustomEditForm
>>> request = TestRequest()
>>> html = MyCustomFormWithCustomSubForm(fake_context, TestRequest())()
仍然使用之前相同的表单
>>> "Delete" in html, "Apply changes" in html, "Capitalize" in html
(False, False, True)
只是更改了用于选择的小部件...
>>> 'type="checkbox"' in html
False

使用批处理

CrudForm 基类支持批量处理。当将 batch_size 属性设置为大于 0 的值时,我们将只在每一页显示这么多项目。

>>> class MyBatchingForm(MyForm):
...     batch_size = 2
>>> request = TestRequest()
>>> html = MyBatchingForm(fake_context, request)()
>>> "Daniel" in html, "Maria" in html
(True, True)
>>> "THOMAS" in html
False

确保下一页的批量链接可用

>>> 'crud-edit.form.page=1' in html
True

显示下一页并检查内容

>>> request = TestRequest(QUERY_STRING='crud-edit.form.page=1')
>>> request.form['crud-edit.form.page'] = '1'
>>> html = MyBatchingForm(fake_context, request)()
>>> "Daniel" in html, "Maria" in html
(False, False)
>>> "THOMAS" in html
True

表单操作还包括批量页面信息,以便正确处理子表单集合

>>> print(html) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<BLANKLINE>
...
<form action="http://nohost/context?crud-edit.form.page=1"
          method="post"...

让我们在第二页更改 Thomas 的年龄

>>> request.form['crud-edit.Thomas.widgets.name'] = u'Thomas'
>>> request.form['crud-edit.Thomas.widgets.age'] = '911'
>>> request.form['crud-edit.form.buttons.edit'] = u'Apply changes'
>>> html = MyBatchingForm(fake_context, request)()
>>> "Successfully updated" in html
True
>>> "911" in html
True

变更日志

2.0.3 (2023-12-14)

错误修复

  • 用简单的类替换已弃用的 cgi.FieldStorage 类。这仅用于将 ZPublisherFileUpload 转换为 zope.publisher 的一个。[maurits] (#1)

2.0.2 (2023-10-07)

内部

  • 更新配置文件。[plone devs] (cfffba8c)

2.0.1 (2023-03-21)

内部

  • 更新配置文件。[plone devs] (a533099d)

2.0.0 (2022-11-23)

错误修复

  • Plone 6.0.0 的最终发布。[maurits] (#600)

2.0.0b1 (2022-06-23)

错误修复

  • 修复到 z3c.form.widget.MultiWidget 小部件的遍历。[petschki] (#22)

2.0.0a3 (2022-05-24)

错误修复

  • 清理 crud-table 标记。[petschki] (#21)

2.0.0a2 (2022-03-23)

新功能

  • 与 z3c.form >= 4.x 兼容。[petschki] (#20)

2.0.0a1 (2021-04-21)

重大变更

1.1.3 (2020-06-16)

错误修复

  • 从 Zope4 复制 HTTPRequest._decode,因为 Zope5 中将不再使用它。[#13]

1.1.2 (2020-04-21)

错误修复

  • crud-table 中的按钮应该是列表项。[erral] (#9)

1.1.1 (2019-10-12)

错误修复

  • 修复分页 crud-forms 中的编辑/删除。[fRiSi]

1.1.0 (2019-04-09)

新功能

  • 将许可证更改为 GPLv2。[petschki]

  • z2.processInputs 中启用 :record[s] 字段。[petschki]

  • 外观调整。[gforcada]

1.0.0 (2018-11-04)

新功能

  • 支持 Python 3 [pbauer, davilima6, ale-rt, jensens]

错误修复

  • 测试清理。[jensens]

  • 由于 ZCML 浏览器指令警告,将文件夹模板重命名为 pagetemplates。[jensens]

  • 在方法定义中删除元组参数解包(修复 #14)[ale-rt]

  • 提供最新的 bootstrap.py [ale-rt]

  • 使用适配器和实现器装饰器 [ale-rt]

0.9.1 (2017-09-03)

错误修复

  • 在 BrowserViews 上删除已弃用的 __of__ 调用 [MrTango]

0.9.0 (2016-05-25)

  • 启用组(即 fieldsets)的可排序性。[jensens]

修复

0.8.1 (2015-01-22)

  • 功能:如果给定,则从父表单获取组类。如果没有给定,则使用默认类。[jensens]

  • 添加乌克兰语翻译 [kroman0]

  • 添加意大利语翻译 [giacomos]

0.8.0 (2012-08-30)

  • 删除对 Zope < 2.12 的向后兼容代码 [davisagli]

  • 使用 plone.testing 为功能测试层提供支持。[hannosch]

  • 使用 zope.browserpage 中的 ViewPageTemplateFile。[hannosch]

  • 使用视图提供的表单操作 URL,而不是在模板中实现它,作为对请求的 getURL 方法的调用。[malthe]

  • 使用 plone.batching 替代 z3c.batching 进行批量处理。[tom_gross]

0.7.8 - 2011-09-24

  • 如果视图中没有标签,则不显示 h1 元素。[thomasdesvenain]

  • 添加中文翻译。[jianaijun]

0.7.7 - 2011-06-30

  • 避免在更新后已发生重定向的情况下渲染包装表单。[davisagli]

  • 从 CRUD 表格 TBODY 元素内部删除 <a name=””/> 元素(否则未使用,并且在该 HTML 内容模型的位置中是非法的)。[mj]

0.7.6 - 2011-05-17

  • 添加查找列表中具有非整数字段名的控件的功能。这通常不应该发生,理想情况下,如果 DataGridField 失去其 'AA' 和 'TT' 行,则应该将其删除。[lentinj]

0.7.5 - 2011-05-03

  • 修复 Zope 2.10 上的遍历测试,以处理 TraversalError 而不是 LocationError。[elro]

  • 修复 traversal.py 语法以兼容 python2.4。

  • 撤销 [120798],因为它在 Zope2.10 / Plone 3.3 上破坏。我们可以在 0.8.x 中处理 Zope 2.14。[elro]

0.7.4 - 2011-05-03

  • 在字段宏中定义 'hidden'。[elro]

  • 如果 ++widget++ 路径以 "form.widgets." 开头,则忽略它。 [lentinj]

  • 重新设计遍历器以处理列表和子表单 [lentinj]

  • 只有当它们存在时才搜索一个组的控件。 collective.z3cform.wizard 不会为当前之外的其他页面/组创建控件 [lentinj, elro]

  • 处理与 Zope 2.14 的向前兼容性。

  • 添加了巴西葡萄牙语翻译。 [davilima6]

0.7.3 - 2011-03-02

  • 在 ++widget++ 遍历器中更干净地处理错误的字段名。 [elro]

0.7.2 - 2011-02-17

  • 确保 CRUD 添加表单不使用独立模板。 [davisagli]

0.7.1 - 2011-01-18

  • 将 zope.app.testing 添加到测试依赖项中,以便它在 Zope 2.13 下继续工作。 [esteele]

0.7.0 - 2010-08-04

  • 添加一个标记接口,该接口可用于由控件在带有 ++widgets++ 命名空间的遍历过程中设置的任何安全检查 [dukebody]

  • 修复了不在默认字段集中的字段排序。感谢 Thomas Buchberger 提供补丁。 [optilude]

  • 添加了挪威翻译。 [regebro]

0.6.0 - 2010-04-20

  • 在 CRUD 表格中,修复了奇偶标签的颠倒问题。 [limi]

  • titlelessform 宏添加了槽位。有关详细信息,请参阅 README.txt。 [optilude, davisagli]

  • 取消区分包装和未包装的子表单。子表单始终由包含它的表单包装,并可以使用 Zope 3 页面模板。 [davisagli]

  • 修复了 Plone 3 中的测试。 [davisagli]

  • 修复了 Plone 4 中的测试。 [optilude]

  • 通过 IWrappedForm 标记接口使区分包装和未包装的表单成为可能。 [optilude]

  • 在 Plone 4 中使使用 z3c.form 表单而不使用 FormWrapper 成为可能。 [optilude]

0.5.10 - 2010-02-01

  • z3c.form.form.AddForm 在其 render 方法中执行重定向。因此,我们必须渲染表单才能查看是否有重定向。在重定向的情况下,我们根本不渲染布局。这个版本删除了 FormWrapper 的 contents 方法,它现在是在 FormWrapper.update 期间设置的属性。此更改修复了状态消息未显示,因为它被从未显示的渲染表单消耗。 [vincentfretin]

0.5.9 - 2010-01-08

  • 修复了 ++widget++ 命名空间的安全问题 [optilude]

0.5.8 - 2009-11-24

  • 如果有重定向,则不进行渲染,使用 update/render 模式。有关如何修改代码的示例,请参阅 http://dev.plone.org/plone/ticket/10022,特别是如果您使用了 FormWrapper 与 ViewletBase。 [vincentfretin]

0.5.7 - 2009-11-17

  • 修复了愚蠢的 doctests,以防止在 Python 2.6 / Zope 2.12 中中断 [optilude]

0.5.6 - 2009-09-25

  • 在 macros.pt 中添加了 title_required msgid,使其与 plone.app.z3cform 相同,因为 plone.app.z3cform 中的 macros.pt 使用 plone.z3cform 翻译。添加了法语翻译,并修复了德语和荷兰语的 label_required 和 title_required 消息的翻译。 [vincentfretin]

0.5.5 - 2009-07-26

  • 从 configure.zcml 中删除了显式的 <includeOverrides /> 调用。这会导致在包含覆盖项时 ZCML 加载时出现竞争条件类型错误。 [optilude]

0.5.4 - 2009-04-17

  • 为 z3c.form 1.9.0 中的 z3c.form 的 ChoiceTerms 添加了猴子补丁以修复错误。 [optilude]

  • 修复了 SingleCheckBoxWidget 中的明显错误和可疑的命名。 [optilude]

  • 如果可用,使用基于 chameleon 的页面模板从 five.pt。 [davisagli]

  • 复制了 z3c.form trunk 中的基本文本行控件,以供使用,直到它发布。 [davisagli]

0.5.3 - 2008-12-09

  • 为批量添加翻译标记,更新翻译文件。 [thefunny42]

  • 处理 z3c.form > 1.9.0 中 widget 提取方法的签名更改。 [davisagli]

  • 将通配符支持添加到字段集 "move" 实用函数的 "before" 和 "after" 参数。 [davisagli]

  • 修复了与 Zope 2.12 兼容性的问题。 [davisagli]

  • 如果您没有定义 update_schema,则不显示“应用更改”按钮。 [thefunny42]

  • 在 "layout.pt" 和 "subform.pt" 模板中声明 xmlnamespace

  • 为 EditForm 添加了 editsubform_factory 支持,因此您现在可以覆盖子表单的默认行为。

  • 在crud-table.pt中对表格的CSS进行了修改,将其设置为“listing”,从而使表格现在看起来像Plone表格。

  • 将翻译文件复制到英文文件夹中,这样如果您的浏览器协商到en,nl,您将得到英文翻译而不是荷兰文(如预期)。[thefunny42]

  • 在CRUD表单中手动更新显示小部件后发送IAfterWidgetUpdateEvent。[thefunny42]

0.5.2 - 2008-08-28

  • 添加一个命名空间遍历适配器,允许遍历到小部件。这对于AJAX调用等非常有用。

0.5.1 - 2008-08-21

  • plone.z3cform.crud CrudForm添加批处理。

  • 将布局模板作为IPageTemplate适配器查找。这意味着Plone可以为未选择此功能的表单提供“Ploneish”默认模板,而无需这些表单直接依赖于Plone。

  • 默认使用无标题表单模板,因为布局模板将提供标题。

  • plone.z3cform.layout中,允许按表单实例定义标签,而不仅仅是按表单类。

0.5.0 - 2008-07-30

  • 不再依赖于<3.5的zope.component。

0.4 - 2008-07-25

  • 依赖zope.component<3.5以避免TypeError("Missing 'provides' attribute")错误。

  • 允许ICrudForm.add抛出ValidationError,这允许显示友好的错误消息。

  • 使默认布局模板与CMFDefault兼容。

0.3 - 2008-07-24

  • 将Plone布局包装器移动到plone.app.z3cform.layout。如果您之前使用plone.z3cform.base.FormWrapper来获取Plone布局,那么现在您必须使用plone.app.z3cform.layout.FormWrapper。 (此外,确保在此情况下包含plone.app.z3cform的ZCML。)

  • 将Plone特定的子包移动到plone.app.z3cform。这些是

    • wysywig:Kupu/Plone集成

    • queryselect:使用z3c.formwidget.query与Archetypes

    清理测试代码和开发buildout.cfg,不再拉入Plone。[nouri]

  • 根据ZPL 2.1重新许可,并移动到Zope存储库。[nouri]

  • 添加德语翻译。[saily]

0.2 - 2008-06-20

  • 修复了与zope.i18n >= 3.4一起使用NumberDataConverter的问题,因为之前的测试设置是部分的,并且没有注册z3c.form的所有适配器(其中一些依赖于zope >= 3.4)[gotcha, jfroche]

  • 更多测试[gotcha, jfroche]

0.1 - 2008-05-21

  • 提供并注册默认表单和子表单模板。这些允许表单使用本包中提供的样式,而无需声明form = ViewPageTemplateFile('form.pt')

    这不会阻碍您像往常一样使用自己的form属性进行覆盖。您还可以注册更专业的IPageTemplate供您的表单使用。

  • 添加自定义FileUploadDataConverter,在将其传递给原始实现之前将Zope 2 FileUpload对象转换为Zope 3对象。同时添加对不同enctypes的支持。[skatja, nouri]

  • 添加了Archetypes引用选择小部件(queryselect)[malthe]

  • 将z3c.form和从Singing & Dancing中的一些有用功能转移到这个新包中。[nouri]

项目详细信息


下载文件

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

源代码分发

plone.z3cform-2.0.3.tar.gz (90.1 kB 查看散列)

上传时间 源代码

构建分发

plone.z3cform-2.0.3-py3-none-any.whl (89.9 kB 查看散列)

上传时间 Python 3

支持