plone.app.blocks


Nameplone.app.blocks JSON
Version 7.0.1 PyPI version JSON
download
home_pagehttps://github.com/plone/plone.app.blocks
SummaryImplements the in-Plone blocks rendering process
upload_time2023-11-16 10:23:07
maintainerPlone Community
docs_urlNone
authorMartin Aspeli, Laurence Rowe
requires_python>=3.8
licenseGPLv2
keywords plone blocks deco
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            ======================
Introduction to Blocks
======================

.. image:: https://coveralls.io/repos/plone/plone.app.blocks/badge.png?branch=master
    :alt: Coveralls badge
    :target: https://coveralls.io/r/plone/plone.app.blocks

This package implements the 'blocks' rendering model,
by providing several transform stages that hook into ``plone.transformchain``.

The rendering stages are:

``plone.app.blocks.parsexml`` (order 8000)
    Turns the response in a ``repoze.xmliter`` ``XMLSerializer`` object.
    This is then used by the subsequent stages.
    If the input is not HTML, the transformation is aborted.

``plone.app.blocks.mergepanels`` (order 8100)
    Looks up the site layout and executes the panel merge algorithm.
    Sets a request variable ('plone.app.blocks.merged') to indicate that it has done its job.

``plone.app.blocks.tiles`` (order 8500)
    Resolve tiles and place them directly into the merged layout.
    This is the fallback for views that do not opt into ``ITilePageRendered``.

``plone.app.blocks.esirender`` (order 9900)
    Only executed if the request key ``plone.app.blocks.esi`` is set and its value is true,
    as would be the case if any ESI-rendered tiles are included and ESI rendering is enabled globally.
    This step will serialise the response down to a string and perform some substitution to make ESI rendering work.


Site layouts
============

The package also registers the ``sitelayout`` ``plone.resource`` resource type,
allowing site layouts to be created easily as static HTML files served from resource directories.
The URL to a site layout is typically something like::

    /++sitelayout++my.layout/site.html

See ``plone.resource`` for more information about how to register resource directories.
For site layouts, the ``type`` of the resource directory is ``sitelayout``.

It is possible to provide a manifest file that gives a title, description and alternative default file for a site layout HTML file in a resource directory.
To create such a manifest, put a ``manifest.cfg`` file in the layout directory with the following structure:

.. code-block:: ini

    [sitelayout]
    title = My layout title
    description = Some description
    file = some-html-file.html

* All keys are optional.
* The file defaults to ``site.html``.
* Single manifest may contain multiple ``[sitelayout]`` sections.

A vocabulary factory called ``plone.availableSiteLayouts`` is registered to allow lookup of all registered site layouts.
The terms in this vocabulary use the URL as a value,
the resource directory name as a token,
and the title from the manifest (falling back on a sanitised version of the resource directory name) as the title.

The current default site layout can be identified by the ``plone.registry`` key ``plone.defaultSiteLayout``,
which is set to ``None`` by default.
To always use the current site default, use:

.. code-block:: html

    <html data-layout="./@@default-site-layout">

The ``@@default-site-layout`` view will render the current default site layout.


Content layouts
===============

The package also registers the ``contentlayout`` ``plone.resource`` resource type,
allowing shared content area layouts to be created easily as static HTML files served from resource directories.
The URL to a content layout is typically something like::

    /++contentlayout++my.layout/content.html

See ``plone.resource`` for more information about how to register resource directories.
For site layouts, the ``type`` of the resource directory is ``contentlayout``.

It is possible to provide a manifest file that gives a title, description and alternative default file for a site layout HTML file in a resource directory.
To create such a manifest, put a ``manifest.cfg`` file in the layout directory with the following structure:

.. code-block:: ini

    [contentlayout]
    title = My layout title
    description = Some description
    file = some-html-file.html
    screenshot = mylayout.png
    for = Document,Folder
    permission = cmf.ModifyPortalContent

* All keys are optional.
* Value for key ``file`` defaults to ``content.html``.
* Single manifest may contain multiple ``[contentlayout]`` sections.
* Values for keys ``for`` and ``permission`` are only for advisory and may not
  be enforced.

A vocabulary factory called ``plone.availableContentLayouts`` is registered to allow lookup of all registered content layouts.
The terms in this vocabulary use the URL as a value,
the resource directory name as a token,
and the title from the manifest (falling back on a sanitized version of the resource directory name) as the title.

The default content layout can be identified by the ``plone.registry`` key ``plone.app.blocks.default_layout``,
and the default content layout for some specific content type with key ``plone.app.blocks.default_layout.my_type``.
The default content layout is supported by the built-in ``layout_view`` browser view for content with ``ILayoutAware`` behavior.


ILayoutAware behavior
=====================

It is possible for the default site layout to be overridden per section,
by having parent objects provide or be adaptable to ``plone.app.blocks.layoutbehavior.ILayoutAware``.
As the module name implies, this interface can be used as a ``plone.behavior`` behavior named ``plone.layoutaware``,
but it can also be implemented directly or used as a standard adapter.

The ``ILayoutAware`` interface defines properties:

``content``
    which contains the body of the page to be rendered.
``contentLayout``
    which contains the path to the selected static content layout,
    which is used instead of ``content`` when set.
``pageSiteLayout``
    which contains the path to the site layout to be used for the given page.
    It can be ``None`` if the default is to be used.
``sectionSiteLayout``
    which contains the path to the site layout to be used for pages *underneath* the given page (but not for the page itself).
    Again, it can be ``None`` if the default is to be used.

To make use of the page site layout, use the following:

.. code-block:: html

    <html data-layout="./@@default-site-layout">

See ``rendering.rst`` for detailed examples of how the processing is applied,
and ``esi.rst`` for details about how Edge Side Includes can be supported.

Blocks rendering in detail
==========================

This doctest illustrates the blocks rendering process.
At a high level, it consists of the following steps:


0. Obtain the content page, an HTML document.


1. Look for a site layout link in the content page.

   This takes the form of an attribute on the html tag like ``<html data-layout="..." />``.

   Usually, the site layout URL will refer to a resource in a resource  directory of type ``sitelayout``,
   e.g. ``/++sitelayout++foo/site.html``,
   although the layout can be any URL.
   An absolute path like this will be adjusted so that it is always relative to the Plone site root.


2. Resolve and obtain the site layout.

   This is another HTML document.


3. Extract panels from the site layout.

   A panel is an element (usually a ``<div />``) in the layout page with a data-panel attribute,
   for example: ``<div data-panel="panel1" />`` or ``<div data-panel="panel1" data-panel-mode="replace" />``.
   The attribute specifies an id which *may* be used in the content page.

   You can specify how the content from the content page is inserted into the panel.
   The default or with ``data-panel-mode="append"`` specified the content from the content page is appended to the layout page panel.
   With ``data-panel-mode="replace"`` the content replaced the layout page panel.


4. Merge panels.

   This is the process which applies the layout to the unstyled page.
   All panels in the layout page that have a matching element in the content page get the content page element appended or are replaced by it.
   The rest of the content page is discarded.


5. Resolve and obtain tiles.

   A tile is a placeholder element in the page which will be replaced by the contents of a document referenced by a URL.

   A tile is identified by a placeholder element with a ``data-tile`` attribute containing the tile URL.

   Note that at this point, panel merging has taken place,
   so if a panel in the content page contains tiles, they will be carried over into the merge page.
   Also note that it is possible to have tiles outside of panels - the two concepts are not directly related.

   The ``plone.tiles`` package provides a framework for writing tiles,
   although in reality a tile can be any HTML page.


6. Place tiles into the page.

   The tile should resolve to a full HTML document.
   Any content found in the ``<head />`` of the tile content will be merged into the ``<head />`` of the rendered content.
   The contents of the ``<body />`` of the tile content are put into the rendered document at the tile placeholder.


Rendering step-by-step
----------------------

Let us now illustrate the rendering process.
We'll need a few variables defined first:

.. code-block:: python

    >>> from plone.testing.z2 import Browser
    >>> import transaction

    >>> app = layer['app']
    >>> portal = layer['portal']

    >>> browser = Browser(app)
    >>> browser.handleErrors = False


Creating a site layout
~~~~~~~~~~~~~~~~~~~~~~

The most common approach for managing site layouts is to use a resource registered using a ``plone.resource`` directory of type ``sitelayout``,
and then use the ``@@default-site-layout`` view to reference the content.
We will illustrate this below, but it is important to realise that ``plone.app.blocks`` works by post-processing responses rendered by Zope.
The content and layout pages could just as easily be created by views of content objects, or even resources external to Zope/Plone.

First, we will create a resource representing the site layout and its panels.
This includes some resources and other elements in the ``<head />``,
``<link />`` tags which identify tile placeholders and panels,
as well as content inside and outside panels.
The tiles in this case are managed by ``plone.tiles``, and are both of the same type.

.. code-block:: python

    >>> layoutHTML = b"""\
    ... <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    ... <html>
    ...     <head>
    ...         <title>Layout title</title>
    ...         <link rel="stylesheet" href="/layout/style.css" />
    ...         <script type="text/javascript">alert('layout');</script>
    ...
    ...         <style type="text/css">
    ...         div {
    ...             margin: 5px;
    ...             border: dotted black 1px;
    ...             padding: 5px;
    ...         }
    ...         </style>
    ...
    ...         <link rel="stylesheet" data-tile="./@@test.tile_nobody/tile_css" />
    ...     </head>
    ...     <body>
    ...         <h1>Welcome!</h1>
    ...         <div data-panel="panel1">Layout panel 1</div>
    ...         <div data-panel="panel2">
    ...             Layout panel 2
    ...             <div id="layout-tile1" data-tile="./@@test.tile1/tile1">Layout tile 1 placeholder</div>
    ...         </div>
    ...         <div data-panel="panel3">
    ...             Layout panel 3
    ...             <div id="layout-tile2" data-tile="./@@test.tile1/tile2">Layout tile 2 placeholder</div>
    ...         </div>
    ...     </body>
    ... </html>
    ... """

We can create an in-ZODB resource directory of type ``sitelayout`` that contains this layout.
Another way would be to register a resource directory in a package using ZCML, or use a global resource directory.
See ``plone.resource`` for more details.

.. code-block:: python

    >>> from Products.CMFCore.utils import getToolByName
    >>> from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2
    >>> from OFS.Image import File

    >>> resources = getToolByName(portal, 'portal_resources')
    >>> resources._setOb('sitelayout', BTreeFolder2('sitelayout'))
    >>> resources['sitelayout']._setOb('mylayout', BTreeFolder2('mylayout'))
    >>> resources['sitelayout']['mylayout']._setOb('site.html', File('site.html', 'site.html', layoutHTML))

    >>> transaction.commit()

This resource can now be accessed using the path ``/++sitelayout++mylayout/site.html``.
Let's render it on its own to verify that.

.. code-block:: python

    >>> browser.open(portal.absolute_url() + '/++sitelayout++mylayout/site.html')
    >>> print(browser.contents)
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html>
        <head>
            <title>Layout title</title>
            <link rel="stylesheet" href="/layout/style.css" />
            <script type="text/javascript">alert('layout');</script>
    <BLANKLINE>
            <style type="text/css">
            div {
                margin: 5px;
                border: dotted black 1px;
                padding: 5px;
            }
            </style>
    <BLANKLINE>
            <link rel="stylesheet" data-tile="./@@test.tile_nobody/tile_css" />
        </head>
        <body>
            <h1>Welcome!</h1>
            <div data-panel="panel1">Layout panel 1</div>
            <div data-panel="panel2">
                Layout panel 2
                <div id="layout-tile1" data-tile="./@@test.tile1/tile1">Layout tile 1 placeholder</div>
            </div>
            <div data-panel="panel3">
                Layout panel 3
                <div id="layout-tile2" data-tile="./@@test.tile1/tile2">Layout tile 2 placeholder</div>
            </div>
        </body>
    </html>

We can now set this as the site-wide default layout by setting the registry key ``plone.defaultSiteLayout``.
There are two indirection views, ``@@default-site-layout`` and ``@@page-site-layout``, that respect this registry setting.
By using one of these views to reference the layout of a given page, we can manage the default site layout centrally.

