Recent Entries:
{% %}
bits. What up with that stuff?
That is Django's template syntax for creating blocks which other templates can plug data into. In this case we're creating a base template with a few blocks (think of them as holes) and our other templates will extend this and fill in the holes.
To see what I mean let's create the blog/list.html template. Add a new blog folder in the templates folder (if you haven't already) and create a file list.html. Now open that up and past in the line {% extends 'base.html' %}
.
Now if you revisit [http://127.0.0.1:8000/blog/ http://127.0.0.1:8000/blog/] the template not found error should be gone and in its place you should see everything we just put in base.html -- not much I'm afraid.
But fear not, now that we're extending base.html we can start plugging in some values for those blocks we created earlier.
Add this below the extends statement:
{% block pagetitle %}Tumblelog{% endblock %} {% block title %}My Page Headline{% endblock %} {% block primary %} {% for object in latest %}Okay, first off we fill in our page title block in the head tags and then we do the same for the displayed title. The next block we fill in is the primary content block. Here's where we display the data that our generic view grabbed for us. The Django template syntax is fairly Pythonic in that your define loops using the same{{ object.title }}
{{ object.pub_date }}
{{ object.body_html|truncatewords_html:"20"|safe }}tags: {% for tag in object.get_tags%}{{tag}}{% endfor %}
{% endfor %} {% endblock %}
for x in dataset
syntax. In this case the generic view function object_detail passes in a variable named latest (by default the fifteen latest entries, though you can go back to the urls.py and increase that number using the num_latest param).
So all we do is construct a loop using the latest
variable. Then within that loop we pull out some of our data -- again accessing an objects attributes uses the python-like dot accessor methods.
The only part that requires additional explaining is the object.body_html
section where we've applied two built-in Django template filters, truncatewords_html
and safe
.
Truncatewords_html should be fairly obvious, this clips the the body of our post after twenty words, but also preserves the structure of the html by appending any closing tags to make sure the html is intact.
The safe
filter simply tells Django that it's okay to display HTML. Without the safe filter Django will automatically escape all HTML entities and tags. Autoescaping is a nice feature for avoiding nefarious XSS attacks and the like, but in this case, since we trust the source, we'll let the HTML through.
Okay, cool, but what about tags? We do after all have tags on our entries, might as well display them which is what we do in the next line. Here we have a loop within a loop. Remember when we created our models we added a get_tags method to return all the tags for each entry? Well here it is in action.
That will loop through all the tags for each entry and display them along with a link to that tag's permalink page. And note that we've used the slugify filter to make sure than any multiword tags will be hyphenated in the URL (if you remember back when we wrote our custom tag view we used a string replace function to "unslugify" the url for lookup in the datebase).
The last line calls the get_absolute_url
function that we defined when we built our model in the last lesson. This provides a link to the permalink page for each entry in the list.
So click that link and what happens? Error page. You need to define the detail.html template. That's not to hard, just create the file, add the extends base.html instruction at the top and fill in the blank blocks like title and primary. This time there's no need to loop through anything since there's only one object. Just call it directly like we did inside the loop on the archive page: {{object.title}}
etc.
The code might look something like this:
{% block pagetitle %}{{object.title}}{% endblock %} {% block title %}{{object.title}}{% endblock %} {% block primary %}
-
{% if object.get_previous_published%}
- « previous {%endif%} {% if object.get_next_published%}
- next » {%endif%}
{{ object.title }}
{{ object.pub_date }}
{{ object.body_html|safe }}tags: {% for tag in object.get_tags%}{{tag}}{% endfor %}
{% endblock %} Note that I've made use of thoseget_next_published
and get_previous_published
functions that we defined way back when wrote our models. That way, users have some handy next and previous links for navigating through your permalink pages.
Naturally you can get much more sophisticated with your HTML than this simple example.
To create the templates for the tag pages you'll essentially do the same thing. In our custom tag view we returned a list of all the entries in an object named object_list
. So in order to display them, just loop through object_list
like we did with the latest
variable above.
==Built-in Template Filters==
Before we move on, it's worth paying a visit to the [http://www.djangoproject.com/documentation/templates/ Django documentation on template filters and tags]. There's a whole bunch of useful stuff built in. You can use {% if %}
tags to narrow and filter results, and there's also {% else %}
, {% ifequal var1 var2 %}
, {% ifnotequal var1 var2 %}
and most other common programming structures.
Then there's a host of template filters, like the truncatewords_html and safe filters we used above. For instance there's a handy date filter if you'd like to display your post date in something a little sexier than a UNIX timestamp.
Here's what that would look like using the "date" filter:
{{object.pub_date|date:"D d M Y"}}
Another great filter is the "timesince" filter which will automatically convert a date into syntax like "1 week, 2 days ago" and so on.
There are also filters for escaping ampersands, escaping javascript, adding linebreaks, removing HTML, converting to upper/lowercase and dozens more. In fact in two and a half years of building sites with Django I've only needed to write a custom filter once. Naturally your mileage may vary somewhat.
==Roll Your Own Template Tags==
One thing I do frequently do is write custom template "tags." Template tags are perhaps one of the more confusing aspects of Django since they still have a little bit of "magic" in them, but luckily it isn't too hard to work with.
Template tags are a way of extending the Django template system to use it in project specific ways. For instance, custom template tags are a perfect way to handle things that don't make sense in a view, but do require some database logic.
Perhaps the best example is something like a sidebar. So far ours is empty, but we can easily add a list of our most recent blog posts.
Now we could write a filter that specifically fetches blog entries, but then what happens when we add links in the next lesson and want to display the most recent links? Write another template filter? That's not very DRY so let's just write a filter that fetches the most recent entries from any model with a date field.
In fact we don't even need to really write it. James Bennett has already [http://www.b-list.org/weblog/2006/jun/07/django-tips-write-better-template-tags/ written some great reusable code] so we'll just use that. I strongly recommend that you have read through James' tutorial so you can see how and why this code works.
Open a new file and paste in this code:
from django.template import Library, Node from django.db.models import get_model register = Library() class LatestContentNode(Node): def __init__(self, model, num, varname): self.num, self.varname = num, varname self.model = get_model(*model.split('.')) def render(self, context): context[self.varname] = self.model._default_manager.all()[:self.num] return '' def get_latest(parser, token): bits = token.contents.split() if len(bits) != 5: raise TemplateSyntaxError, "get_latest tag takes exactly four arguments" if bits[3] != 'as': raise TemplateSyntaxError, "third argument to get_latest tag must be 'as'" return LatestContentNode(bits[1], bits[2], bits[4]) get_latest = register.tag(get_latest)Now save that file in a new folder within the blog app, named templatetags. The folder name and location are important since Django only looks up template tags in specific locations. One thing to note about this code, if you look closely you'll notice that our template tag is going to fetch all entries, not just the public ones. In other words, our model allows for draft posts, but our template tag doesn't. This is the line in question:
self.model._default_manager.all()There are two ways around this, one is quick and dirty: just change that line to filter only published entries. In other words:
self.model._default_manager.filter(status=1)The better and cleaner way to do it would be overwrite the default manager for our Entry model. However, that's a little beyond the scope of this article, so for now we'll just use the quick and dirty method. Now open up base.html again and add this line at the top:
{% load get_latest %}That tells Django to load the template tag, but then we need to grab the actual data, so head down to the sidebar section and replace it with this code:
Recent Entries:
{% get_latest blog.Entry 5 as recent_posts %}{% for obj in recent_posts %}
- {{ obj.title}}
Now if you want to override that in templates that are inheriting from base.html, just wrap that code in a {% block %} tag and then replace it with something else in the new template. == Conclusion == The Django template system is quite vast in terms of capabilities and we've really just scratched the surface. Make sure you [http://www.djangoproject.com/documentation/templates_python/ read through the documentation for Python programmers] as well as [http://www.djangoproject.com/documentation/templates/ the documentation for template authors] and familiarize yourself with all the various built in tags and filters. Another great article is Jeff Croft's [http://jeffcroft.com/blog/2006/feb/21/django-templates-an-introduction/ Django Templates: An Introduction]. It's worth noting that there's an extensive collection of useful filters and template tags on the [http://www.djangosnippets.org/ Django Snippets site], so if you ever find yourself needing a custom tag or filter, have a look through there and see if anyone has already written something that works for your project. I should also point out that if you just don't for whatever reason like Django's template system, it's possible drop in your template language, like [http://cheetahtemplate.sourceforge.net/ Cheetah] or others. Be sure to stop by for our next installment when we'll tackle a plan to import, store and display our del.icio.us bookmarks.