Monday, September 29, 2008

Django From the Ground Up: Episode 7

with host Eric Florenzano

Bookmark and Share

In this episode, we add an archive view for looking at events that have happened in the past, and then we write an inclusion tag for events. Writing an inclusion tag not only makes our code more clean (subjectively, of course), but it allows us to follow the principle of DRY.

What's the Downside?

It seems great to be able to write a template tag that takes care of everything for us. But like any other programming decision, there are both benefits and drawbacks. I mentioned briefly in the screencast that the tradeoff in this case is, in fact, performance. While this is not strictly true, let me give an example of how, if unchecked, performance can suffer from the use of inclusion tags.

Here's a simplified version of our inclusion tag:

def event(context, e):
    request = context['request']
    attendance = Attendance.objects.get(event=e, user=request.User)
    return {'attendance', attendance}

Now there are some differences here between our inclusion tag and the one in the screencast, but the main idea is the same--for each event object, we're querying for whether the user is attending the event. But therein lies the rub, as they say: each of those queries is a database access for a single user-event pair, which is fine for small numbers of events, but if you have a page with many events on it, your database query load per page can easily skyrocket into the hundreds.

The solution? Write a batch query, and pass it into the context. It's not as nice-looking, but it reduces a potentially variable number of queries down to one. Here's how the view for that might look:

def tonight(request):
    events = Event.objects.today().filter(latest=True)
    event_ids = [e.id for e in events]
    attendance = Attendance.objects.filter(event__in=event_ids, user=request.user).select_related(depth=1)
    attendance_dict = dict([(a.event, a) for a in attendance])
    context = {'events': events, 'attendance_dict': attendance_dict}
    return render_to_response(
        'events/archive.html',
        context,
        context_instance=RequestContext(request)
    )

Then, our inclusion tag would have to be modified as well:

def event(context, e):
    request = context['request']
    attendance = context['attendance_dict'].get(e, False)
    return {'attendance', attendance}

In this case, we're looking to see if there are any entries in the dictionary for the specified event, and if not, we're defaulting to a value of False. As you can see, it sacrifices a bit of straightforwardness for speed, but in many cases this simply needs to be done.

  • Running Time: 13:20

Comments - 1 person has already said something. Join the discussion.

  • nono said

    I always make a folder "snippets" when I make a templatetag that refers to a template. In that way its easy to remember from where the data is comming. Else you might be hunting for it if you revisit your project six months later.

    I don't know if that is good practice?