Using Pyramid Layout

To get started with Pyramid Layout, include pyramid_layout in your application’s config:

config = Configurator(...)
config.include('pyramid_layout')

Alternately, instead of using the the Configurator’s include method, you can activate Pyramid Layout by changing your application’s .ini file, using the following line:

pyramid.includes = pyramid_layout

Including pyramid_layout in your application adds two new directives to your configurator: add_layout and add_panel. These directives work very much like add_view, but add registrations for layouts and panels. Including pyramid_layout will also add an attribute, layout_manager, to the request object of each request, which is an instance of LayoutManager.

Finally, three renderer globals are added which will be available to all templates: layout, main_template, and panel. layout is an instance of the layout class of the current layout. main_template is the template object that provides the main template (aka, o-wrap) for the view. panel, a shortcut for LayoutManager.render_panel, is a callable used to render panels in your templates.

Using Layouts

A layout consists of a layout class and main template. The layout class will be instantiated on a per request basis with the context and request as arguments. The layout class can be omitted, in which case a default layout class will be used, which only assigns context and request to the layout instance. Generally, though, you will provide your own layout class which can serve as a place to provide API that will be available to your templates. A simple layout class might look like:

class MyLayout(object):
    page_title = 'Hooray! My App!'

    def __init__(self, context, request):
        self.context = context
        self.request = request
        self.home_url = request.application_url

    def is_user_admin(self):
        return has_permission(self.request, 'manage')

A layout instance will be available in templates as the renderer global, layout. For example, if you are using Mako or ZPT for templating, you can put something like this in a template:

<title>${layout.page_title}</title>

For Jinja2:

<title>{{layout.page_title}}</title>

All layouts must have an associated template which is the main template for the layout and will be present as main_template in renderer globals.

To register a layout, use the add_layout method of the configurator:

config.add_layout('myproject.layout.MyLayout',
                  'myproject.layout:templates/default_layout.pt')

The above registered layout will be the default layout. Layouts can also be named:

config.add_layout('myproject.layout.MyLayout',
                  'myproject.layout:templates/admin_layout.pt',
                  name='admin')

Now that you have a layout, time to use it on a particular view. Layouts can be defined declaratively, next to your renderer, in the view configuration:

@view_config(..., layout='admin')
def myview(context, request):
    ...

In Pyramid < 1.4, to use a named layout, call LayoutManager.use_layout method in your view:

def myview(context, request):
    request.layout_manager.use_layout('admin')
    ...

If you are using traversal you may find that in most cases it is unnecessary to name your layouts. Use of the context argument to the layout configuration can allow you to use a particular layout whenever the context is of a particular type:

from ..models.wiki import WikiPage

config.add_layout('myproject.layout.MyLayout',
                  'myproject.layout:templates/wiki_layout.pt',
                  context=WikiPage)

Similarly, the containment argument allows you to use a particular layout for an entire branch of your resource tree:

from ..models.admin import AdminFolder

config.add_layout('myproject.layout.MyLayout',
                  'myproject.layout:templates/admin_layout.pt',
                  containment=AdminFolder)

The decorator layout_config can be used in conjuction with Configurator.scan to register layouts declaratively:

from pyramid_layout.layout import layout_config

@layout_config(template='templates/default_layout.pt')
@layout_config(name='admin', template='templates/admin_layout.pt')
class MyLayout(object):
    ...

Layouts can also be registered for specific context types and containments. See the api docs for more info.

Using Panels

A panel is similar to a view but is responsible for rendering only a part of a page. A panel is a callable which can accept arbitrary arguments (the first two are always context and request) and either returns an html string or uses a Pyramid renderer to render the html to insert in the page.

Note

You can mix-and-match template languages in a project. Some panels can be implemented in Jinja2, some in Mako, some in ZPT. All can work in layouts implemented in any template language supported by Pyramid Layout.

A panel can be configured using the method, add_panel of the Configurator instance:

config.add_panel('myproject.layout.siblings_panel', 'siblings',
                 renderer='myproject.layout:templates/siblings.pt')

Because panels can be called with arguments, they can be parameterized when used in different ways. The panel callable might look something like:

def siblings_panel(context, request, n_siblings=5):
    return [sibling for sibling in context.__parent__.values()
            if sibling is not context][:n_siblings]

And could be called from a template like this:

${panel('siblings', 8)}  <!-- Show 8 siblings -->

If using Configurator.scan, you can also register the panel declaratively:

from pyramid_layout.panel import panel_config

@panel_config('siblings', renderer='templates/siblings.pt')
def siblings_panel(context, request, n_siblings=5):
    return [sibling for sibling in context.__parent__.values()
            if sibling is not context][:n_siblings]

Like layouts, panels can also be registered for a context type:

from pyramid_layout.panel import panel_config

@panel_config(name='see-also'
              context='myproject.models.Document',
              renderer='templates/see-also.pt')
def see_also(context, request):
    return {'title': context.title,
            'url': request.resource_url(context)}

The context to use to look up a panel defaults to the context found during traversal. A different context may be provided by passing a context keyword argument to panel call. In this hypothetical template, each related_content item can potentially be a different type and wind up invoking a different panel:

<h2>Related Content</h2>
<ul>
  <li tal:repeat="item releated_content">
    ${panel('see-also', context=item)}
  </li>
</ul>

When registering panels by context, the name part of the registration becomes optional. In the example above, we could make the see-also panels the default panels for any registered contexts by simply omitting name:

from pyramid_layout.panel import panel_config

@panel_config(context='myproject.models.Document',
              renderer='templates/see-also.pt')
def see_also(context, request):
    return {'title': context.title,
            'url': request.resource_url(context)}

Also in the template:

<h2>Related Content</h2>
<ul>
  <li tal:repeat="item releated_content">
    ${panel(context=item)}
  </li>
</ul>

See the api docs for more info.

Using the Main Template

The precise syntax for hooking into the main template from a view template varies depending on the templating language you’re using.

ZPT

If you are a ZPT user, connecting your view template to the layout and its main template is pretty easy. Just make this the outermost element in your view template:

<metal:block use-macro="main_template">
...
</metal:block>

You’ll note that we’re taking advantage of a feature in Chameleon that allows us to use a template instance as a macro without having to explicitly define a macro in the main template.

After that, it’s about what you’d expect. The main template has to define at least one slot. The view template has to fill at least one slot.

Mako

In Mako, to use the main template from your layout, use this as the first line in your view template:

<%inherit file="${context['main_template'].uri}"/>

In your main template, insert this line at the point where you’d like for the view template to be inserted:

${next.body()}

Jinja2

For Jinja2, to use the main template for your layout, use this as the first line in your view template:

{% extends main_template %}

From there, blocks defined in your main template can be overridden by blocks defined in your view template, per normal usage.