跳转到主要内容

管理瓷砖的API

项目描述

Travis CI badge Coveralls badge

plone.tiles实现了创建“瓦片”的低级、非Plone/Zope2特定支持。

介绍

在本软件包中,瓷砖是指一个浏览器视图及其提供一些关于该视图元数据的工具。元数据包括标题和描述,一个“添加”权限,以及可选的描述瓷砖可配置方面的方案接口。其思想是,一个UI(如Mosaic)可以向用户展示一个可插入瓷砖的列表,并可选择在插入时渲染一个表单来配置瓷砖。

瓷砖作为链接插入到布局中

<link rel="tile" target="placeholder" href="./@@sample.tile/tile1?option1=value1" />

子路径(在本例中为tile1)用于设置瓷砖的id属性。这允许瓷砖知道其唯一的ID,并在持久化瓷砖的情况下查找其数据。sample.tile是实现瓷砖的浏览器视图的名称。这作为__name__属性提供。其他参数可能被转换为瓷砖数据,在常规瓷砖中通过data属性访问,这是一个字典。对于持久化瓷砖(从PersistentTile基类派生的),数据从注释中获取,基于瓷砖ID。

本软件包中描述瓷砖有三个接口

IBasicTile

是瓷砖的低级接口。它扩展IBrowserView以描述__name__id属性的语义。

ITile

描述了一个可以配置一些数据的瓷砖。数据可以通过名为data的字典访问。该接口的默认实现plone.tiles.Tile将使用瓷砖类型的方案和查询字符串(self.request.form)来构造该字典。此接口还描述了一个属性url,它给出了包括id子路径和任何查询字符串参数的规范瓷砖URL。(注意,瓷砖也正确实现了IAbsoluteURL。)

IPersistentTile

描述了一个将配置存储在对象注释中的瓷砖,当配置值不能编码到查询字符串时需要使用。默认实现是在plone.tiles.PersistentTile中。为了能够在同一布局上拥有同一类型的多个瓷砖,注释是通过瓷砖的__name__键入的。

内部瓷砖通过ITileType描述。它包含瓷砖名称、标题、描述、添加权限和(如果需要)方案等属性。

一个配置正确的瓷砖由以下部分组成

  • 一个工具提供与瓷砖浏览器视图相同的名称的ITileType

  • 一个浏览器视图提供IBasicTile或其衍生的一个。

使用指令<plone:tile ... />一次性注册这两个组件。

为了支持创建适当的瓷砖链接,plone.tiles.data包含两个方法

  1. encode()

decode()

以帮助将数据字典转换为查询字符串,并将一个符合瓷砖方案接口的字典转换为请求表单。

创建简单瓦片

最基本的瓦片看起来像这样

from plone.tiles import Tile

class MyTile(Tile):

    def __call__(self):
        return u'<html><body><p>Hello world</p></body></html>'

请注意,瓦片应返回一个完整的HTML文档。这将根据以下规则插入到页面输出中。

  • 瓦片<head/>部分的内 容将追加到输出文档的<head/>部分。

  • 瓦片<body/>部分的内 容将替换由瓦片链接指定的瓦片占位符。

请注意,此软件包不提供这些插值。有关Plone实现插值算法的信息,请参阅plone.app.blocks

如果您需要持久瓦片,请改用plone.tiles.PersistentTile的子类。如果您想要可配置的瞬态或持久瓦片,可能还需要一个模式接口。

要注册瓦片,请使用以下ZCML

<configure xmlns:plone="http://namespaces.plone.org/plone">

    <plone:tile
        name="sample.tile"

        title="A title for the tile"
        description="My tile's description"
        add_permission="my.add.Permission"
        schema=".interfaces.IMyTileSchema"

        class=".mytile.MyTile"
        permission="zope.Public"
        for="*"
        layer="*"
        />

</configure>

前五个属性通过配置适当的ITileType指令来描述瓦片。其余部分模仿<browser:page/>指令,因此您可以指定一个模板文件并省略类,或者同时使用模板和类。

如果您想使用自定义模式注册持久瓦片,但只有一个模板,可以这样做

<plone:tile
    name="sample.persistenttile"
    title="A title for the tile"
    description="My tile's description"
    add_permission="my.add.Permission"
    schema=".interfaces.IMyTileSchema"
    class="plone.tiles.PersistentTile"
    template="mytile.pt"
    permission="zope.Public"
    for="*"
    />

如果您想覆盖现有瓦片,例如使用新的层或更具体的上下文,必须省略瓦片元数据(标题、描述、图标、添加权限或模式)。如果您包含任何元数据,则在Zope启动时将得到冲突错误。此示例显示了如何为我们的瓦片使用不同的模板

<plone:tile
    name="sample.persistenttile"
    template="override.pt"
    permission="zope.Public"
    for="*"
    layer=".interfaces.IMyLayer"
    />

ZCML参考

plone:tile指令使用命名空间xmlns:plone="http://namespaces.plone.org/plone"。为了启用它,需要加载其meta.zcml,使用

<include package="plone.tiles" file="meta.zcml" />

在注册瓦片时,在后台执行两个注册

  1. 如何将瓦片添加(注册为一个作为plone.tiles.type.TileType实例的实用工具组件)。

    可以注册瓦片而不添加功能。但是,此类瓦片需要直接调用,无法进行TTW添加。

    此注册只能进行一次。

    此注册使用以下属性

    • name(必需)

    • title(必需)

    • description(可选)

    • icon(可选)

    • permission(必需)

    • add_permission(必需,用于添加功能)

    • edit_permission(可选,默认为add_permission)

    • delete_permission(可选,默认为add_permission)

    • schema(可选)

  2. 如何将瓦片渲染(作为常规页面)。

    可以为相同的name注册不同的渲染器,但上下文不同(forlayer)。

    此注册使用以下属性

    • name(必需)

    • for(可选)

    • layer(可选)

    • class(此或template或两者都要)

    • template(此或class或两者都要)

    • permission(必需)

指令属性具有以下含义

name

瓦片的唯一、点分隔的名称。

title

用户友好的标题,在配置瓦片时使用。

description

瓦片目的和功能的更详细的摘要。

icon

代表瓦片目的和功能的图像。

permission

查看瓦片所需的权限名称。

add_permission

实例化瓦片所需的权限名称。

编辑权限

修改标题所需的权限名称。默认为 添加权限

删除权限

删除标题所需的权限名称。默认为 添加权限

模式

标题的配置模式。用于创建标准添加/编辑表单。

for

该标题可用的接口或类。

标题可用的层(请求标记接口)。

实现该标题的类。一个提供 IBasicTile 或其派生类的浏览器视图。

