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.