Metadata-Version: 1.1
Name: plone.tiles
Version: 1.3.0
Summary: APIs for managing tiles
Home-page: https://github.com/plone/plone.tiles
Author: Martin Aspeli
Author-email: optilude@gmail.com
License: BSD
Description: plone.tiles
        ===========
        
        ``plone.tiles`` implements low-level, non-Plone/Zope2-specific support for
        creating "tiles" in the Deco layout system.
        
        .. contents::
        
        .. image:: https://secure.travis-ci.org/plone/plone.tiles.png
           :target: http://travis-ci.org/plone/plone.tiles
        
        
        Introduction
        ------------
        
        For the purposes of this package, a tile is a browser view and an associated
        utility providing some metadata about that view. The metadata includes a title
        and description, an 'add' permission and optionally a schema interface
        describing configurable aspects of the tile. The idea is that a UI (such as
        Deco) can present the user with a list of insertable tiles and optionally
        render a form to configure the tile upon insertion.
        
        A tile is inserted into a layout as a link::
        
            <link rel="tile" target="placeholder" href="./@@sample.tile/tile1?option1=value1" />
        
        The sub-path (`tile1` in this case) is used to set the tile `id` attribute.
        This allows the tile to know its unique id, and, in the case of persistent
        tiles, look up its data. `sample.tile` is the name of the browser view that
        implements the tile. This is made available as the `__name__` attribute. Other
        parameters may be turned into tile data, available under the `data` attribute,
        a dict, for regular tiles. For persistent tiles (those deriving from the
        `PersistentTile` base class), the data is fetched from annotations instead,
        based on the tile id.
        
        There are three interfaces describing tiles in this package:
        
        * `IBasicTile` is the low-level interface for tiles. It extends
          `IBrowserView` to describe the semantics of the `__name__` and  `id`
          attributes.
        * `ITile` describes a tile that can be configured with some data. The data
          is accessible via a dict called `data`. The default implementation of this
          interface, `plone.tiles.Tile`, will use the schema of the tile type and
          the query string (`self.request.form`) to construct that dictionary. This
          interface also describes an attribute `url`, which gives the canonical
          tile URL, including the id sub-path and any query string parameters. (Note
          that tiles also correctly implement `IAbsoluteURL`.)
        * `IPersistentTile` describes a tile that stores its configuration in
          object annotations, and is needed when configuration values cannot be
          encoded into a query string. The default implementation is in
          `plone.tiles.PersistentTile`. To make it possible to have several tiles
          of a given type on the same layout, the annotations are keyed by the
          tile `__name__`.
        
        In addition, tiles are described by `ITileType`, which contains attributes
        for the tile name, title, description, add permission and schema (if
        required).
        
        A properly configured tile, then, consists of a browser view providing
        `IBasicTile` or one of its derivatives, and a utility providing `ITileType`
        with the same name as the tile browser view. There is a convenience ZCML
        directive - `<plone:tile />` - to register both of these components in one
        go.
        
        To support creation of appropriate tile links, `plone.tiles.data` contains two
        methods - `encode()` and `decode()` - to 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.
        
        
        Creating a Simple Tile
        ----------------------
        
        The most basic tile looks like this::
        
            from plone.tiles import Tile
        
            class MyTile(Tile):
        
                def __call__(self):
                    return u"<html><body><p>Hello world</p></body></html>"
        
        Note that the tile is expected to return a complete HTML document. This will
        be interpolated into the page output according to the following rules:
        
        * The contents of the tile's ``<head />`` section is appended to the output
          document's ``<head />`` section.
        * The contents of the tile's ``<body />`` section will replace the tile
          placeholder as indicated by the tile link.
        
        Note that this package does *not* provide these interpolations. For a Plone
        implementation of the interpolation algorithm, see `plone.app.blocks`_
        
        If you require a persistent tile, subclass `plone.tiles.PersistentTile`
        instead. You may also need a schema interface if you want a configurable
        transient or persistent tile.
        
        To register the tile, use ZCML like this::
        
            <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>
        
        The first five attributes describe the tile by configuring an appropriate
        `ITileType` directive. The rest mimics the `<browser:page />` directive,
        so you can specify a `template` file and omit the `class`, or use both a
        `template` and `class`.
        
        If you want to register a persistent tile with a custom schema, but a template
        only, you can do e.g.::
        
                <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="*"
                    />
        
        If you want to override an existing tile, e.g. with a new layer or more
        specific context, you *must* omit the tile metadata (title, description, icon,
        add permission or schema). If you include any metadata you will get a conflict
        error on Zope startup. This example shows how to use a different template
        for our tile::
        
                <plone:tile
                    name="sample.persistenttile"
                    template="override.pt"
                    permission="zope.Public"
                    for="*"
                    layer=".interfaces.IMyLayer"
                    />
        
        See `tiles.rst` and `directives.rst` for more details.
        
        .. _plone.app.blocks: http://pypi.python.org/pypi/plone.app.blocks
        
        
        Tiles in detail
        ===============
        
        Tiles are a form of view component used to compose pages. Think of a tile as
        a view describing one part of a page, that can be configured with some data
        described by a schema and inserted into a layout via a dedicated GUI.
        
        Like a browser view, a tile can be traversed to and published on its own. The
        tile should then return a full HTML page, including a <head /> with any
        required resources, and a <body /> with the visible part of the tile. This
        will then be merged into the page, using a system such as
        ``plone.app.blocks``.
        
        The API in this package provides support for tiles being configured according
        to a schema with data either passed on the query string (transient tiles) or
        retrieved from annotations (persistent tiles).
        
        Note that there is no direct UI support in this package, so the forms that
        allow users to construct and edit tiles must live elsewhere. You may be
        interested in ``plone.app.tiles`` and ``plone.app.deco`` for that purpose.
        
        To use the package, you should first load its ZCML configuration.
        
            >>> 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.app.publisher" file="meta.zcml" />
            ...
            ...     <include package="plone.tiles" file="meta.zcml" />
            ...     <include package="plone.tiles" />
            ...
            ... </configure>
            ... """
        
            >>> from StringIO import StringIO
            >>> from zope.configuration import xmlconfig
            >>> xmlconfig.xmlconfig(StringIO(configuration))
        
        A simple transient tile
        -----------------------
        
        A basic tile is a view that implements the ``ITile`` interface. The easiest
        way to do this is to subclass the ``Tile`` class.
        
            >>> from plone.tiles import Tile
            >>> class SampleTile(Tile):
            ...
            ...     __name__ = 'sample.tile' # would normally be set by ZCML handler
            ...
            ...     def __call__(self):
            ...         return "<html><body><b>My tile</b></body></html>"
        
        The tile is a browser view:
        
            >>> from plone.tiles.interfaces import ITile
            >>> ITile.implementedBy(SampleTile)
            True
        
            >>> from zope.publisher.interfaces.browser import IBrowserView
            >>> IBrowserView.implementedBy(SampleTile)
            True
        
        The tile instance has a ``__name__`` attribute (normally set at class level
        by the ``<plone:tile />`` ZCML directive), as well as a property ``id``. The
        id may be set explicitly, either in code, or by sub-path traversal. For
        example, if the tile name is ``example.tile``, the id may be set to ``tile1``
        using a URL like ``http://example.com/foo/@@example.tile/tile1``.
        
        This tile is registered as a normal browser view, alongside a utility that
        provides some information about the tile itself. Normally, this is done
        using the ``<plone:tile />`` directive. Here's how to create one manually:
        
            >>> from plone.tiles.type import TileType
            >>> sampleTileType = TileType(
            ...     name=u'sample.tile',
            ...     title=u"Sample tile",
            ...     description=u"A tile used for testing",
            ...     add_permission="dummy.Permission",
            ...     schema=None)
        
        The name should match the view name and the name the utility is registered
        under. The title and description may be used by the UI. The add permission
        is the name of a permission that will be required to insert the tile. The
        schema attribute may be used to indicate schema interface describing the
        tile's configurable data - more on this below.
        
        To register a tile in ZCML, we could do::
        
            <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"
                />
        
        **Note:** The tile name should be a dotted name, prefixed by a namespace you
        control. It's a good idea to use a package name for this purpose.
        
        It is also possible to specify a ``layer`` or ``template`` like the
        ``browser:page`` directive, as well as a ``schema``, which we will describe
        below.
        
        We'll register the sample tile directly here, for later testing.
        
            >>> 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")
        
        Tile traversal
        --------------
        
        Tiles are publishable as a normal browser view. They will normally be called
        with a sub-path that specifies a tile id. This allows tiles to be made aware
        of their instance name. The id is unique within the page layout where the tile
        is used, and may be the basis for looking up tile data.
        
        For example, a tile may be saved in a layout as a link like::
        
            <link rel="tile" target="mytile" href="./@@sample.tile/tile1" />
        
        (The idea here is that the tile link tells the rendering algorithm to replace
        the element with id ``mytile`` with the body of the rendered tile - see
        ``plone.app.blocks`` for details).
        
        Let's create a sample context, look up the view as it would be during
        traversal, and verify how the tile is instantiated.
        
            >>> from zope.interface import implements
        
            >>> class IContext(Interface):
            ...     pass
        
            >>> class Context(object):
            ...     implements(IContext)
        
            >>> from zope.publisher.browser import TestRequest
        
            >>> context = Context()
            >>> request = TestRequest()
        
            >>> 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
        
        The tile will now be aware of its name and id:
        
            >>> isinstance(tile, SampleTile)
            True
            >>> tile.__parent__ is context
            True
            >>> tile.id
            'tile1'
            >>> tile.__name__
            'sample.tile'
        
        The sub-path traversal is implemented using a custom ``__getitem__()`` method.
        To look up a view on a tile, you can traverse to it *after* you've traversed
        to the id sub-path:
        
            >>> from zope.interface import Interface
            >>> from zope.component import adapts
            >>> 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'
        
        If there is no view and we have an id already, we will get a ``KeyError``:
        
            >>> tile['not-known'] # doctest: +ELLIPSIS
            Traceback (most recent call last):
            ...
            KeyError: 'not-known'
        
        To ensure consistency with Zope's various tangles publication machines, it
        is also possible to traverse using the ``publishTraverse`` method::
        
            >>> 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'
        
        Transient tile data
        -------------------
        
        Let us now consider how tiles may have data. In the simplest case, tile
        data is passed on the query string, and described according to a schema.
        A simple schema may look like:
        
            >>> 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")
        
        We would normally have listed this interface when registering this tile in
        ZCML. We can simply update the utility here.
        
            >>> sampleTileType.schema = ISampleTileData
        
        Tile data is represented by a simple dictionary. For example:
        
            >>> data = {'title': u"My title", 'count': 5, 'cssClass': 'foo'}
        
        The idea is that a tile add form is built from the schema interface, and its
        data saved to a dictionary.
        
        For transient tiles, this data is then encoded into the tile query string. To
        help with this, a utility function can be used to encode a dict to a query
        string, applying Zope form marshalers according to the types described in
        the schema:
        
            >>> from plone.tiles.data import encode
            >>> encode(data, ISampleTileData)
            'title=My+title&cssClass=foo&count%3Along=5'
        
        The ``count%3Along=5`` bit is the encoded version of ``count:long=5``.
        
        Note that not all field types may be saved. In particular, object, interface,
        set or frozen set fields may not be saved, and will result in a ``KeyError``.
        Lengthy text fields or bytes fields with binary data may also be a problem.
        For these types of fields, look to use persistent tiles instead.
        
        Furthermore, the conversion may not be perfect. For example, Zope's form
        marshalers cannot distinguish between unicode and ascii fields. Therefore,
        there is a corresponding ``decode()`` method that may be used to ensure that
        the values match the schema:
        
            >>> marshaled = {'title': u"My tile", 'count': 5, 'cssClass': u'foo'}
        
            >>> from plone.tiles.data import decode
            >>> decode(marshaled, ISampleTileData)
            {'count': 5, 'cssClass': 'foo', 'title': u'My tile'}
        
        When saved into a layout, the tile link would now look like::
        
            <link rel="tile" target="mytile"
              href="./@@sample.tile/tile1?title=My+title&count%3Along=5&cssClass=foo" />
        
        Let's simulate traversal once more and see how the data is now available to
        the tile instance:
        
            >>> 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', u'My title')]
        
        Notice also how the data has been properly decoded according to the schema.
        
        Transient tiles will get their data directly from the request
        parameters but, if a `_tiledata` JSON-encoded parameter is present in
        the request, this one will be used instead::
        
            >>> try:
            ...     import json
            ... except ImportError:
            ...     import simplejson as 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', u'Your title')]
        
        This 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.
        
        The tile data manager
        ---------------------
        
        The ``data`` attribute is a convenience attribute to get hold of a (cached)
        copy of the data returned by an ``ITileDataManager``. This interface provides
        three methods: ``get()``, to return the tile's data, ``set()``, to update it
        with a new dictionary of data, and ``delete()``, to delete the data.
        
        This adapter is mostly useful for writing UI around tiles. Using our tile
        above, we can get the data like so:
        
            >>> from plone.tiles.interfaces import ITileDataManager
            >>> dataManager = ITileDataManager(tile)
            >>> dataManager.get() == tile.data
            True
        
        We can also update the tile data:
        
            >>> dataManager.set({'count': 1, 'cssClass': 'bar', 'title': u'Another title'})
            >>> sorted(dataManager.get().items())
            [('count', 1), ('cssClass', 'bar'), ('title', u'Another title')]
        
        The data can also be deleted:
        
            >>> dataManager.delete()
            >>> sorted(dataManager.get().items())
            [('count', None), ('cssClass', None), ('title', None)]
        
        Note that in the case of a transient tile, all we are doing is
        modifying the ``form`` dictionary of the request (or the `_tiledata`
        parameter of this dictionary, if present). The data needs to be
        encoded into the query string, either using the ``encode()`` method or
        via the tile's ``IAbsoluteURL`` adapter (see below for details).
        
        For persistent tiles, the data manager is a bit more interesting.
        
        Persistent tiles
        ----------------
        
        Not all types of data can be placed in a query string. For more substantial
        storage requirements, you can use persistent tiles, which store data in
        annotations.
        
        *Note:* If you have more intricate requirements, you can also write your own
        ``ITileDataManager`` to handle data retrieval. In this case, you probably
        still want to derive from ``PersistentTile``, to get the appropriate
        ``IAbsoluteURL`` adapter, among other things.
        
        First, we need to write up annotations support.
        
            >>> from zope.annotation.attribute import AttributeAnnotations
            >>> provideAdapter(AttributeAnnotations)
        
        We also need a context that is annotatable.
        
            >>> from zope.annotation.interfaces import IAttributeAnnotatable
            >>> from zope.interface import alsoProvides
            >>> alsoProvides(context, IAttributeAnnotatable)
        
        Now, let's create a persistent tile with a schema.
        
            >>> 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(
            ...     name=u'sample.persistenttile',
            ...     title=u"Persistent sample tile",
            ...     description=u"A tile used for testing",
            ...     add_permission="dummy.Permission",
            ...     schema=IPersistentSampleData)
        
            >>> provideUtility(persistentSampleTileType, name=u'sample.persistenttile')
            >>> provideAdapter(PersistentSampleTile, (Interface, Interface), IBasicTile, name=u"sample.persistenttile")
        
        We can now traverse to the tile as before. By default, there is no data, and
        the field's missing value will be used.
        
            >>> request = TestRequest()
        
            >>> tile = getMultiAdapter((context, request), name=u"sample.persistenttile")
            >>> tile = tile['tile2']
            >>> tile.__name__
            'sample.persistenttile'
            >>> tile.id
            'tile2'
        
            >>> tile()
            u'<b>You said</b> Missing!'
        
        At this point, there is nothing in the annotations for the type either:
        
            >>> dict(getattr(context, '__annotations__', {})).keys()
            []
        
        We can write data to the context's annotations using an ``ITileDataManager``:
        
            >>> dataManager = ITileDataManager(tile)
            >>> dataManager.set({'text': u"Hello!"})
        
        This writes data to annotations:
        
            >>> dict(context.__annotations__).keys()
            [u'plone.tiles.data.tile2']
            >>> context.__annotations__[u'plone.tiles.data.tile2']
            {'text': u'Hello!'}
        
        We can get this from the data manager too, of course:
        
            >>> dataManager.get()
            {'text': u'Hello!'}
        
        Note that as with transient tiles, the ``data`` attribute is cached and will
        only be looked up once.
        
        If we now look up the tile again, we will get the new value:
        
            >>> tile = getMultiAdapter((context, request), name=u"sample.persistenttile")
            >>> tile = tile['tile2']
            >>> tile()
            u'<b>You said</b> Hello!'
        
            >>> tile.data
            {'text': u'Hello!'}
        
        We can also remove the annotation using the data manager:
        
            >>> dataManager.delete()
            >>> sorted(dict(context.__annotations__).items()) # doctest: +ELLIPSIS
            []
        
        Tile URLs
        ---------
        
        As we have seen, tiles have a canonical URL. For transient tiles, this may
        also encode some tile data.
        
        If you have a tile instance and you need to know the canonical tile URL,
        you can use the ``IAbsoluteURL`` API.
        
        For the purposes of testing, we need to ensure that we can get an absolute URL
        for the context. We'll achieve that with a dummy adapter:
        
            >>> from zope.interface import implements
            >>> from zope.component import adapts
        
            >>> from zope.traversing.browser.interfaces import IAbsoluteURL
            >>> from zope.publisher.interfaces.http import IHTTPRequest
        
            >>> class DummyAbsoluteURL(object):
            ...     implements(IAbsoluteURL)
            ...     adapts(IContext, IHTTPRequest)
            ...
            ...     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
        
        For convenience, the tile URL is also available under the ``url`` property:
        
            >>> transientTile.url
            'http://example.com/context/@@sample.tile/tile1?title=My+title&cssClass=foo&count%3Along=5'
        
        The tile absolute URL structure remains unaltered if the data is
        coming from a `_tiledata` JSON-encoded parameter instead of from the request
        parameters directly::
        
            >>> 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'
        
        For persistent tiles, the are no data parameters:
        
            >>> 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
        
        And again, for convenience:
        
            >>> persistentTile.url
            'http://example.com/context/@@sample.persistenttile/tile2'
        
        If the tile doesn't have an id, we don't get any sub-path
        
            >>> 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'
        
        ZCML directive
        ==============
        
        A tile is really just a browser view providing ``IBasicTile`` (or, more
        commonly, ``ITile`` or ``IPersistentTile``) coupled with a named utility
        providing ``ITileType``. The names of the browser view and the tile should
        match.
        
        To make it easier to register these components, this package provides a
        ``<plone:tile />`` directive that sets up both. It supports several use cases:
        
          * Registering a new tile from a class
          * Registering a new tile from a template only
          * Registering a new tile form a class and a template
          * Registering a new tile for an existing tile type (e.g. for a new layer)
        
        To test this, we have created a dummy schema and a dummy tile in ``tests.py``,
        and a dummy template in ``test.pt``.
        
        Let's show how these may be used by registering several tiles:
        
            >>> 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.app.publisher" file="meta.zcml" />
            ...
            ...     <include package="plone.tiles" file="meta.zcml" />
            ...     <include package="plone.tiles" />
            ...
            ...     <permission
            ...         id="plone.tiles.tests.DummyAdd"
            ...         title="Dummy add permission"
            ...         />
            ...     <permission
            ...         id="plone.tiles.tests.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.tests.DummyAdd"
            ...         schema="plone.tiles.tests.IDummySchema"
            ...         class="plone.tiles.tests.DummyTileWithTemplate"
            ...         template="test.pt"
            ...         for="plone.tiles.tests.IDummyContext"
            ...         layer="plone.tiles.tests.IDummyLayer"
            ...         permission="plone.tiles.tests.DummyView"
            ...         />
            ...
            ...     <!-- A class-only tile -->
            ...     <plone:tile
            ...         name="dummy2"
            ...         title="Dummy tile 2"
            ...         add_permission="plone.tiles.tests.DummyAdd"
            ...         class="plone.tiles.tests.DummyTile"
            ...         for="*"
            ...         permission="plone.tiles.tests.DummyView"
            ...         />
            ...
            ...     <!-- A template-only tile -->
            ...     <plone:tile
            ...         name="dummy3"
            ...         title="Dummy tile 3"
            ...         add_permission="plone.tiles.tests.DummyAdd"
            ...         template="test.pt"
            ...         for="*"
            ...         permission="plone.tiles.tests.DummyView"
            ...         />
            ...
            ...     <!-- Use the PersistentTile class directly with a template-only tile -->
            ...     <plone:tile
            ...         name="dummy4"
            ...         title="Dummy tile 4"
            ...         add_permission="plone.tiles.tests.DummyAdd"
            ...         schema="plone.tiles.tests.IDummySchema"
            ...         class="plone.tiles.PersistentTile"
            ...         template="test.pt"
            ...         for="*"
            ...         permission="plone.tiles.tests.DummyView"
            ...         />
            ...
            ...     <!-- Override dummy3 for a new layer -->
            ...     <plone:tile
            ...         name="dummy3"
            ...         class="plone.tiles.tests.DummyTile"
            ...         for="*"
            ...         layer="plone.tiles.tests.IDummyLayer"
            ...         permission="plone.tiles.tests.DummyView"
            ...         />
            ...
            ... </configure>
            ... """
        
            >>> from StringIO import StringIO
            >>> from zope.configuration import xmlconfig
            >>> xmlconfig.xmlconfig(StringIO(configuration))
        
        Let's check how the tiles were registered:
        
            >>> 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
            u'This one shows all available options'
        
            >>> tile1_type.add_permission
            'plone.tiles.tests.DummyAdd'
        
            >>> tile1_type.schema
            <InterfaceClass plone.tiles.tests.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.tests.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.tests.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.tests.DummyAdd'
            >>> tile4_type.schema
            <InterfaceClass plone.tiles.tests.IDummySchema>
        
        Finally, let's check that we can look up the tiles.
        
            >>> from zope.publisher.browser import TestRequest
            >>> from zope.interface import implements, alsoProvides
        
            >>> from plone.tiles.tests import IDummyContext, IDummyLayer
        
            >>> class Context(object):
            ...     implements(IDummyContext)
        
            >>> context = Context()
            >>> request = TestRequest()
            >>> layer_request = TestRequest(skin=IDummyLayer)
        
            >>> from zope.component import getMultiAdapter
            >>> from plone.tiles import Tile, PersistentTile
            >>> from plone.tiles.tests 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 support
        ===========
        
        Some sites may choose to render tiles in a delayed fashion using Edge Side
        Includes or some similar mechanism. ``plone.tiles`` includes some support to
        help render ESI placeholders. This is used in ``plone.app.blocks`` to
        facilitate ESI rendering. Since ESI normally involves a "dumb" replacement
        operation, ``plone.tiles`` also provides a means of accessing just the head
        and/or just the body of a tile.
        
        To use the package, you should first load its ZCML configuration.
        
            >>> 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.app.publisher" file="meta.zcml" />
            ...
            ...     <include package="plone.tiles" file="meta.zcml" />
            ...     <include package="plone.tiles" />
            ...
            ... </configure>
            ... """
        
            >>> from StringIO import StringIO
            >>> from zope.configuration import xmlconfig
            >>> xmlconfig.xmlconfig(StringIO(configuration))
        
        Marking a tile as ESI-rendered
        ------------------------------
        
        For ESI rendering to be available, the tile must be marked with the
        ``IESIRendered`` marker interface. We can create a dummy tile with this
        interface like so:
        
            >>> from zope.interface import implements
            >>> from plone.tiles.interfaces import IESIRendered
            >>> from plone.tiles import Tile
        
            >>> class SampleTile(Tile):
            ...     implements(IESIRendered)
            ...
            ...     __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>"
        
        Above, we have created a simple HTML string. This would normally be rendered
        using a page template.
        
        We'll register this tile manually here. Ordinarily, of course, it would be
        registered via ZCML.
        
            >>> from plone.tiles.type import TileType
            >>> sampleTileType = TileType(
            ...     name=u'sample.tile',
            ...     title=u"Sample tile",
            ...     description=u"A tile used for testing",
            ...     add_permission="dummy.Permission",
            ...     schema=None)
        
            >>> 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")
        
        ESI lookup
        ----------
        
        When a page is rendered (for example by a system like ``plone.app.blocks``,
        but see below), a tile placeholder may be replaced by a link such as::
        
            <esi:include src="/path/to/context/@@sample.tile/tile1/@@esi-body" />
        
        When this is resolved, it will return the body part of the tile. Equally,
        a tile in the head can be replaced by::
        
            <esi:include src="/path/to/context/@@sample.tile/tile1/@@esi-head" />
        
        To illustrate how this works, let's create a sample context, look up the view
        as it would be during traversal, and instantiate the tile, before looking up
        the ESI views and rendering them.
        
            >>> from zope.interface import implements
        
            >>> class IContext(Interface):
            ...     pass
        
            >>> class Context(object):
            ...     implements(IContext)
        
            >>> from zope.publisher.browser import TestRequest
        
            >>> class IntegratedTestRequest(TestRequest):
            ...     @property
            ...     def environ(self):
            ...         return self._environ
        
            >>> context = Context()
            >>> request = IntegratedTestRequest()
        
            >>> from zope.interface import Interface
            >>> from zope.component import getMultiAdapter
        
        The following simulates traversal to ``context/@@sample.tile/tile1``
        
            >>> tile = getMultiAdapter((context, request), name=u"sample.tile")
            >>> tile = tile['tile1'] # simulates sub-path traversal
        
        This tile should be ESI rendered::
        
            >>> IESIRendered.providedBy(tile)
            True
        
        At this point, we can look up the ESI views:
        
            >>> head = getMultiAdapter((tile, request), name="esi-head")
            >>> print head()
            <title>Title</title>
        
            >>> body = getMultiAdapter((tile, request), name="esi-body")
            >>> print body()
            <b>My tile</b>
        
        Tiles without heads or bodies
        -----------------------------
        
        In general, tiles are supposed to return full HTML documents. The ``esi-head``
        and ``esi-body`` views are tolerant of tiles that do not. If they cannot find
        a ``<head />`` or ``<body />`` element, respectively, they will return the
        underlying tile output unaltered.
        
        For example:
        
            >>> 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>"
        
        We won't bother to register this for this test, instead just instantiating
        it directly:
        
            >>> tile = LazyTile(context, request)['tile1']
        
            >>> IESIRendered.providedBy(tile)
            True
        
            >>> head = getMultiAdapter((tile, request), name="esi-head")
            >>> print head()
            <title>Page title</title>
        
        Of course, the ESI body renderer would return the same thing, since it can't
        extract a specific body either:
        
            >>> body = getMultiAdapter((tile, request), name="esi-body")
            >>> print body()
            <title>Page title</title>
        
        In this case, we would likely end up with invalid HTML, since the
        ``<title />`` tag is not allowed in the body. Whether and how to resolve
        this is left up to the ESI interpolation implementation.
        
        Convenience classes and placeholder rendering
        ---------------------------------------------
        
        Two convenience base classes can be found in the ``plone.tiles.esi`` module.
        These extend the standard ``Tile`` and ``PersistentTile`` classes
        to provide the ``IESIRendered`` interface.
        
        * ``plone.tiles.esi.ESITile``, a transient, ESI-rendered tile
        * ``plone.tiles.esi.ESIPersistentTile``, a persistent, ESI-rendered tile
        
        These are particularly useful if you are creating a template-only tile and
        want ESI rendering. For example::
        
            <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"
                />
        
        Additionally, these base classes implement a ``__call__()`` method that will
        render a tile placeholder if the request contains an ``X-ESI-Enabled``
        header set to the literal 'true'.
        
        The placeholder is a simple HTML ``<a />`` tag, which can be transformed into
        an ``<esi:include />`` tag using the helper function ``substituteESILinks()``.
        The reason for this indirection is that the ``esi`` namespace is not allowed
        in HTML documents and are liable to be stripped out by transforms using the
        ``libxml2`` / ``lxml`` HTML parser.
        
        Let us now create a simple ESI tile. To benefit from the default rendering,
        we should implement the ``render()`` method instead of ``__call__()``. Setting
        a page template as the ``index`` class variable or using the ``template``
        attribute to the ZCML directive will work also.
        
            >>> 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",
            ...     schema=None)
        
            >>> provideUtility(sampleESITileType, name=u'sample.esitile')
            >>> provideAdapter(SampleESITile, (Interface, Interface), IBasicTile, name=u"sample.esitile")
        
        The following simulates traversal to ``context/@@sample.esitile/tile1``
        
            >>> tile = getMultiAdapter((context, request), name=u"sample.esitile")
            >>> tile = tile['tile1'] # simulates sub-path traversal
        
        By default, the tile renders as normal:
        
            >>> print tile()
            <html><head><title>Title</title></head><body><b>My ESI tile</b></body></html>
        
        However, if we opt into ESI rendering via a request header, we get a different
        view:
        
            >>> 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>
        
        This can be transformed into a proper ESI tag with ``substituteESILinks()``:
        
            >>> 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>
        
        It is also possible to render the ESI tile for the head. This is done with
        a class variable 'head' (which would of course normally be set within the
        class):
        
            >>> 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>
        
        Changelog
        =========
        
        1.3.0 (2015-04-21)
        ------------------
        
        - Fix edit_permission and delete_permission to default
          to add_permission only in TileType constructor
          [datakurre]
        
        - Fix argument order in TileType constructor call
          [datakurre]
        
        - Fix absolute_url-adapter to fallback to relative URL
          [datakurre]
        
        - Add response to include absolute X-Tile-Url header
          [bloodbare]
        
        1.2 (2012-11-07)
        ----------------
        
        - Adding icon property for tiletype
          [garbas]
        
        - Url that we pass via X-Tile-Url should be relative to current context
          [garbas]
        
        - Adding support for more robust permissions for edit and delete on tiles
          [cewing calvinhp]
        
        1.1 (2012-06-22)
        ----------------
        
        - X-Tile-Uid header is passed on tile view containing tile's id.
          [garbas]
        
        - PEP 8/Pyflakes (ignoring E121, E123, E126, E127 and E501).
          [hvelarde]
        
        1.0 (2012-05-14)
        ----------------
        
        - Refactor ESI support. To use the ``ESITile`` and ``ESIPersistentTile``
          base classes, you should either use a template assigned via ZCML or
          override the ``render()`` method. See ``esi.rst`` for full details.
          [optilude]
        
        - Internationalized title and description of the tile directive.
          [vincentfretin]
        
        - Use a  json-encoded parameter in transient tiles as first option.
          [dukebody]
        
        - Use adapters for the Zope Publisher type casting
          [dukebody]
        
        - Conditionaly support z3c.relationfield's RelationChoice fields
          [dukebody]
        
        - Ignore type casting for fields without fixed type, like zope.schema.Choice
          [dukebody]
        
        1.0a1 (2010-05-17)
        ------------------
        
        - Initial release.
        
Keywords: plone tiles
Platform: UNKNOWN
Classifier: Framework :: Plone
Classifier: Programming Language :: Python
Classifier: Topic :: Software Development :: Libraries :: Python Modules
