Tuesday, June 19, 2007

Focus on creating the public interface — “views”

A view is a “type” of Web page in your Django application that generally serves a specific function and has a specific template. For example, in a weblog application, you might have the following views:

Blog homepage — displays the latest few entries.

Entry “detail” page — permalink page for a single entry.

Year-based archive page — displays all months with entries in the given year.

Month-based archive page — displays all days with entries in the given month.

Day-based archive page — displays all entries in the given day.

Comment action — handles posting comments to a given entry.

In our poll application, we’ll have the following four views:

Poll “archive” page — displays the latest few polls.

Poll “detail” page — displays a poll question, with no results but with a form to vote.

Poll “results” page — displays results for a particular poll.

Vote action — handles voting for a particular choice in a particular poll.

The poll_id='23' part comes from (?P\d+). Using parenthesis around apattern “captures” the text matched by that pattern and sends it as an argumentto the view function; the ?P defines the name that will be used toidentify the matched pattern; and \d+ is a regular expression to match a sequence ofdigits (i.e., a number).

The get_object_or_404() function takes a Django model module as its first argument and an arbitrary number of keyword arguments, which it passes to the module’s get() function. It raises Http404 if the object doesn’t exist.

There’s also a get_list_or_404() function, which works just as get_object_or_404() — except using filter() instead of get(). It raises Http404 if the list is empty.

While we’re at it, we should take the time to decouple our poll-app URLs from our Django project configuration. Django apps are meant to be pluggable — that is, each particular app should be transferrable to another Django installation with minimal fuss.

Our poll app is pretty decoupled at this point, thanks to the strict directory structure that pythonmanage.pystartapp created, but one part of it is coupled to the Django settings: The URLconf.

We’ve been editing the URLs in mysite/urls.py, but the URL design of an app is specific to the app, not to the Django installation — so let’s move the URLs within the app directory.

Copy the file mysite/urls.py to mysite/polls/urls.py. Then, change mysite/urls.py to remove the poll-specific URLs and insert an include():

(r'^polls/', include('mysite.polls.urls')),

include(), simply, references another URLconf. Note that the regular expression doesn’t have a $ (end-of-string match character) but has the trailing slash. Whenever Django encounters include(), it chops off whatever part of the URL matched up to that point and sends the remaining string to the included URLconf for further processing.

Here’s what happens if a user goes to “/polls/34/” in this system:

Django will find the match at '^polls/'

It will strip off the matching text ("polls/") and send the remaining text — "34/" — to the ‘mysite.polls.urls’ urlconf for further processing.

Now that we’ve decoupled that, we need to decouple the ‘mysite.polls.urls’ urlconf by removing the leading “polls/” from each line:

The idea behind include() and URLconf decoupling is to make it easy to plug-and-play URLs. Now that polls are in their own URLconf, they can be placed under “/polls/”, or under “/fun_polls/”, or under “/content/polls/”, or any other URL root, and the app will still work.

All the poll app cares about is its relative URLs, not its absolute URLs.