模板

渲染此标题的模板名称。指的是包含页面模板的文件。

进一步阅读

有关更多详细信息,请参阅 tiles.rstdirectives.rst

瓦片详情

标题是用于组合页面的视图组件形式。将标题视为描述页面一部分的视图,可以通过模式中描述的一些数据配置,并通过专用GUI插入到布局中。

与浏览器视图一样,标题可以独立遍历和发布。然后,标题应返回一个完整的HTML页面,包括包含所需资源的 <head /> 和包含标题可见部分的 <body />。然后,将使用如 plone.app.blocks 之类的系统将其合并到页面中。

此包中的API提供支持,以便根据模式配置标题,数据通过查询字符串(瞬态标题)或从注释(持久标题)检索。

请注意,此包中没有直接的用户界面支持,因此允许用户构建和编辑标题的表单必须存在于其他地方。您可能对 plone.app.tilesplone.app.mosaic 感兴趣。

要使用此包,您应首先加载其ZCML配置

>>> configuration = """\
... <configure
...      xmlns="http://namespaces.zope.org/zope"
...      xmlns:plone="http://namespaces.plone.org/plone"
...      i18n_domain="plone.tiles.tests">
...
...     <include package="zope.component" file="meta.zcml" />
...     <include package="zope.browserpage" file="meta.zcml" />
...
...     <include package="plone.tiles" file="meta.zcml" />
...     <include package="plone.tiles" />
...
... </configure>
... """

>>> from six import StringIO
>>> from zope.configuration import xmlconfig
>>> xmlconfig.xmlconfig(StringIO(configuration))

一个简单的临时瓦片

基本标题是一个实现 ITile 接口的视图。实现此接口的最简单方法是子类化 Tile

>>> from plone.tiles import Tile
>>> class SampleTile(Tile):
...
...     __name__ = 'sample.tile' # would normally be set by a ZCML handler
...
...     def __call__(self):
...         return '<html><body><b>My tile</b></body></html>'

标题是浏览器视图

>>> from plone.tiles.interfaces import ITile
>>> ITile.implementedBy(SampleTile)
True

>>> from zope.publisher.interfaces.browser import IBrowserView
>>> IBrowserView.implementedBy(SampleTile)
True

标题实例具有 __name__ 属性(通常由 <plone:tile /> ZCML指令在类级别设置),以及一个属性 id。id可以明确设置,无论是通过代码还是通过子路径遍历。例如,如果标题名称是 example.tile,则可以使用类似 http://example.com/foo/@@example.tile/tile1 的URL将其id设置为 tile1

此标题作为正常浏览器视图注册,并与提供有关标题自身信息的实用程序一起注册。通常,这是使用 <plone:tile /> 指令完成的。以下是如何手动创建一个的示例

>>> from plone.tiles.type import TileType
>>> sampleTileType = TileType(
...     u'sample.tile',
...     u'Sample tile',
...     'dummy.Permission',
...     'dummy.Permission',
...     description=u'A tile used for testing',
...     schema=None)

名称应与视图名称和实用程序注册的名称匹配。标题和描述可以由UI使用。添加权限是插入标题所需的权限名称。schema属性可以用来指示描述标题可配置数据的模式接口 - 有关此内容的更多信息请见下文。

要在ZCML中注册一个标题,我们可以这样做

<plone:tile
    name="sample.tile"
    title="Sample tile"
    description="A tile used for testing"
    add_permission="dummy.Permission"
    class=".mytiles.SampleTile"
    for="*"
    permission="zope.Public"
    />

还可以指定类似于 browser:page 指令的 layertemplate,以及一个 schema,我们将在下文中描述。

我们将直接在此处注册示例标题,以便稍后进行测试。

>>> from zope.component import provideAdapter, provideUtility
>>> from zope.interface import Interface
>>> from plone.tiles.interfaces import IBasicTile

>>> provideUtility(sampleTileType, name=u'sample.tile')
>>> provideAdapter(SampleTile, (Interface, Interface), IBasicTile, name=u'sample.tile')

瓦片遍历

瓦片可以作为正常浏览器视图发布。它们通常通过指定瓦片ID的子路径来调用。这允许瓦片知道它们的实例名称。ID在瓦片使用的页面布局中是唯一的,可能是查找瓦片数据的基础。

例如,一个瓦片可以以如下链接形式保存在布局中

<link rel="tile" target="mytile" href="./@@sample.tile/tile1" />

(这里的想法是瓦片链接告诉渲染算法用渲染的瓦片主体替换ID为 mytile 的元素 - 详细信息请参阅 plone.app.blocks)。

让我们创建一个示例上下文,查找在遍历过程中的视图,并验证瓦片是如何实例化的。

>>> from zope.component import getMultiAdapter
>>> from zope.interface import classImplements
>>> from zope.interface import Interface
>>> from zope.interface import implementer
>>> from zope.publisher.browser import TestRequest
>>> from zope.annotation.interfaces import IAnnotations
>>> from zope.annotation.interfaces import IAttributeAnnotatable
>>> classImplements(TestRequest, IAttributeAnnotatable)

>>> class IContext(Interface):
...     pass

>>> @implementer(IContext)
... class Context(object):
...     pass

>>> context = Context()
>>> request = TestRequest()

>>> tile = getMultiAdapter((context, request), name=u'sample.tile')
>>> tile = tile['tile1'] # simulates sub-path traversal

现在瓦片将知道它的名称和ID

>>> isinstance(tile, SampleTile)
True
>>> tile.__parent__ is context
True
>>> tile.id
'tile1'
>>> tile.__name__
'sample.tile'

子路径遍历是通过自定义的 __getitem__() 方法实现的。要查找瓦片上的视图,你可以在遍历到ID子路径之后遍历到它。

>>> from zope.component import adapts
>>> from zope.interface import Interface
>>> from zope.publisher.browser import BrowserView
>>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer

>>> class TestView(BrowserView):
...     adapts(SampleTile, IDefaultBrowserLayer)
...     def __call__(self):
...         return 'Dummy view'

>>> provideAdapter(TestView, provides=Interface, name='test-view')

>>> tile.id is not None
True
>>> tile['test-view']()
'Dummy view'

如果没有视图而我们已经有了一个ID,我们将得到一个 KeyError

>>> tile['not-known'] # doctest: +ELLIPSIS
Traceback (most recent call last):
...
KeyError: 'not-known'

为了与Zope的多个纠缠发布机保持一致,也可以使用 publishTraverse 方法进行遍历

>>> tile = getMultiAdapter((context, request), name=u'sample.tile')
>>> tile = tile.publishTraverse(request, 'tile1') # simulates sub-path traversal