.. code-block:: python

    >>> from zope.component import getUtility
    >>> from plone.registry.interfaces import IRegistry
    >>> registry = getUtility(IRegistry)
    >>> registry['plone.defaultSiteLayout'] = b'/++sitelayout++mylayout/site.html'
    >>> transaction.commit()

Creating a page layout and tiles
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Next, we will define the markup of a content page that uses this layout via the ``@@default-site-layout`` indirection view:

.. code-block:: python

    >>> pageHTML = """\
    ... <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    ... <html data-layout="./@@default-site-layout">
    ...     <body>
    ...         <h1>Welcome!</h1>
    ...         <div data-panel="panel1">
    ...             Page panel 1
    ...             <div id="page-tile2" data-tile="./@@test.tile1/tile2?magicNumber:int=2">Page tile 2 placeholder</div>
    ...         </div>
    ...         <div data-panel="panel2">
    ...             Page panel 2
    ...             <div id="page-tile3" data-tile="./@@test.tile1/tile3">Page tile 3 placeholder</div>
    ...         </div>
    ...         <div data-panel="panel4">
    ...             Page panel 4 (ignored)
    ...             <div id="page-tile4" data-tile="./@@test.tile1/tile4">Page tile 4 placeholder</div>
    ...         </div>
    ...     </body>
    ... </html>
    ... """

We then register a view that simply return this HTML,
and a tile type which we can use to test tile rendering.

We do this in code for the purposes of the test,
and we have to apply security because we will shortly render those pages using the test publisher.
In real life, these could be registered using the standard ``<browser:page />`` and ``<plone:tile />`` directives.

.. code-block:: python

    >>> from zope.publisher.browser import BrowserView
    >>> from zope.interface import Interface, implementer
    >>> from zope import schema
    >>> from plone.tiles import Tile
    >>> from plone.app.blocks.interfaces import IBlocksTransformEnabled

    >>> @implementer(IBlocksTransformEnabled)
    ... class Page(BrowserView):
    ...     __name__ = 'test-page'
    ...     def __call__(self):
    ...         self.request.response.setHeader("Content-Type", "text/html")
    ...         return pageHTML

    >>> class ITestTile(Interface):
    ...     magicNumber = schema.Int(title=u"Magic number", required=False)

    >>> class TestTile(Tile):
    ...     __name__ = 'test.tile1' # normally set by ZCML handler
    ...
    ...     def __call__(self):
    ...         # fake a page template to keep things simple in the test
    ...         self.request.response.setHeader("Content-Type", "text/html")
    ...         return """\
    ... <html>
    ...     <head>
    ...         <meta name="tile-name" content="%(name)s" />
    ...     </head>
    ...     <body>
    ...         <p>
    ...             This is a demo tile with id %(name)s
    ...         </p>
    ...         <p>
    ...             Magic number: %(number)d; Form: %(form)s; Query string: %(queryString)s; URL: %(url)s
    ...         </p>
    ...     </body>
    ... </html>""" % dict(name=self.id, number=self.data['magicNumber'] or -1,
    ...                   form=sorted(self.request.form.items()), queryString=self.request['QUERY_STRING'], url=self.request.getURL())

Let's add another tile, this time only a head part.
This could for example be a tile that only needs to insert some CSS.

.. code-block:: python

    >>> class TestTileNoBody(Tile):
    ...     __name__ = 'test.tile_nobody'
    ...
    ...     def __call__(self):
    ...         return """\
    ... <html>
    ...     <head>
    ...         <link rel="stylesheet" type="text/css" href="tiled.css" />
    ...     </head>
    ... </html>"""

We register these views and tiles in the same way the ZCML handlers for ``<browser:page />`` and ``<plone:tile />`` would:

.. code-block:: python

    >>> from plone.tiles.type import TileType
    >>> from AccessControl.security import protectClass
    >>> from AccessControl.class_init import InitializeClass
    >>> from zope.component import provideAdapter, provideUtility
    >>> from zope.interface import Interface

    >>> testTileType = TileType(
    ...     name=u'test.tile1',
    ...     title=u"Test tile",
    ...     description=u"A tile used for testing",
    ...     add_permission="cmf.ManagePortal",
    ...     view_permission="zope2.View",
    ...     schema=ITestTile)

    >>> testTileTypeNoBody = TileType(
    ...     name=u'test.tile_nobody',
    ...     title=u"Test tile using only a header",
    ...     description=u"Another tile used for testing",
    ...     add_permission="cmf.ManagePortal",
    ...     view_permission="zope2.View")

    >>> protectClass(Page, 'zope2.View')
    >>> protectClass(TestTile, 'zope2.View')

    >>> InitializeClass(Page)
    >>> InitializeClass(TestTile)

    >>> provideAdapter(Page, (Interface, Interface,), Interface, u'test-page')
    >>> provideAdapter(TestTile, (Interface, Interface,), Interface, u'test.tile1',)
    >>> provideAdapter(TestTileNoBody, (Interface, Interface,), Interface, u'test.tile_nobody',)
    >>> provideUtility(testTileType, name=u'test.tile1')
    >>> provideUtility(testTileTypeNoBody, name=u'test.tile_nobody')

Rendering the page
~~~~~~~~~~~~~~~~~~

We can now render the page.
Provided ``plone.app.blocks`` is installed and working, it should perform its magic.
We make sure that Zope is in "development mode" to get pretty-printed output.

.. code-block:: python

    >>> browser.open(portal.absolute_url() + '/@@test-page')
    >>> print(browser.contents.replace('<head><meta', '<head>\n\t<meta'))
    <!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">
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ASCII" />
        <title>Layout title</title>
        <link rel="stylesheet" href="/layout/style.css" />
        <script type="text/javascript">alert('layout');</script>
        <style type="text/css">
            div {
                margin: 5px;
                border: dotted black 1px;
                padding: 5px;
            }
            </style>
        <link rel="stylesheet" type="text/css" href="tiled.css" />
        <meta name="tile-name" content="tile2" />
        <meta name="tile-name" content="tile3" />
        <meta name="tile-name" content="tile2" />
      </head>
      <body>
            <h1>Welcome!</h1>
            <div data-panel="panel1">
                Page panel 1
            <p>
                This is a demo tile with id tile2
            </p>
            <p>
                Magic number: 2; Form: [('magicNumber', 2)]; Query string: magicNumber:int=2; URL: http://nohost/plone/@@test.tile1/tile2
            </p>
            </div>
            <div data-panel="panel2">
                Page panel 2
            <p>
                This is a demo tile with id tile3
            </p>
            <p>
                Magic number: -1; Form: []; Query string: ; URL: http://nohost/plone/@@test.tile1/tile3
            </p>
            </div>
            <div data-panel="panel3">
                Layout panel 3
            <p>
                This is a demo tile with id tile2
            </p>
            <p>
                Magic number: -1; Form: []; Query string: ; URL: http://nohost/plone/@@test.tile1/tile2
            </p>
            </div>
        </body>
    </html>
    <BLANKLINE>

Notice how:

* Panels from the page have been merged into the layout, replacing the corresponding panels there.
* The ``<head />`` sections of the two documents have been merged
* The rest of the layout page is intact
* The rest of the content page is discarded
* The tiles have been rendered, replacing the relevant placeholders
* The ``<head />`` section from the rendered tiles has been merged into the ``<head />`` of the output page.

Using VHM
~~~~~~~~~

Make sure to have a clean browser:

.. code-block:: python

    >>> browser = Browser(app)
    >>> browser.handleErrors = False

Using Virtual Host Monster we rewrite the url to consider all content being under ``/``:

.. code-block:: python

    >>> vhm_url = 'http://nohost/VirtualHostBase/http/nohost:80/plone/VirtualHostRoot/'
    >>> browser.open(vhm_url + '/@@test-page')

Tiles should return an url according to this:

.. code-block:: python

    >>> 'Magic number: -1; Form: []; Query string: ; URL: http://nohost/@@test.tile1/tile2' in browser.contents
    True

Now we deal with _vh_* arguments. We expect our site to be under a subdir with id *subplone*:

.. code-block:: python

    >>> vhm_url = 'http://nohost/VirtualHostBase/http/nohost:80/plone/VirtualHostRoot/_vh_subplone'
    >>> browser.open(vhm_url + '/@@test-page')

Tiles should return an url according to this:

.. code-block:: python

    >>> 'Magic number: -1; Form: []; Query string: ; URL: http://nohost/subplone/@@test.tile1/tile2' in browser.contents
    True


ESI rendering
=============

Blocks supports rendering of tiles for Edge Side Includes (ESI).
A tile will be rendered to ESI provided that:

* The tile itself is marked with the ``IESIRendered`` marker interface.
  See `plone.tiles`_ for more details.
* The ``plone.app.blocks.interfaces.IBlocksSettings.esi`` record in the registry is set to True.
  It is False by default.
  To switch this through-the-web, you can visit the configuration registry control panel in Plone.

Note that if a tile is rendered using ESI, it's <head /> contents are ignored, instead of being merged into the final page.
That is, only the ``@@esi-body`` view form `plone.tiles`_ is used by default.

An ESI link looks like this:

.. code-block:: xml

    <esi:include src="http://example.com/plone/@@some.tile/tile-1/@@esi-body?param1=value1" />

A fronting server such as Varnish will be able to load this on demand and
compose the page from fragments that may be cached individually.

Test setup
----------

Let's first register a two very simple tiles. One uses ESI, one does not.

.. code-block:: python

    >>> from plone.tiles.esi import ESITile
    >>> from plone.tiles import Tile
    >>> from plone.tiles.type import TileType

    >>> class NonESITile(Tile):
    ...     __name__ = 'test.tile2' # normally set by ZCML handler
    ...
    ...     def __call__(self):
    ...         return """\
    ... <html>
    ...     <head>
    ...         <meta name="tile-name" content="%(name)s" />
    ...     </head>
    ...     <body>
    ...         <p>
    ...             Non-ESI tile with query string %(queryString)s
    ...         </p>
    ...     </body>
    ... </html>""" % dict(name=self.id, queryString=self.request['QUERY_STRING'])

    >>> testTile2Type = TileType(
    ...     name=u'test.tile2',
    ...     title=u"Test tile 2",
    ...     description=u"A tile used for testing",
    ...     add_permission="cmf.ManagePortal",
    ...     view_permission="zope2.View")

    >>> class SimpleESITile(ESITile):
    ...     __name__ = 'test.tile3' # normally set by ZCML handler
    ...
    ...     def render(self):
    ...         return """\
    ... <html>
    ...     <head>
    ...         <meta name="tile-name" content="%(name)s" />
    ...     </head>
    ...     <body>
    ...         <p>
    ...             ESI tile with query string %(queryString)s
    ...         </p>
    ...     </body>
    ... </html>""" % dict(name=self.id, queryString=self.request['QUERY_STRING'])

    >>> testTile3Type = TileType(
    ...     name=u'test.tile3',
    ...     title=u"Test tile 3",
    ...     description=u"A tile used for testing",
    ...     add_permission="cmf.ManagePortal",
    ...     view_permission="zope2.View")

Register these in the same way that the ZCML handlers would, more or less.

.. code-block:: python

    >>> from AccessControl.security import protectClass
    >>> protectClass(NonESITile, 'zope2.View')
    >>> protectClass(SimpleESITile, 'zope2.View')

    >>> from App.class_init import InitializeClass
    >>> InitializeClass(NonESITile)
    >>> InitializeClass(SimpleESITile)

    >>> from zope.component import provideAdapter, provideUtility
    >>> from zope.interface import Interface
    >>> provideAdapter(NonESITile, (Interface, Interface,), Interface, u'test.tile2',)
    >>> provideUtility(testTile2Type, name=u'test.tile2')
    >>> provideAdapter(SimpleESITile, (Interface, Interface,), Interface, u'test.tile3',)
    >>> provideUtility(testTile3Type, name=u'test.tile3')

We will also register a simple layout and a simple page using these tiles.

