Section 13.2. Beyond the Basics: Keeping Your Templates DRY (Don t Repeat Yourself)


13.2. Beyond the Basics: Keeping Your Templates DRY (Don't Repeat Yourself)

Most web applications have common headers, footers, sidebars, and other repeated page elements. Sometimes these are static, and sometimes they are dynamic. But unless your template system provides mechanisms for handling this kind of thing, you are going to end up with a lot of repeated code in various page templates. And repeated code makes future changes harder, turns finding all occurrences of a bug into a tedious and complex process, and can introduce subtle bugs.

You've already seen one of the mechanisms Kid provides for factoring similar template code out into named template functions. And if that's all there was, it would be enough to keep your code clean. But there is another more powerful, but also slightly more complicated function: py:match.

13.2.1. Transformations Using py:match

There are a number of advantages to the py:match approach, but perhaps the most important is that it takes only a single line of code per page to get headers, footers, sidebars, and other page elements inserted into your page.

You can use py:match to automatically add JavaScript or CSS references to all your pages, or even to add sidebars and other content to the body of all your pages. You can even use py:match to create custom tags like Java's taglibs.

Given all that power, it can take a while to work your mind around how py:match works. Conceptually it's simple: py:match searches through the rest of your template for matching elements and replaces them with the contents of the element to which the original py:match tag is connected.

For example, you could write something like this at the top of any page (for instance, the welcome page in a newly quickstarted project):

<h2 py:match="item.tag=='h2'">         This replaces your other tag's content. </h2>


This match template's match expression item.tag=='h2' will then be added to the list of match template filters. Every tag that is processed will be run through that match template filter. If one of your tags says <h2>some random test here</h2>, it will match the above tag and be replaced by the contents of the match tag. So, now rather than having a big heading on your page that says some random text here, you'll get a big heading that says This replaces your other tag's content. If you had a second <h2> tag, it too would be replaced with the contents of the mach tag, and you'd have another big py:match test:This replaces your other tag's content in your page.

Unfortunately, the preceding code won't work in a default TurboGears project because we declare a default XML namespace, which gets added to tag names before the match process happens. But simply appending the namespace to the tag name in the match expression solves that problem; and while we're at it, let's add a little something extra:

<h2 py:match="item.tag=='{http://www.w3.org/1999/xhtml}h2'">         This will be added before your tag's original text! ${item.text} </h2>


Notice that we put the namespace reference inside of curly braces right in front of the tag name. This will now match the tag, as it is represented in the welcome.kid file in a default quickstart. This is necessary because of the little xmlns="http://www.w3.org/1999/xhtml" at the top of the page that defines a default namespace.

But the real magic of py:match comes in the second line of our little sample template. ${item.text} returns the text node of the matching <h2> element. This means that our <h2>some random text here</h2> element will now be replaced with this new combined element:

<h2>This will be added before your tag's original text! some random text here</h2>


When there is a match, the item variable contains the entire contents of the matching element as an ElementTree (http://effbot.org/zone/element-index.htm) element object. Element objects are designed to store hierarchical data sets, and much of Kid is built on top of ElementTree. To make scanning and manipulating the original contents of the matching tag as easy as possible, Kid exposes the ElementTree application programming interface (API) directly via the item object, which you can use to mix the match template body with elements from the body of the matching element from the original template.

In the preceding code, we are using the text attribute of an element to return its text node as a string that we can drop into our template text. But far more complex manipulations are possible at this point. This means that our template can produce output that is a combination of the original content along with whatever new information is provided by the match template.

As mentioned previously, item is an ElementTree element. It is the ability to take elements from the original page and mix them with the template code you write in the body of your py:match template that makes the py:match replacement structure so powerful. The possibilities are endless, and in a second we'll take a look at how to use this to create your own custom tag libraries.

But before we move on, let's take a quick look at some of the key features of the structure of an element object. The text method contains the first text node in the object. (Remember, if you have a sentence with <b>some text</b> in it, you have more than one text node, before the subelement <b>, and the text that comes after it.) The item[] object is a Python list that contains the child elements of the matching element. And that brings us to a common idiom for putting the entire contents of the matching tag inside the match template:

py:replace="[item.text]+item[:]"


This snippet of code places the first text node and any other remaining child elements of the matching tag into the body of the match template. So, you get the original tag and all its elements plus whatever your match template adds.

This means that there is a potential bug in the py:match template we used in our first example.

It works fine in the test case we had there. But what if we add a link, or even add an <i> tag around one of the words? Only the text before the first child element would be displayed.

So, if our page contains a tag such as <h2> more <i>random</i> text</h2>, we would have gotten this element in our output:

<h2> This will be added before your tag's original text! more</h2>


Notice how the italic text and everything after it was just dropped from the output. They were hanging out in the item list, but that was never used.

13.2.2. Creating Custom Tags with py:match

You aren't limited to matching existing tag names. You can use py:match to create your own custom tag libraries. This means you can add py:match='item.tag == "sidebar"' which automatically adds a sidebar to your page whenever the page has a <sidebar> tag.

In fact, there's nothing new to learn about how to create custom tags. You just use py:match in the same way we did before, but now you match against your own newly created tag name. Then, whenever you want that match template added to your page, just use the tag.

Because match templates have access to the matching element, you can easily define custom tag attributes that are processed by the match template to give you the custom tag behaviors. Because they are so flexible, you can use custom tags to do all kinds of interesting things. If you are familiar with Struts Tag Library concept, you can implement the same kind of thing in Kidbut your tag library can be specific to your application, and suffer from none of the limitations of the Struts Tag Library.

Just to whet your appetite for custom tag creation, and give you an idea of what you can do with a custom tag, let's look at a little sample page with an <insult> tag designed to deliver a random Monty Python-inspired taunt to everyone who views the page.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ TR/xhtml1/DTD/xhtml1-transitional.dtd"> <?python from random import randrange def random_insult():     """Get select 1 of 5 random insults"""     insult_num = randrange(0,4)     insult_options=["one", "two", "three", "four", "five"]     return insult_options[insult_num] ?> <html xmlns:py="http://purl.org/kid/ns#"    py:extends="'master.kid'">   <b py:match="item.tag == 'insult'"         py:content="item.get(random_insult())"> Random insult will go here!   </b>   <head>     <title>Time of day demo</title>   </head>   <body>     <p> Thanks for visiting our little page.     <br /> Please leave now before we hurt you!     <br /> By the way,       <insult one="Your mother was a hamster!"               two="Your father smelt of elderberries!"                         three="Go and boil your bottoms, you son of a silly person."                         four="I fart in your general direction!"                         five="You look like my mother!" />     </p>   </body> </html>


  1. This Python function just returns a text string, which will be looked up in our match template to determine which insult to use.

  2. This is the match template. It looks up the attribute with matching the string returned by random_insult.

  3. The <insult> tag defines the five possible insults, but says nothing about how they should be displayed. That's all handled in the match template.

13.2.3. Creating Parent Templates with py:extends

So, we have named template functions and match templates. If only we had a way to store these things outside of our template and have them automatically imported into our template, everything would be well with the world. We would then be able to create py:match and py:def directives and store them in a central file so that they could be reused wherever they were needed.

Kid provides exactly that functionality with another processing directive: py:extends. The py:extends directive can only be used at the root of a template. You set it equal to the template you want to extend. Here's the code from welcome.kid that tells it to extend the master.kid template:

py:extends="'master.kid'"


This means that all the named template functions in master.kid are now automatically available in the welcome template (but there aren't any yetalthough you could easily add them). But more important, py:extends also imports match templates; any match expressions in master.kid will be checked against each tag in welcome.kid, and the appropriate match template substitutions will be made automatically.




Rapid Web Applications with TurboGears(c) Using Python to Create Ajax-Powered Sites
Rapid Web Applications with TurboGears: Using Python to Create Ajax-Powered Sites
ISBN: 0132433885
EAN: 2147483647
Year: 2006
Pages: 202

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net