>>> isinstance(tile, SampleTile)
True
>>> tile.__parent__ is context
True
>>> tile.id
'tile1'
>>> tile.__name__
'sample.tile'

临时瓦片数据

现在让我们考虑瓦片可能具有的数据。在最简单的情况下,瓦片数据通过查询字符串传递,并按照模式进行描述。一个简单的模式可能如下所示

>>> import zope.schema

>>> class ISampleTileData(Interface):
...     title = zope.schema.TextLine(title=u'Tile title')
...     cssClass = zope.schema.ASCIILine(title=u'CSS class to apply')
...     count = zope.schema.Int(title=u'Number of things to show in the tile')

我们通常会在这个瓦片注册到ZCML时列出这个接口。我们只需在这里更新这个实用程序。

>>> sampleTileType.schema = ISampleTileData

瓦片数据由一个简单的字典表示。例如

>>> data = {'title': u'My title', 'count': 5, 'cssClass': 'foo'}

想法是瓦片添加表单是从模式接口构建的,其数据保存到字典中。

对于临时瓦片,然后将这些数据编码到瓦片查询字符串中。为了帮助做到这一点,可以使用一个实用函数将字典编码为查询字符串,并根据模式中描述的类型应用Zope表单编解码器

>>> from plone.tiles.data import encode
>>> encode(data, ISampleTileData)
'title=My+title&cssClass=foo&count%3Along=5'

count%3Along=5 这一部分是 count:long=5 的编码版本。

请注意,并非所有字段类型都可以保存。特别是,对象、接口、集合或冻结集合字段可能无法保存,并会导致 KeyError。长文本字段或包含二进制数据的字节字段也可能有问题。对于这些类型的字段,请考虑使用持久瓦片。

此外,转换可能不完美。例如,Zope的表单编解码器无法区分unicode和ascii字段。因此,有一个相应的 decode() 方法,可用于确保值与模式匹配

>>> marshaled = {'title': u'My tile', 'count': 5, 'cssClass': u'foo'}

>>> from plone.tiles.data import decode
>>> sorted(decode(marshaled, ISampleTileData).items())
[('count', 5), ('cssClass', 'foo'), ('title', 'My tile')]

当保存到布局中时,瓦片链接现在看起来像这样

<link rel="tile" target="mytile"
  href="./@@sample.tile/tile1?title=My+title&count%3Along=5&cssClass=foo" />

让我们再次模拟遍历,看看数据现在是如何对瓦片实例可用的

>>> context = Context()
>>> request = TestRequest(form={'title': u'My title', 'count': 5, 'cssClass': u'foo'})

>>> tile = getMultiAdapter((context, request), name=u'sample.tile')
>>> tile = tile['tile1']

>>> sorted(tile.data.items())
[('count', 5), ('cssClass', 'foo'), ('title', 'My title')]

注意数据也已根据模式正确解码。

临时瓦片将直接从请求参数中获取其数据,但如果请求中存在JSON编码的 _tiledata 参数,则将使用该参数

>>> import json

>>> request = TestRequest(form={
...     'title': u'My title', 'count': 5, 'cssClass': u'foo',
...     '_tiledata': json.dumps({'title': u'Your title', 'count': 6, 'cssClass': u'bar'})
...     })
>>> tile = getMultiAdapter((context, request), name=u'sample.tile')
>>> tile = tile['tile1']

>>> sorted(tile.data.items())
[('count', 6), ('cssClass', 'bar'), ('title', 'Your title')]

这样我们可以在瓦片数据可能被与来自表单的原始数据混淆的上下文中安全地使用临时瓦片,例如在编辑表单中。

瓦片数据管理器

data 属性是一个便利属性,用于获取由 ITileDataManager 返回的(缓存的)数据副本。此接口提供了三种方法:get(),用于返回瓦片的数据,set(),用于用新的数据字典更新它,以及 delete(),用于删除数据。

此适配器主要用于编写围绕瓦片的UI。使用我们上面的瓦片,我们可以这样获取数据

>>> from plone.tiles.interfaces import ITileDataManager
>>> dataManager = ITileDataManager(tile)
>>> dataManager.get() == tile.data
True

我们还可以更新瓦片数据

>>> dataManager.set({'count': 1, 'cssClass': 'bar', 'title': u'Another title'})
>>> sorted(dataManager.get().items())
[('count', 1), ('cssClass', 'bar'), ('title', 'Another title')]

数据也可以被删除

>>> dataManager.delete()
>>> sorted(dataManager.get().items())
[('count', None), ('cssClass', None), ('title', None)]

请注意,在临时瓦片的情况下,我们所做的就是修改请求的 form 字典(或者如果存在,则修改该字典的 _tiledata 参数)。数据需要编码到查询字符串中,可以使用 encode() 方法,或者通过瓦片的 IAbsoluteURL 适配器(详情见下文)。

对于持久性瓦片,数据管理器更有趣。

持久化瓷砖

并非所有类型的数据都可以放在查询字符串中。对于更大量的存储需求,您可以使用持久性瓦片,这些瓦片将数据存储在注释中。

首先,我们需要编写注释支持。

>>> from zope.annotation.attribute import AttributeAnnotations
>>> provideAdapter(AttributeAnnotations)

我们还需要一个可注释的上下文。

>>> from zope.annotation.interfaces import IAttributeAnnotatable
>>> from zope.interface import alsoProvides
>>> alsoProvides(context, IAttributeAnnotatable)

现在,让我们创建一个带有模式的持久性瓦片。

>>> class IPersistentSampleData(Interface):
...     text = zope.schema.Text(title=u'Detailed text', missing_value=u'Missing!')

>>> from plone.tiles import PersistentTile
>>> class PersistentSampleTile(PersistentTile):
...
...     __name__ = 'sample.persistenttile' # would normally be set by ZCML handler
...
...     def __call__(self):
...         return u'<b>You said</b> %s' % self.data['text']

>>> persistentSampleTileType = TileType(
...     u'sample.persistenttile',
...     u'Persistent sample tile',
...     'dummy.Permission',
...     'dummy.Permission',
...     description=u'A tile used for testing',
...     schema=IPersistentSampleData)

>>> provideUtility(persistentSampleTileType, name=u'sample.persistenttile')
>>> provideAdapter(PersistentSampleTile, (Interface, Interface), IBasicTile, name=u'sample.persistenttile')

现在我们可以像以前一样遍历到瓦片。默认情况下,没有数据,将使用字段的缺失值。

>>> request = TestRequest()

>>> tile = getMultiAdapter((context, request), name=u'sample.persistenttile')
>>> tile = tile['tile2']
>>> tile.__name__
'sample.persistenttile'
>>> tile.id
'tile2'

