“Practial Django Projects”, Custom Template Tags, and Updates on LinuxLaboratory

Hi all,

If you’ve not been following along, I’ve picked up the 2nd edition of Practical Django Projects, and am using it to help me reinvent LinuxLaboratory.org (LLO). Though LLO is really a documentation site where I republish articles I’ve written for Linux.com, O’Reilly, and others over the past 10 years, I started out by putting a blog application in place as a way for me to communicate with people interested in how LLO is being built. Once I get comments and RSS in place, it will serve that purpose much more effectively. Until then, I’m putting more updates here than there.

So let’s have a look at what I’ve gotten done so far, and the stumbling blocks I’ve run into.

Look and Feel

Just looking at the blog application in its current form, you can see that I’ve done a thing or two to customize the look and feel so it’s not just plain black text on a white background. I added a simple CSS stylesheet that puts the sidebar where it should be, and updates the font in use. If you’re not using IE, you can probably also see a thin dotted line around the entries and the sidebar. This is probably temporary, and helps me quickly debug CSS issues I’m likely to come across early on (I’m not a hardcore designer, if you couldn’t tell). The accomplishment there was understanding my web host’s setup enough to ensure that static files like CSS stylesheets are served by the “main” Apache instance, and not my private instance which is hosting my Django application. This helps keep memory consumption down, and keeps things moving quickly.

Here’s a shot to give you an idea of the current overall look and feel. DISCLAIMER: It’s not impressive.

llo_mainviewIt’s brown. I like brown, and there aren’t tons of brown sites everywhere. I’ll likely change things as I move forward, because I also have a logo for the site and some other ideas and such. I also have aspirations beyond just a blog and CMS site. I’m going to add applications that actually do stuff, in part just to see if I can, in part because I’ll use them, and in part because others might find them useful. For example, I’ll be adding a subnet calculator, and a bandwidth delay product calculator.

The Sidebar: Entries, Categories, Links, and Maybe Tags

First, you should know that what appears on any given page of a Django site, along with what it looks like, the content it holds, and any other attribute can be modified/dynamic/etc for each page of the site without reproducing a whole bunch of code. Django gives you so many shortcuts to use to make this “just happen” that it’s really educational to go through the process of building a Django application, even if you hate Python and refuse to use it for real work. There are lessons to be learned here that you might find useful back in your development platform/framework of choice.

My sidebar right now contains the main “Navigation” links, a “What is this?” entry for each page you land on telling you what you’re looking at, a list of the most recent entries on the blog, and the most recent links posted to the Links section.

The “Recent Links” and “Recent Entries” sections show an interesting bit of Django magic. There are separate tables in the database for Links and Entries, and the fields aren’t all the same in the tables, and yet, I didn’t write one bit of SQL to get the data out, and didn’t write very much code at all to present it. Django supplies a few collections of default “views”, and one collection of them is for presenting data that is “date-based”. Pass in a few parameters to tell it the model (Link or Entry), and the number of recent entries to grab, and it goes off and does it for you.

Believe me, this *does* seem a little confusing at first. Figuring out how an http request is handled, how all of the data is gathered and passed to a template, and then finally rendered, takes a bit of time. Debug enough issues in the development of your first app or two, and you’ll get it cemented in your brain.

The one thing that still doesn’t work right is the ‘Tags” link on the sidebar. In the book, this is implemented using the django-tagging module, which is a third party application. Setting up my first 3rd party app for use with Django was no trouble at all, but I think the book should’ve gone through a bit more hand-holding in dealing with django-tagging. I hit a few glitches in using it at first, but because I had a lot of Django’s basic workings figured out by then, I was able to fix things on my own. Others might not be so lucky.

The django-tagging app’s model doesn’t really look like one I’ve seen so far in my Django travels, and is completely different from the models I created for my blog app. I figured out what was going on, and I have some idea what the path to success will look like for creating the “tag reference” page I’m hoping to build, but I decided that I’d put it aside and move on to dealing with things that are more immediately useful. I rarely if ever use tags on, say, this blog. RSS feeds and comments are in the next chapter, and I really can’t live without those.

