Defying Classification

by Malcolm Tredinnick

Thu 29 Jun 2006

Django Tips: Extending Generic Views

Posted at 1:16 +1000 (last edited: 16 Sep 2006, 12:57)

James Bennett has started what I hope might be a series of Django tips today. Should become required reading for Django users, I suspect. So let's help the guy out a bit here. With any luck, we won't duplicate each other's work.

One thing that keeps coming up again and again on #django on IRC, the mailing list and in the Django bug tracker are requests for some small customisation to generic views to accommodate some particular use-case. In almost every case — I would say, in every case, but I am sure there are exceptions that I haven't seen yet — these cases can be handled with only a few lines of code and then passing off the bulk of the work to the generic views.

Generic views in Django all take a reasonably consistent set of parameters. There are some variations based on the nature of the view, such as date-based views requiring a date to be passed in, but the bulk of the parameters are the same. You must provide a queryset argument that contains the results to display. You can also provide some extra context values (things which are put into the context object passed to the template), the template name to register (a default is used if you don't provide a name), some extra context processors if you need them and some pagination details.

For a wide number of uses, this gives enough flexibility to enable the developer to pass in the correct parameters directly from the URL configuration file. However, as mentioned above, it does not cover all cases.

A real-world example: you would like to present a list of article titles to the user. However, the contents of the list vary, depending upon who the user is. Some users might be permitted to view all articles, others will have their choice filtered by some set of parameters. The heart of this problem is that we cannot determine the right set of results for the queryset without knowing who the user is. A perfect case for a generic view wrapper.

We want to write a little function that acts as the view we are going to call from urls.py (assuming the traditional naming scheme here). This function will use the passed in request object that every view receives to construct the right queryset. At that point, we will have all the information we need to pass off the real presentation heavy lifting (pagination, handling missing results, etc) to a generic view. In cases like this, I find the object_list() generic view to be the most useful.

The code to do this is actually very simple:

from django.views.generic.list_detail import object_list

def generic_wrapper(request):
    """
    Extract the articles based on the logged-in
    user information.
    """
    if request.user.is_anonymous():
        qs = Articles.objects.get_default_articles()
    else:
        qs = Articles.objects.filter(user = request.username)
    return object_list(request, qs)

Now, admittedly, I have cheated a lot in this example by pushing almost all of the exciting bits off to the Article class. I am assuming I have a get_default_articles() manager method that returns a queryset for the default articles. I am also pretending that the right articles to retrieve are those index by logged in user's username. However, I think the pattern is clear, despite the lack of immediate usefulness here. By whatever means necessary, we construct the queryset we want and then just pass off the work to object_list. Of course, there is no reason to restrict this to just creating the primary queryset. You may wish to add in extra things to the context, so set up the extra_context dictionary in your wrapper function. Or perhaps the number of items on a page is a preference of the user. So you extract the user object from the request, use request.user.get_profile() to get the user-specific preferences and then work out the appropriate paginate_by value to pass to the generic view function.

In short, anytime you find yourself thinking "I wish this extra option was available on generic views", turn the question around: work out why you need this and if you were allowed to do a little bit of programming, how you could get the information. Then sit down at your editor and create the three or four lines you need to get pass the information to the view. It's easier than you think.

Generic views in Django are a great tool. They help you get most types of forms going very quickly. There are people like Jeff Croft who are doing impressive things with generic views alone (claiming to be "not a programmer"). But be careful not to let a focus on generic views blinker you to the other options. It isn't a choice of generic view or writing your own views from scratch; you can extend the base views. A view is just a function, so you can call it from anywhere. Do so! It does not make you a bad person.

Documentation Notes

Some people reading through the above will be wondering about some of the things I was doing with the request object. In particular, the user extraction. I didn't want to write a tutorial on using request.user, but in the interests of fairness, I should mention where you can get more information.

  • The documentation for the User management in Django is in the Authentication documentation.

  • The aforementioned James Bennett has written an article about using user profile objects over at his blog. It seems a little tricky to navigate to older articles on James' site at the moment, so just tell him Google sent you (although in this particular case, the Search box on the site did turn up the right article after a couple of attempts).

  • I wrote an earlier entry about custom manager methods that may provide some useful tips if you are wondering about the Articles.objects.get_default_articles() method and how to make your own thing like that.

Topics: software/django/tips, software/django/tutorials