>>> tile()
'<b>You said</b> Missing!'

此时,该类型注释中也没有任何内容

>>> list(dict(getattr(context, '__annotations__', {})).keys())
[]

我们可以使用 ITileDataManager 将数据写入上下文的注释

>>> dataManager = ITileDataManager(tile)
>>> dataManager.set({'text': 'Hello!'})

这将数据写入注释

>>> list(dict(context.__annotations__).keys())
['plone.tiles.data.tile2']
>>> context.__annotations__[u'plone.tiles.data.tile2']
{'text': 'Hello!'}

当然,我们也可以从数据管理器获取这些数据

>>> dataManager.get()
{'text': 'Hello!'}

请注意,与临时瓦片一样,data 属性被缓存,并且只会查找一次。

如果我们现在再次查找瓦片,我们将得到新的值

>>> tile = getMultiAdapter((context, request), name=u'sample.persistenttile')
>>> tile = tile['tile2']
>>> tile()
'<b>You said</b> Hello!'

>>> tile.data
{'text': 'Hello!'}

我们也可以使用数据管理器删除注释

>>> dataManager.delete()
>>> sorted(dict(context.__annotations__).items()) # doctest: +ELLIPSIS
[]

使用持久化覆盖瞬态数据

为了能够重用相同的集中管理瓦片布局为多个上下文对象,同时仍然允许瓦片的可选定制,可以通过上下文特定的持久配置覆盖其他方式的临时瓦片配置。

这是通过设置客户端请求头或查询参数 X-Tile-Persistent 来实现的。

>>> request = TestRequest(
...     form={'title': u'My title', 'count': 5, 'cssClass': u'foo',
...           'X-Tile-Persistent': 'yes'}
... )

然而,仅仅添加标志并不能在 GET 请求上创建新的持久注释

>>> tile = getMultiAdapter((context, request), name=u'sample.tile')
>>> ITileDataManager(tile)
<plone.tiles.data.PersistentTileDataManager object at ...>

>>> sorted(ITileDataManager(tile).get().items(), key=lambda x: x[0])
[('count', 5), ('cssClass', 'foo'), ('title', 'My title')]

>>> list(IAnnotations(context).keys())
[]

这是因为数据只有在设置之后才是持久的

>>> data = ITileDataManager(tile).get()
>>> data.update({'count': 6})
>>> ITileDataManager(tile).set(data)
>>> list(IAnnotations(context).keys())
['plone.tiles.data...']

>>> sorted(list(IAnnotations(context).values())[0].items(), key=lambda x: x[0])
[('count', 6), ('cssClass', 'foo'), ('title', 'My title')]

>>> sorted(ITileDataManager(tile).get().items(), key=lambda x: x[0])
[('count', 6), ('cssClass', 'foo'), ('title', 'My title')]

如果没有持久标志,将返回固定的临时数据

>>> request = TestRequest(
...     form={'title': u'My title', 'count': 5, 'cssClass': u'foo'},
... )
>>> tile = getMultiAdapter((context, request), name=u'sample.tile')
>>> ITileDataManager(tile)
<plone.tiles.data.TransientTileDataManager object at ...>

>>> data = ITileDataManager(tile).get()
>>> sorted(data.items(), key=lambda x: x[0])
[('count', 5), ('cssClass', 'foo'), ('title', 'My title')]

最后,持久覆盖也可以被删除

>>> request = TestRequest(
...     form={'title': u'My title', 'count': 5, 'cssClass': u'foo',
...           'X-Tile-Persistent': 'yes'}
... )
>>> tile = getMultiAdapter((context, request), name=u'sample.tile')
>>> ITileDataManager(tile)
<plone.tiles.data.PersistentTileDataManager object at ...>

>>> sorted(ITileDataManager(tile).get().items(), key=lambda x: x[0])
[('count', 6), ('cssClass', 'foo'), ('title', 'My title')]

>>> ITileDataManager(tile).delete()
>>> list(IAnnotations(context).keys())
[]

>>> sorted(ITileDataManager(tile).get().items(), key=lambda x: x[0])
[('count', 5), ('cssClass', 'foo'), ('title', 'My title')]

>>> request = TestRequest(
...     form={'title': u'My title', 'count': 5, 'cssClass': u'foo'},
... )
>>> tile = getMultiAdapter((context, request), name=u'sample.tile')
>>> ITileDataManager(tile)
<plone.tiles.data.TransientTileDataManager object at ...>

瓷砖URL

正如我们所见,瓦片有一个规范 URL。对于临时瓦片,这也可能编码了一些瓦片数据。

如果您有一个瓦片实例并且需要知道规范瓦片 URL,您可以使用 IAbsoluteURL API。

为了测试目的,我们需要确保我们可以获取上下文的绝对 URL。我们将通过一个虚拟适配器来实现这一点。

>>> from zope.interface import implementer
>>> from zope.component import adapter

>>> from zope.traversing.browser.interfaces import IAbsoluteURL
>>> from zope.publisher.interfaces.http import IHTTPRequest

>>> @implementer(IAbsoluteURL)
... @adapter(IContext, IHTTPRequest)
... class DummyAbsoluteURL(object):
...
...     def __init__(self, context, request):
...         self.context = context
...         self.request = request
...
...     def __unicode__(self):
...         return u'http://example.com/context'
...     def __str__(self):
...         return u'http://example.com/context'
...     def __call__(self):
...         return self.__str__()
...     def breadcrumbs(self):
...         return ({'name': u'context', 'url': 'http://example.com/context'},)
>>> provideAdapter(DummyAbsoluteURL, name=u'absolute_url')
>>> provideAdapter(DummyAbsoluteURL)

>>> from zope.traversing.browser.absoluteurl import absoluteURL
>>> from zope.component import getMultiAdapter

>>> context = Context()
>>> request = TestRequest(form={'title': u'My title', 'count': 5, 'cssClass': u'foo'})
>>> transientTile = getMultiAdapter((context, request), name=u'sample.tile')
>>> transientTile = transientTile['tile1']

>>> absoluteURL(transientTile, request)
'http://example.com/context/@@sample.tile/tile1?title=My+title&cssClass=foo&count%3Along=5'

>>> getMultiAdapter((transientTile, request), IAbsoluteURL).breadcrumbs() == \
... ({'url': 'http://example.com/context', 'name': u'context'},
...  {'url': 'http://example.com/context/@@sample.tile/tile1', 'name': 'sample.tile'})
True

为了方便起见,瓦片 URL 也可在 url 属性下找到

>>> transientTile.url
'http://example.com/context/@@sample.tile/tile1?title=My+title&cssClass=foo&count%3Along=5'