Before I moved on to RSS and comments, though, I wanted to understand “custom template tags” in Django, so I went through the end of Chapter 6 and created one, and then created a more generic one to replace the first one. The more generic one (get_latest_content) caused Django to issue 500 errors. The idea of ‘get_latest_content’ is that it’s more generic to create one template tag that can take arguments telling it what “content” is than to create separate template tags for each type of content on the site. Unfortunately, I *believe*, but am not *sure* that there’s actually a bug in Django that makes this not work.

To implement the custom tag, you need to use a method called “get_model”, which I believe is supposed to return an object of type “model”, which will then have an attribute called “_default_manager”. What *actually* happens is it seems to be returning a “unicode” object, which has no such attribute, and Django tells you so.

So, I created separate “get_latest_entries” and “get_latest_links” template tags. Here are my custom template tag definitions:

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)

def do_latest_links(parser, token):
  return LatestLinksNode()

class LatestLinksNode(template.Node):
   def render(self, context):
     context['latest_links'] = Link.objects.all()[:5]
     return ''

register.tag('get_latest_links', do_latest_links)

If ‘get_latest_content’ worked, I’d only need to register one tag to do the jobs of both of these tags, but it’s not like this is a horribly difficult bit of code to manage, so it’s a workaround until either I find my typo or Django fixes their bug. This is a rare instance in which I do *not* feel like a workaround for a problem is a hack that’s going to chomp down on my “jewels” later on.

If you run into this issue and decide to go this route, don’t do what I did at first and call “register = template.Library()” for each tag. You create *one* new library, and then register *all* of your tags to it. The book (through Chapter 6 anyway) only has one tag at a time in there, so it’s not covered.

Here’s some code from the part of the template that uses the custom tags:

<h2>Recent Entries</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> 
<h2>Recent Links</h2>
<ul>
   {% get_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>

It’s been a lot of text, so here’s another screen shot, this time of my ‘Categories’ page, which I altered a bit from llo_categoriesthe book: I wanted mine to be an index-style listing that shows the category, and all posts in that category, instead of just showing categories and making the user click to see the entries in that category. Chances are the user isn’t really just curious to see what categories exist. Chances are also that some day I’ll be sorry I did it this way because I’ll have so many categories and posts that browsing this page will be cumbersome, but it’s *SO* easy to change it around that I’ll deal with it when I get there.

The Big Win: The Admin Interface

Django fanboys are quick to point out that Django’s admin interface is not just a “battery included”, but rather a “diesel-fueled power generator included”, and I’m inclined to agree. Writing admin interfaces is no fun, in part because end users never see it, so you can’t really show it off. Admin interfaces I’ve seen and written for in-house applications are usually design nightmares, and require some tribal knowledge to use effectively. I applaud the Django folks for doing the best job I’ve seen thus far at automatically creating an admin interface to manage pretty much *every* aspect of the site.

Once I created the data models for links, tags, categories, and entries, I was able to immediately use the admin interface to create new blog entries, add new links, new categories, etc. Of course, I can edit and delete items using the admin interface as well. The admin interface’s goal is to be functional — it doesn’t assume it’s going to be used by folks used to using Microsoft Word, or Emacs for that matter. Input is all just textarea elements, but if you want to you can plug in TinyMCE and give admins a wysiwyg admin interface. The book shows you how to use TinyMCE in its creation of a CMS application. I didn’t do the CMS app, but have downloaded TinyMCE and plan to use it as soon as comments and RSS are working on the blog.

Here’s a shot of the admin interface, which I did just about *nothing* to creatello_admin_entries.

This is, specifically, the part of the admin interface dealing with entries (blog posts). From here I can add a new entry (using that gray button in the top right corner), I can click an existing entry to edit it, or I can check the box next to an entry and choose “delete” from the “Action” drop down menu to delete it. It’s simple, but functional enough that I’d imagine most people using this for their own needs won’t find it necessary to create another one — at least not from scratch. The admin interface *is* customizable.

Apps and Projects

Another reason I started off building the blog app from the book is because it’s a standalone application, and not a project like the CMS, which is the first project in the book. I plan to take this experience with building a standalone application and then go back to build a standalone CMS as well instead of building the CMS as a project, and the blog as an app that can be used by the CMS project. I’m not sure what the logic was in setting the book up that way, but it seems to me (and I could be so, so laughably wrong here) that the project should contain as little code as possible. Preferably none if possible. It should contain URLConfs (and as few of those as possible), and settings.py. Anything else should be decoupled from the project if it’s feasible. Django makes it very easy to decouple URLs and templates and the like, and the community advocates as much decoupling as possible,

In fact, my own blog app is actually a standalone application that lives in its own directory and can be tar’d up and moved elsewhere at the drop of a hat. Django *apps* are linked into a Django *project*, which is what I think of as the “site-specific” collection of settings like database connection info, admin emails, etc. Drop an app in some directory, list it in the “INSTALLED_APPS” setting in your project’s ‘settings.py’ file, and you’re off and running.

Stay Tuned!

I hope to have RSS and Comments enabled on LLO in the next day or so, time allowing. I’m also maintaining my consulting business while I’m doing this, so time isn’t always on my side — speaking of which, I offer discounted consulting rates to work on Python projects, because I really like using Python, and now that I’m making friends with Python on the web, it looks like I might’ve finally achieved the dream of having one language that I can use to do systems development as well as web development. I don’t do much desktop GUI stuff… but who knows?

Until I get RSS and comments up, subscribe to this blog, and follow me (@bkjones) on Twitter.

  • m0j0

    Crap. The images are supposed to be clickable so you can see the larger image :-/

    I’ll figure that out and fix it, but not until later, unfortunately. Come back and see them tomorrow. Sorry, gotta split before AT&T decides it’s time to randomly drop my DHCP lease and force me to re-register :-/

  • http://www.b-list.org/ James Bennett

    I have no earthly idea why get_model() wouldn’t work as advertised, since it’s a pretty integral part of Django’s internals. But I’ve also been swamped with a huge work project the last few weeks and haven’t had time to dig back into the book’s code. Hoping that maybe this weekend I’ll finally get out from under it and be able to make some progress.

  • m0j0

    Well, I hope I’m wrong and it was a typo or some other stupidity in my own code. I’ll come back to it at some point, but if you find something out about it, blog about it (I’m subscribed to your feed)!

  • m0j0

    Fixed the pictures. Clicking on them now should bring you to a full-sized image.

  • Graham Ullrich

    Whenever I’ve seen a unicode object problem after calling a variant of get_model() in my custom template tag code, it’s been my own fault. Usually the problem is fixed after a realization that I forgot to call template.resolve_variable(objname, ctx) to get the actual variable indicated by a (unicode) string before calling ContentType.get_for_model(obj).

  • m0j0

    Thanks for this. I just downloaded the ebook version of the book, and I’ll cut-n-paste the code from there to see if the problem is maybe just a typo that my eyes are consistently failing to find. I *did* have a similar problem with the comments feature of the blog, and debugged it the same way, but I *still* haven’t found the actual typo! I’ll have to properly diff my own code against the book’s code.

  • http://None Siwei Xu

    You wrote:

    To implement the custom tag, you need to use a method called “get_model”, which I believe is supposed to return an object of type “model”, which will then have an attribute called “_default_manager”. What *actually* happens is it seems to be returning a “unicode” object, which has no such attribute, and Django tells you so.

    Well, I suffered same problem. But finally I found that I made mistake on writing:

    return LatestContentNode(model, bits[2], bits[4])

    What I wrote was return LatestContentNode(bits[1], bits[2], bits[4])

    I dont know whether u r making the same mistake…

  • Sebastian Gutierrez

    Siwei Xu’s solution worked for me. It looks like when we first wrote the do_latest_content we return bits[1], bits[2], bits[4]. Then when we start using model = get_model (….) we then should be doing:
    return LatestContentNode(model, bits[2], bits[4].

  • Igor Ganapolsky

    You’re absolutely right. When I changed ‘return LatestContentNode(bits[1], bits[2], bits[4])’ to ‘return LatestContentNode(model, bits[2], bits[4])’ it worked.
    An alternate solution is to change ‘self.model = model’ in LatestContentNode to ‘self.model = get_model(*model.split(‘.’))’