C H A P T E R   6

image

Templates for the Weblog

Your weblog application is almost complete. Over the last two chapters, you've implemented entries, links, and nearly all the attendant functionality you wanted to have with them. There are only two features left to implement—a comment system and syndication feeds—and Django is going to give you quite a bit of help with those, as you'll see in the next chapter.

But so far, you've focused almost exclusively on the “back end” of the site—the Python code that models your data, retrieves it from the database, lays out your URL structure, and so on—at the expense of the “front end,” or the actual HTML you'll show to your site's visitors. You've seen how Django's generic views expose your database objects for use in templates (through the object_list variable in the date-based archives, for example). However, it's a big step from that to an attractive and usable weblog. Let's take a deeper look at Django's template system, and how you can use it to make the front end as easy as the back.

Dealing with Repetitive Elements: The Power of Inheritance

You're using Django's generic views to show both entries and links. Whether you're looking at the detail view of an Entry or of a Link, the actual Python code involved is the date-based object_detail generic view, which provides a variable named object to the template and represents the database object it retrieved. The biggest difference is that the generic view will use a template named coltrane/entry_detail.html for an Entry and one named coltrane/link_detail.html for a Link.

Because the contexts are so similar, the templates will end up looking very much alike; for example, a simple entry_detail template might look like the following:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>Weblog: {{ object.title }}</title>
</head>
<body>
<h1>{{ object.title }}</h1>
{{ object.body_html|safe }}
</body>
</html>

And a simple link_detail might look like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>Weblog: {{ object.title }}</title>
</head>
<body>
<h1>{{ object.title }}</h1>
{{ object.description_html|safe }}
<p><a href="{{ object.url }}">Visit site</a></p>
</body>
</html>

Of course, for a finished site you'd want to do quite a bit more, but already it's apparent that there's a lot of repetition. There's all sorts of HTML boilerplate, which is the same in both templates, and even things like the <title> element and the <h1> heading have the same contents. Typing all of that over and over again is going to be awfully tedious, especially as the HTML gets more complex. And if you ever make changes to the HTML structure of a site, you'll have to make them in every single template. Django's been great so far at helping you avoid this sort of tedious and repetitive work on the Python side of things, so naturally it would be nice if it could do the same on the HTML side as well.

And it can. Django's template system supports a concept of template inheritance, which works similarly to the way subclassing works in normal Python code. Essentially, the Django template system lets you write a template with placeholders (called blocks) for sections of a page. These blocks will vary from one template to the next. Then you'll write templates to “extend” that template and fill in the placeholders.

To see template inheritance in action, let's work through a simple example. Create a file in the root template directory for the project and name it base.html. Using this name isn't required, but it's a common practice and will help others understand the file's purpose. In that file, put the following code:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>Weblog: {{ object.title }}</title>
</head>
<body>
<h1>{{ object.title }}</h1>
{% block content %}
{% endblock %}
</body>
</html>

Now, edit the coltrane/entry_detail.html template so that it contains nothing but this:

{% extends "base.html" %}

{% block content %}
{{ object.body_html|safe }}
{% endblock %}

Next, edit coltrane/link_detail.html so that it contains nothing but this:

{% extends "base.html" %}

{% block content %}
{{ object.description_html|safe }}
<p><a href="{{ object.url }}">Visit site</a></p>
{% endblock %}

Finally, fire up the development server and visit a link or entry in the weblog, and then view the HTML source of the page. You'll see all the HTML boilerplate that's in base.html; note that the area containing an empty content block will be filled in by the appropriate results, according to whether you're looking at an entry or a link.

This is just a simple example. As your templates get more complex, the ability to factor out repetitive pieces like this is going to become a lifesaver. It'll cut down on both the time needed to put templates together and the time needed to change them later (because a change in a single “base” template will automatically show up in any templates that extend it).

How Template Inheritance Works

Template inheritance revolves around the two new tags seen in the previous example: {% block %} and {% extends %}. Essentially, the {% block %} tag lets you carve out a section of a template and give it a name, and possibly even some default content. The {% extends %} tag lets you specify the name of another template—which should contain one or more blocks—and then just fill in content for any blocks you want to use. The rest of the content, including default content from any blocks you didn't override, will automatically be filled in from the template you're extending. Additionally, within a block, you'll have access to the content that would have gone there if you weren't supplying your own. This content is stored in a special variable named block.super. So if you had a base template that contained this:

{% block title %}My weblog:{% endblock %}

you could write a template that extended it, and fill in your own content:

{% block title %}My page{% endblock %}

Using block.super, you could access the default content from the parent block to get a final value of My weblog: My page:

{% block title %}{{ block.super }} My page{% endblock %}

Limits of Template Inheritance

As you start to work with inheritance in templates, you'll want to keep a few caveats in mind:

  • If you use the {% extends %} tag, it must be the first thing in the template. Django needs to know up front that you're going to be extending another template.
  • Each named block, if used, can appear only once in a given template. Just as HTML permits you to have only a single element with a given ID inside a single page, Django's template system permits you to have only a single block with a given name inside a single template.
  • A template can directly extend only one other template—multiple uses of {% extends %} in the same template are invalid. However, the template being extended can, in turn, extend another template, leading to a chain of inheritance down through multiple templates.

This ability to “chain” inherited templates is key to a common pattern in template development. Often, a site will have multiple sections or areas that don't vary much from one another, so the templates end up forming a three-layered structure:

  1. A single base template containing the common HTML of all pages.
  2. Section-specific base templates that fill in appropriate navigation and/or theming. These extend the base template.
  3. The “actual” templates that will be loaded and rendered by the views. These extend the appropriate section-specific templates.