如果数据来自 _tiledata JSON 编码参数而不是直接来自请求参数,瓦片的绝对 URL 结构保持不变

>>> request = TestRequest(form={'_tiledata': json.dumps({'title': u'Your title', 'count': 6, 'cssClass': u'bar'})})
>>> transientTile = getMultiAdapter((context, request), name=u'sample.tile')
>>> transientTile = transientTile['tile1']

>>> absoluteURL(transientTile, request)
'http://example.com/context/@@sample.tile/tile1?title=Your+title&cssClass=bar&count%3Along=6'

对于持久性瓦片,没有数据参数

>>> context = Context()
>>> request = TestRequest(form={'title': u'Ignored', 'count': 0, 'cssClass': u'ignored'})
>>> persistentTile = getMultiAdapter((context, request), name=u'sample.persistenttile')
>>> persistentTile = persistentTile['tile2']

>>> absoluteURL(persistentTile, request)
'http://example.com/context/@@sample.persistenttile/tile2'

>>> getMultiAdapter((persistentTile, request), IAbsoluteURL).breadcrumbs() == \
... ({'url': 'http://example.com/context', 'name': u'context'},
...  {'url': 'http://example.com/context/@@sample.persistenttile/tile2', 'name': 'sample.persistenttile'})
True

并且,为了方便

>>> persistentTile.url
'http://example.com/context/@@sample.persistenttile/tile2'

如果瓦片没有 id,我们不会得到任何子路径

>>> request = TestRequest(form={'title': u'My title', 'count': 5, 'cssClass': u'foo'})
>>> transientTile = getMultiAdapter((context, request), name=u'sample.tile')
>>> absoluteURL(transientTile, request)
'http://example.com/context/@@sample.tile?title=My+title&cssClass=foo&count%3Along=5'

>>> request = TestRequest()
>>> persistentTile = getMultiAdapter((context, request), name=u'sample.persistenttile')
>>> absoluteURL(persistentTile, request)
'http://example.com/context/@@sample.persistenttile'

我们还可以拒绝查询参数提供数据到我们的瓦片

>>> import zope.schema
>>> from plone.tiles.directives import ignore_querystring

>>> class ISampleTileData(Interface):
...     unfiltered = zope.schema.Text(title=u'Unfiltered data')
...     ignore_querystring('unfiltered')
...     filtered = zope.schema.Text(title=u'filtered data')

>>> sampleTileType.schema = ISampleTileData

并创建一个新的带有我们新模式的瓦片

>>> from plone.tiles import Tile
>>> class SampleTile(Tile):
...
...     __name__ = 'sample.unfilteredtile' # would normally be set by a ZCML handler
...
...     def __call__(self):
...         return '<html><body><div>{}{}</div></body></html>'.format(
...             self.data.get('unfiltered') or '',
...             self.data.get('filtered') or '')

我们将直接在这里注册样本未过滤瓦片以进行测试。

>>> from zope.component import provideAdapter, provideUtility
>>> from zope.interface import Interface
>>> from plone.tiles.interfaces import IBasicTile

>>> provideUtility(sampleTileType, name=u'sample.unfilteredtile')
>>> provideAdapter(SampleTile, (Interface, Interface), IBasicTile, name=u'sample.unfilteredtile')

让我们模拟遍历来测试是否使用了表单数据

>>> context = Context()
>>> request = TestRequest(form={'unfiltered': 'foobar', 'filtered': 'safe'})

>>> tile = getMultiAdapter((context, request), name=u'sample.unfilteredtile')
>>> tile = tile['tile1']

数据不应包含未过滤的字段

>>> sorted(tile.data.items())
[('filtered', 'safe')]

渲染瓦片不应包括忽略的查询字符串

>>> 'foobar' in tile()
False

>>> tile()
'<html><body><div>safe</div></body></html>'

ZCML指令

瓦片实际上只是一个提供IBasicTile(或更常见的是ITileIPersistentTile)的浏览器视图,并与一个提供ITileType的命名实用工具相结合。浏览器视图和瓦片的名称应该匹配。

为了更容易注册这些组件,此包提供了一个<plone:tile />指令,用于设置这两个组件。它支持几个用例

  • 从类注册新的瓦片

  • 仅从模板注册新的瓦片

  • 从类和模板注册新的瓦片

  • 为现有的瓦片类型(例如,为新的层)注册新的瓦片

为了测试这一点,我们在testing.py中创建了一个虚拟的架构和一个虚拟的瓦片,以及一个虚拟的模板在test.pt

让我们看看如何通过注册几个瓦片来使用这些

>>> configuration = """\
... <configure package="plone.tiles"
...      xmlns="http://namespaces.zope.org/zope"
...      xmlns:plone="http://namespaces.plone.org/plone"
...      i18n_domain="plone.tiles.tests">
...
...     <include package="zope.component" file="meta.zcml" />
...     <include package="zope.security" file="meta.zcml" />
...     <include package="zope.browserpage" file="meta.zcml" />
...
...     <include package="plone.tiles" file="meta.zcml" />
...     <include package="plone.tiles" />
...
...     <permission
...         id="plone.tiles.testing.DummyAdd"
...         title="Dummy add permission"
...         />
...     <permission
...         id="plone.tiles.testing.DummyView"
...         title="Dummy view permission"
...         />
...
...     <!-- A tile configured with all available attributes -->
...     <plone:tile
...         name="dummy1"
...         title="Dummy tile 1"
...         description="This one shows all available options"
...         add_permission="plone.tiles.testing.DummyAdd"
...         schema="plone.tiles.testing.IDummySchema"
...         class="plone.tiles.testing.DummyTileWithTemplate"
...         template="test.pt"
...         for="plone.tiles.testing.IDummyContext"
...         layer="plone.tiles.testing.IDummyLayer"
...         permission="plone.tiles.testing.DummyView"
...         />
...
...     <!-- A class-only tile -->
...     <plone:tile
...         name="dummy2"
...         title="Dummy tile 2"
...         add_permission="plone.tiles.testing.DummyAdd"
...         class="plone.tiles.testing.DummyTile"
...         for="*"
...         permission="plone.tiles.testing.DummyView"
...         />
...
...     <!-- A template-only tile -->
...     <plone:tile
...         name="dummy3"
...         title="Dummy tile 3"
...         add_permission="plone.tiles.testing.DummyAdd"
...         template="test.pt"
...         for="*"
...         permission="plone.tiles.testing.DummyView"
...         />
...
...     <!-- Use the PersistentTile class directly with a template-only tile -->
...     <plone:tile
...         name="dummy4"
...         title="Dummy tile 4"
...         add_permission="plone.tiles.testing.DummyAdd"
...         schema="plone.tiles.testing.IDummySchema"
...         class="plone.tiles.PersistentTile"
...         template="test.pt"
...         for="*"
...         permission="plone.tiles.testing.DummyView"
...         />
...
...     <!-- Override dummy3 for a new layer -->
...     <plone:tile
...         name="dummy3"
...         class="plone.tiles.testing.DummyTile"
...         for="*"
...         layer="plone.tiles.testing.IDummyLayer"
...         permission="plone.tiles.testing.DummyView"
...         />
...
... </configure>
... """

