{ "info": { "author": "Martin Aspeli", "author_email": "optilude@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Plone", "Framework :: Plone :: 4.3", "Framework :: Plone :: 5.0", "Framework :: Plone :: 5.1", "Framework :: Plone :: 5.2", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "===========\nplone.tiles\n===========\n\n.. image:: https://secure.travis-ci.org/plone/plone.tiles.png?branch=master\n :alt: Travis CI badge\n :target: http://travis-ci.org/plone/plone.tiles\n\n.. image:: https://coveralls.io/repos/plone/plone.tiles/badge.png?branch=master\n :alt: Coveralls badge\n :target: https://coveralls.io/r/plone/plone.tiles\n\n``plone.tiles`` implements a low-level, non-Plone/Zope2-specific support for creating \"tiles\".\n\n.. contents::\n\n\nIntroduction\n============\n\nFor the purposes of this package,\na tile is a browser view and an associated utility providing some metadata about that view.\nThe metadata includes a title and description,\nan 'add' permission and optionally a schema interface describing configurable aspects of the tile.\nThe idea is that a UI (such as Mosaic) can present the user with a list of insertable tiles and optionally render a form to configure the tile upon insertion.\n\nA tile is inserted into a layout as a link:\n\n.. code-block:: xml\n\n \n\nThe sub-path (``tile1`` in this case) is used to set the tile ``id`` attribute.\nThis allows the tile to know its unique id, and, in the case of persistent tiles, look up its data.\n``sample.tile`` is the name of the browser view that implements the tile.\nThis is made available as the ``__name__`` attribute.\nOther parameters may be turned into tile data, available under the ``data`` attribute, a dict, for regular tiles.\nFor persistent tiles\n(those deriving from the ``PersistentTile`` base class),\nthe data is fetched from annotations instead,\nbased on the tile id.\n\nThere are three interfaces describing tiles in this package:\n\n``IBasicTile``\n is the low-level interface for tiles.\n It extends ``IBrowserView`` to describe the semantics of the ``__name__`` and ``id`` attributes.\n``ITile``\n describes a tile that can be configured with some data.\n The data is accessible via a dict called ``data``.\n The default implementation of this interface, ``plone.tiles.Tile``,\n will use the schema of the tile type and the query string (``self.request.form``) to construct that dictionary.\n This interface also describes an attribute ``url``,\n which gives the canonical tile URL,\n including the id sub-path and any query string parameters.\n (Note that tiles also correctly implement ``IAbsoluteURL``.)\n``IPersistentTile``\n describes a tile that stores its configuration in object annotations,\n and is needed when configuration values cannot be encoded into a query string.\n The default implementation is in ``plone.tiles.PersistentTile``.\n To make it possible to have several tiles of a given type on the same layout,\n the annotations are keyed by the tile ``__name__``.\n\nInternally tiles are described by ``ITileType``.\nIt contains attributes for the tile name, title, description, add permission and schema (if required).\n\nA properly configured tile consists of\n\n- a utility providing ``ITileType`` with the same name as the tile browser view.\n- a browser view providing ``IBasicTile`` or one of its derivatives.\n\nThe directive ```` is used to register both of these components in one go.\n\nTo support creation of appropriate tile links, ``plone.tiles.data`` contains two methods:\n\n1) ``encode()`` and\n2) ``decode()``\n\nto help turn a data dictionary into a query string and turn a `request.form` dict into a data dict that complies with a tile's schema interface.\n\nIn addition to the default tile configuration sources\n(transient query strings for ITile and persistent annotations for IPersistentTile),\nit is possible to define custom tile configuration sources by registering dictionary like\nITileDataStorage-adapter for your context, request and tile interface.\nWill be accessed dictionary like by tile data managers with tile id and its data dictionary.\n\nCreating a Simple Tile\n----------------------\n\nThe most basic tile looks like this:\n\n.. code-block:: python\n\n from plone.tiles import Tile\n\n class MyTile(Tile):\n\n def __call__(self):\n return u'

Hello world

'\n\nNote that the tile is expected to return a complete HTML document.\nThis will be interpolated into the page output according to the following rules:\n\n* The contents of the tile's ```` section is appended to the output document's ```` section.\n* The contents of the tile's ```` section will replace the tile placeholder as indicated by the tile link.\n\nNote that this package does *not* provide these interpolations.\nFor a Plone implementation of the interpolation algorithm, see `plone.app.blocks`_.\n\nIf you require a persistent tile, subclass ``plone.tiles.PersistentTile`` instead.\nYou may also need a schema interface if you want a configurable transient or persistent tile.\n\nTo register the tile, use ZCML like this:\n\n.. code-block:: xml\n\n \n\n \n\n \n\nThe first five attributes describe the tile by configuring an appropriate ``ITileType`` directive.\nThe rest mimics the ```` directive,\nso you can specify a ``template`` file and omit the ``class``, or use both a ``template`` and ``class``.\n\nIf you want to register a persistent tile with a custom schema, but a template only, you can do e.g.:\n\n.. code-block:: xml\n\n \n\nIf you want to override an existing tile, e.g. with a new layer or more specific context,\nyou *must* omit the tile metadata (title, description, icon, add permission or schema).\nIf you include any metadata you will get a conflict error on Zope startup.\nThis example shows how to use a different template for our tile:\n\n.. code-block:: xml\n\n \n\nZCML Reference\n--------------\n\nThe ``plone:tile`` directive uses the namespace ``xmlns:plone=\"http://namespaces.plone.org/plone\"``.\nIn order to enable it loading of its ``meta.zcml`` is needed, use:\n\n.. code-block:: xml\n\n \n\nWhen registering a tile, in the background two registrations are done:\n\n1) How to **add** the tile (registered as a utility component as an instance of ``plone.tiles.type.TileType``).\n\n It is possible to register a tile without adding capabilities.\n However, such a tile needs to be directly called, there won't be any TTW adding possible.\n\n This registration can be done once only.\n\n This registration uses the following attributes:\n\n - ``name`` (required)\n - ``title`` (required)\n - ``description`` (optional)\n - ``icon`` (optional)\n - ``permission`` (required)\n - ``add_permission`` (required for adding capabilities)\n - ``edit_permission`` (optional, default to add_permission)\n - ``delete_permission`` (optional, default to add_permission)\n - ``schema`` (optional)\n\n2) How to **render** the tile (as a usual page).\n\n It is possible to register different renderers for the same ``name`` but for different contexts (``for`` or ``layer``).\n\n This registration uses the following attributes:\n\n - ``name`` (required)\n - ``for`` (optional)\n - ``layer`` (optional)\n - ``class`` (this or ``template`` or both is required)\n - ``template`` (this or ``class`` or both is required)\n - ``permission`` (required)\n\nThe **directives attributes** have the following meaning:\n\n``name``\n A unique, dotted name for the tile.\n\n``title``\n A user friendly title, used when configuring the tile.\n\n``description``\n A longer summary of the tile's purpose and function.\n\n``icon``\n Image that represents tile purpose and function.\n\n``permission``\n Name of the permission required to view the tile.\n\n``add_permission``\n Name of the permission required to instantiate the tile.\n\n``edit_permission``\n Name of the permission required to modify the tile.\n Defaults to the ``add_permission``.\n\n``delete_permission``\n Name of the permission required to remove the tile.\n Defaults to the ``add_permission``.\n\n``schema``\n Configuration schema for the tile.\n This is used to create standard add/edit forms.\n\n``for``\n The interface or class this tile is available for.\n\n``layer``\n The layer (request marker interface) the tile is available for.\n\n``class``\n Class implementing this tile. A browser view providing ``IBasicTile`` or one of its derivatives.\n\n``template``\n The name of a template that renders this tile.\n Refers to a file containing a page template.\n\n\nFurther Reading\n---------------\n\nSee `tiles.rst` and `directives.rst` for more details.\n\n.. _plone.app.blocks: http://pypi.python.org/pypi/plone.app.blocks\n\n\nTiles in detail\n===============\n\nTiles are a form of view component used to compose pages.\nThink of a tile as a view describing one part of a page,\nthat can be configured with some data described by a schema and inserted into a layout via a dedicated GUI.\n\nLike a browser view, a tile can be traversed to and published on its own.\nThe tile should then return a full HTML page,\nincluding a with any required resources,\nand a with the visible part of the tile.\nThis will then be merged into the page, using a system such as ``plone.app.blocks``.\n\nThe API in this package provides support for tiles being configured according to a schema,\nwith data either passed on the query string (transient tiles) or retrieved from annotations (persistent tiles).\n\nNote that there is no direct UI support in this package,\nso the forms that allow users to construct and edit tiles must live elsewhere.\nYou may be interested in ``plone.app.tiles`` and ``plone.app.mosaic`` for that purpose.\n\nTo use the package, you should first load its ZCML configuration:\n\n.. code-block:: python\n\n >>> configuration = \"\"\"\\\n ... \n ...\n ... \n ... \n ...\n ... \n ... \n ...\n ... \n ... \"\"\"\n\n >>> from six import StringIO\n >>> from zope.configuration import xmlconfig\n >>> xmlconfig.xmlconfig(StringIO(configuration))\n\nA simple transient tile\n-----------------------\n\nA basic tile is a view that implements the ``ITile`` interface.\nThe easiest way to do this is to subclass the ``Tile`` class:\n\n.. code-block:: python\n\n >>> from plone.tiles import Tile\n >>> class SampleTile(Tile):\n ...\n ... __name__ = 'sample.tile' # would normally be set by a ZCML handler\n ...\n ... def __call__(self):\n ... return 'My tile'\n\nThe tile is a browser view:\n\n.. code-block:: python\n\n >>> from plone.tiles.interfaces import ITile\n >>> ITile.implementedBy(SampleTile)\n True\n\n >>> from zope.publisher.interfaces.browser import IBrowserView\n >>> IBrowserView.implementedBy(SampleTile)\n True\n\nThe tile instance has a ``__name__`` attribute\n(normally set at class level by the ```` ZCML directive),\nas well as a property ``id``.\nThe id may be set explicitly, either in code, or by sub-path traversal.\nFor example, if the tile name is ``example.tile``,\nthe id may be set to ``tile1`` using an URL like ``http://example.com/foo/@@example.tile/tile1``.\n\nThis tile is registered as a normal browser view,\nalongside a utility that provides some information about the tile itself.\nNormally, this is done using the ```` directive.\nHere's how to create one manually:\n\n.. code-block:: python\n\n >>> from plone.tiles.type import TileType\n >>> sampleTileType = TileType(\n ... u'sample.tile',\n ... u'Sample tile',\n ... 'dummy.Permission',\n ... 'dummy.Permission',\n ... description=u'A tile used for testing',\n ... schema=None)\n\nThe name should match the view name and the name the utility is registered under.\nThe title and description may be used by the UI.\nThe add permission is the name of a permission that will be required to insert the tile.\nThe schema attribute may be used to indicate schema interface describing the tile's configurable data - more on this below.\n\nTo register a tile in ZCML, we could do:\n\n.. code-block:: xml\n\n \n\n.. note:: The tile name should be a dotted name, prefixed by a namespace you control.\n It's a good idea to use a package name for this purpose.\n\nIt is also possible to specify a ``layer`` or ``template`` like the ``browser:page`` directive, as well as a ``schema``,\nwhich we will describe below.\n\nWe'll register the sample tile directly here, for later testing.\n\n.. code-block:: python\n\n >>> from zope.component import provideAdapter, provideUtility\n >>> from zope.interface import Interface\n >>> from plone.tiles.interfaces import IBasicTile\n\n >>> provideUtility(sampleTileType, name=u'sample.tile')\n >>> provideAdapter(SampleTile, (Interface, Interface), IBasicTile, name=u'sample.tile')\n\nTile traversal\n--------------\n\nTiles are publishable as a normal browser view.\nThey will normally be called with a sub-path that specifies a tile id.\nThis allows tiles to be made aware of their instance name.\nThe id is unique within the page layout where the tile is used,\nand may be the basis for looking up tile data.\n\nFor example, a tile may be saved in a layout as a link like:\n\n.. code-block:: html\n\n \n\n(The idea here is that the tile link tells the rendering algorithm to replace\nthe element with id ``mytile`` with the body of the rendered tile - see\n``plone.app.blocks`` for details).\n\nLet's create a sample context,\nlook up the view as it would be during traversal,\nand verify how the tile is instantiated.\n\n.. code-block:: python\n\n >>> from zope.component import getMultiAdapter\n >>> from zope.interface import classImplements\n >>> from zope.interface import Interface\n >>> from zope.interface import implementer\n >>> from zope.publisher.browser import TestRequest\n >>> from zope.annotation.interfaces import IAnnotations\n >>> from zope.annotation.interfaces import IAttributeAnnotatable\n >>> classImplements(TestRequest, IAttributeAnnotatable)\n\n >>> class IContext(Interface):\n ... pass\n\n >>> @implementer(IContext)\n ... class Context(object):\n ... pass\n\n >>> context = Context()\n >>> request = TestRequest()\n\n >>> tile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> tile = tile['tile1'] # simulates sub-path traversal\n\nThe tile will now be aware of its name and id:\n\n.. code-block:: python\n\n >>> isinstance(tile, SampleTile)\n True\n >>> tile.__parent__ is context\n True\n >>> tile.id\n 'tile1'\n >>> tile.__name__\n 'sample.tile'\n\nThe sub-path traversal is implemented using a custom ``__getitem__()`` method.\nTo look up a view on a tile,\nyou can traverse to it *after* you've traversed to the id sub-path:\n\n.. code-block:: python\n\n >>> from zope.component import adapts\n >>> from zope.interface import Interface\n >>> from zope.publisher.browser import BrowserView\n >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer\n\n >>> class TestView(BrowserView):\n ... adapts(SampleTile, IDefaultBrowserLayer)\n ... def __call__(self):\n ... return 'Dummy view'\n\n >>> provideAdapter(TestView, provides=Interface, name='test-view')\n\n >>> tile.id is not None\n True\n >>> tile['test-view']()\n 'Dummy view'\n\nIf there is no view and we have an id already, we will get a ``KeyError``:\n\n.. code-block:: python\n\n >>> tile['not-known'] # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n KeyError: 'not-known'\n\nTo ensure consistency with Zope's various tangles publication machines,\nit is also possible to traverse using the ``publishTraverse`` method:\n\n.. code-block:: python\n\n >>> tile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> tile = tile.publishTraverse(request, 'tile1') # simulates sub-path traversal\n\n >>> isinstance(tile, SampleTile)\n True\n >>> tile.__parent__ is context\n True\n >>> tile.id\n 'tile1'\n >>> tile.__name__\n 'sample.tile'\n\nTransient tile data\n-------------------\n\nLet us now consider how tiles may have data.\nIn the simplest case, tile data is passed on the query string, and described according to a schema.\nA simple schema may look like:\n\n.. code-block:: python\n\n >>> import zope.schema\n\n >>> class ISampleTileData(Interface):\n ... title = zope.schema.TextLine(title=u'Tile title')\n ... cssClass = zope.schema.ASCIILine(title=u'CSS class to apply')\n ... count = zope.schema.Int(title=u'Number of things to show in the tile')\n\nWe would normally have listed this interface when registering this tile in ZCML.\nWe can simply update the utility here.\n\n.. code-block:: python\n\n >>> sampleTileType.schema = ISampleTileData\n\nTile data is represented by a simple dictionary. For example:\n\n.. code-block:: python\n\n >>> data = {'title': u'My title', 'count': 5, 'cssClass': 'foo'}\n\nThe idea is that a tile add form is built from the schema interface, and its data saved to a dictionary.\n\nFor transient tiles,\nthis data is then encoded into the tile query string.\nTo help with this,\na utility function can be used to encode a dict to a query string,\napplying Zope form marshalers according to the types described in the schema:\n\n.. code-block:: python\n\n >>> from plone.tiles.data import encode\n >>> encode(data, ISampleTileData)\n 'title=My+title&cssClass=foo&count%3Along=5'\n\nThe ``count%3Along=5`` bit is the encoded version of ``count:long=5``.\n\nNote that not all field types may be saved.\nIn particular, object, interface, set or frozen set fields may not be saved, and will result in a ``KeyError``.\nLengthy text fields or bytes fields with binary data may also be a problem.\nFor these types of fields,\nlook to use persistent tiles instead.\n\nFurthermore, the conversion may not be perfect.\nFor example, Zope's form marshalers cannot distinguish between unicode and ascii fields.\nTherefore, there is a corresponding ``decode()`` method that may be used to ensure that the values match the schema:\n\n.. code-block:: python\n\n >>> marshaled = {'title': u'My tile', 'count': 5, 'cssClass': u'foo'}\n\n >>> from plone.tiles.data import decode\n >>> sorted(decode(marshaled, ISampleTileData).items())\n [('count', 5), ('cssClass', 'foo'), ('title', 'My tile')]\n\nWhen saved into a layout, the tile link would now look like:\n\n.. code-block:: html\n\n \n\nLet's simulate traversal once more and see how the data is now available to the tile instance:\n\n.. code-block:: python\n\n >>> context = Context()\n >>> request = TestRequest(form={'title': u'My title', 'count': 5, 'cssClass': u'foo'})\n\n >>> tile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> tile = tile['tile1']\n\n >>> sorted(tile.data.items())\n [('count', 5), ('cssClass', 'foo'), ('title', 'My title')]\n\nNotice also how the data has been properly decoded according to the schema.\n\nTransient tiles will get their data directly from the request parameters but,\nif a `_tiledata` JSON-encoded parameter is present in the request,\nthis one will be used instead:\n\n.. code-block:: python\n\n >>> import json\n\n >>> request = TestRequest(form={\n ... 'title': u'My title', 'count': 5, 'cssClass': u'foo',\n ... '_tiledata': json.dumps({'title': u'Your title', 'count': 6, 'cssClass': u'bar'})\n ... })\n >>> tile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> tile = tile['tile1']\n\n >>> sorted(tile.data.items())\n [('count', 6), ('cssClass', 'bar'), ('title', 'Your title')]\n\nThis way we can use transient tiles safely in contexts where the tile data can be confused with raw data coming from a form, e.g. in an edit form.\n\nThe tile data manager\n---------------------\n\nThe ``data`` attribute is a convenience attribute to get hold of a (cached) copy of the data returned by an ``ITileDataManager``.\nThis interface provides three methods:\n``get()``, to return the tile's data,\n``set()``, to update it with a new dictionary of data,\nand ``delete()``, to delete the data.\n\nThis adapter is mostly useful for writing UI around tiles.\nUsing our tile above, we can get the data like so:\n\n.. code-block:: python\n\n >>> from plone.tiles.interfaces import ITileDataManager\n >>> dataManager = ITileDataManager(tile)\n >>> dataManager.get() == tile.data\n True\n\nWe can also update the tile data:\n\n.. code-block:: python\n\n >>> dataManager.set({'count': 1, 'cssClass': 'bar', 'title': u'Another title'})\n >>> sorted(dataManager.get().items())\n [('count', 1), ('cssClass', 'bar'), ('title', 'Another title')]\n\nThe data can also be deleted:\n\n.. code-block:: python\n\n >>> dataManager.delete()\n >>> sorted(dataManager.get().items())\n [('count', None), ('cssClass', None), ('title', None)]\n\nNote that in the case of a transient tile,\nall we are doing is modifying the ``form`` dictionary of the request\n(or the `_tiledata` parameter of this dictionary, if present).\nThe data needs to be encoded into the query string,\neither using the ``encode()`` method or via the tile's ``IAbsoluteURL`` adapter (see below for details).\n\nFor persistent tiles, the data manager is a bit more interesting.\n\nPersistent tiles\n----------------\n\nNot all types of data can be placed in a query string.\nFor more substantial storage requirements,\nyou can use persistent tiles, which store data in annotations.\n\n.. note:: If you have more intricate requirements,\n you can also write your own ``ITileDataManager`` to handle data retrieval.\n In this case, you probably still want to derive from ``PersistentTile``,\n to get the appropriate ``IAbsoluteURL`` adapter, among other things.\n\nFirst, we need to write up annotations support.\n\n.. code-block:: python\n\n >>> from zope.annotation.attribute import AttributeAnnotations\n >>> provideAdapter(AttributeAnnotations)\n\nWe also need a context that is annotatable.\n\n.. code-block:: python\n\n >>> from zope.annotation.interfaces import IAttributeAnnotatable\n >>> from zope.interface import alsoProvides\n >>> alsoProvides(context, IAttributeAnnotatable)\n\nNow, let's create a persistent tile with a schema.\n\n.. code-block:: python\n\n >>> class IPersistentSampleData(Interface):\n ... text = zope.schema.Text(title=u'Detailed text', missing_value=u'Missing!')\n\n >>> from plone.tiles import PersistentTile\n >>> class PersistentSampleTile(PersistentTile):\n ...\n ... __name__ = 'sample.persistenttile' # would normally be set by ZCML handler\n ...\n ... def __call__(self):\n ... return u'You said %s' % self.data['text']\n\n >>> persistentSampleTileType = TileType(\n ... u'sample.persistenttile',\n ... u'Persistent sample tile',\n ... 'dummy.Permission',\n ... 'dummy.Permission',\n ... description=u'A tile used for testing',\n ... schema=IPersistentSampleData)\n\n >>> provideUtility(persistentSampleTileType, name=u'sample.persistenttile')\n >>> provideAdapter(PersistentSampleTile, (Interface, Interface), IBasicTile, name=u'sample.persistenttile')\n\nWe can now traverse to the tile as before.\nBy default, there is no data, and the field's missing value will be used.\n\n.. code-block:: python\n\n >>> request = TestRequest()\n\n >>> tile = getMultiAdapter((context, request), name=u'sample.persistenttile')\n >>> tile = tile['tile2']\n >>> tile.__name__\n 'sample.persistenttile'\n >>> tile.id\n 'tile2'\n\n >>> tile()\n 'You said Missing!'\n\nAt this point, there is nothing in the annotations for the type either:\n\n.. code-block:: python\n\n >>> list(dict(getattr(context, '__annotations__', {})).keys())\n []\n\nWe can write data to the context's annotations using an ``ITileDataManager``:\n\n.. code-block:: python\n\n >>> dataManager = ITileDataManager(tile)\n >>> dataManager.set({'text': 'Hello!'})\n\nThis writes data to annotations:\n\n.. code-block:: python\n\n >>> list(dict(context.__annotations__).keys())\n ['plone.tiles.data.tile2']\n >>> context.__annotations__[u'plone.tiles.data.tile2']\n {'text': 'Hello!'}\n\nWe can get this from the data manager too, of course:\n\n.. code-block:: python\n\n >>> dataManager.get()\n {'text': 'Hello!'}\n\nNote that as with transient tiles,\nthe ``data`` attribute is cached and will only be looked up once.\n\nIf we now look up the tile again, we will get the new value:\n\n.. code-block:: python\n\n >>> tile = getMultiAdapter((context, request), name=u'sample.persistenttile')\n >>> tile = tile['tile2']\n >>> tile()\n 'You said Hello!'\n\n >>> tile.data\n {'text': 'Hello!'}\n\nWe can also remove the annotation using the data manager:\n\n.. code-block:: python\n\n >>> dataManager.delete()\n >>> sorted(dict(context.__annotations__).items()) # doctest: +ELLIPSIS\n []\n\nOverriding transient data with persistent\n-----------------------------------------\n\nTo be able to re-use the same centrally managed tile based layouts for multiple context objects,\nbut still allow optional customization for tiles,\nit's possible to override otherwise transient tile configuration with context specific persistent configuration.\n\nThis is done by either by setting a client side request header or query param ``X-Tile-Persistent``:\n\n.. code-block:: python\n\n >>> request = TestRequest(\n ... form={'title': u'My title', 'count': 5, 'cssClass': u'foo',\n ... 'X-Tile-Persistent': 'yes'}\n ... )\n\nYet, just adding the flag, doesn't create new persistent annotations on GET requests:\n\n.. code-block:: python\n\n >>> tile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> ITileDataManager(tile)\n \n\n >>> sorted(ITileDataManager(tile).get().items(), key=lambda x: x[0])\n [('count', 5), ('cssClass', 'foo'), ('title', 'My title')]\n\n >>> list(IAnnotations(context).keys())\n []\n\nThat's because the data is persistent only once it's set:\n\n.. code-block:: python\n\n >>> data = ITileDataManager(tile).get()\n >>> data.update({'count': 6})\n >>> ITileDataManager(tile).set(data)\n >>> list(IAnnotations(context).keys())\n ['plone.tiles.data...']\n\n >>> sorted(list(IAnnotations(context).values())[0].items(), key=lambda x: x[0])\n [('count', 6), ('cssClass', 'foo'), ('title', 'My title')]\n\n >>> sorted(ITileDataManager(tile).get().items(), key=lambda x: x[0])\n [('count', 6), ('cssClass', 'foo'), ('title', 'My title')]\n\nWithout the persistent flag, fixed transient data would be returned:\n\n.. code-block:: python\n\n >>> request = TestRequest(\n ... form={'title': u'My title', 'count': 5, 'cssClass': u'foo'},\n ... )\n >>> tile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> ITileDataManager(tile)\n \n\n >>> data = ITileDataManager(tile).get()\n >>> sorted(data.items(), key=lambda x: x[0])\n [('count', 5), ('cssClass', 'foo'), ('title', 'My title')]\n\nFinally, the persistent override could also be deleted:\n\n.. code-block:: python\n\n >>> request = TestRequest(\n ... form={'title': u'My title', 'count': 5, 'cssClass': u'foo',\n ... 'X-Tile-Persistent': 'yes'}\n ... )\n >>> tile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> ITileDataManager(tile)\n \n\n >>> sorted(ITileDataManager(tile).get().items(), key=lambda x: x[0])\n [('count', 6), ('cssClass', 'foo'), ('title', 'My title')]\n\n >>> ITileDataManager(tile).delete()\n >>> list(IAnnotations(context).keys())\n []\n\n >>> sorted(ITileDataManager(tile).get().items(), key=lambda x: x[0])\n [('count', 5), ('cssClass', 'foo'), ('title', 'My title')]\n\n >>> request = TestRequest(\n ... form={'title': u'My title', 'count': 5, 'cssClass': u'foo'},\n ... )\n >>> tile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> ITileDataManager(tile)\n \n\n\nTile URLs\n---------\n\nAs we have seen, tiles have a canonical URL.\nFor transient tiles, this may also encode some tile data.\n\nIf you have a tile instance and you need to know the canonical tile URL,\nyou can use the ``IAbsoluteURL`` API.\n\nFor the purposes of testing,\nwe need to ensure that we can get an absolute URL for the context.\nWe'll achieve that with a dummy adapter:\n\n.. code-block:: python\n\n >>> from zope.interface import implementer\n >>> from zope.component import adapter\n\n >>> from zope.traversing.browser.interfaces import IAbsoluteURL\n >>> from zope.publisher.interfaces.http import IHTTPRequest\n\n >>> @implementer(IAbsoluteURL)\n ... @adapter(IContext, IHTTPRequest)\n ... class DummyAbsoluteURL(object):\n ...\n ... def __init__(self, context, request):\n ... self.context = context\n ... self.request = request\n ...\n ... def __unicode__(self):\n ... return u'http://example.com/context'\n ... def __str__(self):\n ... return u'http://example.com/context'\n ... def __call__(self):\n ... return self.__str__()\n ... def breadcrumbs(self):\n ... return ({'name': u'context', 'url': 'http://example.com/context'},)\n >>> provideAdapter(DummyAbsoluteURL, name=u'absolute_url')\n >>> provideAdapter(DummyAbsoluteURL)\n\n >>> from zope.traversing.browser.absoluteurl import absoluteURL\n >>> from zope.component import getMultiAdapter\n\n >>> context = Context()\n >>> request = TestRequest(form={'title': u'My title', 'count': 5, 'cssClass': u'foo'})\n >>> transientTile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> transientTile = transientTile['tile1']\n\n >>> absoluteURL(transientTile, request)\n 'http://example.com/context/@@sample.tile/tile1?title=My+title&cssClass=foo&count%3Along=5'\n\n >>> getMultiAdapter((transientTile, request), IAbsoluteURL).breadcrumbs() == \\\n ... ({'url': 'http://example.com/context', 'name': u'context'},\n ... {'url': 'http://example.com/context/@@sample.tile/tile1', 'name': 'sample.tile'})\n True\n\nFor convenience, the tile URL is also available under the ``url`` property:\n\n.. code-block:: python\n\n >>> transientTile.url\n 'http://example.com/context/@@sample.tile/tile1?title=My+title&cssClass=foo&count%3Along=5'\n\nThe tile absolute URL structure remains unaltered if the data is\ncoming from a `_tiledata` JSON-encoded parameter instead of from the request\nparameters directly:\n\n.. code-block:: python\n\n >>> request = TestRequest(form={'_tiledata': json.dumps({'title': u'Your title', 'count': 6, 'cssClass': u'bar'})})\n >>> transientTile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> transientTile = transientTile['tile1']\n\n >>> absoluteURL(transientTile, request)\n 'http://example.com/context/@@sample.tile/tile1?title=Your+title&cssClass=bar&count%3Along=6'\n\nFor persistent tiles, the are no data parameters:\n\n.. code-block:: python\n\n >>> context = Context()\n >>> request = TestRequest(form={'title': u'Ignored', 'count': 0, 'cssClass': u'ignored'})\n >>> persistentTile = getMultiAdapter((context, request), name=u'sample.persistenttile')\n >>> persistentTile = persistentTile['tile2']\n\n >>> absoluteURL(persistentTile, request)\n 'http://example.com/context/@@sample.persistenttile/tile2'\n\n >>> getMultiAdapter((persistentTile, request), IAbsoluteURL).breadcrumbs() == \\\n ... ({'url': 'http://example.com/context', 'name': u'context'},\n ... {'url': 'http://example.com/context/@@sample.persistenttile/tile2', 'name': 'sample.persistenttile'})\n True\n\nAnd again, for convenience:\n\n.. code-block:: python\n\n >>> persistentTile.url\n 'http://example.com/context/@@sample.persistenttile/tile2'\n\nIf the tile doesn't have an id, we don't get any sub-path:\n\n.. code-block:: python\n\n >>> request = TestRequest(form={'title': u'My title', 'count': 5, 'cssClass': u'foo'})\n >>> transientTile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> absoluteURL(transientTile, request)\n 'http://example.com/context/@@sample.tile?title=My+title&cssClass=foo&count%3Along=5'\n\n >>> request = TestRequest()\n >>> persistentTile = getMultiAdapter((context, request), name=u'sample.persistenttile')\n >>> absoluteURL(persistentTile, request)\n 'http://example.com/context/@@sample.persistenttile'\n\n\nWe can also disallow query parameters providing data into our tiles\n\n.. code-block:: python\n\n >>> import zope.schema\n >>> from plone.tiles.directives import ignore_querystring\n\n >>> class ISampleTileData(Interface):\n ... unfiltered = zope.schema.Text(title=u'Unfiltered data')\n ... ignore_querystring('unfiltered')\n ... filtered = zope.schema.Text(title=u'filtered data')\n\n >>> sampleTileType.schema = ISampleTileData\n\nAnd create a tile with our new schema\n\n.. code-block:: python\n\n >>> from plone.tiles import Tile\n >>> class SampleTile(Tile):\n ...\n ... __name__ = 'sample.unfilteredtile' # would normally be set by a ZCML handler\n ...\n ... def __call__(self):\n ... return '
{}{}
'.format(\n ... self.data.get('unfiltered') or '',\n ... self.data.get('filtered') or '')\n\nWe'll register the sample unfiltered tile directly here, for testing.\n\n.. code-block:: python\n\n >>> from zope.component import provideAdapter, provideUtility\n >>> from zope.interface import Interface\n >>> from plone.tiles.interfaces import IBasicTile\n\n >>> provideUtility(sampleTileType, name=u'sample.unfilteredtile')\n >>> provideAdapter(SampleTile, (Interface, Interface), IBasicTile, name=u'sample.unfilteredtile')\n\n\nLet's simulate traversal to test if form data is used:\n\n.. code-block:: python\n\n >>> context = Context()\n >>> request = TestRequest(form={'unfiltered': 'foobar', 'filtered': 'safe'})\n\n >>> tile = getMultiAdapter((context, request), name=u'sample.unfilteredtile')\n >>> tile = tile['tile1']\n\nData should not contain unfiltered field:\n\n.. code-block:: python\n\n >>> sorted(tile.data.items())\n [('filtered', 'safe')]\n\n\nRendering the tile should not include ignored query string:\n\n.. code-block:: python\n\n >>> 'foobar' in tile()\n False\n\n >>> tile()\n '
safe
'\n\nZCML directive\n==============\n\nA tile is really just a browser view providing ``IBasicTile``\n(or, more commonly, ``ITile`` or ``IPersistentTile``)\ncoupled with a named utility providing ``ITileType``.\nThe names of the browser view and the tile should match.\n\nTo make it easier to register these components,\nthis package provides a ```` directive that sets up both.\nIt supports several use cases:\n\n* Registering a new tile from a class\n* Registering a new tile from a template only\n* Registering a new tile form a class and a template\n* Registering a new tile for an existing tile type (e.g. for a new layer)\n\nTo test this,\nwe have created a dummy schema and a dummy tile in ``testing.py``,\nand a dummy template in ``test.pt``.\n\nLet's show how these may be used by registering several tiles:\n\n.. code-block:: python\n\n >>> configuration = \"\"\"\\\n ... \n ...\n ... \n ... \n ... \n ...\n ... \n ... \n ...\n ... \n ... \n ...\n ... \n ... \n ...\n ... \n ... \n ...\n ... \n ... \n ...\n ... \n ... \n ...\n ... \n ... \n ...\n ... \n ... \"\"\"\n\n >>> from six import StringIO\n >>> from zope.configuration import xmlconfig\n >>> xmlconfig.xmlconfig(StringIO(configuration))\n\nLet's check how the tiles were registered:\n\n.. code-block:: python\n\n >>> from zope.component import getUtility\n >>> from plone.tiles.interfaces import ITileType\n\n >>> tile1_type = getUtility(ITileType, name=u'dummy1')\n >>> tile1_type\n \n >>> tile1_type.description\n 'This one shows all available options'\n\n >>> tile1_type.add_permission\n 'plone.tiles.testing.DummyAdd'\n\n >>> tile1_type.view_permission\n 'plone.tiles.testing.DummyView'\n\n >>> tile1_type.schema\n \n\n >>> tile2_type = getUtility(ITileType, name=u'dummy2')\n >>> tile2_type\n \n >>> tile2_type.description is None\n True\n >>> tile2_type.add_permission\n 'plone.tiles.testing.DummyAdd'\n >>> tile2_type.schema is None\n True\n\n >>> tile3_type = getUtility(ITileType, name=u'dummy3')\n >>> tile3_type\n \n >>> tile3_type.description is None\n True\n >>> tile3_type.add_permission\n 'plone.tiles.testing.DummyAdd'\n >>> tile3_type.schema is None\n True\n\n >>> tile4_type = getUtility(ITileType, name=u'dummy4')\n >>> tile4_type\n \n >>> tile4_type.description is None\n True\n >>> tile4_type.add_permission\n 'plone.tiles.testing.DummyAdd'\n >>> tile4_type.schema\n \n\nFinally, let's check that we can look up the tiles:\n\n.. code-block:: python\n\n >>> from zope.publisher.browser import TestRequest\n >>> from zope.interface import implementer, alsoProvides\n\n >>> from plone.tiles.testing import IDummyContext, IDummyLayer\n\n >>> @implementer(IDummyContext)\n ... class Context(object):\n ... pass\n\n >>> context = Context()\n >>> request = TestRequest()\n >>> layer_request = TestRequest(skin=IDummyLayer)\n\n >>> from zope.component import getMultiAdapter\n >>> from plone.tiles import Tile, PersistentTile\n >>> from plone.tiles.testing import DummyTile, DummyTileWithTemplate\n\n >>> tile1 = getMultiAdapter((context, layer_request), name='dummy1')\n >>> isinstance(tile1, DummyTileWithTemplate)\n True\n >>> print(tile1())\n test!\n >>> tile1.__name__\n 'dummy1'\n\n >>> tile2 = getMultiAdapter((context, request), name='dummy2')\n >>> isinstance(tile2, DummyTile)\n True\n >>> print(tile2())\n dummy\n >>> tile2.__name__\n 'dummy2'\n\n >>> tile3 = getMultiAdapter((context, request), name='dummy3')\n >>> isinstance(tile3, Tile)\n True\n >>> print(tile3())\n test!\n >>> tile3.__name__\n 'dummy3'\n\n >>> tile4 = getMultiAdapter((context, request), name='dummy4')\n >>> isinstance(tile4, PersistentTile)\n True\n >>> print(tile4())\n test!\n >>> tile4.__name__\n 'dummy4'\n\n >>> tile3_layer = getMultiAdapter((context, layer_request), name='dummy3')\n >>> isinstance(tile3_layer, DummyTile)\n True\n >>> print(tile3_layer())\n dummy\n >>> tile3_layer.__name__\n 'dummy3'\n\nESI support\n===========\n\nSome sites may choose to render tiles in a delayed fashion using Edge Side Includes or some similar mechanism.\n``plone.tiles`` includes some support to help render ESI placeholders.\nThis is used in ``plone.app.blocks`` to facilitate ESI rendering.\nSince ESI normally involves a \"dumb\" replacement operation,\n``plone.tiles`` also provides a means of accessing just the head and/or just the body of a tile.\n\nTo use the package, you should first load its ZCML configuration.\n\n.. code-block:: python\n\n >>> configuration = \"\"\"\\\n ... \n ...\n ... \n ... \n ...\n ... \n ... \n ...\n ... \n ... \"\"\"\n\n >>> from six import StringIO\n >>> from zope.configuration import xmlconfig\n >>> xmlconfig.xmlconfig(StringIO(configuration))\n\nMarking a tile as ESI-rendered\n------------------------------\n\nFor ESI rendering to be available, the tile must be marked with the ``IESIRendered`` marker interface.\nWe can create a dummy tile with this interface like so:\n\n.. code-block:: python\n\n >>> from zope.interface import implementer\n >>> from plone.tiles.interfaces import IESIRendered\n >>> from plone.tiles import Tile\n\n >>> @implementer(IESIRendered)\n ... class SampleTile(Tile):\n ...\n ... __name__ = 'sample.tile' # would normally be set by ZCML handler\n ...\n ... def __call__(self):\n ... return 'TitleMy tile'\n\nAbove, we have created a simple HTML string.\nThis would normally be rendered using a page template.\n\nWe'll register this tile manually here.\nOrdinarily, of course, it would be registered via ZCML.\n\n.. code-block:: python\n\n >>> from plone.tiles.type import TileType\n >>> from zope.security.permission import Permission\n >>> permission = Permission('dummy.Permission')\n >>> sampleTileType = TileType(\n ... name=u'sample.tile',\n ... title=u'Sample tile',\n ... description=u'A tile used for testing',\n ... add_permission='dummy.Permission',\n ... view_permission='dummy.Permission',\n ... schema=None)\n\n >>> from zope.component import provideAdapter, provideUtility\n >>> from zope.interface import Interface\n >>> from plone.tiles.interfaces import IBasicTile\n\n >>> provideUtility(permission, name=u'dummy.Permission')\n >>> provideUtility(sampleTileType, name=u'sample.tile')\n >>> provideAdapter(SampleTile, (Interface, Interface), IBasicTile, name=u'sample.tile')\n\nESI lookup\n----------\n\nWhen a page is rendered\n(for example by a system like ``plone.app.blocks``, but see below),\na tile placeholder may be replaced by a link such as:\n\n.. code-block:: xml\n\n \n\nWhen this is resolved, it will return the body part of the tile.\nEqually, a tile in the head can be replaced by:\n\n.. code-block:: xml\n\n \n\nTo illustrate how this works,\nlet's create a sample context,\nlook up the view as it would be during traversal,\nand instantiate the tile,\nbefore looking up the ESI views and rendering them.\n\n.. code-block:: python\n\n >>> from zope.interface import implementer\n >>> from zope.publisher.browser import TestRequest\n\n >>> class IContext(Interface):\n ... pass\n\n >>> @implementer(IContext)\n ... class Context(object):\n ... pass\n\n >>> class IntegratedTestRequest(TestRequest):\n ... @property\n ... def environ(self):\n ... return self._environ\n\n >>> context = Context()\n >>> request = IntegratedTestRequest()\n\nThe following simulates traversal to ``context/@@sample.tile/tile1``\n\n.. code-block:: python\n\n >>> from zope.interface import Interface\n >>> from zope.component import getMultiAdapter\n >>> tile = getMultiAdapter((context, request), name=u'sample.tile')\n >>> tile = tile['tile1'] # simulates sub-path traversal\n\nThis tile should be ESI rendered:\n\n.. code-block:: python\n\n >>> IESIRendered.providedBy(tile)\n True\n\nAt this point, we can look up the ESI views:\n\n.. code-block:: python\n\n >>> head = getMultiAdapter((tile, request), name='esi-head')\n >>> head()\n Traceback (most recent call last):\n ...\n zExceptions.unauthorized.Unauthorized: Unauthorized()\n\nBut we can only render them when we have the required permissions:\n\n >>> from AccessControl.SecurityManagement import newSecurityManager\n >>> from AccessControl.User import UnrestrictedUser\n >>> newSecurityManager(None, UnrestrictedUser('manager', '', ['Manager'], []))\n >>> print(head())\n Title\n\n >>> body = getMultiAdapter((tile, request), name='esi-body')\n >>> print(body())\n My tile\n\nTiles without heads or bodies\n-----------------------------\n\nIn general, tiles are supposed to return full HTML documents.\nThe ``esi-head`` and ``esi-body`` views are tolerant of tiles that do not.\nIf they cannot find a ```` or ```` element, respectively, they will return the underlying tile output unaltered.\n\nFor example:\n\n.. code-block:: python\n\n >>> from plone.tiles.esi import ESITile\n >>> class LazyTile(ESITile):\n ... __name__ = 'sample.esi1' # would normally be set by ZCML handler\n ... def __call__(self):\n ... return 'Page title'\n\nWe won't bother to register this for this test, instead just instantiating it directly:\n\n.. code-block:: python\n\n >>> tile = LazyTile(context, request)['tile1']\n\n >>> IESIRendered.providedBy(tile)\n True\n\n >>> head = getMultiAdapter((tile, request), name='esi-head')\n >>> print(head())\n Page title\n\nOf course, the ESI body renderer would return the same thing,\nsince it can't extract a specific body either:\n\n.. code-block:: python\n\n >>> body = getMultiAdapter((tile, request), name='esi-body')\n >>> print(body())\n Page title\n\nIn this case, we would likely end up with invalid HTML,\nsince the ```` tag is not allowed in the body.\nWhether and how to resolve this is left up to the ESI interpolation implementation.\n\nConvenience classes and placeholder rendering\n---------------------------------------------\n\nTwo convenience base classes can be found in the ``plone.tiles.esi`` module.\nThese extend the standard ``Tile`` and ``PersistentTile`` classes to provide the ``IESIRendered`` interface.\n\n* ``plone.tiles.esi.ESITile``, a transient, ESI-rendered tile\n* ``plone.tiles.esi.ESIPersistentTile``, a persistent, ESI-rendered tile\n\nThese are particularly useful if you are creating a template-only tile and want ESI rendering.\nFor example:\n\n.. code-block:: xml\n\n <plone:tile\n name=\"sample.esitile\"\n title=\"An ESI-rendered tile\"\n add_permission=\"plone.tiles.tests.DummyAdd\"\n template=\"esitile.pt\"\n class=\"plone.tiles.esi.ESITile\"\n for=\"*\"\n permission=\"zope.View\"\n />\n\nAdditionally,\nthese base classes implement a ``__call__()`` method that will render a tile placeholder,\nif the request contains an ``X-ESI-Enabled`` header set to the literal 'true'.\n\nThe placeholder is a simple HTML ``<a />`` tag,\nwhich can be transformed into an ``<esi:include />`` tag using the helper function ``substituteESILinks()``.\nThe reason for this indirection is that the ``esi`` namespace is not allowed in HTML documents,\nand are liable to be stripped out by transforms using the ``libxml2`` / ``lxml`` HTML parser.\n\nLet us now create a simple ESI tile. To benefit from the default rendering,\nwe should implement the ``render()`` method instead of ``__call__()``. Setting\na page template as the ``index`` class variable or using the ``template``\nattribute to the ZCML directive will work also.\n\n.. code-block:: python\n\n >>> from plone.tiles.esi import ESITile\n\n >>> class SampleESITile(ESITile):\n ... __name__ = 'sample.esitile' # would normally be set by ZCML handler\n ...\n ... def render(self):\n ... return '<html><head><title>TitleMy ESI tile'\n\n >>> sampleESITileType = TileType(\n ... name=u'sample.esitile',\n ... title=u'Sample ESI tile',\n ... description=u'A tile used for testing ESI',\n ... add_permission='dummy.Permission',\n ... view_permission='dummy.Permission',\n ... schema=None)\n\n >>> provideUtility(sampleESITileType, name=u'sample.esitile')\n >>> provideAdapter(SampleESITile, (Interface, Interface), IBasicTile, name=u'sample.esitile')\n\nThe following simulates traversal to ``context/@@sample.esitile/tile1``\n\n.. code-block:: python\n\n >>> tile = getMultiAdapter((context, request), name=u'sample.esitile')\n >>> tile = tile['tile1'] # simulates sub-path traversal\n\nBy default, the tile renders as normal:\n\n.. code-block:: python\n\n >>> print(tile())\n TitleMy ESI tile\n\nHowever, if we opt into ESI rendering via a request header, we get a different view:\n\n.. code-block:: python\n\n >>> from plone.tiles.interfaces import ESI_HEADER_KEY\n >>> request.environ[ESI_HEADER_KEY] = 'true'\n >>> print(tile()) # doctest: +NORMALIZE_WHITESPACE\n \n \n \n \n \n \n\nThis can be transformed into a proper ESI tag with ``substituteESILinks()``:\n\n.. code-block:: python\n\n >>> from plone.tiles.esi import substituteESILinks\n >>> print(substituteESILinks(tile())) # doctest: +NORMALIZE_WHITESPACE\n \n \n \n \n \n \n\nIt is also possible to render the ESI tile for the head.\nThis is done with a class variable 'head'\n(which would of course normally be set within the class):\n\n.. code-block:: python\n\n >>> SampleESITile.head = True\n >>> print(tile()) # doctest: +NORMALIZE_WHITESPACE\n \n \n \n \n \n \n\nChangelog\n=========\n\n.. You should *NOT* be adding new change log entries to this file.\n You should create a file in the news directory instead.\n For helpful instructions, please see:\n https://github.com/plone/plone.releaser/blob/master/ADD-A-NEWS-ITEM.rst\n\n.. towncrier release notes start\n\n2.2.1 (2019-05-01)\n------------------\n\nBug fixes:\n\n\n- Fixed issue where creating a Mosaic page with shared content layout with filled rich text fields ended up having empty rich text fields, because the rich text field is marked primary (as it should be), and primary fields were never parsed from query string by default. (#30)\n\n\n2.2.0 (2018-11-21)\n------------------\n\nNew features:\n\n\n- Add support for Python 3. [pbauer] (#29)\n\n\nBug fixes:\n\n\n- Initialize towncrier. [gforcada] (#2548)\n\n\n2.1 (2018-07-05)\n----------------\n\n- Dependency on zope.app.publisher removed,\n needed parts were moved to zope.browserpage long time ago.\n This probably breaks Plone 4.2 support (removed).\n [jensens]\n\n\n2.0.0 (2018-04-13)\n------------------\n\n- Fix license classifier.\n\n\n2.0.0b3 (2017-08-22)\n--------------------\n\nBreaking changes:\n\n- Fix querystring en-/decoder to always skip primary fields\n [datakurre]\n\n\n2.0.0b2 (2017-03-29)\n--------------------\n\nBug fixes:\n\n- Do not swallow ``AttributeError`` inside ``index()`` on template rendering.\n [hvelarde]\n\n- Fix code analysis errors.\n [gforcada]\n\n\n2.0.0b1 (2017-02-24)\n--------------------\n\nBreaking changes:\n\n- Tiles no longer add relative ``X-Tile-Url``-header in ``__call__``.\n Tiles still add absolute ``X-Tile-Url``-header during traversal, but\n it gets removed after rendering when request is not CSRF-authorized.\n [datakurre]\n\n- Generic ESI helper check now taht the request is authorized to render\n the tile according to the registered view permission fo the tile.\n [datakurre]\n\n- Transactions of requests to ESI helper views are automatically aborted,\n because ESI requests should always be immutable GET requests\n [datakurre]\n\n- plone.app.theming (transform) is now disabled with X-Theme-Disabled-header\n for requests rendering tiles\n [datakurre]\n\n- plone.protect's ProtectTransform is skipped for tile requests with correct\n CSRF token prevent its side-effects on tile editors rendering tiles\n individually\n [datakurre]\n\nNew features:\n\n- Added X-Frame-Options -header for ESI-tile views with matching behavior\n with plone.protect\n [datakurre]\n\nBug fixes:\n\n- Fix issue where ESI-tile helper views didn't get correct\n Cache-Control-headers, because ESI-helpers views were not acquisition\n wrapped\n [datakurre]\n\n\n1.8.2 (2017-01-10)\n------------------\n\nBug fixes:\n\n- Fix issue where transient tile was unable to encode data\n with list of unicode strings\n [datakurre]\n\n- Remove unused unittest2 requirement\n [tomgross]\n\n\n1.8.1 (2016-11-24)\n------------------\n\nBugfix:\n\n- Fix encode error in nested unicodes (like in plone.app.querystring)\n [tomgross]\n\n- Restructure testing\n [tomgross]\n\n\n1.8.0 (2016-09-13)\n------------------\n\nNew features:\n\n - Provide ignore_querystring form directive to mark particular tiles fields\n that are not allowed to default data from query string data\n [vangheem]\n\n\n1.7.1 (2016-09-12)\n------------------\n\n- Fix issue where collective.cover was broken, because internal changes in\n default data managers\n [datakurre]\n\n1.7.0 (2016-09-08)\n------------------\n\nNew features:\n\n- Option to customize storage layer with ITileDataStorage adapter\n [datakurre]\n\n\n1.6.1 (2016-09-07)\n------------------\n\nBug fixes:\n\n- Reformat docs.\n [gforcada]\n\n- Add coveralls shield.\n [gforcada]\n\n\n1.6 (2016-06-27)\n----------------\n\n- Let TileType instances (tile registration utility) know about the view\n permission too.\n [jensens]\n\n\n1.5.2 (2016-03-28)\n------------------\n\n- Fix issue where ESI href was not properly replaced.\n [jensens]\n\n- Add section \"ZCML Reference\" to README.rst.\n [jensens]\n\n- PEP8, code-analysis, documentation and packaging fixes.\n [jensens, mauritsvanrees]\n\n\n1.5.1 (2015-10-09)\n------------------\n\n- Fix decoding List type of Choice value types\n [vangheem]\n\n\n1.5.0 (2015-09-04)\n------------------\n\n- Add support for overriding transient data manager with persistent data\n manager by adding X-Tile-Persistent=1 into tile URL\n [datakurre]\n\n- Fix persistent data manager to read its default from query string\n [vangheem]\n\n1.4.0 (2015-05-25)\n------------------\n\n- Add support for encoding dictionary fields into tile URL\n [datakurre]\n- Fix issue where saving or deleting transient tile data mutated the current request\n [datakurre]\n- Fix issue where non-ascii characters in tile data raised UnicodeEncode/DecodeErrors\n [datakurre]\n\n1.3.0 (2015-04-21)\n------------------\n\n- Fix edit_permission and delete_permission to default\n to add_permission only in TileType constructor\n [datakurre]\n\n- Fix argument order in TileType constructor call\n [datakurre]\n\n- Fix absolute_url-adapter to fallback to relative URL\n [datakurre]\n\n- Add response to include absolute X-Tile-Url header\n [bloodbare]\n\n1.2 (2012-11-07)\n----------------\n\n- Adding icon property for tiletype\n [garbas]\n\n- Url that we pass via X-Tile-Url should be relative to current context\n [garbas]\n\n- Adding support for more robust permissions for edit and delete on tiles\n [cewing calvinhp]\n\n1.1 (2012-06-22)\n----------------\n\n- X-Tile-Uid header is passed on tile view containing tile's id.\n [garbas]\n\n- PEP 8/Pyflakes (ignoring E121, E123, E126, E127 and E501).\n [hvelarde]\n\n1.0 (2012-05-14)\n----------------\n\n- Refactor ESI support. To use the ``ESITile`` and ``ESIPersistentTile``\n base classes, you should either use a template assigned via ZCML or\n override the ``render()`` method. See ``esi.rst`` for full details.\n [optilude]\n\n- Internationalized title and description of the tile directive.\n [vincentfretin]\n\n- Use a json-encoded parameter in transient tiles as first option.\n [dukebody]\n\n- Use adapters for the Zope Publisher type casting\n [dukebody]\n\n- Conditionaly support z3c.relationfield's RelationChoice fields\n [dukebody]\n\n- Ignore type casting for fields without fixed type, like zope.schema.Choice\n [dukebody]\n\n1.0a1 (2010-05-17)\n------------------\n\n- Initial release.\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/plone/plone.tiles", "keywords": "plone tiles", "license": "GPL version 2", "maintainer": "", "maintainer_email": "", "name": "plone.tiles", "package_url": "https://pypi.org/project/plone.tiles/", "platform": "", "project_url": "https://pypi.org/project/plone.tiles/", "project_urls": { "Homepage": "https://github.com/plone/plone.tiles" }, "release_url": "https://pypi.org/project/plone.tiles/2.2.1/", "requires_dist": [ "plone.subrequest", "setuptools", "zope.annotation", "zope.browserpage", "zope.component", "zope.configuration", "zope.interface", "zope.publisher", "zope.schema", "zope.security", "zope.traversing", "Zope2", "plone.testing[z2,zca] ; extra == 'test'", "plone.rfc822 ; extra == 'test'" ], "requires_python": "", "summary": "APIs for managing tiles", "version": "2.2.1" }, "last_serial": 5214759, "releases": { "1.0": [ { "comment_text": "", "digests": { "md5": "b98c093d6f3b3b9453a77673a891cedd", "sha256": "e7882a98bb602fcfa13b1e29de9b154ca5eca4f945840dd596fdea37e8d181ba" }, "downloads": -1, "filename": "plone.tiles-1.0.tar.gz", "has_sig": false, "md5_digest": "b98c093d6f3b3b9453a77673a891cedd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 42752, "upload_time": "2012-05-14T19:48:14", "url": "https://files.pythonhosted.org/packages/a2/ea/d096146a81001aefbf4677091c9c1f72307c1c4bed1ddbc1e0ba76ae7f75/plone.tiles-1.0.tar.gz" } ], "1.0a1": [ { "comment_text": "", "digests": { "md5": "4084d7547dcd874a2d49ef1886e288c0", "sha256": "7e0f62d1b89e2732e5fab6298b3730209c30f80576d28d4eee37c5c0b41e5051" }, "downloads": -1, "filename": "plone.tiles-1.0a1.tar.gz", "has_sig": false, "md5_digest": "4084d7547dcd874a2d49ef1886e288c0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 36795, "upload_time": "2010-05-17T15:58:57", "url": "https://files.pythonhosted.org/packages/de/70/b5cc41cd96f8c4013f96d4399d6d23db4e571e5eb8bac789ffa6747eaa4a/plone.tiles-1.0a1.tar.gz" } ], "1.1": [ { "comment_text": "", "digests": { "md5": "adcfe7dc02a464b26c0d8b3f2c3baee5", "sha256": "4a4c04f729e4591eaf22970bbb509aad7a60ef501903e55659208b78a5d614e5" }, "downloads": -1, "filename": "plone.tiles-1.1.tar.gz", "has_sig": false, "md5_digest": "adcfe7dc02a464b26c0d8b3f2c3baee5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 43024, "upload_time": "2012-06-22T23:52:02", "url": "https://files.pythonhosted.org/packages/75/1c/3b4fe10336958e03d6dca570f6d51b8f4c1cb0331d84390eb401d5fd2958/plone.tiles-1.1.tar.gz" } ], "1.2": [ { "comment_text": "", "digests": { "md5": "1ba09b368a41a0e448d1170ac523c829", "sha256": "7c43edc00bfdf1fc3b1e7f7091820514dc0d930956b01e5c6d4a57076fab93e3" }, "downloads": -1, "filename": "plone.tiles-1.2.tar.gz", "has_sig": false, "md5_digest": "1ba09b368a41a0e448d1170ac523c829", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 50671, "upload_time": "2012-11-07T00:37:26", "url": "https://files.pythonhosted.org/packages/81/52/1e0417622a4a1a94ee806d5e47026ca8899d6e83f359d4bf01e387495a6c/plone.tiles-1.2.tar.gz" } ], "1.3.0": [ { "comment_text": "", "digests": { "md5": "ab263253791759eead3c2c135d14a501", "sha256": "9d9c9e5a8b2491dac6d723b9a4a4d50db77b7c110c08a79b2840156cf2f5c0a1" }, "downloads": -1, "filename": "plone.tiles-1.3.0.zip", "has_sig": false, "md5_digest": "ab263253791759eead3c2c135d14a501", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 64011, "upload_time": "2015-04-21T13:08:00", "url": "https://files.pythonhosted.org/packages/ba/54/ea5018d73969a010e03be30e7da79ef7c9d1a57c5eaed59878ba2dd27ce4/plone.tiles-1.3.0.zip" } ], "1.4.0": [ { "comment_text": "", "digests": { "md5": "04da597a62d33764d7b10598cbb412ed", "sha256": "b72420f04da2a05c5d1246816739ef52c1e5ab08f29a808a221375700f118970" }, "downloads": -1, "filename": "plone.tiles-1.4.0.tar.gz", "has_sig": false, "md5_digest": "04da597a62d33764d7b10598cbb412ed", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 55416, "upload_time": "2015-05-25T14:13:59", "url": "https://files.pythonhosted.org/packages/c5/2d/71e2180751567a04bbfdc70bef9faad934600abf7f563aba33497aac1b64/plone.tiles-1.4.0.tar.gz" } ], "1.5.0": [ { "comment_text": "", "digests": { "md5": "c475ae084736c77fa093b63f2049d899", "sha256": "9dfb0a629e96dab9f8c667aafc665c308f0e29356b4ebd38caac8e1826f6977b" }, "downloads": -1, "filename": "plone.tiles-1.5.0.tar.gz", "has_sig": false, "md5_digest": "c475ae084736c77fa093b63f2049d899", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 55182, "upload_time": "2015-09-04T15:22:38", "url": "https://files.pythonhosted.org/packages/06/5c/090a7e0c2472cd3d38d230a3e699709942e6eb77c82fe4a8bd0ac48f7e63/plone.tiles-1.5.0.tar.gz" } ], "1.5.1": [ { "comment_text": "", "digests": { "md5": "b6ee36348f90df93c2c68b447d850ba9", "sha256": "d018cd2b267399aaf666a90adeca5843afe2a68a9f1e07fb8b11d9b6322dbdc0" }, "downloads": -1, "filename": "plone.tiles-1.5.1.tar.gz", "has_sig": false, "md5_digest": "b6ee36348f90df93c2c68b447d850ba9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 55382, "upload_time": "2015-10-09T20:32:53", "url": "https://files.pythonhosted.org/packages/23/e8/e46020d8470668b976789acb06d04422eade0eb12c5196480fd49d83db08/plone.tiles-1.5.1.tar.gz" } ], "1.5.2": [ { "comment_text": "", "digests": { "md5": "a1c29b70d51f705b7ee32342b78e91ff", "sha256": "762f582605cec99c532998e805941a24204031c66be8ebebf6183ea5161d4df7" }, "downloads": -1, "filename": "plone.tiles-1.5.2.tar.gz", "has_sig": false, "md5_digest": "a1c29b70d51f705b7ee32342b78e91ff", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 58173, "upload_time": "2016-03-28T16:56:23", "url": "https://files.pythonhosted.org/packages/f1/46/d163361791dd38a3d2be33b88b33a8374e4fd02ea55144209ce973034d75/plone.tiles-1.5.2.tar.gz" } ], "1.6": [ { "comment_text": "", "digests": { "md5": "a2a97a27e1f8dc8030bcfa366b99582a", "sha256": "4e502a272f2ee0b1dd79870901393c2e2200833525a918a2098ffb501138ec67" }, "downloads": -1, "filename": "plone.tiles-1.6.tar.gz", "has_sig": false, "md5_digest": "a2a97a27e1f8dc8030bcfa366b99582a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 58489, "upload_time": "2016-06-27T11:26:34", "url": "https://files.pythonhosted.org/packages/40/21/44af89b713c9631002a0050daa3bf3eeb5b141967c77595cd4d28cabc9cf/plone.tiles-1.6.tar.gz" } ], "1.6.1": [ { "comment_text": "", "digests": { "md5": "8cf3f08d58ef83a2ffbfc520dfd1092e", "sha256": "dec996826503fcad41cab67819621c2d3be9524ce94253cff865b6843cc1516d" }, "downloads": -1, "filename": "plone.tiles-1.6.1.tar.gz", "has_sig": false, "md5_digest": "8cf3f08d58ef83a2ffbfc520dfd1092e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 58895, "upload_time": "2016-09-06T23:51:49", "url": "https://files.pythonhosted.org/packages/22/c1/e96a8e7d70844f97a1bb388f0c7b17d4c12bbf9b86cce13570bf978c8046/plone.tiles-1.6.1.tar.gz" } ], "1.6.dev0": [ { "comment_text": "", "digests": { "md5": "81d2737420f02b2810d2e235518fedfe", "sha256": "3345765e5b74a2053e30485b2bd1d7f93c06fc988dfa6eb0e17c61571fa1769f" }, "downloads": -1, "filename": "plone.tiles-1.6.dev0.tar.gz", "has_sig": false, "md5_digest": "81d2737420f02b2810d2e235518fedfe", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 58426, "upload_time": "2016-05-23T07:20:00", "url": "https://files.pythonhosted.org/packages/26/ea/2e318b54ea1b78339f1d8533390708ceb7f4b5b8b5235307f350993171d2/plone.tiles-1.6.dev0.tar.gz" } ], "1.7.0": [ { "comment_text": "", "digests": { "md5": "68256a585d6a7e72a5defbc8c595985b", "sha256": "fbdbcb4fd1fce30ecb1713893653019cf2f97edf6eafb2729b6fddb054c740ba" }, "downloads": -1, "filename": "plone.tiles-1.7.0.tar.gz", "has_sig": false, "md5_digest": "68256a585d6a7e72a5defbc8c595985b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 58995, "upload_time": "2016-09-08T16:56:30", "url": "https://files.pythonhosted.org/packages/7c/4f/58dd11b8a45d6dc1a026df6acd9055bb645464052964aecd2921bcd7c5b7/plone.tiles-1.7.0.tar.gz" } ], "1.7.1": [ { "comment_text": "", "digests": { "md5": "26bbecf4a16aef15e81b3762e3007cc4", "sha256": "7f1637f17c5f3e5f6f601fc655e59cc4ff528d939e877a81c628acdf53110d6f" }, "downloads": -1, "filename": "plone.tiles-1.7.1.tar.gz", "has_sig": false, "md5_digest": "26bbecf4a16aef15e81b3762e3007cc4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 59198, "upload_time": "2016-09-12T07:39:41", "url": "https://files.pythonhosted.org/packages/9c/3e/997f6c90304e13ac0b54749720a0f838d485abe649286ee93ae441ea7a5b/plone.tiles-1.7.1.tar.gz" } ], "1.8.0": [ { "comment_text": "", "digests": { "md5": "44c89633ed486af8e71cc1fd512172bb", "sha256": "0cd94fabd0fdaca7676b7d40b66095d0615155cd2c3b720bdd0aeed637d197c2" }, "downloads": -1, "filename": "plone.tiles-1.8.0.tar.gz", "has_sig": false, "md5_digest": "44c89633ed486af8e71cc1fd512172bb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 60673, "upload_time": "2016-09-13T08:02:41", "url": "https://files.pythonhosted.org/packages/02/30/33340584865132901f1a5c7ce3d4b6196333b1de6128ba5a87f6ab7f16ee/plone.tiles-1.8.0.tar.gz" } ], "1.8.1": [ { "comment_text": "", "digests": { "md5": "86a81b30ac0195f2b754402f3c47c931", "sha256": "5ac4bc3299bc3bb2520e22188418b3ee1a772bb95e7d97d9c180b04b4528b486" }, "downloads": -1, "filename": "plone.tiles-1.8.1.tar.gz", "has_sig": false, "md5_digest": "86a81b30ac0195f2b754402f3c47c931", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 61157, "upload_time": "2016-11-24T05:29:46", "url": "https://files.pythonhosted.org/packages/82/58/0ee9201a1e5cbe7bc923ea83a0335ff611bd29142d0bfab876d29bc15742/plone.tiles-1.8.1.tar.gz" } ], "1.8.2": [ { "comment_text": "", "digests": { "md5": "d0595bc356aca2443f574ccb082f3142", "sha256": "c36b01a660908edc814691aca392f669d8666846e680425c354caa525ddbf060" }, "downloads": -1, "filename": "plone.tiles-1.8.2.tar.gz", "has_sig": false, "md5_digest": "d0595bc356aca2443f574ccb082f3142", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 61374, "upload_time": "2017-01-10T07:19:52", "url": "https://files.pythonhosted.org/packages/e7/77/d36b3facd0f6dd2e2a02eb5aed50a3ebafecf815af0d9f55ecae900cbd74/plone.tiles-1.8.2.tar.gz" } ], "1.8.3": [ { "comment_text": "", "digests": { "md5": "dceaca87def0b0abcde95b586b42d314", "sha256": "543a95165c3c87f3ff66f3efbf2a169fa512ed4e088d0849c9394ab4810598fa" }, "downloads": -1, "filename": "plone.tiles-1.8.3.tar.gz", "has_sig": false, "md5_digest": "dceaca87def0b0abcde95b586b42d314", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 62683, "upload_time": "2017-06-08T10:16:31", "url": "https://files.pythonhosted.org/packages/95/c7/9115508298bd20fb224898f4f29f5e2593a1c26b9340392299de01a4e694/plone.tiles-1.8.3.tar.gz" } ], "2.0.0": [ { "comment_text": "", "digests": { "md5": "13d66446308b80dc7e8327e52ea55cef", "sha256": "962caa9b3223e57ab41402d7e110dea21b9434e816cc240366754e0f56380e94" }, "downloads": -1, "filename": "plone.tiles-2.0.0-py2-none-any.whl", "has_sig": false, "md5_digest": "13d66446308b80dc7e8327e52ea55cef", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 63298, "upload_time": "2018-04-13T11:09:31", "url": "https://files.pythonhosted.org/packages/d6/03/0ab6013a6b1de293f71663b01d2d37bcbd99ee3d53c2bef404cd13814c3f/plone.tiles-2.0.0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "8e94ebf9b2ef6060a9fcda331c3b50b1", "sha256": "bafd409d4850fefecd4d3506dd8000cf4e64efdf5c37dbed3f95877f3cdedd1c" }, "downloads": -1, "filename": "plone.tiles-2.0.0.tar.gz", "has_sig": false, "md5_digest": "8e94ebf9b2ef6060a9fcda331c3b50b1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 65471, "upload_time": "2018-04-13T11:09:33", "url": "https://files.pythonhosted.org/packages/b8/bd/e0f17072839572d3ad66c6efea84a426e961f9d648e9700edad37b6dcdee/plone.tiles-2.0.0.tar.gz" } ], "2.0.0b1": [ { "comment_text": "", "digests": { "md5": "1109f98c6ce8527a8c2dab6d911311ff", "sha256": "b6f9bd5ad500b794db808154a8eca953764d2e171408df79c32927fc0527e724" }, "downloads": -1, "filename": "plone.tiles-2.0.0b1.tar.gz", "has_sig": false, "md5_digest": "1109f98c6ce8527a8c2dab6d911311ff", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 64785, "upload_time": "2017-02-24T07:39:56", "url": "https://files.pythonhosted.org/packages/36/56/e4ef61fa6183fbeb514128928faef5324ccb79029e30a78d22ca62fa4b23/plone.tiles-2.0.0b1.tar.gz" } ], "2.0.0b2": [ { "comment_text": "", "digests": { "md5": "3bb98e3a5e78353437cedada8ab67e17", "sha256": "bc98e30d8f433ce2f2e224dc88267850999378c2c1852a0154e3e37fab7b5059" }, "downloads": -1, "filename": "plone.tiles-2.0.0b2.tar.gz", "has_sig": false, "md5_digest": "3bb98e3a5e78353437cedada8ab67e17", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 64036, "upload_time": "2017-03-29T08:29:38", "url": "https://files.pythonhosted.org/packages/b4/0d/5c944d4e0a45c54898936eb0199082785e3006479beb8bdf99e876714358/plone.tiles-2.0.0b2.tar.gz" } ], "2.0.0b3": [ { "comment_text": "", "digests": { "md5": "54aa5cb34f0a7a01519cebde183193b9", "sha256": "f6cc3adbe1a283177ded4ecaea6f47e5d90e4dd77b2f14832d65d29bd002fdd5" }, "downloads": -1, "filename": "plone.tiles-2.0.0b3-py2-none-any.whl", "has_sig": false, "md5_digest": "54aa5cb34f0a7a01519cebde183193b9", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 63235, "upload_time": "2017-08-21T21:23:40", "url": "https://files.pythonhosted.org/packages/52/34/e276150fdb056182c2f6bab3050c981f91c67e39d251487884a88499da17/plone.tiles-2.0.0b3-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "888461a132e9548e230724533ceaaa90", "sha256": "efd037519b8ef37278ea862b25cf497089f8cc22e5b7a4c6fd50adf28af4aea8" }, "downloads": -1, "filename": "plone.tiles-2.0.0b3.tar.gz", "has_sig": false, "md5_digest": "888461a132e9548e230724533ceaaa90", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 66118, "upload_time": "2017-08-21T21:23:45", "url": "https://files.pythonhosted.org/packages/4c/b0/f6388549181f5864908b9b183dafd8402f36d526fd1f752e62b43b8ee737/plone.tiles-2.0.0b3.tar.gz" } ], "2.1": [ { "comment_text": "", "digests": { "md5": "367315922440a795dcc2a6b91eb3496d", "sha256": "ee38a5cd40c94558bb15fd092e315a483b7230523820a39f97180861d865b0a0" }, "downloads": -1, "filename": "plone.tiles-2.1-py2-none-any.whl", "has_sig": false, "md5_digest": "367315922440a795dcc2a6b91eb3496d", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 47968, "upload_time": "2018-07-05T12:01:21", "url": "https://files.pythonhosted.org/packages/2d/0e/c2a448edf8000c64e18e5e1a11ca4f355862ba86b98257f5828950124654/plone.tiles-2.1-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "8a0954b4e026d6c2e8e30fec7a1e986d", "sha256": "706dc4d9c4389f8046258d298c0b2cbd1d315a21339650075729f9282fec78d7" }, "downloads": -1, "filename": "plone.tiles-2.1.tar.gz", "has_sig": false, "md5_digest": "8a0954b4e026d6c2e8e30fec7a1e986d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 65686, "upload_time": "2018-07-05T12:01:22", "url": "https://files.pythonhosted.org/packages/0e/41/cdc7b15712784b3f27e6a5a1f50fff7d60282a94add8174ee9b7d26e86ba/plone.tiles-2.1.tar.gz" } ], "2.2.0": [ { "comment_text": "", "digests": { "md5": "e9c009b296a0ed6abac3b325a68ecf40", "sha256": "d7e22355018b9ea6744f908d2a683094d085d07ea42a3314ec10289a50f5fc79" }, "downloads": -1, "filename": "plone.tiles-2.2.0-py2-none-any.whl", "has_sig": false, "md5_digest": "e9c009b296a0ed6abac3b325a68ecf40", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 56087, "upload_time": "2018-11-21T22:31:28", "url": "https://files.pythonhosted.org/packages/f6/b2/8e22820b24e82d009cf7fff31a4fe37cad928fdbf6476c8abb4789b0336a/plone.tiles-2.2.0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "fd07117ddf9d32c2783d7e0b183fc892", "sha256": "85b12eae6ed30b8afc24371d381893c16b894935ec6f445246232c2e1cbd2971" }, "downloads": -1, "filename": "plone.tiles-2.2.0.tar.gz", "has_sig": false, "md5_digest": "fd07117ddf9d32c2783d7e0b183fc892", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 67508, "upload_time": "2018-11-21T22:31:31", "url": "https://files.pythonhosted.org/packages/d1/bd/f5633f28180cc8c6dcec55773eab97e5a1ab0efa5bb5e51f8fa7f2679618/plone.tiles-2.2.0.tar.gz" } ], "2.2.1": [ { "comment_text": "", "digests": { "md5": "d025db4756108394d5080bdbe0f4981b", "sha256": "99d49a188f64d25bfd35e1459e8e0cf779e5525e462c3ab4cb4b50eec5c4b4ad" }, "downloads": -1, "filename": "plone.tiles-2.2.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "d025db4756108394d5080bdbe0f4981b", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 56314, "upload_time": "2019-05-01T23:25:11", "url": "https://files.pythonhosted.org/packages/f5/e1/bc55fdc6f0235ff3000fb42bcb538762bffe88b3e966780969e80e2c6676/plone.tiles-2.2.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4dfacd82e85fce5f0f3c32e01a23272b", "sha256": "c577074ebfbd88269845b3c3cf425abaa833807c18b995c72acf46d25efc8401" }, "downloads": -1, "filename": "plone.tiles-2.2.1.tar.gz", "has_sig": false, "md5_digest": "4dfacd82e85fce5f0f3c32e01a23272b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 68234, "upload_time": "2019-05-01T23:25:13", "url": "https://files.pythonhosted.org/packages/30/ac/f2780c54db89a9076ac71b5be423adb20b9f473664639a870495381cd312/plone.tiles-2.2.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "d025db4756108394d5080bdbe0f4981b", "sha256": "99d49a188f64d25bfd35e1459e8e0cf779e5525e462c3ab4cb4b50eec5c4b4ad" }, "downloads": -1, "filename": "plone.tiles-2.2.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "d025db4756108394d5080bdbe0f4981b", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 56314, "upload_time": "2019-05-01T23:25:11", "url": "https://files.pythonhosted.org/packages/f5/e1/bc55fdc6f0235ff3000fb42bcb538762bffe88b3e966780969e80e2c6676/plone.tiles-2.2.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4dfacd82e85fce5f0f3c32e01a23272b", "sha256": "c577074ebfbd88269845b3c3cf425abaa833807c18b995c72acf46d25efc8401" }, "downloads": -1, "filename": "plone.tiles-2.2.1.tar.gz", "has_sig": false, "md5_digest": "4dfacd82e85fce5f0f3c32e01a23272b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 68234, "upload_time": "2019-05-01T23:25:13", "url": "https://files.pythonhosted.org/packages/30/ac/f2780c54db89a9076ac71b5be423adb20b9f473664639a870495381cd312/plone.tiles-2.2.1.tar.gz" } ] }