Python Web Framework Series – Pylons: Part 3 Views with Mako
This is a huge post and I should have split this into several smaller ones so please bear with me while I get my series format tweaked. We last left off with Controllers, Views and Testing with a basic test, basic view and basic controller. Now with our basic scaffold built we can focus on making our views reusable, dynamic, and more pleasant to look at.
First things first
First before we dive too deeply into Mako, lets setup a default home page by doing the following:
- delete the index.html in the pylonsforumpublic folder.
- in pylonsforumconfigrouting.py add the following line somewhere under the line “# CUSTOM ROUTES HERE” but before “return map”.
- map.connect(“home”, “/”, controller=”home”, action=”index”)
- assuming you have your Pylons command we used earlier paster serve –-reload development.ini then open your browser to http://127.0.0.1:5000 and you should see the view we created in the last lesson.
###
Basics
Mako uses standard python mixed with HTML to display views. This gives you both a great deal of flexibility and a great deal of ability to hang yourself. So be warned, avoid putting to much logic into your view, since you can do about anything there.
Open up templatesindex.mako __replace the table rows in the table body with the following:
%for p in c.posts:
%endfor
</tbody> </div> </div> for good measure in the beginning of the body before the “recent posts” div place the following
username: ${c.username}
<div id=“recentposts” style=“float: right”> </div> </div> Looking at this, for the tables we’ve done a for loop over some variable called c.posts. In the second we’re accessing another variable c.username. “c” is shorthand for Template Context, similar to ViewData in Asp.Net MVC and PropertyBag in Monorail, except we’re not accessing a string dictionary. Add the following tests in “**pylonsforumtestsfunctionaltest_home.py**” for what we need to add to the controller:
response = self.app.get(url(controller=‘home’, action=‘index’))
assert “username: rsvihla” in response </p>
def test_recent_posts(self):
response = self.app.get(url(controller=‘home’, action=‘index’))
assert “ ” in response </div> </div>
jkruse New Kindle 06/24/2009
running nosetests –-with-pylons=test.init should give you two assertion failures.
now change “pylonsforumcontrollershome.py” to look like so:
from pylons import request, response, session, tmpl_context as c
from pylons.controllers.util import abort, redirect_to
from pylonsforum.lib.base import BaseController, render
log = logging.getLogger(__name__)
class Post(object):
def __init__(self, author, subject, dateadded):
self.author = author
self.subject = subject
self.dateadded = dateadded
class HomeController(BaseController):
def index(self):
c.username = “rsvihla”
c.posts = [Post(“jkruse”, “New Kindle”, “06/24/2009”)]
return render(‘index.mako’) </div> </div>
We’ve added a Post class with basic attributes and placed them in an array in the c.posts variable, also we’ve hardcoded the username “rsvihla”. I know the more experience developers here are cringing at my awful little Post class, don’t worry its a just a place holder and will be removed later. The point here is building functionality in steps with test coverage. Now run nosetests just as before and you should have all tests passing. For bonus measure refresh http://127.0.0.1:5000 .
Base Layouts
We’ve built a very basic page now, but suppose we want to build several and have some bits of information show up over and over again, like user name or menu structure.
create a base template named “base.mako” in the templates directory that has the following in it:
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
</b></span>
</b></span>
${self.head_tags()}
<style type=“text/css”>
#header{
height: 70px;
width: 100%;
border-width: .5px;
border-style: solid;
font-size:30px;
text-align: center;
background-color: #9B2C25;
}
#sidebar{
background-color: #C1AD72;
border-width: .5px;
border-style: dotted;
width: 150px;
height: 600px;
float: right;
font-size: 15px;
padding-top:10px;
padding-left:5px;
}
#content{
margin-top:10px;
}
body {
background-color: #DACEAB;
}
</style>
</head>
</b></span></p>
<div id=“header”>Pylons Forum</div>
<div id=“sidebar”>
username: ${user.get_current_user()}
</div>
<div id=“content” >
${self.body()}
</div>
</div>
</body>
</html> </div> </div>
Most everything is standard HTML so lets restrict this to the interesting bits:
The namespace directive here is basically doing an import of a custom module and giving it an alias of user. This is no different than in normal python code typing:
This is used later in the div sidebar by pulling the current user from my customer users module:
the module source only contains a simple hard coded value for now so in pylonsforummodel make a users.py file with only the following:
return “rsvihla”
Now onto the next non-HTML tidbits
${self.head_tags()}
${self.body()} </div> </div>
Everyone of the above methods establishes a base method that the child templates must now call with the exception of self.body (which is called when the templates actually render the content anyway). So lets adjust “index.mako” template to the following:
<%def name=“title()”>Pylons Forum</%def>
<%def name=“head_tags()”></%def>
<div id=“recentposts”>