>>> from six import StringIO
>>> from zope.configuration import xmlconfig
>>> xmlconfig.xmlconfig(StringIO(configuration))

让我们检查瓦片是如何注册的

>>> from zope.component import getUtility
>>> from plone.tiles.interfaces import ITileType

>>> tile1_type = getUtility(ITileType, name=u'dummy1')
>>> tile1_type
<TileType dummy1 (Dummy tile 1)>
>>> tile1_type.description
'This one shows all available options'

>>> tile1_type.add_permission
'plone.tiles.testing.DummyAdd'

>>> tile1_type.view_permission
'plone.tiles.testing.DummyView'

>>> tile1_type.schema
<InterfaceClass plone.tiles.testing.IDummySchema>

>>> tile2_type = getUtility(ITileType, name=u'dummy2')
>>> tile2_type
<TileType dummy2 (Dummy tile 2)>
>>> tile2_type.description is None
True
>>> tile2_type.add_permission
'plone.tiles.testing.DummyAdd'
>>> tile2_type.schema is None
True

>>> tile3_type = getUtility(ITileType, name=u'dummy3')
>>> tile3_type
<TileType dummy3 (Dummy tile 3)>
>>> tile3_type.description is None
True
>>> tile3_type.add_permission
'plone.tiles.testing.DummyAdd'
>>> tile3_type.schema is None
True

>>> tile4_type = getUtility(ITileType, name=u'dummy4')
>>> tile4_type
<TileType dummy4 (Dummy tile 4)>
>>> tile4_type.description is None
True
>>> tile4_type.add_permission
'plone.tiles.testing.DummyAdd'
>>> tile4_type.schema
<InterfaceClass plone.tiles.testing.IDummySchema>

最后,让我们检查我们是否可以查找瓦片

>>> from zope.publisher.browser import TestRequest
>>> from zope.interface import implementer, alsoProvides

>>> from plone.tiles.testing import IDummyContext, IDummyLayer

>>> @implementer(IDummyContext)
... class Context(object):
...     pass

>>> context = Context()
>>> request = TestRequest()
>>> layer_request = TestRequest(skin=IDummyLayer)

>>> from zope.component import getMultiAdapter
>>> from plone.tiles import Tile, PersistentTile
>>> from plone.tiles.testing import DummyTile, DummyTileWithTemplate

>>> tile1 = getMultiAdapter((context, layer_request), name='dummy1')
>>> isinstance(tile1, DummyTileWithTemplate)
True
>>> print(tile1())
<b>test!</b>
>>> tile1.__name__
'dummy1'

>>> tile2 = getMultiAdapter((context, request), name='dummy2')
>>> isinstance(tile2, DummyTile)
True
>>> print(tile2())
dummy
>>> tile2.__name__
'dummy2'

>>> tile3 = getMultiAdapter((context, request), name='dummy3')
>>> isinstance(tile3, Tile)
True
>>> print(tile3())
<b>test!</b>
>>> tile3.__name__
'dummy3'

>>> tile4 = getMultiAdapter((context, request), name='dummy4')
>>> isinstance(tile4, PersistentTile)
True
>>> print(tile4())
<b>test!</b>
>>> tile4.__name__
'dummy4'

>>> tile3_layer = getMultiAdapter((context, layer_request), name='dummy3')
>>> isinstance(tile3_layer, DummyTile)
True
>>> print(tile3_layer())
dummy
>>> tile3_layer.__name__
'dummy3'

ESI支持

一些网站可能会选择使用边缘包含或类似机制以延迟的方式渲染瓦片。plone.tiles包括一些支持来帮助渲染ESI占位符。这用于plone.app.blocks以方便ESI渲染。由于ESI通常涉及“愚笨”的替换操作,plone.tiles还提供了一种访问瓦片头和/或正文的方法。

要使用此包,您应首先加载其ZCML配置。

>>> configuration = """\
... <configure
...      xmlns="http://namespaces.zope.org/zope"
...      xmlns:plone="http://namespaces.plone.org/plone"
...      i18n_domain="plone.tiles.tests">
...
...     <include package="zope.component" file="meta.zcml" />
...     <include package="zope.browserpage" file="meta.zcml" />
...
...     <include package="plone.tiles" file="meta.zcml" />
...     <include package="plone.tiles" />
...
... </configure>
... """

>>> from six import StringIO
>>> from zope.configuration import xmlconfig
>>> xmlconfig.xmlconfig(StringIO(configuration))

标记瓷砖为ESI渲染

要使ESI渲染可用,瓦片必须标记有IESIRendered标记接口。我们可以创建一个具有此接口的虚拟瓦片,如下所示

>>> from zope.interface import implementer
>>> from plone.tiles.interfaces import IESIRendered
>>> from plone.tiles import Tile

>>> @implementer(IESIRendered)
... class SampleTile(Tile):
...
...     __name__ = 'sample.tile' # would normally be set by ZCML handler
...
...     def __call__(self):
...         return '<html><head><title>Title</title></head><body><b>My tile</b></body></html>'

在上面的例子中,我们创建了一个简单的HTML字符串。这通常会用页面模板来渲染。

我们在这里手动注册这个瓦片。当然,通常它将通过ZCML注册。

>>> from plone.tiles.type import TileType
>>> from zope.security.permission import Permission
>>> permission = Permission('dummy.Permission')
>>> sampleTileType = TileType(
...     name=u'sample.tile',
...     title=u'Sample tile',
...     description=u'A tile used for testing',
...     add_permission='dummy.Permission',
...     view_permission='dummy.Permission',
...     schema=None)

>>> from zope.component import provideAdapter, provideUtility
>>> from zope.interface import Interface
>>> from plone.tiles.interfaces import IBasicTile

>>> provideUtility(permission, name=u'dummy.Permission')
>>> provideUtility(sampleTileType, name=u'sample.tile')
>>> provideAdapter(SampleTile, (Interface, Interface), IBasicTile, name=u'sample.tile')

ESI查找

当页面渲染时(例如,通过plone.app.blocks之类的系统,但请参见下面),瓦片占位符可能被以下链接替换

