Demo App With Pyramid Layout¶
Let’s see Pyramid Layout in action with the demo application provided
in demo
.
Installation¶
Normal Pyramid stuff:
- Make a virtualenv
env/bin/python demo/setup.py develop
env/bin/pserve demo/development.ini
- Open
http://0.0.0.0:6543/
in a browser - Click on the
Home Mako
,Home Chameleon
, andHome Jinja2
links in the header to see views for that use each.
Now let’s look at some of the code.
Registration¶
Pyramid Layout defines configuration directives and decorators you can
use in your project. We need those loaded into our code. The demo does
this in the etc/development.ini
file:
pyramid.includes =
pyramid_debugtoolbar
mako.directories = demo:templates
The development.ini
entry point starts in demo/__init__.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from pyramid.config import Configurator
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(settings=settings)
config.include('pyramid_chameleon')
config.include('pyramid_jinja2')
config.include('pyramid_mako')
config.include('pyramid_layout')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home.mako', '/')
config.add_route('home.chameleon', '/chameleon')
config.add_route('home.jinja2', '/jinja2')
config.scan('.layouts')
config.scan('.panels')
config.scan('.views')
return config.make_wsgi_app()
|
This is all Configurator action. We register a route for each view. We
then scan our demo/layouts.py
, demo/panels.py
, and
demo/views.py
for registrations.
Layout¶
Let’s start with the big picture: the global look-and-feel via a layout:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | from pyramid_layout.layout import layout_config
@layout_config(template='demo:templates/layouts/layout.mako')
@layout_config(
name='chameleon',
template='demo:templates/layouts/layout.pt'
)
@layout_config(
name='jinja2',
template='demo:templates/layouts/layout.jinja2'
)
class AppLayout(object):
def __init__(self, context, request):
self.context = context
self.request = request
self.home_url = request.application_url
self.headings = []
self.portlets = (Thing1(), Thing2(), LittleCat("A"))
@property
def project_title(self):
return 'Pyramid Layout App!'
def add_heading(self, name, *args, **kw):
self.headings.append((name, args, kw))
class Thing1(object):
title = "Thing 1"
content = "I am Thing 1!"
class Thing2(object):
title = "Thing 2"
content = "I am Thing 2!"
class LittleCat(object):
talent = "removing pink spots"
def __init__(self, name):
self.name = name
|
The @layout_config
decorator comes from Pyramid Layout and allows
us to define and register a layout. In this case we’ve stacked 3
decorators, thus making 3 layouts, one for each template language.
Note
The first @layout_config
doesn’t have a name
and is thus
the layout that you will get if your view doesn’t specifically
choose which layout it wants.
Lines 21-24 illustrates the concept of keeping templates and the template
logic close together. All views need to show the project_title
.
It’s part of the global look-and-feel main template. So we put this
logic on the layout, in one place as part of the global contract,
rather than having each view supply that data/logic.
Let’s next look at where this is used in the template for one of the
3 layouts. In this case, the Mako template at
demo/templates/layouts/layout.mako
:
<title>${layout.project_title}, from Pylons Project</title>
Here we see an important concept and some important magic: the template
has a top-level variable layout
available. This is an instance of
your layout class.
For the ZPT crowd, if you look at the master template in
demo/templates/layouts/layout.pt
, you might notice something weird
at the top: there’s no metal:define-macro
. Since Chameleon allows a
template to be a top-level macro, Pyramid Layout automatically binds
the entire template to the macro named main_template
.
How does your view know to use a layout? Let’s take a look.
Connecting Views to a Layout¶
Our demo app has a very simple set of views:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | from pyramid.view import view_config
@view_config(
route_name='home.mako',
renderer='demo:templates/home.mako'
)
@view_config(
route_name='home.chameleon',
renderer='demo:templates/home.pt',
layout='chameleon'
)
@view_config(
route_name='home.jinja2',
renderer='demo:templates/home.jinja2',
layout='jinja2'
)
def home(request):
lm = request.layout_manager
lm.layout.add_heading('heading-mako')
lm.layout.add_heading('heading-chameleon')
lm.layout.add_heading('heading-jinja2')
return {}
|
We again have one callable with 3 stacked decorators. The decorators
are all normal Pyramid @view_config
stuff.
The second one points at a Chameleon template in
demo/templates/home.pt
:
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<!-- Main hero unit for a primary marketing message or call to action -->
${panel('hero', title='Chameleon')}
<!-- Example row of columns -->
<div class="row">
<p>${panel('headings')}</p>
</div>
<div class="row">
<p>${panel('contextual_panels')}</p>
</div>
<div class="row">
<h2>User Info</h2>
<p>${panel('usermenu',
user_info={
'first_name': 'Jane',
'last_name': 'Doe',
'username': 'jdoe'}
)}</p>
</div>
</div>
</metal:block>
The first line is the one that opts the template into the layout. In
home.jinja2
that line looks like:
{% extends main_template %}
For both of these, main_template
is inserted by Pyramid Layout,
via a Pyramid renderer global, into the template’s global namespace.
After that, it’s normal semantics for that template language.
Back to views.py
. The view function grabs the Layout Manager
,
which Pyramid Layout conveniently stashes on the request. The
LayoutManager
‘s primary job is getting/setting the current layout.
Which, of course, we do in this function.
Our function then grabs the layout instance and manipulates some state
that is needed in the global look and feel. This, of course, could have been
done in our AppLayout
class, but in some cases, different views have
different values for the headings.
Re-Usable Snippets with Panels¶
Our main template has something interesting in it:
<body>
${panel('navbar')}
<div class="container">
${next.body()}
<hr>
<footer>
${panel('footer')}
</footer>
</div> <!-- /container -->
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="${request.static_url('demo:static/js/jquery-1.8.0.min.js')}"></script>
<script src="${request.static_url('demo:static/js/bootstrap.min.js')}"></script>
</body>
Here we break our global layout into reusable parts via panels.
Where do these come from? @panel_config
decorators, as shown in
panels.py
. For example, this:
${panel('navbar')}
...comes from this:
@panel_config(
name='navbar',
renderer='demo:templates/panels/navbar.mako'
)
def navbar(context, request):
def nav_item(name, url):
active = request.current_route_url() == url
item = dict(
name=name,
url=url,
active=active
)
return item
nav = [
nav_item('Mako', request.route_url('home.mako')),
nav_item('Chameleon', request.route_url('home.chameleon')),
nav_item('Jinja2', request.route_url('home.jinja2'))
]
return {
'title': 'Demo App',
The @panel_config
registered a panel under the name navbar
,
which our template could then use or override.
The home.mako
view template has a more interesting panel:
${panel('hero', title='Mako')}
...which calls:
1 2 3 4 5 6 |
@panel_config(
name='hero',
renderer='demo:templates/panels/hero.mako'
)
|
This shows that a panel can be parameterized and used in different places in different ways.