.. code-block:: python

    >>> layoutHTML = u"""\
    ... <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    ... <html>
    ...     <head>
    ...         <title>Layout title</title>
    ...     </head>
    ...     <body>
    ...         <h1>Welcome!</h1>
    ...         <div data-panel="panel1">Content goes here</div>
    ...         <div id="layout-non-esi-tile" data-tile="./@@test.tile2/tile1">Layout tile 1 placeholder</div>
    ...         <div id="layout-esi-tile" data-tile="./@@test.tile3/tile2">Layout tile 2 placeholder</div>
    ...     </body>
    ... </html>
    ... """

To keep things simple, we'll skip the resource directory and layout indirection view,
instead just referencing a view containing the layout directly.

.. code-block:: python

    >>> from zope.publisher.browser import BrowserView
    >>> class Layout(BrowserView):
    ...     __name__ = 'test-layout'
    ...     def __call__(self):
    ...         return layoutHTML

    >>> protectClass(Layout, 'zope2.View')
    >>> InitializeClass(Layout)
    >>> provideAdapter(Layout, (Interface, Interface,), Interface, u'test-layout',)

    >>> pageHTML = u"""\
    ... <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    ... <html data-layout="./@@test-layout">
    ...     <body>
    ...         <div data-panel="panel1">
    ...             <div id="page-non-esi-tile" data-tile="./@@test.tile2/tile3?foo=bar">Page tile 3 placeholder</div>
    ...             <div id="page-esi-tile" data-tile="./@@test.tile3/tile4?foo=bar">Page tile 4 placeholder</div>
    ...         </div>
    ...     </body>
    ... </html>
    ... """

    >>> from zope.interface import implementer
    >>> from plone.app.blocks.interfaces import IBlocksTransformEnabled
    >>> @implementer(IBlocksTransformEnabled)
    ... class Page(BrowserView):
    ...     __name__ = 'test-page'
    ...     def __call__(self):
    ...         return pageHTML

    >>> protectClass(Page, 'zope2.View')
    >>> InitializeClass(Page)
    >>> provideAdapter(Page, (Interface, Interface,), Interface, u'test-page',)

ESI disabled
------------

We first render the page without enabling ESI.
The ESI-capable tiles should be rendered as normal.

.. code-block:: python

    >>> from plone.testing.z2 import Browser
    >>> app = layer['app']
    >>> browser = Browser(app)
    >>> browser.handleErrors = False

    >>> portal = layer['portal']
    >>> browser.open(portal.absolute_url() + '/@@test-page')

Some cleanup is needed to cover lxml platform discrepancies...

.. code-block:: python

    >>> print(browser.contents.replace('<head><meta', '<head>\n\t<meta'))
    <!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">
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ASCII" />
        <title>Layout title</title>
        <meta name="tile-name" content="tile3" />
        <meta name="tile-name" content="tile4" />
        <meta name="tile-name" content="tile1" />
        <meta name="tile-name" content="tile2" />
        </head>
        <body>
            <h1>Welcome!</h1>
            <div data-panel="panel1">
            <p>
                Non-ESI tile with query string foo=bar
            </p>
            <p>
                ESI tile with query string foo=bar
            </p>
            </div>
            <p>
                Non-ESI tile with query string
            </p>
            <p>
                ESI tile with query string
            </p>
        </body>
    </html>
    <BLANKLINE>

ESI enabled
-----------

We can now enable ESI. This could be done using GenericSetup (with the
``registry.xml`` import step), or through the configuration registry
control panel. In code, it is done like so:

.. code-block:: python

    >>> from zope.component import getUtility
    >>> from plone.registry.interfaces import IRegistry
    >>> from plone.app.blocks.interfaces import IBlocksSettings
    >>> registry = getUtility(IRegistry)
    >>> registry.forInterface(IBlocksSettings).esi = True
    >>> import transaction
    >>> transaction.commit()

We can now perform the same rendering again. This time, the ESI-capable
tiles should be rendered as ESI links. See `plone.tiles`_ for more details.

.. code-block:: python

    >>> browser.open(portal.absolute_url() + '/@@test-page')
    >>> print(browser.contents.replace('<head><meta', '<head>\n\t<meta'))
    <!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">
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ASCII" />
        <title>Layout title</title>
        <meta name="tile-name" content="tile3" />
        <meta name="tile-name" content="tile1" />
        </head>
        <body>
            <h1>Welcome!</h1>
            <div data-panel="panel1">
            <p>
                Non-ESI tile with query string foo=bar
            </p>
            <esi:include src="/plone/@@test.tile3/tile4/@@esi-body?foo=bar" />
            </div>
            <p>
                Non-ESI tile with query string
            </p>
            <esi:include src="/plone/@@test.tile3/tile2/@@esi-body?" />
        </body>
    </html>
    <BLANKLINE>

ESI links are substituted by ``ESIRender``-transform, which should always be
after all transforms registered ``plone.transformchain`` with DOM-manipulation
using ``lxml``. That's because ``lxml`` does not support ESI-namespace in HTML
and ESI substitution can only be done once ``lxml`` tree is serialized into
publishable byte string.

If ``ESIRender``-transform transforms any ESI-links, an additional HTML header
``X-Esi`` being is set on the response. The existence of this header can be
used to enable ESI-support in Varnish:

.. code-block:: python

    >>> browser.headers.get('X-Esi')
    '1'

When ESI rendering takes place, the following URLs will be called:

.. code-block:: python

    >>> browser.open("http://nohost/plone/@@test.tile3/tile4/@@esi-body?foo=bar")
    >>> print(browser.contents)
    <p>
        ESI tile with query string foo=bar
    </p>

    >>> browser.open("http://nohost/plone/@@test.tile3/tile2/@@esi-body?")
    >>> print(browser.contents)
    <p>
        ESI tile with query string
    </p>

.. _plone.tiles: http://pypi.python.org/pypi/plone.tiles

Changelog
=========

7.0.1 (2023-11-16)
------------------

- Fix deprecated dependencies to ``Products.CMFPlone``.
  [petschki]
- Fix requirements.txt, --install-options is obsolete, use --pre instead
  [cillianderoiste]
- Fix for AttributeError in linkintegrity code when pasting a folder containing a page with tiles.
  Related to `issue 97 <https://github.com/plone/plone.app.blocks/issues/97>`_.
  [cillianderoiste]


7.0.0 (2022-12-21)
------------------

- Fix ``layout_view`` and ``tile_layout_view`` to work with the Zope security fix.
  Needed when you use Plone 5.2.10.1 or Plone 6.0.0.1.
  Fixes `issue 101 <https://github.com/plone/plone.app.blocks/issues/101>_`.
  [maurits]

- Implement support for link integrity (see version 5.2.0).
  [petschki]

- Drop support for Plone < 6.
  [petschki]


6.0.1 (2022-07-20)
------------------

- Add support for dexteritytextindexer in Plone 6 core.
  Keep support for collective.dexteritytextindexer in Plone 5.
  [zworkb]


6.0.0 (2022-03-23)
------------------

- Drop Plone 5.1 and Python 2 support.
  [jensens]

- Remove long deprecated functions and attributes.
  [jensens]

- Cleanup, make tests run on Plone 6 and include on GHA.
  [jensens]


5.0.0 (2021-06-11)
------------------

- New ``data-panel-mode`` attribute.
  Add ``data-panel-mode`` attribute with possible values ``append`` (default) and ``replace`` for layout panels.
  This allows for replacing the layout panel with the page panel instead of appending to it.
  [thet]

- Allow tox tests to run single tests e.g. via: ``tox -e plone52-py38 -- -t test_resolve_resource``.
  [thet]

- Remove support for Plone 4.3 and 5.0.
  [maurits, thet]

- Tests: Refactor buildout configs, use tox, use GitHub actions.
  [maurits]

- Format code according to Plone standards: black, isort.
  [thet]

- getSite from zope.component.hooks
  [ksuess]

- Fix unicode parsing error on Python 3, resulting in empty mosaic page (`#480 <https://github.com/plone/plone.app.mosaic/issues/480>`_).
  [agitator, maurits]

- Update test setup
  [ksuess]


4.3.2 (2019-10-18)
------------------

Bug fixes:

- Catch errors on resolving tiles and return an error message instead of breaking the whole UI
  [MrTango]

