Dexterity开发手册:第六章高级配置

Dexterity开发手册:第六章高级配置
1.缺省值
[align=left]在添加表单中为字段指定缺省值[/align] 我们经常需要为一个字段指定缺省值。继续使用我们的 conference包为例,给start和end指定缺省值,我们做这个通过在program.py文件中添加如下内容:
@form.default_value(field=IProgram['start']) def startDefaultValue(data): # To get hold of the folder, do: context = data.context return datetime.datetime.today() + datetime.timedelta(7) @form.default_value(field=IProgram['end']) def endDefaultValue(data): # To get hold of the folder, do: context = data.context return datetime.datetime.today() + datetime.timedelta(10)由于要使用datetime模块,我们要在文件顶部导入这个模块。注意,这个函数怎样为一个指定的字段提供缺省值。这个装扮申明符将实际注册这些为z3c.form作为一个 "value adapters" 。但你无须关心这一部分具体是怎么操作的。这个data参数是一个为schema中每个字段包含一个属性的对象。 On the add form, most of these are likely to be None, but on a different form, the values may be populated from the context. 该 data 对象也有一个context属性,用于获得表单的上下文。对于 add forms, 这个上下文指向容器文件夹;对于其他表单,这个上下文是一个正在被编辑或显示的对象。 如果你需要查询工具(如getToolByName) 或者想一个来自父亲对象的值,可以按下例使用data.context
from Products.CMFCore.utils import getToolByName ... catalog = getToolByName(data.context, 'portal_catalog')由上面方法返回的值应当是该字段允许的值。象Datetime字段这种情况,这是一个 Python datetime 对象。取决于context的类型,一个request layer,表单类型,widget类型,该方法可以提供不同的缺省值。详细情况参考plone.directives.form
例如:如果你想针对一个特别的表单获得不同的缺省值,可以采用下述方法:

@form.default_value(field=IProgram['start'], form=FormClass)我们将在本手册中稍后部分讨论创建定制的表单。
2. 验证
[align=left]为你类型创建定制的验证器[/align] 大多数应用要求在输入表单数据时进行验证。Z3c.form库能确保为Dexterity内容类型在输入数据时得到最简单的验证,即输入的数据必须和字段的类型一致。也可以在字段中设置某些属性,以添加更严格的验证(甚至可以用定制的验证逻辑创建你自己的字段)。当schema insterface被创建时,这些属性被设置作为参数传到字段构造器。详细资料查看 zope.schema package ,但常见的约束是:

  • required=True/False, 标记字段是必填还是可选
  • min and max, 用于 Int, Float, Datetime, Date, and Timedelta 字段,指定给定类型的值的最小最大范围
  • min_length and max_length, 用于集合字段(如Tuple, List, Set, Frozenset, Dict) 和文本字段 (如Bytes, BytesLine, ASCII, ASCIILine, Text, TextLine),设定最小最大的长度范围
约束 
如果系统默认的约束还不能达到要求,可以自己定义约束函数到一个字段。该类型函数只有一个参数:被验证的值。这个函数将返回布尔值: True or False.

