Most web sites want a common look and feel for their web pages. The site template pattern allows you to separate the unchanging portions of the HTML from the page-specific content, so that you can change the entire site's layout in one place. How to do this depends on the template engine, but all engines used with Pylons provide some way to do it.
Mako
Create a site template site.html in your templates directory. This should be a complete HTML page with a
placeholder where the page content should go. Every page template should have this at the top:
1 | <%inherit file="/site.html" />
|
Think of the site template as a base class and the page template as a subclass. They aren't really, but they behave that way. (Some template engines like Cheetah actually do use classes for this.) Mako's site template support is well explained in the Mako manual
under "Inheritance" and "<%inherit>".
Example 1 (Mako)
But what about the page title, breadcrumbs, Javascript, and other page customizations that might be part of the common layout? Here's a more elaborate site template that handles all these cases. It's just an example, not the way you have to do it. Feel free to modify it to meet your needs.
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 | <%def name="format_ht_title(title)">\
My Site\
% if title:
: ${title}\
% endif
</%def>
<%def name="head_extra()">
</%def>
<%def name="header()">
% if not request.params.get("print"):
<div style="margin:10px 0">
${h.image_tag("logo.jpg", "My Site", "740x108", id="banner")}
</div>
% endif print
</%def>
<%def name="body_tag()">
<body>
</%def>
## ***************** MAIN CONTENT *************************************
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>${format_ht_title(c.ht_title or c.title) | h}</title>
${h.stylesheet_link_tag("default")}
${h.javascript_include_tag(builtins=True)}
${self.head_extra()}
</head>
${self.body_tag()}
<p> <!-- HTML5 tag: nav -->
<a href="#content" id="skip-to-content">Skip to main content</a>
<span id="print-icon">[print icon, etc]</span>
<span id="breadcrumbs">
${h.link("Home", "home")}
% for crumb in c.crumbs:
>>
${crumb}
% endfor crumb
</span>
</p> <!-- HTML5 tag: /nav -->
<div id="header" class="header"> <!-- HTML5 tag: header -->
${self.header()}
</div> <!-- HTML5 tag: /header -->
<div id="content"> <!-- *** BEGIN page content *** -->
% if "warning" in config:
<div id="system-warning">
${config["warning"]}
</div>
% endif warning
% if c.title:
<h1 class="page-title">${c.title | h}</h1>
% endif
<% flash_message = h.flash.pop_message() %>
% if flash_message:
<div id="flash-message">${flash_message | h}</div>
% endif
${self.body()}
</div> <!-- *** END page content *** -->
% if not request.params.get("print"):
<div id="footer" class="footer"> <!-- HTML5 tag: footer -->
<p class="sitelinks">
<a href="${h.url_for('contact')}">Contact Us</a> |
<a href="${h.url_for('privacy')}">Privacy Policy</a>
</p>
<p>
[decorative footer goes here: secondary logo, copyright info, etc]
</p>
</div> <!-- HTML5 tag: footer -->
% endif print
</body></html>
|
It depends on a few c variables, which should be initialized in the base controller. Put the following in myapp/lib/base.py at the beginning of the .__call__ or .__before__ method:
1
2
3 | c.title = "" # Blank title.
c.ht_title = "" # If empty, use c.title instead.
c.crumbs = [] # List of h.link calls, last element is string.
|
This site template has the following features:
- The controller can set c.title to the appropriate page title (shown in the page body).
- The <%def> method format_ht_title() decides what the <title> tag should be. By default it's the same as the page title unless the controller sets c.ht_title to something different. the formatter automatically prepends the site name to the <title>.
- c.crumbs controls the breadcrumbs links. See "Breadcrumbs" below for usage.
- If query parameter "print" is present and not empty, most of the decorations are suppressed to produce a printable page. Any page can make itself printable by providing a link to itself with "?print=1".
- The page template can define a <%def> method head_extra() to add extra stuff to the header, such as Javascript, styles, extra <link> or <meta> tags, etc.
- The page template can define a <%def> method body_tag() to add Javascript attributes to the body tag.
- The author makes extensive use of WebHelpers
such as h.javascript_include_tag, h.stylesheet_link_tag, h.image_tag. You can replace these with literal HTML tags if you wish.
- A controller can set a "flash" message in the session for the subsequent page to show. See "Flash" below.
- A warning message can be set in the config file, such as "Site is temporarily read-only for maintenance." It uses an optional 'warning' variable in the config file, which can be set to any HTML chunk, and a '#system-warning' style in your stylesheet.
- The page is built with accessibility in mind. The navigation row contains the breadcrumbs and two other features: a "skip to main content" link, and a space for a print icon, search box, etc. "Skip to main content" is useful for blind users so they can avoid listening to the unchanging header again and again. It's put first in the HTML so that screen readers will encounter it immediately, while an "id" attribute allows a stylesheet to move it to the right of the row (the "float:right" style) so that graphical browsers will display the breadcrumbs first.
- The "| h" syntax in some placeholders HTML-escapes the value. See the Mako manual for details.
- HTML 5 tags are noted in comments. HTML 5 provides semantic tags to mark the page header, footer, and navigation section. Because the popular browsers do not support HTML 5 yet, they are emulated with standard <div>'s.
Breadcrumbs
This example allows the following breadcrumbs syntax:
1
2
3
4
5
6
7
8
9
10
11
12
13 | crumbs = []
# Default. Show only the "Home" link.
crumbs = ['<a href="/birds">Birds</a>']
# Show "Home > Birds" links. The current page is a subsection of Birds.
crumbs = ['<a href="/birds">Birds</a>', 'Sparrow']
# Same but the desired layout includes a non-link for the current page.
# This shows "Home > Birds > Sparrow".
crumbs = [h.link_to("Birds", h.url_for("birds"))]
# Same but using url_for and WebHelpers.
crumbs = [h.link("Birds", "birds")]
# Same using the link() function below.
crumbs = None
# Don't display the breadcrumbs bar at all. (This is a special case.)
|
link() function
This is a useful function for creating hyperlinks. Put it in myapp/lib/helpers.py and you can access it under h.
1
2
3
4
5
6
7
8
9
10
11 | from cgi import escape as html_escape
# url_for and link_to have already been imported.
def link(label, *url_for_args, **url_for_kw):
"""Shortcut for h.link_to("label", h.url_for(*args, **kw)).
The label will be converted to Unicode and HTML-escaped.
"""
label = html_escape(unicode(label))
url = url_for(*url_for_args, **url_for_kw)
return link_to(label, url)
|
Flash
Sometimes you want to redirect to a page and also show a confirmation message on that page, such as "Record added", "Cancelled delete operation", "You are unauthorized", etc. This is called a "flash" message. (The term comes from a TurboGears tutorial. It has nothing to do with Adobe Flash.) You put the message in the session or in a cookie, and the subsequent page extracts it, displays it, and deletes it. The example uses a simple class in myapp/lib/helpers.py to manage the message:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | class Flash(object):
def __call__(self, message):
session = self._get_session()
session["flash"] = message
session.save()
def pop_message(self):
session = self._get_session()
message = session.pop("flash", None)
if not message:
return None
session.save()
return message
def _get_session(self):
from pylons import session
return session
flash = Flash()
|
Genshi
Match templates. (somebody please elaborate)
Cheetah
See the inheritance chapter and #extends in the Cheetah User's Guide
. You'll have to precompile the site template and put the .py template module somewhere where it can be imported, because Cheetah doesn't use Buffet or render() to find inherited templates.