In fact, this pattern is so common and so useful that you're going to use it for your blog's templates. Let's get started.

Defining the Base Template for the Blog

Building up a useful base template for a site largely consists of determining what the site's overall look and feel will be, writing out the appropriate HTML to support it, and then determining which areas will need to vary from page to page and turning them into blocks.

For this blog, let's go with a common visual layout—a header at the top of the page with room for a site logo, and two columns below it. One column will contain the page's main content, and the other column will have a sidebar with navigation, metadata, and other useful information.

In HTML terms, this works out to three div elements: one for the header area, one for the content area, and one for the sidebar. The structure looks like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
  <title></title>
</head>
<body>
  <div id="header"></div>
  <div id="content"></div>
  <div id="sidebar"></div>
</body>
</html>

Note that I've gone ahead and filled in some HTML id attributes on these div tags so that it'll be easy to set up the layout with cascading style sheets (CSS).

Now, one thing that jumps out is the fact that the title element is empty. This is definitely something that will vary, according to which part of the site you're in and what you're looking at, so let's go ahead and put a block there:

<title>My weblog {% block title %}{% endblock %}</title>

When you extend this template, you'll add more things here. The final effect will be to get a title like My weblog | Entries | February 2008, as you'll see in a moment.

Now let's fill in the header. It probably won't change a lot, so you don't need a block here:

<div id="header">
  <h1 id="branding">My weblog</h1>
</div>

Again, I've added an id attribute so you can easily use CSS to style the header later. For example, you could use an image-replacement technique to replace the text of the h1 with a logo.

Because the main content will vary quite a bit, you'll make it a block:

<div id="content">
  {% block content %}
  {% endblock %}
</div>

All that's left is the sidebar. The first thing you'll need there is a list of links to different weblog features so that visitors can easily navigate around the site. You can do that easily enough (again, using id attributes makes it easy to come back later and style the sidebar):

<div id="sidebar">
  <h2>Navigation</h2>
  <ul id="main-nav">
    <li id="main-nav-entries">
      <a href="/weblog/">Entries</a></li>
    <li id="main-nav-links">
      <a href=""/weblog/links/">Links</a></li>
    <li id="main-nav-categories">
     <a href="/weblog/categories/">Categories</a></li>
    <li id="main-nav-tags"><a href="/weblog/tags/">Tags</a></li>
  </ul>
</div>

But one thing stands out: you have hard-coded URLs here. They match what you've set up in your URLConf module. But after you went to all the trouble to modularize and decouple the URLs on the Python side, it would be a shame to just turn around and hard-code them into your templates.

A better solution is to use the {% url %} template tag, which—like the permalink decorator you used on the get_absolute_url() methods of your models—can perform a reverse lookup in your URLConf to determine the appropriate URL. This tag offers quite a few options, but the one you care about right now is pretty simple: you can feed it the name of a URL pattern, and it will output the correct URL.

Using the {% url %} tag, you can rewrite your navigation list like this:

<ul id="main-nav">
  <li id="main-nav-entries">
    <a href="{% url coltrane_entry_archive_index %}">Entries</a>
  </li>
  <li id="main-nav-links">
    <a href="{% url coltrane_link_archive_index %}">Links</a>
  </li>
  <li id="main-nav-categories">
    <a href="{% url coltrane_category_list %}">Categories</a>
  </li>
  <li id="main-nav-tags">
    <a href="{% url coltrane_tag_list %}">Tags</a>
  </li>
</ul>

Now you won't have to make changes to your templates if you decide to shuffle some URLs around later.

While you're dealing with the navigation, let's add a block inside the body tag:

<body class="{% block bodyclass %}{% endblock %}">

A common technique in CSS-based web design is to use a class attribute on the body tag to trigger changes to a page's style. For example, you'll have a list of navigation options in the sidebar, representing different parts of the blog—entries, links, and so forth—and it would be nice to highlight the part a visitor is currently looking at. By changing the class of the body tag in different parts of the site, you can easily use CSS to highlight the correct item in the navigation list.

For the rest of the sidebar's content, you might want to have a little explanation of what a visitor is looking at, such as “An entry in my blog, published on February 7, 2008” or “A list of entries in the category 'Django.'” You can add a block for that as well:

<h2>What is this?</h2>
{% block whatis %}
{% endblock %}

You're done with the base template—for now. (You'll add a few things to it later on.) Here's what it looks like:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
  <title>My weblog {% block title %}{% endblock %}</title>
</head>
<body class="{% block bodyclass %}{% endblock %}">
  <div id="header">
    <h1 id="branding">My weblog</h1>
  </div>
  <div id="content">
    {% block content %}
    {% endblock %}
  </div>
  <div id="sidebar">
    <h2>Navigation</h2>
    <ul id="main-nav">
      <li id="main-nav-entries">
        <a href="{% url coltrane_entry_archive_index %}">Entries</a>
      </li>
      <li id="main-nav-links">
        <a href="{% url coltrane_link_archive_index %}">Links</a>
      </li>
      <li id="main-nav-categories">
        <a href="{% url coltrane_category_list %}">Categories</a>
      </li>
      <li id="main-nav-tags">
        <a href="{% url coltrane_tag_list %}">Tags</a>
      </li>
    </ul>
    <h2>What is this?</h2>
    {% block whatis %}
    {% endblock %}
  </div>
</body>
</html>

Setting Up Section Templates