def checkForMagic(value): return 'magic' in valueHint: The constraint function does not have access to the context, but if you need to acquire a tool, you can use the zope.app.component.hooks.getSite() method to obtain the site root对于使用该约束,传输该函数作为约束参数到一个字段构造器中,例如:
my_field = schema.TextLine(title=_(u"My field"), constraint=checkForMagic)约束很容易写,它不需要输出错误信息。然而可以通过z3c.form的error view snippets来定制这些错误信息。详情参见: z3c.form documentation
Invariants 关联约束 你已经注意到这个约束机制仅仅检测单个字段值。如果你需要写一个验证器来比较多个值,这时,你可以采用关联约束。关联约束通过甩出 exceptions 来表明验证错误,这些甩出的错误信息被显示在表单的顶部,而不显示在特点的字段旁边。to signal errors, which are displayed at the top of the form rather than next to a particular field.
为了举例说明关联约束,让我们肯定一个节目的开始日期必须在结束日期之前。 在 program.py模块中,我们添加如下代码, 和我们这样例无关的代码用 (…)省略:
... from zope.interface import invariant, Invalid class StartBeforeEnd(Invalid): __doc__ = _(u"The start or end date is invalid") class IProgram(form.Schema): ... start = schema.Datetime( title=_(u"Start date"), required=False, ) end = schema.Datetime( title=_(u"End date"), required=False, ) ... @invariant def validateStartEnd(data): if data.start is not None and data.end is not None: if data.start > data.end: raise StartBeforeEnd(_(u"The start date must be before the end date.")) ...表单验证器(Form validators) 最后,如果你要写更强大的验证器,可以通过 z3c.form widget 验证器来实现。详情参考 the z3c.form documentation for details.
3. 词汇
[align=left]创建自定义的静态或动态词汇[/align] 词汇通常被用在选择类字段中,由zope.schema 提供支持,采用choice字段类型,由z3c.form的选择字段提供widgets。 对于允许用户选择单个值,直接用一个 Choice 字段即可:
class IMySchema(form.Schema): myChoice = schema.Choice(...)对于一个 multi-select 字段,采用一个 List, Tuple, Set or Frozenset 字段类型,指定value_type为schema.Choice :
class IMySchema(form.Schema): myList = schema.List(..., value_type=schema.Choice(...))这个 choice 字段必须被传输一个下面的参数:
  • values 用于指定一个静态值列表
  • source 用于引用一个提供ContextSourceBinder 或者 ISource接口的实例
  • vocabulary 用于引用一个提供IVocabulary 接口的实例或者一个表明IVocabularyFactory named utility的字符串.
In the remainder of this section, we will show the various techniques for defining vocabularies through several iterations of a new field addedto the Program type allowing the user to pick the organiser responsiblefor the program.
静态词汇 首先,我们来看一个静态组织列表。我们用 message factory 来保证 labels (term titles) 可以被翻译。保存在organizer字段的值应该是一个 unicode 字符串来表现选定的label,如果没有任何值被选定,则为 None
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm organizers = SimpleVocabulary( [SimpleTerm(value=u'Bill', title=_(u'Bill')), SimpleTerm(value=u'Bob', title=_(u'Bob')), SimpleTerm(value=u'Jim', title=_(u'Jim'))] ) organizer = schema.Choice( title=_(u"Organiser"), vocabulary=organizers, required=False, )因为 required is False,所以在该字段的下拉列表中有一个 "no value" 选项。
动态词汇(Dynamic sources)静态词汇明星受到太多限制。 不仅被硬编码写死,而且还不允许分离存储的值和字段界面的标签。
我们能建立一个 one-off 动态词汇通过一个context sourcebinder。这应当是一个提供IContextSourceBinder接口的可调用对象,该对象取得一个context参数。这个个context参数是该表单的上下文(例如:添加表单的文件夹,编辑表单的当前被编辑对象)。这个可调用对象将返回一个词汇,最简单的实现方式是采用由zope.schema提供的SimpleVocabulary 类来构造。
下面是一个例子通过一个函数返回指定组的所有用户。
from zope.schema.interfaces import IContextSourceBinder from zope.schema.vocabulary import SimpleVocabulary from Products.CMFCore.utils import getToolByName @grok.provider(IContextSourceBinder) def possibleOrganizers(context): acl_users = getToolByName(context, 'acl_users') group = acl_users.getGroupById('organizers') terms = [] if group is not None: for member_id in group.getMemberIds(): user = acl_users.getUserById(member_id) if user is not None: member_name = user.getProperty('fullname') or member_id terms.append(SimpleVocabulary.createTerm(member_id, str(member_id), member_name)) return SimpleVocabulary(terms)我们用 PAS API 获取组和他的成员,建立一个list,该list将被转换到一个词汇,当采用词汇时,将面临的一些术语解释如下:

  • A term 是在词汇中的一个词条。词条有一个值。绝大多数的标记化的词条有一个标记,某些标题化的词条还有一个不同于标记的标题。
  • 这个标记( token)必须是一个 ASCII字符串。当表单被提交时,它是被request传输的值。一个标记(token)必须唯一标识一个词条。 .
  • 这个值(value)是实际被存储到对象里面,它不会传输到浏览器前端或者出现在表单中,这个值通常是一个 unicode 字符串,但也可以是任意的对象类型。
  • 这个标题(title)是一个unicode 字符串,或者是一个可以被翻译的信息。它被用在表单中。
