The World Through the Eyes of John Brennan
Without presentation a web page is just text. In the early days of building web sites styles and colors were mixed with JavaScript and large animated gifs. Yes, during those days they were referred to as “web sites,” no one called them “web apps” yet. And to be frank, with all that work that went into it that hurt my feelings. Anyway, today I’m going to go beyond just showing how to get Mako working with your templates, but go as far as show you a better way to structure your templates in a Rails-like fashion.
I’m going to leave explain Mako to the developers, but in exchange I will give you some real examples.
Some of my methods are borrowed for Ruby on Rails. I essentially have 3 parts to my presentation layer — layouts, views, and elements.
A layout is the largest of the 3 containers and contains the page’s chrome (header, footer, sidebar) as well as all page scripts and stylesheets.
A view is the meat of the page. This is where all the content resides. Views are organized into folders based on their respective controller. For example, the home controller would have a folder called home where all of its views would reside.
An element is a component that may be shared by multiple views. For example, the home controller may have 2 different views, say the welcome screen and a dashboard screen. Both views might want to share pieces of the view that are the same. These would be elements. Elements are located within a specific view folder.
Below is a figure of the relationship:

And here is what the file structure would look like:

Let’s get started by looking at the layout. As I said before, this is the chrome of the page. I like to keep all my external scripts (CSS and JavaScript) in 1 place though. At first it wasn’t so straightforward with Mako, but by overwriting function definitions this is possible.
Here’s a bare bones default layout I have created for this project.
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 | <% # # layouts/default.mako # The default layout to wrap the page contents. # %> ######################################################### ######################################################### ### Begin HTML Template ### <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>My first app</title> ${ h.javascript_include_tag('global', builtins=True) } ${ h.stylesheet_link_tag('/css/styles.css',media='all') } ${self.additional_head_tags()} <script type="text/javascript"> <!-- ### ### Scripts to execute once DOM is loaded function init() { ${self.additional_jscript_init()} } Event.observe(window, "load", init); ### ### Custom scripts for the specific page inheriting this layout ${self.additional_jscript()} //--> </script> </head> <body> <div id="wrap"> ### ********************************** ### HEADER ### ********************************** <div id="header"><h1>Simple Pylons Examples - My First App</h1></div> ### ********************************** ### MAIN CONTENT ### ********************************** <div id="main"> ${ next.body() } </div> ### ********************************** ### SIDEBAR ### ********************************** <div id="sidebar"> ${self.sidebar()} </div> </div> </body> </html> ### End HTML Template ### ######################################################### ######################################################### ### Define funcs that may be inherited and overwritten in child templates ### <%def name="additional_head_tags()"></%def> <%def name="additional_jscript_init()"></%def> <%def name="additional_jscript()"></%def> <%def name="sidebar()"></%def> |
You might have noticed that aside from the common HTML declarations I have a few other python expressions that make function calls and look like that are pulling in additional content. If you noticed that, you are right!
I have set up a simple way to get JavaScript code included when the DOM loads, the ability to load other external script files, as well as modify the sidebar.
For example, I’m telling Mako to take all JavaScript initialization calls and place them at line 26.
26 | ${self.additional_jscript_init()} |
Towards the bottom of the page I am defining these functions, although I plan to overwrite them in the views, which I will show you next.
Although the layout is the chrome of the page, your view is the entry point, and the doorway from the controller out to the user.
An entry point in your controller would look something like this:
return render("/home/list.mako")
Here we are calling the list view for the home controller. The view will then decide what layout it should use, bring in any elements it needs, fill in any places where variables need to be filled, and send it off to the user.
Here is an example view of the list view:
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 | <%inherit file="../layouts/default.mako"/> <% # # templates/home/list.mako # Displays list of user names to demonstrate end-to-end # %> ######################################################### ######################################################### ### Override inherited funcs ### ############################### ### additional_head_tags() <%def name="additional_head_tags()"> ${parent.additional_head_tags()} <script src="${h.url_for('javascript')}/example.js" type="text/javascript"></script> </%def> ############################### ### additional_jscript_init() <%def name="additional_jscript_init()"> ${parent.additional_jscript_init()} modify_footer(); </%def> ############################### ### sidebar() <%def name="sidebar()"> ${parent.sidebar()} <%include file="elements/sidebar.mako"/> </%def> ### ######################################################### ######################################################### ### Begin HTML Template ### <h2>Welcome</h2> Hey, how are ya? |
In line 1 we are telling the view what layout to use.
1 | <%inherit file="../layouts/default.mako"/> |
Starting in line 14, we are overwriting a function that we defined in our layout. Since we don’t want to completely overwrite it thought, we make a call to the parent (the layout) first. Here we are including an additional JavaScript file.
14 15 16 17 | <%def name="additional_head_tags()"> ${parent.additional_head_tags()} <script src="${h.url_for('javascript')}/example.js" type="text/javascript"></script> </%def> |
From the example above, we are including a sidebar element at line 28.
28 | <%include file="elements/sidebar.mako"/> |
This allows us to share components between views and even layouts. Elements have access to the global c variable just as the layout and views do. As a convention I like to call out what variables need to be defined for an element to work towards the top of the file. As such, an example should follow…
1 2 3 4 5 6 7 8 9 10 11 12 | #
# element/sidebar.mako
# Sidebar element
#
# Required defined vars:
# c.active_users - An array of active users {Users database objects}
#
%>
<p>Ads</p>
% for user in c.active_users:
${ user.name }<br/>
% endfor |
Here we are assuming that we have several active users that we wish to loop over. The object would be obtained in the calling controller as follows:
users = meta.Session.query(model.User).filter_by(date >= time.now()-86400*7) c.active_users = users
That concludes the 3 part series of Zero to 60 with Pylons
If you wish to see the full, bare bones project in action you can download here.
Code. Design. Explore. is the blog of John Brennan, a web developer/designer, entrepreneur, and avid world traveler. I currently live in San Diego, CA, USA.
My first passion is to create. I want to be part of a successful startup that will empower others. I believe in designing for the user and appreciate other web apps that design for usability.
My second passion is to help. My heart lies in philanthropy and helping others that are just as able, but haven't been afforded the same opportunities only because they were born at a different coordinate on this Earth.
This blog will mostly be around building cool things, although I will surely include my travel experiences when I am abroad. Feel free to subscribe to a specific category if that is only what interests you. And please connect with me. I always enjoy meeting new, interesting people!