Now let's set up some templates that will handle the different main areas of the blog. You'll want one each for entries, links, tags, and categories. You'll call the template for entries base_entries.html, and all you really need to do is extend the base template and fill in a couple of blocks:

{% extends "base.html" %}

{% block title %}| Entries{% endblock %}

{% block bodyclass %}entries{% endblock %}

If you were to use this template all by itself, you'd get the output from base.html, but with two changes:

  • The title tag's contents would be My weblog | Entries.
  • The body tag's class attribute would have a value of entries, which means it would be easy to highlight the Entries item in the navigation sidebar.

The rest of the section templates are pretty easy to fill in. For example, you can write a base_links.html like this:

{% extends "base.html" %}

{% block title %}| Links{% endblock %}

{% block bodyclass %}links{% endblock %}

You'll also need a base_tags.html template and a base_categories.html template; you can fill them in using the pattern I just described. These templates are slightly repetitive, and probably always will be, but the use of template inheritance means you've boiled down the repetitive bits to a bare minimum—you're specifying only the things that change, not the things that stay the same.

Displaying Archives of Entries

For displaying entries, you need five templates:

  • The main (home) page showing the latest entries
  • A yearly archive
  • A monthly archive
  • A daily archive
  • An individual entry

These correspond directly to the generic views you're using.

Entry Index

Let's start with the main index of entries. You'll recall that the generic view will look for the template coltrane/entry_archive.html and will provide a variable named latest containing a list of the latest entries. So you can fill in the template coltrane/entry_archive.html as follows (remembering to extend base_entries.html instead of base.html):

{% extends "base_entries.html" %}

{% block title %}{{ block.super }} | Latest entries{% endblock %}

{% block content %}
{% for entry in latest %}
  <h2>{{ entry.title }}</h2>
  <p>Published on {{ entry.pub_date|date:"F j, Y" }}</p>
  {% if entry.excerpt_html %}
    {{ entry.excerpt_html|safe }}
  {% else %}
    {{ entry.body_html|truncatewords_html:"50"|safe }}
  {% endif %}
  <p><a href="{{ entry.get_absolute_url }}">Read full entry. . .</a></p>
{% endfor %}
{% endblock %}

{% block whatis %}
<p>This is a list of the latest {{ latest.count }} entries published in
 my blog.</p>
{% endblock %}

Most of this should be pretty familiar. You're using the {% for %} tag to loop over the entries and display each one. And in the sidebar, you just have a short paragraph describing what's being shown on this page. The code relies on the count() method of a Django QuerySet to find out how many entries were passed to the template in the latest variable.

There are a couple of new things here worth noting, though:

  • The use of the date filter to format each entry's pub_date: This filter accepts a formatting string, similar to the strftime() method you've already seen, and outputs the date accordingly. In this case, the date will print out in the form February 6, 2008.
  • The use of the truncatewords_html filter: This filter takes a number as its argument and outputs that number of words from the variable it's applied to, adding an ellipsis (…) at the end. This is useful for generating a short excerpt when the entry doesn't have its excerpt field filled in.

Yearly Archive

The generic view that generates the yearly archive will provide two variables:

  • year: The year being displayed.
  • date_list: A list of Python datetime objects representing months in that year that have entries.

This generic view is going to look for the template coltrane/entry_archive_year.html, which you can fill in as follows:

{% extends "base_entries.html" %}

{% block title %}{{ block.super }} | {{ year }}{% endblock %}

{% block content %}
<ul>
  {% for month in date_list %}
    <li>
      <a href="/weblog/entries/{{ year }}/{{ month|date:"b" }}/">{{ month|image
date:"F" }}</a>
        </li>
       {% endfor %}
     </ul>
     {% endblock %}

     {% block whatis %}
     <p>This is a list of months in {{ year }} in which I published entries in
      my blog.</p>
     {% endblock %}

Here you're looping over the date_list and, for each month, showing a link to the archive for that month.

But there's a problem here: you can build up the URLs by using Django's built-in date filter, but once again you're hard-coding a URL. Previously, you got around that by using the {% url %} tag with the name of a URL pattern. You can do that again, but this time you'll need to supply some extra data: the year and month needed to generate the correct URL for a monthly archive. All you have to do is pass the {% url %} tag a second argument containing a comma-separated list of the values it needs, and you can even use filters to make sure the URLs are correctly formatted:

<li><a href="{% url coltrane_entry_archive_month year,month|date:"b" %}">
  {{ month|date:"F" }}
</a></li>

With the current URL setup, this HTML will correctly output URLs like /weblog/2008/jan/, /weblog/2008/feb/, and so on.

Monthly and Daily Archives

The generic views that generate the monthly and daily archives are extremely similar. Both will provide a list of entries in a variable named object_list, and the only real difference is that one will have a variable called month (representing the month for a monthly archive) and the other will have a variable called day (representing the day for a daily archive).

Here's the monthly-archive template, which will be coltrane/entry_archive_month.html:

{% extends "base_entries.html" %}

{% block title %}
{{ block.super }} | Entries in {{ month|date:"F, Y" }}
{% endblock %}
{% block content %}
{% for entry in object_list %}
  <h2>{{ entry.title }}</h2>
  <p>Published on {{ entry.pub_date|date:"F j, Y" }}</p>
  {% if entry.excerpt_html %}
    {{ entry.excerpt_html|safe }}
  {% else %}
  {{ entry.body_html|truncatewords_html:"50"|safe }}
  {% endif %}
  <p><a href="{{ entry.get_absolute_url }}">Read full entry. . .</a></p>
{% endfor %}
{% endblock %}