这个 SimpleVocabulary类包含两个类方法用于从一个lists中创建词汇:
  • fromValues() 获取一个值的简单列表,返回标记化的词汇,标记(token)通过在值上调用str()方法创建。
  • fromItems() 获取一个形如 (token, value)的元组的列表,返回一个标题化的词汇,词条指定token 和 value。
也可以通过传入一个词条列表来实例化一个SimpleVocabulary。这个 createTerm() 类方法用于从一个value,token,title来创建一个词条(term),其中,仅仅value是必需的。
在上面的例子中,我们通过用userid 作为值(value)和标记(token),fullname作为标题(title)结合SimpleVocabulary 的CreateTerm()方法来创建词汇。
对于使用该词汇,我们在Choice 构造申明中用source参数指向该词汇:
organizer = schema.Choice( title=_(u"Organiser"), source=possibleOrganizers, required=False, )带参数的源(Parameterised sources) 我们通过将组名移出函数外,来进一步优化每字段基础的词汇构造。我们转换 IContextSourceBinder 到一个类,如下:
class GroupMembers(object): """Context source binder to provide a vocabulary of users in a given group. """ grok.implements(IContextSourceBinder) def __init__(self, group_name): self.group_name = group_name def __call__(self, context): acl_users = getToolByName(context, 'acl_users') group = acl_users.getGroupById(self.group_name) terms = [] if group is not None: for member_id in group.getMemberIds(): user = acl_users.getUserById(member_id) if user is not None: member_name = user.getProperty('fullname') or member_id terms.append(SimpleVocabulary.createTerm(member_id, str(member_id), member_name)) return SimpleVocabulary(terms)同样,设置source参数如下:
organizer = schema.Choice( title=_(u"Organiser"), source=GroupMembers('organizers'), required=False, )
命名词汇(Named vocabularies) 上下文(Context) source binders 对于获取简单的动态词汇是非常好的方法。这种简单词汇也可以重用,你可以从单个地方导入这个source,然后在多个实例中使用他。有时我们想用命名词汇,这个类似于 context source binders, 但是被注册作为一个命名utilities部件,在schema定义中通过名称引用。这个允许通过部件体系架构来实现本地词汇覆盖,也容易区分三方包中的词汇。
注意:命名词汇不能作为参数词汇,象我们在GroupMembers context source binder中使用参数词汇那样,因为命名词汇仅仅由名称来查找。
我们通过创建一个提供IVocabularyFactory的命名utility,能将我们首个 "members in the organizers group"词汇转换为一个命名词汇,如下:
from zope.schema.interfaces import IVocabularyFactory ... class OrganizersVocabulary(object): grok.implements(IVocabularyFactory) def __call__(self, context): acl_users = getToolByName(context, 'acl_users') group = acl_users.getGroupById('organizers') terms = [] if group is not None: for member_id in group.getMemberIds(): user = acl_users.getUserById(member_id) if user is not None: member_name = user.getProperty('fullname') or member_id terms.append(SimpleVocabulary.createTerm(member_id, str(member_id), member_name)) return SimpleVocabulary(terms) grok.global_utility(OrganizersVocabulary, name=u"example.conference.Organizers")
按照惯例,词汇的名称要加上它所在包的名称作为前缀,以确保词汇名称唯一。
在schema定义中,我们通过 vocabulary 参数指向该命名词汇的名称,来使用该命名词汇,如下:
organizer = schema.Choice( title=_(u"Organiser"), vocabulary=u"example.conference.Organizers", required=False, )一些常见词汇
正如你所期望的那样,Plone已定义了大量的标注词汇,他们被定义在 plone.app.vocabularies 包中。包括但不限如:
  • plone.app.vocabularies.AvailableContentLanguages, 所有有效的内容语言列表
  • plone.app.vocabularies.SupportedContentLanguages,当前支持的内容语言列表
  • plone.app.vocabularies.Roles, 在站点当中有效的用户角色列表
  • plone.app.vocabularies.PortalTypes, 已安装到站点的内容类型列表
  • plone.app.vocabularies.ReallyUserFriendlyTypes, 对用户有意义的那些内容类型列表
  • plone.app.vocabularies.Workflows, 一个工作流列表
  • plone.app.vocabularies.WorkflowStates,一个从所有工作流当中提取的所有状态的列表
  • plone.app.vocabularies.WorkflowTransitions, 一个从所有工作流当中提取的所有动作的列表