<esi:include src="/path/to/context/@@sample.tile/tile1/@@esi-body" />

当这被解析时,它将返回瓦片的主体部分。同样,瓦片的头可以被以下替换

<esi:include src="/path/to/context/@@sample.tile/tile1/@@esi-head" />

为了说明这是如何工作的,让我们创建一个示例上下文,查找在遍历过程中的视图,并实例化瓦片,然后查找ESI视图并渲染它们。

>>> from zope.interface import implementer
>>> from zope.publisher.browser import TestRequest

>>> class IContext(Interface):
...     pass

>>> @implementer(IContext)
... class Context(object):
...     pass

>>> class IntegratedTestRequest(TestRequest):
...     @property
...     def environ(self):
...         return self._environ

>>> context = Context()
>>> request = IntegratedTestRequest()

以下模拟遍历到context/@@sample.tile/tile1

>>> from zope.interface import Interface
>>> from zope.component import getMultiAdapter
>>> tile = getMultiAdapter((context, request), name=u'sample.tile')
>>> tile = tile['tile1'] # simulates sub-path traversal

这个瓦片应该被ESI渲染

>>> IESIRendered.providedBy(tile)
True

在这个时候,我们可以查找ESI视图

>>> head = getMultiAdapter((tile, request), name='esi-head')
>>> head()
Traceback (most recent call last):
...
zExceptions.unauthorized.Unauthorized: Unauthorized()

但只有当我们有所需的权限时,我们才能渲染它们

>>> from AccessControl.SecurityManagement import newSecurityManager
>>> from AccessControl.User import Super
>>> newSecurityManager(None, Super('manager', '', ['Manager'], []))
>>> print(head())
<title>Title</title>
>>> body = getMultiAdapter((tile, request), name='esi-body')
>>> print(body())
<b>My tile</b>

无头部或主体的瓷砖

通常,瓦片应该返回完整的HTML文档。对于esi-headesi-body视图,它们对不返回完整HTML的瓦片是宽容的。如果它们找不到相应的<head /><body />元素,它们将返回未更改的底层瓦片输出。

例如

>>> from plone.tiles.esi import ESITile
>>> class LazyTile(ESITile):
...     __name__ = 'sample.esi1' # would normally be set by ZCML handler
...     def __call__(self):
...         return '<title>Page title</title>'

我们不会为此测试注册此内容,而是直接实例化它

>>> tile = LazyTile(context, request)['tile1']

>>> IESIRendered.providedBy(tile)
True

>>> head = getMultiAdapter((tile, request), name='esi-head')
>>> print(head())
<title>Page title</title>

当然,ESI正文渲染器将返回相同的内容,因为它也无法提取特定的正文

>>> body = getMultiAdapter((tile, request), name='esi-body')
>>> print(body())
<title>Page title</title>

在这种情况下,我们可能得到无效的HTML,因为不允许在主体中使用<title />标签。是否以及如何解决这个问题留给ESI插值实现。

便利类和占位符渲染

plone.tiles.esi模块中可以找到两个便利的基类。这些扩展了标准的TilePersistentTile类,以提供IESIRendered接口。

  • plone.tiles.esi.ESITile,一个短暂的、ESI渲染的瓦片

  • plone.tiles.esi.ESIPersistentTile,一个持久的、ESI渲染的瓦片

这些特别有用,如果您正在创建仅模板的瓷砖并想要ESI渲染。例如

<plone:tile
    name="sample.esitile"
    title="An ESI-rendered tile"
    add_permission="plone.tiles.tests.DummyAdd"
    template="esitile.pt"
    class="plone.tiles.esi.ESITile"
    for="*"
    permission="zope.View"
    />

此外,这些基类实现了一个__call__()方法,如果请求包含将X-ESI-Enabled头设置为字面值‘true’的X-ESI-Enabled头。

占位符是一个简单的HTML <a />标签,可以使用辅助函数substituteESILinks()将其转换为<esi:include />标签。这种间接性的原因是因为HTML文档中不允许使用esi命名空间,并且可能会被使用libxml2 / lxml HTML解析器的转换删除。

现在让我们创建一个简单的ESI瓷砖。为了利用默认渲染,我们应该实现render()方法而不是__call__()。将页面模板设置为index类变量或使用ZCML指令的template属性也将有效。

>>> from plone.tiles.esi import ESITile

>>> class SampleESITile(ESITile):
...     __name__ = 'sample.esitile' # would normally be set by ZCML handler
...
...     def render(self):
...         return '<html><head><title>Title</title></head><body><b>My ESI tile</b></body></html>'

>>> sampleESITileType = TileType(
...     name=u'sample.esitile',
...     title=u'Sample ESI tile',
...     description=u'A tile used for testing ESI',
...     add_permission='dummy.Permission',
...     view_permission='dummy.Permission',
...     schema=None)

>>> provideUtility(sampleESITileType, name=u'sample.esitile')
>>> provideAdapter(SampleESITile, (Interface, Interface), IBasicTile, name=u'sample.esitile')

以下模拟了对context/@@sample.esitile/tile1的遍历

>>> tile = getMultiAdapter((context, request), name=u'sample.esitile')
>>> tile = tile['tile1'] # simulates sub-path traversal

默认情况下,瓷砖以正常方式渲染

>>> print(tile())
<html><head><title>Title</title></head><body><b>My ESI tile</b></body></html>

然而,如果我们通过请求头选择ESI渲染,我们将获得不同的视图

>>> from plone.tiles.interfaces import ESI_HEADER_KEY
>>> request.environ[ESI_HEADER_KEY] = 'true'
>>> print(tile()) # doctest: +NORMALIZE_WHITESPACE
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <body>
        <a class="_esi_placeholder"
           rel="esi"
           href="http://127.0.0.1/@@esi-body?"></a>
    </body>
</html>

这可以通过substituteESILinks()转换为正确的ESI标签

>>> from plone.tiles.esi import substituteESILinks
>>> print(substituteESILinks(tile())) # doctest: +NORMALIZE_WHITESPACE
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:esi="http://www.edge-delivery.org/esi/1.0" xmlns="http://www.w3.org/1999/xhtml">
    <body>
        <esi:include src="http://127.0.0.1/@@esi-body?" />
    </body>
</html>

还可能为头渲染ESI瓷砖。这是通过类变量‘head’(当然,通常在类内部设置)来完成的

>>> SampleESITile.head = True
>>> print(tile()) # doctest: +NORMALIZE_WHITESPACE
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <body>
        <a class="_esi_placeholder"
           rel="esi"
           href="http://127.0.0.1/@@esi-head?"></a>
    </body>