{% block whatis %}
<p>This is a list of entries published in my blog in
 {{ month|date:"F, Y" }}.</p>
{% endblock %}

Except for a couple of changes to variable names and the use of the date filter to format the month (it will print in the form February, 2008), this isn't too different from what you've already seen. The daily-archive template (coltrane/entry_archive_day.html) will be almost identical except for the use of the variable day and the appropriate formatting, so go ahead and fill that in. (You can find a full list of available date-formatting options in the Django template documentation online at www.djangoproject.com/documentation/templates/.)

Entry Detail

The generic view that shows a single entry uses the template coltrane/entry_detail.html and provides one variable, object, which will be the entry. The first part of this template is easy:

{% extends "base_entries.html" %}

{% block title %}{{ block.super }} | {{ object.title }}{% endblock %}

{% block content %}
<h2>{{ object.title }}</h2>
{{ object.body_html|safe }}
{% endblock %}

The sidebar is a bit trickier. You can start out by showing the entry's pub_date:

{% block whatis %}
<p>This is an entry posted to my blog on
 {{ object.pub_date|date:"F j, Y" }}.</p>

Now, it would be nice to show the categories by displaying text such as, “This entry is part of the categories 'Django' and 'Python.'” But there are several things to take into account here:

  • For an entry with one category, you want to say “part of the category.” But for an entry with more than one category, you need to say “part of the categories.” And for an entry with no categories, you need to say, “This entry isn't part of any categories.”
  • For an entry with more than two categories, you'll need commas between category names and the word “and” before the final category. But for an entry with two categories, you don't need the commas, and for an entry with only one category, you don't need commas or the “and.”

If there aren't any categories for an entry, {{ object.categories.count }} will be 0, which is False inside an {% if %} tag, so you can start with a test for that:

{% if object.categories.count %}
. . .you'll fill this in momentarily. . .
{% else %}
<p>This entry isn't part of any categories.</p>
{% endif %}

Now you need to handle the difference between “category” and “categories.” Because this is a common problem, Django includes a filter called pluralize that can take care of it. The pluralize filter, by default, outputs nothing if applied to a variable that evaluates to the number 1, but outputs an “s” otherwise. It also accepts an argument that lets you specify other text to output. In this case, you want a “y” for the singular case and “ies” for the plural, so you can write this:

{% if object.categories.count %}
<p>This entry is part of the
 categor{{ object.categories.count|pluralize:"y,ies" }}

You'll get “category” when there's only one category and “categories” otherwise.

Finally, you need to loop over the categories. One option would be to join the list of categories using commas. In Python code, you'd write this:

', '.join(object.categories.all())

And Django's template system provides a join filter, which works the same way:

{{ object.categories.all|join:", " }}

But you want to have the word “and” inserted before the final category in the list, and join can't do that. The solution is to use the {% for %} tag and to take advantage of some useful variables it makes available. Within the {% for %} loop, the following variables will automatically be available:

  • forloop.counter: The current iteration of the loop, counting from 1. The fourth time through the loop, for example, this will be the number 4.
  • forloop.counter0: Same as forloop.counter, but starts counting at 0 instead of 1. The fourth time through the loop, for example, this will be the number 3.
  • forloop.revcounter: The number of iterations left until the end of the loop, counting down to 1. When there are four iterations left, for example, this will be the number 4.
  • forloop.revcounter0: Same as forloop.revcounter, but counts down to 0 instead of 1.
  • forloop.first: A boolean value—it will be True the first time through the loop and False the rest of the time.
  • forloop.last: Another boolean—this one is True the last time through the loop and False the rest of the time.

Using these variables, you can work out the proper presentation. Expressed in English, the logic works like this:

  1. Display a link to the category.
  2. If this is the last time through the loop, don't display anything else.
  3. If this is the next-to-last time through the loop, display the word “and.”
  4. Otherwise, display a comma.

And here it is in template code:

{% for category in object.categories.all %}
  <a href="{{ category.get_absolute_url }}">{{ category.title }}</a>
  {% if forloop.last %}{% else %}
  {% ifequal forloop.revcounter0 1 %}and {% else %}, {% endifequal %}
  {% endif %}
{% endfor %}

There are two important bits here:

  • {% if forloop.last %}{% else %}: This does absolutely nothing if you're in the last trip through the loop.
  • {% ifequal forloop.revcounter0 1 %}: This determines whether you're in the next-to-last trip through the loop in order to print the “and” before the final category.

Here's the full sidebar block so far:

{% block whatis %}
<p>This is an entry posted to my blog on
 {{ object.pub_date|date:"F j, Y" }}.</p>

{% if object.categories.count %}
  <p>This entry is part of the
  categor{{ object.categories.count|pluralize:"y,ies" }}
  {% for category in object.categories.all %}
    <a href="{{ category.get_absolute_url }}">{{ category.title }}</a>
    {% if forloop.last %}{% else %}
    {% ifequal forloop.revcounter0 1 %}and {% else %}, {% endifequal %}
    {% endif %}
  {% endfor %}
  </p>
{% else %}
  <p>This entry isn't part of any categories.</p>
{% endif %}
{% endblock %}

Handling tags will work much the same way. {{ object.tags }} will return the tags for the Entry, and a similar bit of template code can handle them. And with that, you have a pretty good entry-detail template:

{% extends "base_entries.html" %}

{% block title %}{{ block.super }} | {{ object.title }}{% endblock %}