另外,这个plone.principalsource包提供几个词汇用于选择用户或组在一个 Dexterity context中:
  • plone.principalsource.Users 提供用户词汇
  • plone.principalsource.Groups 提供组词汇
  • plone.principalsource.Principals 提供安全主体 (users or groups)
注意,最重要的是,这些sources是不可递归的,也即意味着你不能用他们来提供一个站点的所有用户的列表。我们是有意这样限制:因为计算一个有较多用户的站点的成员列表是一个非常耗费资源的过程,尤其当你连接到LDAP或活动目录时,这种情况下,应该使用一个基于搜索的source。
我们将利用这些技术和 auto-complete widget 来优化我们的organizer 字段定义。为了这样做,我们需要添加 plone.principalsource 作为example.conference的依赖包,在 setup.py, 我们添加:
install_requires=[ ... 'plone.principalsource', ],
由于我们在configure.zcml中有了 <includeDependencies /> line,因此 我们不在需要在configure.zcml中增加另外的<include />行。
这个organizer字段现在看起来象这样:
organizer = schema.Choice( title=_(u"Organiser"), vocabulary=u"plone.principalsource.Users", required=False, )The autocomplete selection widget
这个organizer字段现在有了一个基于查询的source。标注的 selection widget (a drop-down list) 不能恰当呈现这种 source。我们需要一个更强大的 widget。对于基本的 widget,参考 z3c.formwidget.query, 但在一个 Plone context,你有更好的选择 用 plone.formwidget.autocomplete, 这个是扩充自 z3c.formwidget.query 以提供更友好的用户界面。
这个widget被 plone.app.dexterity 提供,因此我们无须自己配置它。我们仅仅需要告诉Dexterity用这个widget代替缺省的,通过之前提到过的表单hint方式来完成。在 program.py顶部,我们添加下述的导入:
如果我们采用多值字段,如 a List with a Choice value_type,我们应该采用 AutocompleteMultiFieldWidget
IProgram schema 中(which, recall, derives from form.Schema and is therefore processed for form hints at startup), 我们添加如下代码:
form.widget(organizer=AutocompleteFieldWidget) organizer = schema.Choice( title=_(u"Organiser"), vocabulary=u"plone.principalsource.Users", required=False, )你现在应该可以在表单中看到一个动态的自动完成 widget,只要你启用了JavaScript。 这个 widget也对未启用JavaScript的浏览器有一个容错。