- Fix issue where layout aware tile data storage read cache was not purged when
  data was updated programmatically [fixes #75]
  [datakurre]

- Fix issue where resolveResource would break when url html content param contains other ++-url's
  [MrTango]

4.3.1 (2019-02-20)
------------------

Bug fixes:

- fix multidict feature for python 3
  [petschki]


4.3.0 (2019-02-10)
------------------

Bug fixes:

- Enforce usage of plone.subrequest >= 1.7.0;
  this avoids ``TypeError`` on package upgrades (refs. `#62 <https://github.com/plone/plone.app.blocks/issues/62>`_).
  [hvelarde]

New features:

- python3 compatibility
  [petschki]

4.2.0 (2018-07-02)
------------------

New features:

- Allow rendering of subtiles.
  Now it's possible to reference and resolve tiles in tiles.
  [thet]

- Added events to notify before/after tile rendering.
  [thet]

Bug fixes:

- Allow head tiles without a html/head structure.
  [thet]

- Fix issue where resolving layout url with ajax_load parameter caused fail
  on direct resolve directory lookup
  [datakurre]

- Fix issue where failed resource lookup into filesystem resource directory
  raised IOError
  [datakurre]

- Fix deprecated `import Globals`. This adds Zope 4 compatibility.
  [petschki]


4.1.2 (2018-07-02)
------------------

Bug fixes:

- remove `pretty_print` when loading the tile data.
  This fixes `forced_root_block` problems in TinyMCE (#63)
  [petschki]


4.1.1 (2017-10-20)
------------------

Bug fixes:

- Fix to properly store primary rich text field values through layout aware
  tile data storage adapter
  [datakurre]

- Fix diazo tile rules cache key to require less memory by using hexdigest
  [datakurre]


4.1.0 (2017-08-17)
------------------

New Features:

- ESITransforms add a new header ``X-Esi: 1`` when any ESI tiles have
  been transformed. This allows e.g. Varnish to enable ESI only when
  it's really required.
  [datakurre]

- Add to allow ``permission`` key in ``[contentlayout]``-sections of content
  layout manifests (``manifest.cfg``)
  [datakurre]


4.0.6 (2017-02-09)
------------------

Fixes:

- Fix issue where layout related fields could have been acquired
  (only sectionSiteLayout can be allowed to be acquired)
  [datakurre]


4.0.5 (2017-02-08)
------------------

Fixes:

- Fix issue where page site layout could have been accidentally acquired
  (page site layout should never be acquired)
  [datakurre]

- Fix transforms to comply with
  plone.transformchain.interfaces.ITransform
  [datakurre]


4.0.4 (2017-01-30)
------------------

Fixes:

- Fix issue where ESIRender has been broken since plone.protect's
  ProtectTransform was introduced, because of protect transform breaking
  ESI-tags; Change ESIRender transform order from 8900 to 9900
  [datakurre]

4.0.3 (2017-01-15)
------------------

Fixes:

- Fix issue where default layouts paths were not found if they were stored
  unicode (TextLine) instead of str (ASCIILine or BytesLine)
  [datakurre]

- Fix issue where tiles merge failed for addresses with space, because
  subrequest was called with quoted ('%20') paths
  [datakurre]


4.0.2 (2017-01-03)
------------------

Fixes:

- Fix issue where error in diazo transform for a single tile aborted tile
  merge as whole
  [datakurre]


4.0.1 (2016-12-28)
------------------

Fixes:

- Fix issue where tile data storage decoded HTML primary fields
  using ASCII instead of utf-8 causing broken broken latin
  characters in attribute values
  [datakurre]


4.0.0 (2016-12-13)
------------------

Incompatibilities:

- Remove grid transform, because it did not serve its purpose as as well
  expected and required HTML-syntax not editable by humans; Instead using
  grid framework agnostic CSS class names and building CSS grid against
  those class names is recommended
  [agitator]

- Remove ``IOmittedField`` marker from layout behavior fields not meant to be
  displayed on legacy Deco UIs
  [jensens]

- Rename ``ILayoutAware.content`` to ``ILayoutAware.customContentLayout``
  [datakurre]

- Move functions ``getDefaultAjaxLayout``, ``getDefaultSiteLayout``,
  ``getLayout`` and ``getLayoutAwareSiteLayout`` to ``.layoutbehavior`` in
  order to avoid circular imports (all deprecated now, see section New).
  [jensens]

- Move views from ``.layoutbehavior`` to new module ``.layoutviews`` in order
  to avoid circular imports.  Deprecated deferred imports are in place.
  [jensens]

New:

- Add ``ILayoutAware.content`` as layout independent "layout like" tile
  configuration and data storage for all serializable tile configurations
  [datakurre]

- Add ``@@layout_preview`` view for previewing currently drafted layout aware
  content
  [datakurre]

- ``ILayoutAware`` is now also responsible to lookup the behaviors.
  [jensens]

- Get layouts always by adapting with ``ILayoutAware``.  This introduces a
  generic adapter and a behavior adapter.  Deprecated the formerly used functions
  ``getLayout`` ``getDefaultSiteLayout`` just calls
  ``ILayoutAware().site_layout`` and is deprected.  ``getLayout`` just calls
  ``ILayoutAware().content_layout`` and is deprecated.
  [jensens]

- Behavior shortname ``plone.layoutaware`` added.
  [jensens]

Fixes:

- Handle missing content layouts so they do not cause an error
  [vangheem]

- A tile raising an 401 Unauthorized on traversal,
  results in a status rewriting to a 302 which results in 200 login form.
  The whole login form page then is rendered as the tile contents.
  This patch catches the 401 by providing a custom exception handler.
  The 401 is catched and ignored. This is not pefect yet and need some work,
  but it at least does not break design and intended behavior of tiles.
  [jensens]

Refactoring:

- Housekeeping: ZCA decorators, sorted imports, line-lengths and related.
  [jensens]

- Reformat documentation.
  [gforcada]

- Update travis configuration.
  [gforcada]


3.1.0 (2016-03-28)
------------------

New:

- Don't make a tile exception break other tiles (closes `#27`_).
  [rodfersou, datakurre]

- Provide new getLayoutsFromDirectory utility to get layouts from any
  plone.resource directory, not just the base resource directory
  [vangheem]

- Index layout data; When collective.dexteritytextindexer is present,
  its *Dynamic SearchableText indexer behavior* must be enabled for content
  type
  [vangheem, datakurre]

- Cleanup tile data on save/edit
  [vangheem]


3.0.1 (2015-09-23)
------------------

- Remove the default 'Custom layout' display menu registration for
  'layout_view', because it was not possible to customize it with more exact
  registration
  [datakurre]

- Fix the default view to report template name as 'template-layout'
  [datakurre]


3.0.0 (2015-09-16)
------------------

- Change layout behavior default view name from ``view`` to ``layout_view``
  [datakurre]

- Add to be able to set default grid system in registry settings
  [vangheem]

- Add support for provide more than one layout with a layout directory
  and manifest (replaces removed layout variants)
  [vangheem]

- Add ``contentlayout`` resource type with ``plone.availableContentLayouts``
  vocabulary and ``++contentlayout++`` traverser
  [vangheem]

- Add ``contentLayout`` field to layoutbehavior to select the rendered layout
  from centrally managed content layouts
  [vangheem]

- Add content type specific registry configuration with key
  ``plone.app.blocks.default_layout.portal_type`` for used default content
  layout when custom layout is not defined
  [vangheem]

- Add to check ``plone.app.blocks.default_layout`` registry key for a default
  content layout path when content type specific default content layout path is
  not set
  [datakurre]

- Fixed layout behavior to apply Plone outputfilters for rendered content
  [datakurre]

- Add default grid system registry setting
  [vangheem]

- Restore support for Plone 4.2.x
  [datakurre]

- Remove layout variants introduced in 2.0.0, in favor of ability to
  provide more than one layout with a layout directory and manifest by
  using multiple ``[...layout]`` directive in the same manifest
  [vangheem]


2.1.2 (2015-06-10)
------------------

- Fix issue where grid transform did replaced class names instead of appending
  to them
  [datakurre]


2.1.1 (2015-06-10)
------------------

- Fix BS3 grid transform to only introduce offset when the tile position is
  greater than the current position in the current row
  [datakurre]

- Fix issue where tiles with empty response or syntax error broke tiles
  transform (add to log syntax errors instead)
  [datakurre]


2.1.0 (2015-05-25)
------------------

- Add support for indexing layout field into SearchableText index when
  collective.dexteritytextindexer is installed and its Dynamic SearchableText
  indexer behavior is enabled for the indexed content type with Layout support
  behavior
  [datakurre]


2.0.0 (2015-04-21)
------------------

- Fix package dependencies; remove dependency on unittest2.
  [hvelarde]

- Change blocks transforms to be opt-in for only published objects e.g. views
  or requests with IBlocksTransformEnabled (marker) interface [fixes #11]
  [datakurre]

- Change tags with data-tiles-attrs to be completely replaced (by
  replace_with_children instad of replace_content) to restore original
  design and support for site layout tiles in HTML document head tag
  [datakurre]

- Change default site layout to be optional by adding an implicit
  main_template-based site layout when the default site layout is not set
  [datakurre]

- Change to retry resolveResources with 301 or 302 response when redirect
  location is for the same site
  [datakurre]

- Add support for AJAX site layout for requests with ``ajax_load`` parameter
  either by getting a layout from a reqistry key ``plone.defaultAjaxLayout``
  or by using an implicit main_template-based AJAX layout
  [simahawk, datakurre]

- Add extensible CSS grid transform with built-in transforms for Deco
  and Bootstrap 3 grid systems
  [bloodbare, ACatila]

  .. code:: xml

     <utility
         provides=".gridsystem.IGridSystem"
         component=".gridsystem.DecoGridSystem"
         name="deco"
         />

  .. code:: html

     <html data-gridsystem="deco">
       ...
       <div data-grid='{"type": "row"}'>
         <div data-grid='{"type": "cell",
                          "info": {"xs": "false",
                                   "sm": "False",
                                   "lg": "True",
                          "pos": {"x":1,
                                  "width": 12}}}'>
          </div>
       </div>
     </html>

  .. code:: html

     <div class="row">
        <div class="cell position-1 width-12">
        </div>
     </div>

- Add default view for ILayoutAware content and register a localizable display
  menu item called *Custom layout* for it when *plone.app.contentmenu* is
  present
  [datakurre]

- Add Layout-fieldset for ILayoutAware behavior
  [datakurre]

- Add support to use the whole tile as its body when both head and body tags
  are missing (add support for using Dexterithy display widgets as tiles)
  [datakurre]

- Add support for layout variants (for supporting multiple layouts in a single
  resource folder)
  [datakurre]

  .. code:: ini

     [sitelayout]
     ...

     [sitelayout:variants]
     document_layout = document.html

- Add experimental support for tile-specific Diazo-rules
  with data-attribute ``data-rules="/++sitelayout++name/rules.xml"``.
  [datakurre]

- Fix issue with tile without body-tag breaking the tile composition (fixes
  issues with some p.a.standardtiles returning only <html/> in some conditions)
  [datakurre]

- Fix issue where <![CDATA[...]]> block was quoted (and therefore broken) by
  lxml serializer
  [datakurre]

- Fix issue where XML parser dropped head for layout with CRLF-endings
  [datakurre]

- Fix plone.app.blocks re-install to not reset existing plone.defaultSiteLayout
  and plone.defaultAjaxLayout settings (by setting the values in a custom
  setuphandler)
  [datakurre]

- Fix and update tests, PEP8
  [gyst, datakurre, gforcada]

- Fix to set the merging request flag before testing the merge results to allow
  staticly placed tiles in content templates to be rendered properly.
  [cewing]

- Solve issue with VHM and tile rendering. Fixes
  https://dev.plone.org/ticket/13581 [ericof]

- Add z3c.autoinclude support
  [cdw9, calvinhp]


1.1 (2012-12-17)
----------------

- make sure to use correct url of tile
  [vangheem]

- handle not found errors while rendering tiles so layout
  isn't borked
  [vangheem]


1.0 (2012-06-23)
----------------

- initial release.
  [garbas]

.. _`#27`: https://github.com/plone/plone.app.blocks/issues/27

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/plone/plone.app.blocks",
    "name": "plone.app.blocks",
    "maintainer": "Plone Community",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "releaseteam@plone.org",
    "keywords": "plone blocks deco",
    "author": "Martin Aspeli, Laurence Rowe",
    "author_email": "optilude@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/f8/2d/538e9196a09ab8dbd9e15ff43a3cf16b09f100a54ea5afca2121447b06f2/plone.app.blocks-7.0.1.tar.gz",
    "platform": null,
    "description": "======================\nIntroduction to Blocks\n======================\n\n.. image:: https://coveralls.io/repos/plone/plone.app.blocks/badge.png?branch=master\n    :alt: Coveralls badge\n    :target: https://coveralls.io/r/plone/plone.app.blocks\n\nThis package implements the 'blocks' rendering model,\nby providing several transform stages that hook into ``plone.transformchain``.\n\nThe rendering stages are:\n\n``plone.app.blocks.parsexml`` (order 8000)\n    Turns the response in a ``repoze.xmliter`` ``XMLSerializer`` object.\n    This is then used by the subsequent stages.\n    If the input is not HTML, the transformation is aborted.\n\n``plone.app.blocks.mergepanels`` (order 8100)\n    Looks up the site layout and executes the panel merge algorithm.\n    Sets a request variable ('plone.app.blocks.merged') to indicate that it has done its job.\n\n``plone.app.blocks.tiles`` (order 8500)\n    Resolve tiles and place them directly into the merged layout.\n    This is the fallback for views that do not opt into ``ITilePageRendered``.\n\n``plone.app.blocks.esirender`` (order 9900)\n    Only executed if the request key ``plone.app.blocks.esi`` is set and its value is true,\n    as would be the case if any ESI-rendered tiles are included and ESI rendering is enabled globally.\n    This step will serialise the response down to a string and perform some substitution to make ESI rendering work.\n\n\nSite layouts\n============\n\nThe package also registers the ``sitelayout`` ``plone.resource`` resource type,\nallowing site layouts to be created easily as static HTML files served from resource directories.\nThe URL to a site layout is typically something like::\n\n    /++sitelayout++my.layout/site.html\n\nSee ``plone.resource`` for more information about how to register resource directories.\nFor site layouts, the ``type`` of the resource directory is ``sitelayout``.\n\nIt is possible to provide a manifest file that gives a title, description and alternative default file for a site layout HTML file in a resource directory.\nTo create such a manifest, put a ``manifest.cfg`` file in the layout directory with the following structure:\n\n.. code-block:: ini\n\n    [sitelayout]\n    title = My layout title\n    description = Some description\n    file = some-html-file.html\n\n* All keys are optional.\n* The file defaults to ``site.html``.\n* Single manifest may contain multiple ``[sitelayout]`` sections.\n\nA vocabulary factory called ``plone.availableSiteLayouts`` is registered to allow lookup of all registered site layouts.\nThe terms in this vocabulary use the URL as a value,\nthe resource directory name as a token,\nand the title from the manifest (falling back on a sanitised version of the resource directory name) as the title.\n\nThe current default site layout can be identified by the ``plone.registry`` key ``plone.defaultSiteLayout``,\nwhich is set to ``None`` by default.\nTo always use the current site default, use:\n\n.. code-block:: html\n\n    <html data-layout=\"./@@default-site-layout\">\n\nThe ``@@default-site-layout`` view will render the current default site layout.\n\n\nContent layouts\n===============\n\nThe package also registers the ``contentlayout`` ``plone.resource`` resource type,\nallowing shared content area layouts to be created easily as static HTML files served from resource directories.\nThe URL to a content layout is typically something like::\n\n    /++contentlayout++my.layout/content.html\n\nSee ``plone.resource`` for more information about how to register resource directories.\nFor site layouts, the ``type`` of the resource directory is ``contentlayout``.\n\nIt is possible to provide a manifest file that gives a title, description and alternative default file for a site layout HTML file in a resource directory.\nTo create such a manifest, put a ``manifest.cfg`` file in the layout directory with the following structure:\n\n.. code-block:: ini\n\n    [contentlayout]\n    title = My layout title\n    description = Some description\n    file = some-html-file.html\n    screenshot = mylayout.png\n    for = Document,Folder\n    permission = cmf.ModifyPortalContent\n\n* All keys are optional.\n* Value for key ``file`` defaults to ``content.html``.\n* Single manifest may contain multiple ``[contentlayout]`` sections.\n* Values for keys ``for`` and ``permission`` are only for advisory and may not\n  be enforced.\n\nA vocabulary factory called ``plone.availableContentLayouts`` is registered to allow lookup of all registered content layouts.\nThe terms in this vocabulary use the URL as a value,\nthe resource directory name as a token,\nand the title from the manifest (falling back on a sanitized version of the resource directory name) as the title.\n\nThe default content layout can be identified by the ``plone.registry`` key ``plone.app.blocks.default_layout``,\nand the default content layout for some specific content type with key ``plone.app.blocks.default_layout.my_type``.\nThe default content layout is supported by the built-in ``layout_view`` browser view for content with ``ILayoutAware`` behavior.\n\n\nILayoutAware behavior\n=====================\n\nIt is possible for the default site layout to be overridden per section,\nby having parent objects provide or be adaptable to ``plone.app.blocks.layoutbehavior.ILayoutAware``.\nAs the module name implies, this interface can be used as a ``plone.behavior`` behavior named ``plone.layoutaware``,\nbut it can also be implemented directly or used as a standard adapter.\n\nThe ``ILayoutAware`` interface defines properties:\n\n``content``\n    which contains the body of the page to be rendered.\n``contentLayout``\n    which contains the path to the selected static content layout,\n    which is used instead of ``content`` when set.\n``pageSiteLayout``\n    which contains the path to the site layout to be used for the given page.\n    It can be ``None`` if the default is to be used.\n``sectionSiteLayout``\n    which contains the path to the site layout to be used for pages *underneath* the given page (but not for the page itself).\n    Again, it can be ``None`` if the default is to be used.\n\nTo make use of the page site layout, use the following:\n\n.. code-block:: html\n\n    <html data-layout=\"./@@default-site-layout\">\n\nSee ``rendering.rst`` for detailed examples of how the processing is applied,\nand ``esi.rst`` for details about how Edge Side Includes can be supported.\n\nBlocks rendering in detail\n==========================\n\nThis doctest illustrates the blocks rendering process.\nAt a high level, it consists of the following steps:\n\n\n0. Obtain the content page, an HTML document.\n\n\n1. Look for a site layout link in the content page.\n\n   This takes the form of an attribute on the html tag like ``<html data-layout=\"...\" />``.\n\n   Usually, the site layout URL will refer to a resource in a resource  directory of type ``sitelayout``,\n   e.g. ``/++sitelayout++foo/site.html``,\n   although the layout can be any URL.\n   An absolute path like this will be adjusted so that it is always relative to the Plone site root.\n\n\n2. Resolve and obtain the site layout.\n\n   This is another HTML document.\n\n\n3. Extract panels from the site layout.\n\n   A panel is an element (usually a ``<div />``) in the layout page with a data-panel attribute,\n   for example: ``<div data-panel=\"panel1\" />`` or ``<div data-panel=\"panel1\" data-panel-mode=\"replace\" />``.\n   The attribute specifies an id which *may* be used in the content page.\n\n   You can specify how the content from the content page is inserted into the panel.\n   The default or with ``data-panel-mode=\"append\"`` specified the content from the content page is appended to the layout page panel.\n   With ``data-panel-mode=\"replace\"`` the content replaced the layout page panel.\n\n\n4. Merge panels.\n\n   This is the process which applies the layout to the unstyled page.\n   All panels in the layout page that have a matching element in the content page get the content page element appended or are replaced by it.\n   The rest of the content page is discarded.\n\n\n5. Resolve and obtain tiles.\n\n   A tile is a placeholder element in the page which will be replaced by the contents of a document referenced by a URL.\n\n   A tile is identified by a placeholder element with a ``data-tile`` attribute containing the tile URL.\n\n   Note that at this point, panel merging has taken place,\n   so if a panel in the content page contains tiles, they will be carried over into the merge page.\n   Also note that it is possible to have tiles outside of panels - the two concepts are not directly related.\n\n   The ``plone.tiles`` package provides a framework for writing tiles,\n   although in reality a tile can be any HTML page.\n\n\n6. Place tiles into the page.\n\n   The tile should resolve to a full HTML document.\n   Any content found in the ``<head />`` of the tile content will be merged into the ``<head />`` of the rendered content.\n   The contents of the ``<body />`` of the tile content are put into the rendered document at the tile placeholder.\n\n\nRendering step-by-step\n----------------------\n\nLet us now illustrate the rendering process.\nWe'll need a few variables defined first:\n\n.. code-block:: python\n\n    >>> from plone.testing.z2 import Browser\n    >>> import transaction\n\n    >>> app = layer['app']\n    >>> portal = layer['portal']\n\n    >>> browser = Browser(app)\n    >>> browser.handleErrors = False\n\n\nCreating a site layout\n~~~~~~~~~~~~~~~~~~~~~~\n\nThe most common approach for managing site layouts is to use a resource registered using a ``plone.resource`` directory of type ``sitelayout``,\nand then use the ``@@default-site-layout`` view to reference the content.\nWe will illustrate this below, but it is important to realise that ``plone.app.blocks`` works by post-processing responses rendered by Zope.\nThe content and layout pages could just as easily be created by views of content objects, or even resources external to Zope/Plone.\n\nFirst, we will create a resource representing the site layout and its panels.\nThis includes some resources and other elements in the ``<head />``,\n``<link />`` tags which identify tile placeholders and panels,\nas well as content inside and outside panels.\nThe tiles in this case are managed by ``plone.tiles``, and are both of the same type.\n\n.. code-block:: python\n\n    >>> layoutHTML = b\"\"\"\\\n    ... <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n    ... <html>\n    ...     <head>\n    ...         <title>Layout title</title>\n    ...         <link rel=\"stylesheet\" href=\"/layout/style.css\" />\n    ...         <script type=\"text/javascript\">alert('layout');</script>\n    ...\n    ...         <style type=\"text/css\">\n    ...         div {\n    ...             margin: 5px;\n    ...             border: dotted black 1px;\n    ...             padding: 5px;\n    ...         }\n    ...         </style>\n    ...\n    ...         <link rel=\"stylesheet\" data-tile=\"./@@test.tile_nobody/tile_css\" />\n    ...     </head>\n    ...     <body>\n    ...         <h1>Welcome!</h1>\n    ...         <div data-panel=\"panel1\">Layout panel 1</div>\n    ...         <div data-panel=\"panel2\">\n    ...             Layout panel 2\n    ...             <div id=\"layout-tile1\" data-tile=\"./@@test.tile1/tile1\">Layout tile 1 placeholder</div>\n    ...         </div>\n    ...         <div data-panel=\"panel3\">\n    ...             Layout panel 3\n    ...             <div id=\"layout-tile2\" data-tile=\"./@@test.tile1/tile2\">Layout tile 2 placeholder</div>\n    ...         </div>\n    ...     </body>\n    ... </html>\n    ... \"\"\"\n\nWe can create an in-ZODB resource directory of type ``sitelayout`` that contains this layout.\nAnother way would be to register a resource directory in a package using ZCML, or use a global resource directory.\nSee ``plone.resource`` for more details.\n\n.. code-block:: python\n\n    >>> from Products.CMFCore.utils import getToolByName\n    >>> from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2\n    >>> from OFS.Image import File\n\n    >>> resources = getToolByName(portal, 'portal_resources')\n    >>> resources._setOb('sitelayout', BTreeFolder2('sitelayout'))\n    >>> resources['sitelayout']._setOb('mylayout', BTreeFolder2('mylayout'))\n    >>> resources['sitelayout']['mylayout']._setOb('site.html', File('site.html', 'site.html', layoutHTML))\n\n    >>> transaction.commit()\n\nThis resource can now be accessed using the path ``/++sitelayout++mylayout/site.html``.\nLet's render it on its own to verify that.\n\n.. code-block:: python\n\n    >>> browser.open(portal.absolute_url() + '/++sitelayout++mylayout/site.html')\n    >>> print(browser.contents)\n    <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n    <html>\n        <head>\n            <title>Layout title</title>\n            <link rel=\"stylesheet\" href=\"/layout/style.css\" />\n            <script type=\"text/javascript\">alert('layout');</script>\n    <BLANKLINE>\n            <style type=\"text/css\">\n            div {\n                margin: 5px;\n                border: dotted black 1px;\n                padding: 5px;\n            }\n            </style>\n    <BLANKLINE>\n            <link rel=\"stylesheet\" data-tile=\"./@@test.tile_nobody/tile_css\" />\n        </head>\n        <body>\n            <h1>Welcome!</h1>\n            <div data-panel=\"panel1\">Layout panel 1</div>\n            <div data-panel=\"panel2\">\n                Layout panel 2\n                <div id=\"layout-tile1\" data-tile=\"./@@test.tile1/tile1\">Layout tile 1 placeholder</div>\n            </div>\n            <div data-panel=\"panel3\">\n                Layout panel 3\n                <div id=\"layout-tile2\" data-tile=\"./@@test.tile1/tile2\">Layout tile 2 placeholder</div>\n            </div>\n        </body>\n    </html>\n\nWe can now set this as the site-wide default layout by setting the registry key ``plone.defaultSiteLayout``.\nThere are two indirection views, ``@@default-site-layout`` and ``@@page-site-layout``, that respect this registry setting.\nBy using one of these views to reference the layout of a given page, we can manage the default site layout centrally.\n\n.. code-block:: python\n\n    >>> from zope.component import getUtility\n    >>> from plone.registry.interfaces import IRegistry\n    >>> registry = getUtility(IRegistry)\n    >>> registry['plone.defaultSiteLayout'] = b'/++sitelayout++mylayout/site.html'\n    >>> transaction.commit()\n\nCreating a page layout and tiles\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nNext, we will define the markup of a content page that uses this layout via the ``@@default-site-layout`` indirection view:\n\n.. code-block:: python\n\n    >>> pageHTML = \"\"\"\\\n    ... <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n    ... <html data-layout=\"./@@default-site-layout\">\n    ...     <body>\n    ...         <h1>Welcome!</h1>\n    ...         <div data-panel=\"panel1\">\n    ...             Page panel 1\n    ...             <div id=\"page-tile2\" data-tile=\"./@@test.tile1/tile2?magicNumber:int=2\">Page tile 2 placeholder</div>\n    ...         </div>\n    ...         <div data-panel=\"panel2\">\n    ...             Page panel 2\n    ...             <div id=\"page-tile3\" data-tile=\"./@@test.tile1/tile3\">Page tile 3 placeholder</div>\n    ...         </div>\n    ...         <div data-panel=\"panel4\">\n    ...             Page panel 4 (ignored)\n    ...             <div id=\"page-tile4\" data-tile=\"./@@test.tile1/tile4\">Page tile 4 placeholder</div>\n    ...         </div>\n    ...     </body>\n    ... </html>\n    ... \"\"\"\n\nWe then register a view that simply return this HTML,\nand a tile type which we can use to test tile rendering.\n\nWe do this in code for the purposes of the test,\nand we have to apply security because we will shortly render those pages using the test publisher.\nIn real life, these could be registered using the standard ``<browser:page />`` and ``<plone:tile />`` directives.\n\n.. code-block:: python\n\n    >>> from zope.publisher.browser import BrowserView\n    >>> from zope.interface import Interface, implementer\n    >>> from zope import schema\n    >>> from plone.tiles import Tile\n    >>> from plone.app.blocks.interfaces import IBlocksTransformEnabled\n\n    >>> @implementer(IBlocksTransformEnabled)\n    ... class Page(BrowserView):\n    ...     __name__ = 'test-page'\n    ...     def __call__(self):\n    ...         self.request.response.setHeader(\"Content-Type\", \"text/html\")\n    ...         return pageHTML\n\n    >>> class ITestTile(Interface):\n    ...     magicNumber = schema.Int(title=u\"Magic number\", required=False)\n\n    >>> class TestTile(Tile):\n    ...     __name__ = 'test.tile1' # normally set by ZCML handler\n    ...\n    ...     def __call__(self):\n    ...         # fake a page template to keep things simple in the test\n    ...         self.request.response.setHeader(\"Content-Type\", \"text/html\")\n    ...         return \"\"\"\\\n    ... <html>\n    ...     <head>\n    ...         <meta name=\"tile-name\" content=\"%(name)s\" />\n    ...     </head>\n    ...     <body>\n    ...         <p>\n    ...             This is a demo tile with id %(name)s\n    ...         </p>\n    ...         <p>\n    ...             Magic number: %(number)d; Form: %(form)s; Query string: %(queryString)s; URL: %(url)s\n    ...         </p>\n    ...     </body>\n    ... </html>\"\"\" % dict(name=self.id, number=self.data['magicNumber'] or -1,\n    ...                   form=sorted(self.request.form.items()), queryString=self.request['QUERY_STRING'], url=self.request.getURL())\n\nLet's add another tile, this time only a head part.\nThis could for example be a tile that only needs to insert some CSS.\n\n.. code-block:: python\n\n    >>> class TestTileNoBody(Tile):\n    ...     __name__ = 'test.tile_nobody'\n    ...\n    ...     def __call__(self):\n    ...         return \"\"\"\\\n    ... <html>\n    ...     <head>\n    ...         <link rel=\"stylesheet\" type=\"text/css\" href=\"tiled.css\" />\n    ...     </head>\n    ... </html>\"\"\"\n\nWe register these views and tiles in the same way the ZCML handlers for ``<browser:page />`` and ``<plone:tile />`` would:\n\n.. code-block:: python\n\n    >>> from plone.tiles.type import TileType\n    >>> from AccessControl.security import protectClass\n    >>> from AccessControl.class_init import InitializeClass\n    >>> from zope.component import provideAdapter, provideUtility\n    >>> from zope.interface import Interface\n\n    >>> testTileType = TileType(\n    ...     name=u'test.tile1',\n    ...     title=u\"Test tile\",\n    ...     description=u\"A tile used for testing\",\n    ...     add_permission=\"cmf.ManagePortal\",\n    ...     view_permission=\"zope2.View\",\n    ...     schema=ITestTile)\n\n    >>> testTileTypeNoBody = TileType(\n    ...     name=u'test.tile_nobody',\n    ...     title=u\"Test tile using only a header\",\n    ...     description=u\"Another tile used for testing\",\n    ...     add_permission=\"cmf.ManagePortal\",\n    ...     view_permission=\"zope2.View\")\n\n    >>> protectClass(Page, 'zope2.View')\n    >>> protectClass(TestTile, 'zope2.View')\n\n    >>> InitializeClass(Page)\n    >>> InitializeClass(TestTile)\n\n    >>> provideAdapter(Page, (Interface, Interface,), Interface, u'test-page')\n    >>> provideAdapter(TestTile, (Interface, Interface,), Interface, u'test.tile1',)\n    >>> provideAdapter(TestTileNoBody, (Interface, Interface,), Interface, u'test.tile_nobody',)\n    >>> provideUtility(testTileType, name=u'test.tile1')\n    >>> provideUtility(testTileTypeNoBody, name=u'test.tile_nobody')\n\nRendering the page\n~~~~~~~~~~~~~~~~~~\n\nWe can now render the page.\nProvided ``plone.app.blocks`` is installed and working, it should perform its magic.\nWe make sure that Zope is in \"development mode\" to get pretty-printed output.\n\n.. code-block:: python\n\n    >>> browser.open(portal.absolute_url() + '/@@test-page')\n    >>> print(browser.contents.replace('<head><meta', '<head>\\n\\t<meta'))\n    <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n    <html xmlns=\"http://www.w3.org/1999/xhtml\">\n      <head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=ASCII\" />\n        <title>Layout title</title>\n        <link rel=\"stylesheet\" href=\"/layout/style.css\" />\n        <script type=\"text/javascript\">alert('layout');</script>\n        <style type=\"text/css\">\n            div {\n                margin: 5px;\n                border: dotted black 1px;\n                padding: 5px;\n            }\n            </style>\n        <link rel=\"stylesheet\" type=\"text/css\" href=\"tiled.css\" />\n        <meta name=\"tile-name\" content=\"tile2\" />\n        <meta name=\"tile-name\" content=\"tile3\" />\n        <meta name=\"tile-name\" content=\"tile2\" />\n      </head>\n      <body>\n            <h1>Welcome!</h1>\n            <div data-panel=\"panel1\">\n                Page panel 1\n            <p>\n                This is a demo tile with id tile2\n            </p>\n            <p>\n                Magic number: 2; Form: [('magicNumber', 2)]; Query string: magicNumber:int=2; URL: http://nohost/plone/@@test.tile1/tile2\n            </p>\n            </div>\n            <div data-panel=\"panel2\">\n                Page panel 2\n            <p>\n                This is a demo tile with id tile3\n            </p>\n            <p>\n                Magic number: -1; Form: []; Query string: ; URL: http://nohost/plone/@@test.tile1/tile3\n            </p>\n            </div>\n            <div data-panel=\"panel3\">\n                Layout panel 3\n            <p>\n                This is a demo tile with id tile2\n            </p>\n            <p>\n                Magic number: -1; Form: []; Query string: ; URL: http://nohost/plone/@@test.tile1/tile2\n            </p>\n            </div>\n        </body>\n    </html>\n    <BLANKLINE>\n\nNotice how:\n\n* Panels from the page have been merged into the layout, replacing the corresponding panels there.\n* The ``<head />`` sections of the two documents have been merged\n* The rest of the layout page is intact\n* The rest of the content page is discarded\n* The tiles have been rendered, replacing the relevant placeholders\n* The ``<head />`` section from the rendered tiles has been merged into the ``<head />`` of the output page.\n\nUsing VHM\n~~~~~~~~~\n\nMake sure to have a clean browser:\n\n.. code-block:: python\n\n    >>> browser = Browser(app)\n    >>> browser.handleErrors = False\n\nUsing Virtual Host Monster we rewrite the url to consider all content being under ``/``:\n\n.. code-block:: python\n\n    >>> vhm_url = 'http://nohost/VirtualHostBase/http/nohost:80/plone/VirtualHostRoot/'\n    >>> browser.open(vhm_url + '/@@test-page')\n\nTiles should return an url according to this:\n\n.. code-block:: python\n\n    >>> 'Magic number: -1; Form: []; Query string: ; URL: http://nohost/@@test.tile1/tile2' in browser.contents\n    True\n\nNow we deal with _vh_* arguments. We expect our site to be under a subdir with id *subplone*:\n\n.. code-block:: python\n\n    >>> vhm_url = 'http://nohost/VirtualHostBase/http/nohost:80/plone/VirtualHostRoot/_vh_subplone'\n    >>> browser.open(vhm_url + '/@@test-page')\n\nTiles should return an url according to this:\n\n.. code-block:: python\n\n    >>> 'Magic number: -1; Form: []; Query string: ; URL: http://nohost/subplone/@@test.tile1/tile2' in browser.contents\n    True\n\n\nESI rendering\n=============\n\nBlocks supports rendering of tiles for Edge Side Includes (ESI).\nA tile will be rendered to ESI provided that:\n\n* The tile itself is marked with the ``IESIRendered`` marker interface.\n  See `plone.tiles`_ for more details.\n* The ``plone.app.blocks.interfaces.IBlocksSettings.esi`` record in the registry is set to True.\n  It is False by default.\n  To switch this through-the-web, you can visit the configuration registry control panel in Plone.\n\nNote that if a tile is rendered using ESI, it's <head /> contents are ignored, instead of being merged into the final page.\nThat is, only the ``@@esi-body`` view form `plone.tiles`_ is used by default.\n\nAn ESI link looks like this:\n\n.. code-block:: xml\n\n    <esi:include src=\"http://example.com/plone/@@some.tile/tile-1/@@esi-body?param1=value1\" />\n\nA fronting server such as Varnish will be able to load this on demand and\ncompose the page from fragments that may be cached individually.\n\nTest setup\n----------\n\nLet's first register a two very simple tiles. One uses ESI, one does not.\n\n.. code-block:: python\n\n    >>> from plone.tiles.esi import ESITile\n    >>> from plone.tiles import Tile\n    >>> from plone.tiles.type import TileType\n\n    >>> class NonESITile(Tile):\n    ...     __name__ = 'test.tile2' # normally set by ZCML handler\n    ...\n    ...     def __call__(self):\n    ...         return \"\"\"\\\n    ... <html>\n    ...     <head>\n    ...         <meta name=\"tile-name\" content=\"%(name)s\" />\n    ...     </head>\n    ...     <body>\n    ...         <p>\n    ...             Non-ESI tile with query string %(queryString)s\n    ...         </p>\n    ...     </body>\n    ... </html>\"\"\" % dict(name=self.id, queryString=self.request['QUERY_STRING'])\n\n    >>> testTile2Type = TileType(\n    ...     name=u'test.tile2',\n    ...     title=u\"Test tile 2\",\n    ...     description=u\"A tile used for testing\",\n    ...     add_permission=\"cmf.ManagePortal\",\n    ...     view_permission=\"zope2.View\")\n\n    >>> class SimpleESITile(ESITile):\n    ...     __name__ = 'test.tile3' # normally set by ZCML handler\n    ...\n    ...     def render(self):\n    ...         return \"\"\"\\\n    ... <html>\n    ...     <head>\n    ...         <meta name=\"tile-name\" content=\"%(name)s\" />\n    ...     </head>\n    ...     <body>\n    ...         <p>\n    ...             ESI tile with query string %(queryString)s\n    ...         </p>\n    ...     </body>\n    ... </html>\"\"\" % dict(name=self.id, queryString=self.request['QUERY_STRING'])\n\n    >>> testTile3Type = TileType(\n    ...     name=u'test.tile3',\n    ...     title=u\"Test tile 3\",\n    ...     description=u\"A tile used for testing\",\n    ...     add_permission=\"cmf.ManagePortal\",\n    ...     view_permission=\"zope2.View\")\n\nRegister these in the same way that the ZCML handlers would, more or less.\n\n.. code-block:: python\n\n    >>> from AccessControl.security import protectClass\n    >>> protectClass(NonESITile, 'zope2.View')\n    >>> protectClass(SimpleESITile, 'zope2.View')\n\n    >>> from App.class_init import InitializeClass\n    >>> InitializeClass(NonESITile)\n    >>> InitializeClass(SimpleESITile)\n\n    >>> from zope.component import provideAdapter, provideUtility\n    >>> from zope.interface import Interface\n    >>> provideAdapter(NonESITile, (Interface, Interface,), Interface, u'test.tile2',)\n    >>> provideUtility(testTile2Type, name=u'test.tile2')\n    >>> provideAdapter(SimpleESITile, (Interface, Interface,), Interface, u'test.tile3',)\n    >>> provideUtility(testTile3Type, name=u'test.tile3')\n\nWe will also register a simple layout and a simple page using these tiles.\n\n.. code-block:: python\n\n    >>> layoutHTML = u\"\"\"\\\n    ... <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n    ... <html>\n    ...     <head>\n    ...         <title>Layout title</title>\n    ...     </head>\n    ...     <body>\n    ...         <h1>Welcome!</h1>\n    ...         <div data-panel=\"panel1\">Content goes here</div>\n    ...         <div id=\"layout-non-esi-tile\" data-tile=\"./@@test.tile2/tile1\">Layout tile 1 placeholder</div>\n    ...         <div id=\"layout-esi-tile\" data-tile=\"./@@test.tile3/tile2\">Layout tile 2 placeholder</div>\n    ...     </body>\n    ... </html>\n    ... \"\"\"\n\nTo keep things simple, we'll skip the resource directory and layout indirection view,\ninstead just referencing a view containing the layout directly.\n\n.. code-block:: python\n\n    >>> from zope.publisher.browser import BrowserView\n    >>> class Layout(BrowserView):\n    ...     __name__ = 'test-layout'\n    ...     def __call__(self):\n    ...         return layoutHTML\n\n    >>> protectClass(Layout, 'zope2.View')\n    >>> InitializeClass(Layout)\n    >>> provideAdapter(Layout, (Interface, Interface,), Interface, u'test-layout',)\n\n    >>> pageHTML = u\"\"\"\\\n    ... <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n    ... <html data-layout=\"./@@test-layout\">\n    ...     <body>\n    ...         <div data-panel=\"panel1\">\n    ...             <div id=\"page-non-esi-tile\" data-tile=\"./@@test.tile2/tile3?foo=bar\">Page tile 3 placeholder</div>\n    ...             <div id=\"page-esi-tile\" data-tile=\"./@@test.tile3/tile4?foo=bar\">Page tile 4 placeholder</div>\n    ...         </div>\n    ...     </body>\n    ... </html>\n    ... \"\"\"\n\n    >>> from zope.interface import implementer\n    >>> from plone.app.blocks.interfaces import IBlocksTransformEnabled\n    >>> @implementer(IBlocksTransformEnabled)\n    ... class Page(BrowserView):\n    ...     __name__ = 'test-page'\n    ...     def __call__(self):\n    ...         return pageHTML\n\n    >>> protectClass(Page, 'zope2.View')\n    >>> InitializeClass(Page)\n    >>> provideAdapter(Page, (Interface, Interface,), Interface, u'test-page',)\n\nESI disabled\n------------\n\nWe first render the page without enabling ESI.\nThe ESI-capable tiles should be rendered as normal.\n\n.. code-block:: python\n\n    >>> from plone.testing.z2 import Browser\n    >>> app = layer['app']\n    >>> browser = Browser(app)\n    >>> browser.handleErrors = False\n\n    >>> portal = layer['portal']\n    >>> browser.open(portal.absolute_url() + '/@@test-page')\n\nSome cleanup is needed to cover lxml platform discrepancies...\n\n.. code-block:: python\n\n    >>> print(browser.contents.replace('<head><meta', '<head>\\n\\t<meta'))\n    <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n    <html xmlns=\"http://www.w3.org/1999/xhtml\">\n        <head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=ASCII\" />\n        <title>Layout title</title>\n        <meta name=\"tile-name\" content=\"tile3\" />\n        <meta name=\"tile-name\" content=\"tile4\" />\n        <meta name=\"tile-name\" content=\"tile1\" />\n        <meta name=\"tile-name\" content=\"tile2\" />\n        </head>\n        <body>\n            <h1>Welcome!</h1>\n            <div data-panel=\"panel1\">\n            <p>\n                Non-ESI tile with query string foo=bar\n            </p>\n            <p>\n                ESI tile with query string foo=bar\n            </p>\n            </div>\n            <p>\n                Non-ESI tile with query string\n            </p>\n            <p>\n                ESI tile with query string\n            </p>\n        </body>\n    </html>\n    <BLANKLINE>\n\nESI enabled\n-----------\n\nWe can now enable ESI. This could be done using GenericSetup (with the\n``registry.xml`` import step), or through the configuration registry\ncontrol panel. In code, it is done like so:\n\n.. code-block:: python\n\n    >>> from zope.component import getUtility\n    >>> from plone.registry.interfaces import IRegistry\n    >>> from plone.app.blocks.interfaces import IBlocksSettings\n    >>> registry = getUtility(IRegistry)\n    >>> registry.forInterface(IBlocksSettings).esi = True\n    >>> import transaction\n    >>> transaction.commit()\n\nWe can now perform the same rendering again. This time, the ESI-capable\ntiles should be rendered as ESI links. See `plone.tiles`_ for more details.\n\n.. code-block:: python\n\n    >>> browser.open(portal.absolute_url() + '/@@test-page')\n    >>> print(browser.contents.replace('<head><meta', '<head>\\n\\t<meta'))\n    <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n    <html xmlns:esi=\"http://www.edge-delivery.org/esi/1.0\" xmlns=\"http://www.w3.org/1999/xhtml\">\n        <head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=ASCII\" />\n        <title>Layout title</title>\n        <meta name=\"tile-name\" content=\"tile3\" />\n        <meta name=\"tile-name\" content=\"tile1\" />\n        </head>\n        <body>\n            <h1>Welcome!</h1>\n            <div data-panel=\"panel1\">\n            <p>\n                Non-ESI tile with query string foo=bar\n            </p>\n            <esi:include src=\"/plone/@@test.tile3/tile4/@@esi-body?foo=bar\" />\n            </div>\n            <p>\n                Non-ESI tile with query string\n            </p>\n            <esi:include src=\"/plone/@@test.tile3/tile2/@@esi-body?\" />\n        </body>\n    </html>\n    <BLANKLINE>\n\nESI links are substituted by ``ESIRender``-transform, which should always be\nafter all transforms registered ``plone.transformchain`` with DOM-manipulation\nusing ``lxml``. That's because ``lxml`` does not support ESI-namespace in HTML\nand ESI substitution can only be done once ``lxml`` tree is serialized into\npublishable byte string.\n\nIf ``ESIRender``-transform transforms any ESI-links, an additional HTML header\n``X-Esi`` being is set on the response. The existence of this header can be\nused to enable ESI-support in Varnish:\n\n.. code-block:: python\n\n    >>> browser.headers.get('X-Esi')\n    '1'\n\nWhen ESI rendering takes place, the following URLs will be called:\n\n.. code-block:: python\n\n    >>> browser.open(\"http://nohost/plone/@@test.tile3/tile4/@@esi-body?foo=bar\")\n    >>> print(browser.contents)\n    <p>\n        ESI tile with query string foo=bar\n    </p>\n\n    >>> browser.open(\"http://nohost/plone/@@test.tile3/tile2/@@esi-body?\")\n    >>> print(browser.contents)\n    <p>\n        ESI tile with query string\n    </p>\n\n.. _plone.tiles: http://pypi.python.org/pypi/plone.tiles\n\nChangelog\n=========\n\n7.0.1 (2023-11-16)\n------------------\n\n- Fix deprecated dependencies to ``Products.CMFPlone``.\n  [petschki]\n- Fix requirements.txt, --install-options is obsolete, use --pre instead\n  [cillianderoiste]\n- Fix for AttributeError in linkintegrity code when pasting a folder containing a page with tiles.\n  Related to `issue 97 <https://github.com/plone/plone.app.blocks/issues/97>`_.\n  [cillianderoiste]\n\n\n7.0.0 (2022-12-21)\n------------------\n\n- Fix ``layout_view`` and ``tile_layout_view`` to work with the Zope security fix.\n  Needed when you use Plone 5.2.10.1 or Plone 6.0.0.1.\n  Fixes `issue 101 <https://github.com/plone/plone.app.blocks/issues/101>_`.\n  [maurits]\n\n- Implement support for link integrity (see version 5.2.0).\n  [petschki]\n\n- Drop support for Plone < 6.\n  [petschki]\n\n\n6.0.1 (2022-07-20)\n------------------\n\n- Add support for dexteritytextindexer in Plone 6 core.\n  Keep support for collective.dexteritytextindexer in Plone 5.\n  [zworkb]\n\n\n6.0.0 (2022-03-23)\n------------------\n\n- Drop Plone 5.1 and Python 2 support.\n  [jensens]\n\n- Remove long deprecated functions and attributes.\n  [jensens]\n\n- Cleanup, make tests run on Plone 6 and include on GHA.\n  [jensens]\n\n\n5.0.0 (2021-06-11)\n------------------\n\n- New ``data-panel-mode`` attribute.\n  Add ``data-panel-mode`` attribute with possible values ``append`` (default) and ``replace`` for layout panels.\n  This allows for replacing the layout panel with the page panel instead of appending to it.\n  [thet]\n\n- Allow tox tests to run single tests e.g. via: ``tox -e plone52-py38 -- -t test_resolve_resource``.\n  [thet]\n\n- Remove support for Plone 4.3 and 5.0.\n  [maurits, thet]\n\n- Tests: Refactor buildout configs, use tox, use GitHub actions.\n  [maurits]\n\n- Format code according to Plone standards: black, isort.\n  [thet]\n\n- getSite from zope.component.hooks\n  [ksuess]\n\n- Fix unicode parsing error on Python 3, resulting in empty mosaic page (`#480 <https://github.com/plone/plone.app.mosaic/issues/480>`_).\n  [agitator, maurits]\n\n- Update test setup\n  [ksuess]\n\n\n4.3.2 (2019-10-18)\n------------------\n\nBug fixes:\n\n- Catch errors on resolving tiles and return an error message instead of breaking the whole UI\n  [MrTango]\n\n- Fix issue where layout aware tile data storage read cache was not purged when\n  data was updated programmatically [fixes #75]\n  [datakurre]\n\n- Fix issue where resolveResource would break when url html content param contains other ++-url's\n  [MrTango]\n\n4.3.1 (2019-02-20)\n------------------\n\nBug fixes:\n\n- fix multidict feature for python 3\n  [petschki]\n\n\n4.3.0 (2019-02-10)\n------------------\n\nBug fixes:\n\n- Enforce usage of plone.subrequest >= 1.7.0;\n  this avoids ``TypeError`` on package upgrades (refs. `#62 <https://github.com/plone/plone.app.blocks/issues/62>`_).\n  [hvelarde]\n\nNew features:\n\n- python3 compatibility\n  [petschki]\n\n4.2.0 (2018-07-02)\n------------------\n\nNew features:\n\n- Allow rendering of subtiles.\n  Now it's possible to reference and resolve tiles in tiles.\n  [thet]\n\n- Added events to notify before/after tile rendering.\n  [thet]\n\nBug fixes:\n\n- Allow head tiles without a html/head structure.\n  [thet]\n\n- Fix issue where resolving layout url with ajax_load parameter caused fail\n  on direct resolve directory lookup\n  [datakurre]\n\n- Fix issue where failed resource lookup into filesystem resource directory\n  raised IOError\n  [datakurre]\n\n- Fix deprecated `import Globals`. This adds Zope 4 compatibility.\n  [petschki]\n\n\n4.1.2 (2018-07-02)\n------------------\n\nBug fixes:\n\n- remove `pretty_print` when loading the tile data.\n  This fixes `forced_root_block` problems in TinyMCE (#63)\n  [petschki]\n\n\n4.1.1 (2017-10-20)\n------------------\n\nBug fixes:\n\n- Fix to properly store primary rich text field values through layout aware\n  tile data storage adapter\n  [datakurre]\n\n- Fix diazo tile rules cache key to require less memory by using hexdigest\n  [datakurre]\n\n\n4.1.0 (2017-08-17)\n------------------\n\nNew Features:\n\n- ESITransforms add a new header ``X-Esi: 1`` when any ESI tiles have\n  been transformed. This allows e.g. Varnish to enable ESI only when\n  it's really required.\n  [datakurre]\n\n- Add to allow ``permission`` key in ``[contentlayout]``-sections of content\n  layout manifests (``manifest.cfg``)\n  [datakurre]\n\n\n4.0.6 (2017-02-09)\n------------------\n\nFixes:\n\n- Fix issue where layout related fields could have been acquired\n  (only sectionSiteLayout can be allowed to be acquired)\n  [datakurre]\n\n\n4.0.5 (2017-02-08)\n------------------\n\nFixes:\n\n- Fix issue where page site layout could have been accidentally acquired\n  (page site layout should never be acquired)\n  [datakurre]\n\n- Fix transforms to comply with\n  plone.transformchain.interfaces.ITransform\n  [datakurre]\n\n\n4.0.4 (2017-01-30)\n------------------\n\nFixes:\n\n- Fix issue where ESIRender has been broken since plone.protect's\n  ProtectTransform was introduced, because of protect transform breaking\n  ESI-tags; Change ESIRender transform order from 8900 to 9900\n  [datakurre]\n\n4.0.3 (2017-01-15)\n------------------\n\nFixes:\n\n- Fix issue where default layouts paths were not found if they were stored\n  unicode (TextLine) instead of str (ASCIILine or BytesLine)\n  [datakurre]\n\n- Fix issue where tiles merge failed for addresses with space, because\n  subrequest was called with quoted ('%20') paths\n  [datakurre]\n\n\n4.0.2 (2017-01-03)\n------------------\n\nFixes:\n\n- Fix issue where error in diazo transform for a single tile aborted tile\n  merge as whole\n  [datakurre]\n\n\n4.0.1 (2016-12-28)\n------------------\n\nFixes:\n\n- Fix issue where tile data storage decoded HTML primary fields\n  using ASCII instead of utf-8 causing broken broken latin\n  characters in attribute values\n  [datakurre]\n\n\n4.0.0 (2016-12-13)\n------------------\n\nIncompatibilities:\n\n- Remove grid transform, because it did not serve its purpose as as well\n  expected and required HTML-syntax not editable by humans; Instead using\n  grid framework agnostic CSS class names and building CSS grid against\n  those class names is recommended\n  [agitator]\n\n- Remove ``IOmittedField`` marker from layout behavior fields not meant to be\n  displayed on legacy Deco UIs\n  [jensens]\n\n- Rename ``ILayoutAware.content`` to ``ILayoutAware.customContentLayout``\n  [datakurre]\n\n- Move functions ``getDefaultAjaxLayout``, ``getDefaultSiteLayout``,\n  ``getLayout`` and ``getLayoutAwareSiteLayout`` to ``.layoutbehavior`` in\n  order to avoid circular imports (all deprecated now, see section New).\n  [jensens]\n\n- Move views from ``.layoutbehavior`` to new module ``.layoutviews`` in order\n  to avoid circular imports.  Deprecated deferred imports are in place.\n  [jensens]\n\nNew:\n\n- Add ``ILayoutAware.content`` as layout independent \"layout like\" tile\n  configuration and data storage for all serializable tile configurations\n  [datakurre]\n\n- Add ``@@layout_preview`` view for previewing currently drafted layout aware\n  content\n  [datakurre]\n\n- ``ILayoutAware`` is now also responsible to lookup the behaviors.\n  [jensens]\n\n- Get layouts always by adapting with ``ILayoutAware``.  This introduces a\n  generic adapter and a behavior adapter.  Deprecated the formerly used functions\n  ``getLayout`` ``getDefaultSiteLayout`` just calls\n  ``ILayoutAware().site_layout`` and is deprected.  ``getLayout`` just calls\n  ``ILayoutAware().content_layout`` and is deprecated.\n  [jensens]\n\n- Behavior shortname ``plone.layoutaware`` added.\n  [jensens]\n\nFixes:\n\n- Handle missing content layouts so they do not cause an error\n  [vangheem]\n\n- A tile raising an 401 Unauthorized on traversal,\n  results in a status rewriting to a 302 which results in 200 login form.\n  The whole login form page then is rendered as the tile contents.\n  This patch catches the 401 by providing a custom exception handler.\n  The 401 is catched and ignored. This is not pefect yet and need some work,\n  but it at least does not break design and intended behavior of tiles.\n  [jensens]\n\nRefactoring:\n\n- Housekeeping: ZCA decorators, sorted imports, line-lengths and related.\n  [jensens]\n\n- Reformat documentation.\n  [gforcada]\n\n- Update travis configuration.\n  [gforcada]\n\n\n3.1.0 (2016-03-28)\n------------------\n\nNew:\n\n- Don't make a tile exception break other tiles (closes `#27`_).\n  [rodfersou, datakurre]\n\n- Provide new getLayoutsFromDirectory utility to get layouts from any\n  plone.resource directory, not just the base resource directory\n  [vangheem]\n\n- Index layout data; When collective.dexteritytextindexer is present,\n  its *Dynamic SearchableText indexer behavior* must be enabled for content\n  type\n  [vangheem, datakurre]\n\n- Cleanup tile data on save/edit\n  [vangheem]\n\n\n3.0.1 (2015-09-23)\n------------------\n\n- Remove the default 'Custom layout' display menu registration for\n  'layout_view', because it was not possible to customize it with more exact\n  registration\n  [datakurre]\n\n- Fix the default view to report template name as 'template-layout'\n  [datakurre]\n\n\n3.0.0 (2015-09-16)\n------------------\n\n- Change layout behavior default view name from ``view`` to ``layout_view``\n  [datakurre]\n\n- Add to be able to set default grid system in registry settings\n  [vangheem]\n\n- Add support for provide more than one layout with a layout directory\n  and manifest (replaces removed layout variants)\n  [vangheem]\n\n- Add ``contentlayout`` resource type with ``plone.availableContentLayouts``\n  vocabulary and ``++contentlayout++`` traverser\n  [vangheem]\n\n- Add ``contentLayout`` field to layoutbehavior to select the rendered layout\n  from centrally managed content layouts\n  [vangheem]\n\n- Add content type specific registry configuration with key\n  ``plone.app.blocks.default_layout.portal_type`` for used default content\n  layout when custom layout is not defined\n  [vangheem]\n\n- Add to check ``plone.app.blocks.default_layout`` registry key for a default\n  content layout path when content type specific default content layout path is\n  not set\n  [datakurre]\n\n- Fixed layout behavior to apply Plone outputfilters for rendered content\n  [datakurre]\n\n- Add default grid system registry setting\n  [vangheem]\n\n- Restore support for Plone 4.2.x\n  [datakurre]\n\n- Remove layout variants introduced in 2.0.0, in favor of ability to\n  provide more than one layout with a layout directory and manifest by\n  using multiple ``[...layout]`` directive in the same manifest\n  [vangheem]\n\n\n2.1.2 (2015-06-10)\n------------------\n\n- Fix issue where grid transform did replaced class names instead of appending\n  to them\n  [datakurre]\n\n\n2.1.1 (2015-06-10)\n------------------\n\n- Fix BS3 grid transform to only introduce offset when the tile position is\n  greater than the current position in the current row\n  [datakurre]\n\n- Fix issue where tiles with empty response or syntax error broke tiles\n  transform (add to log syntax errors instead)\n  [datakurre]\n\n\n2.1.0 (2015-05-25)\n------------------\n\n- Add support for indexing layout field into SearchableText index when\n  collective.dexteritytextindexer is installed and its Dynamic SearchableText\n  indexer behavior is enabled for the indexed content type with Layout support\n  behavior\n  [datakurre]\n\n\n2.0.0 (2015-04-21)\n------------------\n\n- Fix package dependencies; remove dependency on unittest2.\n  [hvelarde]\n\n- Change blocks transforms to be opt-in for only published objects e.g. views\n  or requests with IBlocksTransformEnabled (marker) interface [fixes #11]\n  [datakurre]\n\n- Change tags with data-tiles-attrs to be completely replaced (by\n  replace_with_children instad of replace_content) to restore original\n  design and support for site layout tiles in HTML document head tag\n  [datakurre]\n\n- Change default site layout to be optional by adding an implicit\n  main_template-based site layout when the default site layout is not set\n  [datakurre]\n\n- Change to retry resolveResources with 301 or 302 response when redirect\n  location is for the same site\n  [datakurre]\n\n- Add support for AJAX site layout for requests with ``ajax_load`` parameter\n  either by getting a layout from a reqistry key ``plone.defaultAjaxLayout``\n  or by using an implicit main_template-based AJAX layout\n  [simahawk, datakurre]\n\n- Add extensible CSS grid transform with built-in transforms for Deco\n  and Bootstrap 3 grid systems\n  [bloodbare, ACatila]\n\n  .. code:: xml\n\n     <utility\n         provides=\".gridsystem.IGridSystem\"\n         component=\".gridsystem.DecoGridSystem\"\n         name=\"deco\"\n         />\n\n  .. code:: html\n\n     <html data-gridsystem=\"deco\">\n       ...\n       <div data-grid='{\"type\": \"row\"}'>\n         <div data-grid='{\"type\": \"cell\",\n                          \"info\": {\"xs\": \"false\",\n                                   \"sm\": \"False\",\n                                   \"lg\": \"True\",\n                          \"pos\": {\"x\":1,\n                                  \"width\": 12}}}'>\n          </div>\n       </div>\n     </html>\n\n  .. code:: html\n\n     <div class=\"row\">\n        <div class=\"cell position-1 width-12\">\n        </div>\n     </div>\n\n- Add default view for ILayoutAware content and register a localizable display\n  menu item called *Custom layout* for it when *plone.app.contentmenu* is\n  present\n  [datakurre]\n\n- Add Layout-fieldset for ILayoutAware behavior\n  [datakurre]\n\n- Add support to use the whole tile as its body when both head and body tags\n  are missing (add support for using Dexterithy display widgets as tiles)\n  [datakurre]\n\n- Add support for layout variants (for supporting multiple layouts in a single\n  resource folder)\n  [datakurre]\n\n  .. code:: ini\n\n     [sitelayout]\n     ...\n\n     [sitelayout:variants]\n     document_layout = document.html\n\n- Add experimental support for tile-specific Diazo-rules\n  with data-attribute ``data-rules=\"/++sitelayout++name/rules.xml\"``.\n  [datakurre]\n\n- Fix issue with tile without body-tag breaking the tile composition (fixes\n  issues with some p.a.standardtiles returning only <html/> in some conditions)\n  [datakurre]\n\n- Fix issue where <![CDATA[...]]> block was quoted (and therefore broken) by\n  lxml serializer\n  [datakurre]\n\n- Fix issue where XML parser dropped head for layout with CRLF-endings\n  [datakurre]\n\n- Fix plone.app.blocks re-install to not reset existing plone.defaultSiteLayout\n  and plone.defaultAjaxLayout settings (by setting the values in a custom\n  setuphandler)\n  [datakurre]\n\n- Fix and update tests, PEP8\n  [gyst, datakurre, gforcada]\n\n- Fix to set the merging request flag before testing the merge results to allow\n  staticly placed tiles in content templates to be rendered properly.\n  [cewing]\n\n- Solve issue with VHM and tile rendering. Fixes\n  https://dev.plone.org/ticket/13581 [ericof]\n\n- Add z3c.autoinclude support\n  [cdw9, calvinhp]\n\n\n1.1 (2012-12-17)\n----------------\n\n- make sure to use correct url of tile\n  [vangheem]\n\n- handle not found errors while rendering tiles so layout\n  isn't borked\n  [vangheem]\n\n\n1.0 (2012-06-23)\n----------------\n\n- initial release.\n  [garbas]\n\n.. _`#27`: https://github.com/plone/plone.app.blocks/issues/27\n",
    "bugtrack_url": null,
    "license": "GPLv2",
    "summary": "Implements the in-Plone blocks rendering process",
    "version": "7.0.1",
    "project_urls": {
        "Homepage": "https://github.com/plone/plone.app.blocks"
    },
    "split_keywords": [
        "plone",
        "blocks",
        "deco"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "69b6ddcb3404065cf18d64f2edfc057dc95e8432d40f0cbbdc5b399aec1f1b41",
                "md5": "6a7231a82cfca2fd5fdc39f0a8178ff1",
                "sha256": "2def9891583cb68ac3fb32dc1a5ffc926e0cf6f2238ae5880d2182b877f80037"
            },
            "downloads": -1,
            "filename": "plone.app.blocks-7.0.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "6a7231a82cfca2fd5fdc39f0a8178ff1",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 79967,
            "upload_time": "2023-11-16T10:23:04",
            "upload_time_iso_8601": "2023-11-16T10:23:04.136450Z",
            "url": "https://files.pythonhosted.org/packages/69/b6/ddcb3404065cf18d64f2edfc057dc95e8432d40f0cbbdc5b399aec1f1b41/plone.app.blocks-7.0.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f82d538e9196a09ab8dbd9e15ff43a3cf16b09f100a54ea5afca2121447b06f2",
                "md5": "d3277b6cb91177e3873e846052a82f05",
                "sha256": "69ad695c15ec5638bddb30ae815e7aacb898b29524fb89b8674ac8d23aed285e"
            },
            "downloads": -1,
            "filename": "plone.app.blocks-7.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "d3277b6cb91177e3873e846052a82f05",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 80027,
            "upload_time": "2023-11-16T10:23:07",
            "upload_time_iso_8601": "2023-11-16T10:23:07.006548Z",
            "url": "https://files.pythonhosted.org/packages/f8/2d/538e9196a09ab8dbd9e15ff43a3cf16b09f100a54ea5afca2121447b06f2/plone.app.blocks-7.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-11-16 10:23:07",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "plone",
    "github_project": "plone.app.blocks",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": true,
    "requirements": [],
    "lcname": "plone.app.blocks"
}
        
Elapsed time: 0.21793s