{% block content %}
<h2>{{ object.title }}</h2>
{{ object.body_html }}
{% endblock %}

{% block whatis %}
<p>This is an entry posted to my blog on
 {{ object.pub_date|date:"F j, Y" }}.</p>

{% if object.categories.count %}
  <p>This entry is part of the
  categor{{ object.categories.count|pluralize:"y,ies" }}
  {% for category in object.categories.all %}
    <a href="{{ category.get_absolute_url }}">{{ category.title }}</a>
    {% if forloop.last %}{% else %}
    {% ifequal forloop.revcounter0 1 %}and {% else %}, {% endifequal %}
    {% endif %}
  {% endfor %}
  </p>
{% else %}
  <p>This entry isn't part of any categories.</p>
{% endif %}
{% endblock %}

Defining Templates for Other Types of Content

The templates for displaying links in the blog aren't much different from the templates that display the blog entries. They'll extend base_links.html instead of base_entries.html, of course, but the variable names available in the various templates will be the same. The only difference is that the link templates will have access to Link objects, so they should display the links based on the fields you've defined on the Link model. Here's an example of what coltrane/link_detail.html might look like:

{% extends "base_links.html" %}

{% block title %}{{ block.super }} | {{ object.title }}{% endblock %}

{% block content %}
<h2>{{ object.title }}</h2>
{{ object.description_html }}
<p><a href="{{ object.url }}">Visit site</a></p>
{% endblock %}

{% block whatis %}
<p>This is a link posted to my blog on {{ object.pub_date|date:"F j, Y" }}.</p>

{% if object.tags.count %}
  <p>This link is tagged with
  {% for tag in object.categories.all %}
    <a href="{{ tag.get_absolute_url }}">{{ tag.title }}</a>
    {% if forloop.last %}{% else %}
    {% ifequal forloop.revcounter0 1 %}and {% else %}, {% endifequal %}
    {% endif %}
  {% endfor %}
  </p>
{% else %}
  <p>This link doesn't have any tags.</p>
{% endif %}
{% endblock %}

Note that because links have tags instead of categories, this template just loops through the tags the same way coltrane/entry_detail.html loops through categories.

Similarly, the category and tag templates are easy to set up at this point. They just need to extend the correct template for the part of the site they represent and use the correct fields from the Category and Tag models, respectively (though remember that the detail view of categories and tags will actually return lists of Entry or Link objects for a particular Category or Tag). You can find full examples in the book's sample code, available from the Apress web site.

Extending the Template System with Custom Tags

Right now, the only thing in your blog's sidebar will be the list of navigation links and the short “What is this?” blurb for each page. While this is simple and usable, it would be nice to emulate what a lot of popular prebuilt blogging packages do—display a list of recent entries and recent links farther down in the sidebar so that visitors can quickly find fresh content.

But that poses a dilemma: it seems like you'd need to go back and rewrite every one of your views to also query for, say, the latest five entries and the latest five links, and then make them available to the template. That would be awfully cumbersome and repetitive, and it would get even worse if you ever wanted to change the number of recent items displayed or add new types of content to your blog. Once again, it feels like Django should provide some easy way to handle this without lots of repetitive code.

And it does. In fact, Django provides two easy ways to do this. One is a mechanism for writing a function—called a context processor—that can add extra variables to any template's context. The other way is to extend Django's template system to add the ability to fetch recent content using a custom template tag. Using this approach, you could simply use the appropriate tag in the base.html template, and all the other templates would have that automatically, courtesy of template inheritance.

For this situation, let's go ahead and use a custom template tag to get a feel for how you can extend Django's template system when you need to add new features to it.

How a Django Template Works

Before you can dive into writing your own custom extensions to Django's template system, you need to understand the actual mechanism behind it. Knowing how things work “under the hood” makes the process of writing custom template functionality much simpler.