4.引用
[align=left]内容对象之间怎样相互引用?[/align] 引用是维护内容之间链接的一种方法,即便一个或两个内容项目被移动或重命名,也不丢失这种链接关系。
Under the hood, Dexterity's reference system uses five.intid, a Zope 2 integration layer for zope.intid, to give each content item a unique integer id. These are the basis for relationships maintained with the zc.relationship package, which in turn is accessed via an API provided by z3c.relationfield, integrated into Zope 2 with plone.app.relationfield. 大多数情况下,你仅仅需要关心z3c.relationfield API,这个API为引用和搜索关心catalog提供发现源和目的对象的方法。
引用大多被用在表单字段的选择(select)或内容浏览(content brower)两种widget中。 Dexterity 系统c配有一个标准的 widget在 plone.formwidget.contenttree 包,提供为 来自z3c.relationfield的RelationListRelationChoice 字段 。
对于演示引用的使用,我们将允许用户在一个Session 和它的Presenter之间创建一个链接。.由于 Dexterity系统已经配有 plone.formwidget.contenttree z3c.relationfield, 我们无须另外添加配置代码,我们能直接在session.py使用这种字段。
... from z3c.relationfield.schema import RelationChoice from plone.formwidget.contenttree import ObjPathSourceBinder ... from example.conference.presenter import IPresenter class ISession(form.Schema): """A conference session. Sessions are managed inside Programs. """ ... presenter = RelationChoice( title=_(u"Presenter"), source=ObjPathSourceBinder(object_provides=IPresenter.__identifier__), required=False, )为了允许选择多个内容项目,我们应该采用RelationList 如下:
relatedItems = RelationList( title=u"Related Items", default=[], value_type=RelationChoice(title=_(u"Related"), source=ObjPathSourceBinder()), required=False, )这个 ObjPathSourceBinder 类是一个 IContextSourceBinder 将返回一个用内容对象作为values的词汇,对象的的标题作为词条的标题(title),对象的路径作为词条的标记(token)。
你也能传输关键词参数到ObjPathSourceBinder() 的构造器以限制可以选择的对象。这儿,我们要求对象必须提供 IPresenter i接口。这里的语法有点象catalog search, 除了仅仅简单的values 和 lists 能够被允许外。(例如,你不能用一个 a dict 来为一个字段index指定一个范围或values)。
如果你想在content browser显示中限制文件夹和其他内容,你可以传输一个 dictionary with catalog search parameters (and here, any valid catalog query will do) as the first non-keyword argument (navigation_tree_query) to the ObjPathSourceBinder() constructor.
If you want to use a different widget, you can use the same source(or a custom source that has content objects as values) with some thing like the autocomplete widget. The following line added to the interface will make the presenter selection similar to the organizer selection widget we showed in the previous section:
form.widget(presenter=AutocompleteFieldWidget)一旦用户创建了某些关系,存储在relation 字段的值是一个RelationValue 对象。这个提供一些属性,包括下面的:
  • from_object, 关系从该对象建立的对象
  • to_object, 关系被建立到的对象
  • from_id and to_id, 源和目的的整数ids
  • from_path and to_path,源和目的的路径
这个isBroken()方法能被用于判断该关系是否被破坏。这个一般在目标对象被删除时发生。
为了在我们的表单中显示这个关系,我们或者在一个DisplayForm表单中用一个 display widget,或者用这个 API 找到这个对象并显示它。我们将稍后在 session_templates/view.pt中演示:
<div tal:condition="context/presenter"> <label i18n:translate="presenter">Presenter:</label> <span tal:content="context/presenter/to_object/Title | nothing" /> </div>5.富文本,标记和转换
[align=left]怎样通过一个转换保存markup (such as HTML or reStructuredText) 并呈现他们。[/align] 许多内容项目需要允许用户提供富文本,可能是HTML (perhaps entered using a WYSIWYG editor),reStructuredText, Markdown 或者某些其他格式。这个markup在查看模板时典型的要被转换为 HTML,但是我们也想保留原始的markup轨迹,以便其能被再次编辑。即便输入格式本来就是HTML,也经常需要一个转换来实现剔除脏的tags保留干净的HTML。
可以存储 HTML 在一个标注的Text 字段。通过这样定义schema,你也可获得一个 WYSIWYG widget:
from plone.directives import form from zope import schema from plone.app.z3cform.wysiwyg import WysiwygFieldWidget class ITestSchema(form.Schema): form.widget(body=WysiwygFieldWidget) body = schema.Text(title=u"Body text") 然而,这种方式不允许 for alternative markups 也不允许任何表单的内容过来。为了满足这些,我们可以采用更强大的 字段: RichText 来自 plone.app.textfield 包。
from plone.directives import form from plone.app.textfield import RichText class ITestSchema(form.Schema): body = RichText(title=u"Body text")这个 RichText field 构造器能在普通的Text字段的基础上取得如下附加的参数:
  • default_mime_type, 表现输入markup的缺省MIME类型的一个字符串,缺省是text/html.
  • output_mime_type, 表现缺省输出MIME类型的一个字符串。缺省是 text/x-html-safe, 这是Plone定义的一种 MIME 类型,该类型不允许某些tags,这些tags用t HTML Filtering control panel 来控制。
  • allowed_mime_types, 一个字符串元组表明被允许的输入MIME类型的一个词汇。如果这个值为None (the default), 这个被允许的内容类型将被限制到Plone's Markup control panel 的设置。
也请注意:这个缺省字段能被设置或者一个 unicode 字符串(in which case it will be assumed to be a string of the default MIME type) 或者一个 RichTextValue 对象 (如下:).
下面是一个字段允许StructuredText 和 reStructuredText, 转换为 HTML 的样例:
from plone.directives import form from plone.app.textfield import RichText defaultBody = """\ Background ========== Please fill this in Details ======= And this """ class ITestSchema(form.Schema): body = RichText( title=u"Body text", default_mime_type='text/x-rst', output_mime_type='text/x-html', allowed_mime_types=('text/x-rst', 'text/structured',), default=defaultBody, )The RichTextValue
这个 RichText 字段不存储一个字符串,而是存储一个RichTextValue 对象。这是一个不变的对象,有如下属性:
  • raw, 一个unicode 字符串用原始输入 markup
  • mimeType, 原始markup的 MIME 类型,如 text/htmltext/structured.
  • encoding, 缺省的字符编码用于转换输入markup,大多数情况是utf-8
  • raw_encoded, the raw input encoded in the given encoding
  • outputMimeType, the MIME type of the default output, taken from the field at the time of instantiation
  • output, 一个unicode 字符串表现转换的输出。这个值尽可能被缓存,直到RichTextValue 被新值替换 (如当一个编辑表单被保存时,发生)。
这个RichTextValue对象的存储针对转换器输出被优化,以便于频繁地读这个输出。(i例如,在内容对象的视图页),而这个原始的值不将被频繁地读出。 (例如在对象的编辑页面)。由于 转换器的输出值被永久缓存,如果任何转换参数更改,你将需要用一个新的值替换这个RichTextValue对象。然而,正如我们下面即将看到可以应用不同的转换信息来满足你的要求。
下面的代码段显示一个RichTextValue对象怎样在代码中被构造。在这个例子中我们有一个类型为text/plain的原始输入字符串,将被转换到一个缺省的text/htmlT类型。(注意我们将从这个字段实例来查看缺省的输出类型)

from plone.app.textfield.value import RichTextValue ... context.body = RichTextValue(u"Some input text", 'text/plain', 'text/html') 当然用于RichText字段标准的widget可以正确保存你的对象的类型,因此你很少需要创建自己的widget。
在模板中调用富文本字段
如果你正在写TAL ,你可以试下下面的语法:
<div tal:content="structure context/body" />这样的话,就呈现类似如下的信息:
RichTextValue object. (Did you mean <attribute>.raw or <attribute>.output?)正确的语法是:
<div tal:content="structure context/body/output" /> 这将呈现缓存的转换后的输出。 这个操纵是近似于呈现一个简单的Text field, 因此这个转换操作仅仅当这个值被首次保存时,被执行一次。
可替换的转换(Alternative transformations)
有时,我们需要调用可替换的转换,在这种背景下,缺省的实现方式是采用portal_transforms tool 计算从原始值的输入MIME type 到期望的output MIME type的转换链。 (如果需要写自己的 transforms, 参考这个 this tutorial.) 这个transform被提取在一个 ITransformer adapter后,允许替换转换器实现。
在代码中调用一个转换,可以采用如下语法:

from plone.app.textfield.interfaces import ITransformer transformer = ITransformer(context) transformedValue = transformer(context.body, 'text/plain')这个ITransformer 适配器的 __call__()方法取得一个 RichTextValue 对象和一个输出MIME类型作为参数。
如果你要写一个页面模板,这里有一个更方便的语法:
<div tal:content="structure context/@@text-transform/body/text/plain" />[align=left]
这个首个遍历名指定在上下文的字段名 (body in this case)。第二个第三个给出输出的 MIME 类型。如果输出类型隐含,缺省的MIME 类型将被采用。
Dexterity开发手册:第六章 第7节静态资源
Dexterity开发手册:第六章 第8节应用行为
Dexterity开发手册:第六章 第9节 事件操作
Dexterity开发手册:第六章 第10节 权限
Dexterity开发手册:第六章 第11节 工作流
Dexterity开发手册:第六章 第12节 catalog
Dexterity开发手册:第六章 第13节 定制添加或编辑表单
Dexterity开发手册:第六章 第14节 定制内容类
Dexterity开发手册:第六章 第15节 WebDAV和其他文件表现形式 [/align][align=left]
英文原版《《Dexterity开发手册:第五章 定制视图[/align][align=right]
Dexterity开发手册:第七章 测试内容类型 》》
[/align]
设置