</html>

变更日志

2.3.1 (2021-10-07)

错误修复

  • 修复了plone.protect设置错误的条件。[thet] (#33)

2.3.0 (2020-09-07)

新功能

  • 删除对Plone 4.3的支持。[maurits] (#3130)

错误修复

  • 修复了ComponentLookupError的弃用警告。[maurits] (#3130)

2.2.2 (2020-04-22)

错误修复

  • 小的包装更新。[#1]

  • 修复了Travis上的测试。[maurits] (#33)

2.2.1 (2019-05-01)

错误修复

  • 修复了创建具有填充的富文本字段的内容布局的Mosaic页面时,由于富文本字段被标记为主字段(应该如此),并且默认情况下从未从查询字符串中解析主字段而导致富文本字段为空的问题。[#30]

2.2.0 (2018-11-21)

新功能

  • 添加对Python 3的支持。[pbauer] (#29)

错误修复

  • 初始化towncrier。[gforcada] (#2548)

2.1 (2018-07-05)

  • 移除对zope.app.publisher的依赖,所需的部分早已移动到zope.browserpage。这可能会破坏Plone 4.2的支持(已删除)。[jensens]

2.0.0 (2018-04-13)

  • 修复许可证分类器。

2.0.0b3 (2017-08-22)

破坏性更改

  • 修复查询字符串编解码器以始终跳过主字段 [datakurre]

2.0.0b2 (2017-03-29)

错误修复

  • 在模板渲染期间在index()内部不吞下AttributeError。[hvelarde]

  • 修复代码分析错误。[gforcada]

2.0.0b1 (2017-02-24)

破坏性更改

  • 瓷砖不再在__call__中添加相对的X-Tile-Url头,但瓷砖在遍历时仍然添加绝对的头,如果请求未通过CSRF授权,则它在渲染后会被移除。[datakurre]

  • 通用的ESI辅助检查现在确保请求有权根据注册的视图权限渲染瓷砖。[datakurre]

  • 对ESI辅助视图的请求的事务会自动中止,因为ESI请求始终应该是不可变的GET请求 [datakurre]

  • 对于渲染瓷砖的请求,现在使用X-Theme-Disabled-header禁用了plone.app.theming (transform) [datakurre]

  • 对于具有正确CSRF令牌的瓷砖请求,跳过了plone.protect的ProtectTransform以防止其对渲染单个瓷砖的瓷砖编辑器产生副作用 [datakurre]

新功能

  • 为ESI瓷砖视图添加了X-Frame-Options -头,与plone.protect的行为相匹配 [datakurre]

错误修复

  • 修复了ESI瓷砖辅助视图未获得正确的Cache-Control-头的问题,因为ESI辅助视图没有被acquisition包装 [datakurre]

1.8.2 (2017-01-10)

错误修复

  • 修复了瞬态瓷砖无法使用Unicode字符串列表编码数据的问题 [datakurre]

  • 删除未使用的unittest2要求 [tomgross]

1.8.1 (2016-11-24)

错误修复

  • 修复嵌套Unicode(如plone.app.querystring)中的编码错误 [tomgross]

  • 重构测试 [tomgross]

1.8.0 (2016-09-13)

新功能

  • 提供ignore_querystring表单指令,以标记不允许从查询字符串数据中获取默认数据的特定拼图字段 [vangheem]

1.7.1 (2016-09-12)

  • 修复collective.cover因默认数据管理器内部更改而损坏的问题 [datakurre]

1.7.0 (2016-09-08)

新功能

  • 选项:使用ITileDataStorage适配器自定义存储层 [datakurre]

1.6.1 (2016-09-07)

错误修复

  • 重新格式化文档。 [gforcada]

  • 添加coveralls盾牌。 [gforcada]

1.6 (2016-06-27)

  • 让TileType实例(拼图注册实用工具)了解视图权限。 [jensens]

1.5.2 (2016-03-28)

  • 修复ESI href未正确替换的问题。 [jensens]

  • 在README.rst中添加“ZCML参考”部分。 [jensens]

  • PEP8,代码分析,文档和打包修复。 [jensens, mauritsvanrees]

1.5.1 (2015-10-09)

  • 修复解码Choice值类型的List类型 [vangheem]

1.5.0 (2015-09-04)

  • 通过在拼图URL中添加X-Tile-Persistent=1来支持使用持久数据管理器覆盖临时数据管理器 [datakurre]

  • 修复持久数据管理器,使其从查询字符串中读取默认值 [vangheem]

1.4.0 (2015-05-25)

  • 添加支持将字典字段编码到拼图URL中 [datakurre]

  • 修复保存或删除临时拼图数据时修改当前请求的问题 [datakurre]

  • 修复拼图数据中的非ASCII字符引发UnicodeEncode/DecodeError的问题 [datakurre]

1.3.0 (2015-04-21)

  • 修复在TileType构造函数中默认将edit_permission和delete_permission设置为add_permission [datakurre]

  • 修复TileType构造函数调用中的参数顺序问题 [datakurre]

  • 修复absolute_url-adapter以回退到相对URL [datakurre]

  • 添加响应,包括绝对X-Tile-Url标题 [bloodbare]

1.2 (2012-11-07)

  • 为tiletype添加图标属性 [garbas]

  • 通过X-Tile-Url传递的URL应相对于当前上下文 [garbas]

  • 添加对编辑和删除拼图更健壮的权限支持 [cewing calvinhp]

1.1 (2012-06-22)

  • X-Tile-Uid标题在包含拼图ID的拼图视图中传递。 [garbas]

  • PEP 8/Pyflakes(忽略E121、E123、E126、E127和E501)。 [hvelarde]

1.0 (2012-05-14)

  • 重构ESI支持。要使用ESITileESIPersistentTile基类,您应使用通过ZCML分配的模板或重写render()方法。有关详细信息,请参阅esi.rst。 [optilude]

  • 国际化拼图指令的标题和描述。 [vincentfretin]

  • 将json编码的参数作为临时拼图的首选选项。 [dukebody]

  • 使用适配器进行Zope Publisher类型转换 [dukebody]

  • 条件性地支持z3c.relationfield的RelationChoice字段 [dukebody]

  • 忽略没有固定类型的字段(如zope.schema.Choice)的类型转换 [dukebody]

1.0a1 (2010-05-17)

  • 初始版本。

项目详情


下载文件

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

源分发

plone.tiles-2.3.1.tar.gz (69.0 kB 查看哈希值)

上传时间

构建分发

plone.tiles-2.3.1-py2.py3-none-any.whl (56.5 kB 查看哈希值)

上传时间 Python 2 Python 3

由以下支持