The process Django goes through when loading a template works—roughly—like this:

  1. Read the actual template contents: Most often this means reading out of a template file on disk, but that's not always the case. Django can work with anything that hands over a string containing the contents you want it to treat as a template.
  2. Parse through the template, looking for tags and variables: Each tag in the template, including all of Django's built-in tags, will correspond to a particular Python function defined somewhere (inside django/template/defaulttags.py in the case of the built-in tags). You'll see in a moment how to tell Django that a particular tag maps to a particular function. Typically this function is referred to as the tag's compilation function because it's called while Django is compiling a list of the eventual template contents.
  3. For each tag, call the appropriate function, passing in two arguments: One argument is the parsing class that is reading the template (useful for doing tricky things with the way the template gets processed), and the other is a string containing the contents of the tag. So, for example, the tag {% if foo %} results in Django passing a function (called do_if(), in Django's default tag library) an instance of the parsing class and an object that holds the tag contents “if foo.”
  4. Make a note of the return value of the Python function called for each tag: Each function is required to return an instance of a special class—django.template.Node—or a subclass of it, and choosing an appropriate Node subclass based on the particular tag.

The result is an instance of the class django.template.Template, which contains a list of Node instances (or instances of Node subclasses). This is the actual “thing” that will be rendered to produce the output. Each Node is required to have a method named render(), which accepts a copy of the current template context (the dictionary of variables available to the template) and returns a string. The output of the template comes from concatenating those strings together.

A Simple Custom Tag

Extending Django's template system with a custom template tag can be a bit tricky at first, so let's start simply. You'll write a tag that fetches the latest five entries and puts them into a template variable named latest_entries.

To start, you'll need to create a place for this tag's code to live. In the coltrane application directory, add a new directory called templatetags. In that, create two empty files: __init__.py (remember, this is necessary to tell Python that a directory contains loadable Python code) and coltrane_tags.py, which will be the file where your library of custom template tags lives. Next, inside coltrane_tags.py, add a couple of import statements at the top:

from django import template
from coltrane.models import Entry

Writing the Compilation Function

The custom tag is going to be called get_latest_entries—so that in templates you'll eventually be able to do {% get_latest_entries %}—but you can name its compilation function (and its Node class) anything you'd like. It's generally a good idea to give the function a meaningful name for the tag it goes with, though, so call it do_latest_entries():

def do_latest_entries(parser, token):

The two arguments to this function are the template parser and a token. (You won't be using the template parser now, but in Chapter 10 you'll write a tag that uses it to implement more advanced features.) token is an object representing part of the template that's being parsed. You also won't need that just yet, but later in this chapter when you expand this tag's functionality, you'll use it to work out the arguments passed to the tag from the template.

The only thing this function is required to do is return an instance of django.template.Node, or a subclass of Node. You'll define the Node for this tag in a moment, but it's going to be called LatestEntriesNode, so go ahead and fill that in:

def do_latest_entries(parser, token):
    return LatestEntriesNode()

Writing the Node

Next, you need to write the LatestEntriesNode class. This must be a subclass of django.template.Node, and it must have a method named render(). Django places two requirements on this method:

  • It must accept a template context—the dictionary of variables available to the template—as an argument.
  • It must return a string, even if the string doesn't contain anything. For a tag that produces its output directly, the returned string is the mechanism by which the output gets into the final template output.

So you can start writing your Node as follows:

class LatestEntriesNode(template.Node):
    def render(self, context):

This tag will simply fetch the five latest entries and add them to the context as the variable latest_entries, so it doesn't have any direct output. All it does is add the new item to the context dictionary, then return an empty string:

class LatestEntriesNode(template.Node):
    def render(self, context):
        context['latest_entries'] = Entry.live.all()[:5]
        return ''

Note that even when a tag doesn't directly output anything, the render() method of its Node must return a string.

Registering the New Tag

Finally, you need to tell Django that the compilation function should be used when the {% get_latest_entries %} tag is encountered in a template. To do this, you create a new library of template tags and register your function with it, like this:

register = template.Library()
register.tag('get_latest_entries', do_latest_entries)

The syntax for this is simple. Once you create a new Library, you just call its tag() method and pass in the name you want to give your tag and the function that will handle it.

Here's what the full coltrane_tags.py file looks like now:

from django import template
from coltrane.models import Entry

def do_latest_entries(parser, token):
    return LatestEntriesNode()

class LatestEntriesNode(template.Node):
    def render(self, context):
        context['latest_entries'] = Entry.live.all()[:5]
        return ''

register = template.Library()
register.tag('get_latest_entries', do_latest_entries)

Using the New Tag

Now your new tag is ready for use. Open up the base.html template and go to the sidebar portion of it, which still looks like this:

<div id="sidebar">
  <h2>Navigation</h2>
  <ul id="main-nav">
    <li id="main-nav-entries">
      <a href="{% url coltrane_entry_archive_index %}">Entries</a>
    </li>
<li id="main-nav-links">
      <a href="{% url coltrane_link_archive_index %}">Links</a>
    </li>
    <li id="main-nav-categories">
      <a href="{% url coltrane_category_list %}">Categories</a>
    </li>
    <li id="main-nav-tags">
      <a href="{% url coltrane_tag_list %}">Tags</a>
    </li>
  </ul>
  <h2>What is this?</h2>
  {% block whatis %}
  {% endblock %}
</div>

Now add the list of latest entries just below the "What is this?" block:

{% load coltrane_tags %}
<h2>Latest entries in the weblog</h2>
<ul>
  {% get_latest_entries %}
  {% for entry in latest_entries %}
  <li>
    <a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a>,
    posted {{ entry.pub_date|timesince }} ago.
  </li>
  {% endfor %}
</ul>

Here's what's going on:

  • The {% load coltrane_tags %} tag tells Django you want to load a custom template tag library named coltrane_tags. When Django sees this, it will look through all of your installed applications for a templatetags directory containing a file named coltrane_tags.py, and it will load any tags defined there.
  • Once your tag library has been loaded, the {% get_latest_entries %} tag can be called. This tag creates the new template variable, latest_entries, containing the five latest entries.
  • Then you just loop through latest_entries using the {% for %} tag, displaying a link to each and showing when it was posted. Here you're using a new filter called timesince. Built into Django, this filter formats a date and time according to how long ago something occurred. The result (with the word “ago” added afterward) will be something like “3 days, 10 hours ago,” and will give a visitor an idea of how recently the blog has been updated.

Writing a More Flexible Tag with Arguments

Now, you also want to show the latest links posted in the blog. You could do this by writing a new {% get_latest_links %} tag and having it add a latest_links variable to the template context. However, that's the start of a long and tedious path of writing a new tag every time you add a new type of content to your site, so it would be better to turn your existing {% get_latest_entries %} tag into a slightly more generic {% get_latest_content %} tag, which can fetch any of several types of content.

And while you're at it, it would be nice to add a bit more flexibility to the tag by letting it take arguments to specify how many items to retrieve, as well as the name of the variable to put them in. That way, you could have several lists of recent content that don't trample all over one another's variables. What you're going to end up with is a tag that works like this:

{% get_latest_content coltrane.link 5 as latest_links %}

which will, as the syntax indicates, fetch the five most recently published Link objects in the coltrane application and place them in a template variable named latest_links.

Writing the Compilation Function for the New Tag

You can start out the same way you did with the first version of the custom tag. That is, define a compilation function for your new tag:

def do_latest_content(parser, token):

But now you'll need to read some arguments. The full contents, residing in token.contents, will be a string of the form get_latest_content coltrane.link 5 as latest_links. So you can use Python's built-in string-splitting function, which defaults to splitting on spaces, to turn the string into a list of arguments:

def do_latest_content(parser, token):
    bits = token.contents.split()

Or you can use split_contents, a method of the token object that knows how to split the arguments. This method works much like Python's split() method, but it knows how to take a few special cases into account:

def do_latest_content(parser, token):
    bits = token.split_contents()

Now the variable bits should contain a list that looks like ["get_latest_content", "coltrane.link", "5", "as", "latest_links"]. Because that's five arguments in all, you can check the length of bits and raise a template syntax error if you don't find the right number of arguments:

def do_latest_content(parser, token):
    bits = token.split_contents()
    if len(bits) != 5:
        raise template.TemplateSyntaxError("'get_latest_content'image
tag takes exactly four arguments")

This ensures that you never try to render a malformed use of the tag. Note that the syntax error says “four arguments,” not “five arguments.” Although bits has five items in it, the first item is the name the tag was called with, not an argument. (Sometimes it's useful to write a single compilation function and register it multiple times under different names, allowing it to represent a family of similar tags and tell them apart by the tag name it receives.)

Next you want to return a Node. It will be called LatestContentNode, and you'll need to pass some information to it: the model to retrieve content from, the number of items to retrieve, and the variable name to store the results in. When you write LatestContentNode in a moment, you'll set up its constructor to accept this information:

def do_latest_content(parser, token):
    bits = token.split_contents()
    if len(bits) != 5:
        raise template.TemplateSyntaxError("'get_latest_content'image
tag takes exactly four arguments")
    return LatestContentNode(bits[1], bits[2], bits[4])

Note that because Python lists have indexes starting at 0, the model name—although it's the second item in bits—is bits[1], the number of items is bits[2], and so on.

Now you need to determine the model to retrieve content from. In the original {% get_latest_entries %} tag, you simply imported the Entry model and referenced it directly. Your new tag, however, is going to get an argument like coltrane.link or coltrane.entry, so you will need to import the correct model class dynamically.

Python provides a way to do this through a special built-in function named __import__(), which takes strings as arguments. But loading a model class dynamically is a common enough need that Django provides a helper function to handle it more concisely. This function is django.db.models.get_model(), and it takes two arguments:

  • The name of the application the model is defined in, as a string
  • The name of the model class, as a string

It's customary to make both of these strings entirely lowercase because Django maintains a registry of installed models with the names normalized to lowercase. If you want to, you can pass mixed-case names to get_model(), but because they'll just be lowercased anyway, it's often easier to start with them that way.

To see how get_model() works, go to your project directory and run the command python manage.py shell. This will start a Python interpreter. In it, type the following:

>>> from django.db.models import get_model
>>> entry_model = get_model('coltrane', 'entry')

The get_model() function will retrieve the Entry model from the coltrane application and assign it to the variable entry_model. From there, you can query against the same way you would if you'd imported it normally. To verify this, type the following into the interpreter:

>>> entry_model.live.all()[:5]

You'll see that this returns the latest five live entries.

Let's go ahead and change the do_latest_content compilation function to use the get_model() function and retrieve the model class. One obvious way to do this would be as follows:

from django.db.models import get_model

def do_latest_content(parser, token):
    bits = token.split_contents()
    if len(bits) != 5:
        raise template.TemplateSyntaxError("'get_latest_content'image
tag takes exactly four arguments")
    model_args = bits[1].split('.')
    model = get_model(model_args[0], model_args[1])
    return LatestContentNode(model, bits[2], bits[4])

This code has a couple of problems, though:

  • If the first argument isn't an application name/model name pair separated by a dot (.), or if it has too few or too many parts, this code might retrieve the wrong model or no model at all.
  • If the arguments you pass to get_model() don't actually correspond to any model class, get_model() will return the value None, and that will trip up the LatestContentNode when it tries to retrieve the content.

So you need a little bit of error checking. You want to verify the following:

  • When split on the dot (.) character, the first argument becomes a list of exactly two items.
  • These items, when passed to get_model(), do indeed return a model class.

You can do that in only a few lines of code:

   model_args = bits[1].split('.')
   if len(model_args) != 2:
       raise template.TemplateSyntaxError("First argument toimage
'get_latest_content' must be an 'application name'.'model name' string")
   model = get_model(*model_args)
   if model is None:
       raise template.TemplateSyntaxError("'get_latest_content'image
tag got an invalid model: %s" % bits[1])

If you're wondering about this line:

model = get_model(*model_args)

remember that the asterisk (*) is special Python syntax for taking a list (the result of calling split()) and turning in a set of arguments to a function. Here's the finished compilation function:

def do_latest_content(parser, token):
        bits = token.split_contents()
        if len(bits) != 5:
            raise template.TemplateSyntaxError("'get_latest_content'image
tag takes exactly four arguments")
       model_args = bits[1].split('.')
       if len(model_args) != 2:
           raise template.TemplateSyntaxError("First argument toimage
'get_latest_content' must be an 'application name'.'model name' string")
       model = get_model(*model_args)
       if model is None:
           raise template.TemplateSyntaxError("'get_latest_content'image
tag got an invalid model: %s" % bits[1])
       return LatestContentNode(model, bits[2], bits[4])

Writing the LatestContentNode

You already know that LatestContentNode needs to accept three arguments in its constructor:

  • The model to retrieve items from
  • The number of items to retrieve
  • The name of a variable to store the items in

So you can start by writing its constructor (remember that a Python object's constructor is always called __init__()) and simply storing those arguments as instance variables:

class LatestContentNode(template.Node):
    def __init__(self, model, num, varname):
        self.model = model
        self.num = int(num)
        self.varname = varname

Notice that you force num to be an int here. All the arguments to the tag came in as strings, so before you can use num to control the number of items to retrieve, you need to convert it to an actual number. Here's a simple way you could write the render() method to accomplish that:

def render(self, context):
    context[self.varname] = self.model.objects.all()[:self.num]
    return ''

At first, this looks fine, but it's got a hidden problem. When you call the template tag like this:

{% get_latest_content coltrane.entry 5 as latest_entries %}

the query it performs will be the equivalent of

Entry.objects.all()[:5]

which isn't what you want. This will return any entry, including entries that aren't meant to be publicly displayed. Instead, you want it to do the equivalent of the following:

Entry.live.all()[:5]

You could write special-case code to see when you're working with the Entry model, but that's not good practice. If you later need to use this tag on other models with similar needs, you'll have to keep adding new pieces of special-case code.

The solution is to ask Django to use the model's default manager. The first manager defined in a model class is given special status. It becomes the default manager for that model, in addition to the name it was defined with. It will also be available as the attribute _default_manager, so you can actually write this as:

def render(self, context):
    context[self.varname] = self.model._default_manager.all()[:self.num]
    return ''

Because the live manager was defined first in the Entry model, this will do the right thing.

Registering and Using the New Tag

Now you can simply register your new tag, and it's ready to go. The final coltrane_tags.py file looks like this:

   from django.db.models import get_model
   from django import template

   def do_latest_content(parser, token):
       bits = token.split_contents()
       if len(bits) != 5:
           raise template.TemplateSyntaxError("'get_latest_content'image
tag takes exactly four arguments")
       model_args = bits[1].split('.')
       if len(model_args) != 2:
           raise template.TemplateSyntaxError("First argument toimage
'get_latest_content' must be an 'application name'.'model name' string")
       model = get_model(*model_args)
       if model is None:
           raise template.TemplateSyntaxError("'get_latest_content'image
tag got an invalid model: %s" % bits[1])
       return LatestContentNode(model, bits[2], bits[4])

   class LatestContentNode(template.Node):
       def __init__(self, model, num, varname):
           self.model = model
           self.num = int(num)
           self.varname = varname

       def render(self, context):
           context[self.varname] = self.model._default_manager.all()[:self.num]
           return ''

   register = template.Library()
   register.tag('get_latest_content', do_latest_content)

So you can rewrite the sidebar in the base.html template, like this:

<div id="sidebar">
      <h2>Navigation</h2>
      <ul id="main-nav">
        <li id="main-nav-entries">
          <a href="{% url coltrane_entry_archive_index %}">Entries</a>
        </li>
        <li id="main-nav-links">
          <a href="{% url coltrane_link_archive_index %}">Links</a>
        </li>
        <li id="main-nav-categories">
          <a href="{% url coltrane_category_list %}">Categories</a>
        </li>
        <li id="main-nav-tags">
          <a href="{% url coltrane_tag_list %}">Tags</a>
        </li>
      </ul>
      <h2>What is this?</h2>
      {% block whatis %}
      {% endblock %}
      {% load coltrane_tags %}
      <h2>Latest entries in the weblog</h2>
      <ul>
        {% get_latest_content coltrane.entry 5 as latest_entries %}
        {% for entry in latest_entries %}
        <li>
          <a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a>,
          posted {{ entry.pub_date|timesince }} ago.
        </li>
        {% endfor %}
      </ul>
      <h2>Latest links in the weblog</h2>
      <ul>
        {% get_latest_content coltrane.link 5 as latest_links %}
        {% for link in latest_links %}
        <li>
          <a href="{{ link.get_absolute_url }}">{{ link.title }}</a>,
          posted {{ link.pub_date|timesince }} ago.
        </li>
        {% endfor %}
      </ul>
    </div>

This will ensure that every page has the list of the latest five entries and links, and it offers two big advantages over the original {% get_latest_entries %} tag:

  • When you add new types of content to the blog (in the next chapter you'll add comments), you don't have to write a new tag. You can just reuse get_latest_content with different arguments.
  • If you decide to change the number of entries or links to show, or the variables you want to use for them, it's just a matter of sending different arguments to the {% get_latest_content %} tag. You won't have to rewrite the tag to change this.

Looking Ahead

In the next chapter, you'll wrap up the weblog by adding comments, moderation, and RSS feeds. For now, though, feel free to play with the template system and get the blog looking exactly how you want it. A sample style sheet that implements the two-column layout is included with the sample code for this book (downloadable from the Apress web site), so feel free to try it out. To get Django to serve this as a plain file, add the following URL pattern in the project's root URLConf module (once again using the static file-serving view you saw in Chapter 3):

(r'^media/(?P<path>.*)$',
 'django.views.static.serve',
 { 'document_root': '/path/to/stylesheet/directory' }),

Simply fill in the path to the directory where the style-sheet file resides on your computer, and Django will serve it. (Although note that for production deployment of Django, it's best not to have Django serve static files like this.)

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset