Writing the Link Model
This is part 6 of a series of posts on James Bennett's excellent Practical Django Projects. The table of contents and explanation can be found here.Read through and implement the all the templates up to p111 where the book covers Custom Tags. If you try running your base site at http://localhost:8000/weblog/ you'll probably get some template errors so we need to go back and fix up some url files. I updated my post for chapter 5 on 5-Sep-08, so if you missed that bit go back and read the end of that post, but here's the updated urls.categories.py
from django.conf.urls.defaults import *
from coltrane.models import Category
urlpatterns = patterns('',
(r'^$', 'django.views.generic.list_detail.object_list',
{ 'queryset': Category.objects.all() },'coltrane_category_list'),
url(r'^(?P[-\w]+)/$',
'coltrane.views.category_detail',name='coltrane_category_detail'),
)
Note I've added a name for the category list view called coltrane_category_list which matches what is the base.html template is looking for. Next go and do the same with coltrane_tag_list which is also missing from urls.tags.py.
There is also a new way in 1.0 (possibly still in development version) that you can deal with these errors a little better. You'll probably have noticed that if you use a variable in the template that can't be displayed because you've mistyped it or the data isn't there then it will silently ignore it. If we are using the url tag however, we do get an error if it can't do a reverse lookup on your named URLconf. But lets say you wanted to put that in the tags list view for later development and wanted to include it now in the base template as a placeholder for your layout.
Here's how you can do that in 1.x:
{% url coltrane_tag_list as tag_list %}
{% if tag_list %}
<li id="main-nav-tags">
<a href="{{ tag_list }}">Tags</a>
</li>
{% endif %}
Now the url reverse lookup will silently fail instead of producing an error.
Going back and playing with some of the templates there are a few other little things that have changed.
You might have noticed that your blog entries are converting from markdown to html but are displaying the html tags like <p> in the view. In one section of the chapter the {{ object.body_html }} is written correctly like this {{ object.body_html|safe }} but in the other on p110 the safe is dropped off. The default in 1.0 is to auto-escape html unless you turn the behaviour off using a {% autoescape off %} {% endautoescape %} block, so you need to add a safe filter to any html output.
Extending the Template System with Custom Tags
In this section we learn how to write custom tags for templates, and on p120 there is discussion about using default managers whenever possible. Unfortunately for us this conflicts with the problem overriding the admin manager that I discussed in the last post on Chapter 5, and until there is a simple way to override the manager for admin it is easier for us to declare a manager specifically for the Entry class. Once you've finished writing up the examples in this chapter alter your coltrane_tags.py render function to accomodate the live manager for the Entry model.
def render(self, context):
manager = self.model._default_manager
if self.model.__name__ == 'Entry':
manager = self.model.live
context[self.varname] = manager.all()[:self.num]
return ''
That's it for this chapter. On a final note for this post, I heard the author James Bennett on episode 36 of the excellent Django podcast, This week in django, talking about the book among other things. The good news is he will be working on making the source code available real soon. I look forward to it though I'm enjoying puzzling my way through the code changes.
Onto chapter 7 and the comments system which should be interesting as it has undergone a complete re-write.
7 comments:
Hi,
Many thanks for the great blog, it's been so helpful. I'm having some trouble with ch. 6, around p. 111. Tags aren't just playing nicely. In base.html:
Reverse for 'cms.coltrane_tag_list' with arguments '()' and keyword arguments '{}' not found.
I tried the workaround described here, but {% if tag_list %} is always null. Likewise in the detail templates:
{% if object.tags.count %}
is also always null. I noticed the first issue can be fixed by moving tag_list.html out of /cms/ and into /tagging/, not ideal but it works, but the latter is more of a problem: how do you list the tags for an entry or link from the detail pages?
Thanks!
Joe
Ps. I'm working w/ version (1, 0, 2, 'final', 0)
Just a followup, you can get this working, sorta, by changing:
{% if object.tags.count %}
to simply
{% if object.tags %}
Weirdly, it never hits the database, though the data is kept there. Also, from the django-tagging project page, it looks like the tagging application might be abandoned and only available in 0.96.
Anyway, the following works, but note the weird output on some dummy data - every character in the tag list is a separate object!
*** code ***
{% if object.tags %}
Tags: {{ object.tags }}
{% for tag in object.tags %}
{{ tag }}
{% if forloop.last %}{% else %},
{% endif %}
{% endfor %}
{% endif %}
*** results ***
Tags: Office Space Peter Lawrence
O , f , f , i , c , e , , S , p , a , c , e , , P , e , t , e , r , , L , a , w , r , e , n , c , e
Thank you!
If you use this piece of template code the tags works as they are supposed to (I guess).
The key is the next two tags, load and tags_for_object, sometimes it's good to read the docs :)
(http://django-tagging.googlecode.com/svn/trunk/docs/overview.txt)
Load to load the custom template tags that django-tagging provides and then the tags_for_object provided. So instead of object.tags.all you use the tag_list you get from tags_for_object.
{% load tagging_tags %}
{% tags_for_object object as tag_list %}
{% if tag_list.count %}
<p>Tag{{ tag_list.count|pluralize:",s" }}:
{% for tag in tag_list %}
<a href="{{ tag.get_absolute_url }}">{{ tag }}</a>
{% if forloop.last %}
{% comment %}Does nothing for the last time through the loop {% endcomment %}
{% else %}
{% ifequal forloop.revcounter0 1 %}
and
{% else %}
,
{% endifequal %}
{% endif %}
{% endfor %}
{% else %}
<p>There are no tags for this entry.</p>
{% endif %}
I'm using the svn trunk of django-tagging after version 0.2.1.
Now I just have to fix the space after each tag...annoying ;)
I'm working through the 2nd edition of the book, with latest (as of today) svn versions of various applications the Weblog app needs.
The newest version of tagging requires that the models with tagging need to be registered which involves adding this to the end of coltrane/models.py:
import tagging
tagging.register(Entry)
tagging.register(Link)
without that code, {{ object.tags.count }} (among others) is not populated.
That done, the template changes required for a working link_detail.html are less drastic:
{% if object.tags.count %}
<p>This link is tagged with
{% for tag in object.tags %}
<a href="{% url coltrane_link_archive_tag tag.name %}">{{ tag.name }}</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 %}
It's a pity that many of the typos and errors in the 1st edition have been allowed through to the new edition. And a bigger pity that a book about web applications has a page at APress with the worst errata submission form in the world! The book itself has admirably performed in its job of teaching me Django... maybe more so, because I had to debug so many of the examples!!
...forgot to mention that, in order for the url tag to do a successful reverse lookup, coltrane/urls/tags.py need some names adding to each pattern as follows:
urlpatterns = patterns('',
(r'^$',
'django.views.generic.list_detail.object_list',
{ 'queryset': Tag.objects.all() }, 'coltrane_tag_list'),
(r'^entries/(?P<tag>[-\w]+)/$',
'tagging.views.tagged_object_list',
{ 'queryset_or_model': Entry.live.all(),
'template_name': 'coltrane/entries_by_tag.html' }),
(r'^links/(?P<tag>[-\w]+)/$',
'tagging.views.tagged_object_list',
{ 'queryset_or_model': Link,
'template_name': 'coltrane/links_by_tag.html' },
'coltrane_link_archive_tag'))
Post a Comment