At the Forge - Django Views and Templates

by Reuven M. Lerner

Last month, we began looking at Django, a popular framework for creating Web applications in Python. Django has the reputation of being the “Ruby on Rails of the Python world”, and there is some justification for that description. Even though Django was developed in parallel with Rails and has many unique features that distinguish it from its Ruby counterpart, it's difficult to avoid comparing the two.

In the introduction to Django last month, we saw a number of subtle, but important, differences between the two systems. Django handles a “project” at a time, where each project may contain one or more applications. This contrasts with the Rails approach, in which there is no real equivalent to Django's projects, because everything is an application.

We also saw that Django comes with a built-in Web-based administrative system. It takes a tiny bit of fiddling with a configuration file to activate this administrative system, but it provides a great number of benefits to any system that uses it.

Despite their differences, both Django and Rails use an approach that is best known as MVC (model, view, controller) developed in the Smalltalk community but adopted by many other languages and frameworks since then. These terms, used verbatim in the Rails world, are called models, templates and views in the Django world, and they form the bulk of a Django-based site.

This month, we work with templates and views, ignoring models and the database API until next month. But, don't worry; Django's templates are quite powerful, and they provide an interesting lesson in restraint.

Creating an Application

Our first step involves creating a new application within our project. For a number of reasons, including the simplicity with which we can create a page, our example application is a very simple blog program. We create our blog application by switching into our project directory, and then by using our management program, manage.py:

$ cd /opt/atf/mysite
$ python manage.py startapp blog

If this executes successfully, Django won't print any messages. Rather, we can get a listing of the current directory, which should now include a blog subdirectory:

$ ls blog
__init__.py  models.py  views.py

Django is smart enough to stop us from creating an application twice, if we try to do so:

$ python manage.py startapp blog
Error: [Errno 17] File exists: '/opt/atf/mysite/blog'

Now, let's create a simple “Hello, world” view, just so we can demonstrate that things are working. Thus, we open blog/views.py, which starts off empty except for a comment indicating that this is the file in which views are defined.

The simplest possible “Hello, world” view will be called index, acting sort of like the index method in Rails or the index.html file on a Web site—providing the default content when no specific method is invoked for an application. We also have to import the Python module in which Django's HTTP response capabilities are defined. When we're done, the entire views.py file looks like this:

from django.http import HttpResponse

def index(request):
return HttpResponse("Hello, world.")

If our server isn't already running, we can start it with:

python manage.py runserver

Or, if we want to run on a publicly accessible IP address, rather than 127.0.0.1 (“localhost”), we can do something like this:

python manage.py runserver 69.55.232.87:8000
URLConf

To see the output from our view, let's point our browser to the following URL, expecting to see “Hello, world”: http://69.55.232.87:8000/blog/. Instead, we get an error message, indicating that Django has no idea what we're talking about. The good news is that this error message, which is turned on only when we are developing an application, tells us what we did wrong—namely, that our URLConf definition failed to have any entry for blog in it.

This is a fundamental difference between Django and Rails, and it reflects the different philosophies of the two communities. In Rails, you expect the system to do the right thing by default; you should have to say something only when it deviates from that norm. In contrast, Django assumes that you want to make everything explicit.

Among the things that you need to make explicit is the way in which URLs are translated into method calls. The Django system for doing this is called URLConf, and it is a set of regular expressions defined for the entire project. (It functions something like routes in Rails.) If you are familiar with regular expressions, it should be pretty easy to add or modify URLConf.

For example, let's say we want the /blog/ URL to go to our blog application. So, we would open the URLConf file, which for the mysite project will be in mysite/urls.py. (We already modified this last month, when we added administrative capabilities to our Django site.) We then add a line that looks like the following:

(r'^blog/$', 'mysite.blog.views.index')

In other words, if the system sees a URL that begins with blog and ends with /, it should invoke the index method within views.py in our blog application. And, sure enough, as soon as we save urls.py to disk, we can reload our URL http://69.55.232.87:8000/blog/, and in our browser, we see “Hello, world”. Our Django application is starting to come together.

More Complex URLs

Things are not that interesting if all we have is a single method and if it always does the same thing. For a blog application, we'll presumably want to be able to read a particular posting, or postings, on a particular day. And although we'll get into the model for our blog application next month, it stands to reason that this means we'll need to be able to request blog postings by an individual ID or by date.

Our current URLConf doesn't handle such situations. Indeed, Django requires that we explicitly indicate each possible URL that a user might request and how that URL should be handled. So although we have taken care of blog/, we need to handle such URLs as:

^blog/ID

Luckily, it should be pretty straightforward for us to set up a regular expression that captures such functionality. If we open urls.py once again, we can add another statement:


(r'^blog/(?P<post_id>\d+)/$', 'mysite.blog.views.view_one_posting')

Here, we see something a bit strange and different, namely the use of capturing parentheses, along with ?P and names inside angle brackets. We tell URLConf that whenever we receive a URL that looks like /blog/NUMBER, with one or more digits, we should invoke view_one_posting. Moreover, because we have captured the digits (\d+, in regular expressions) with the name post_id, view_one_posting will be invoked with an additional parameter of post_id.

In other words, once we've modified urls.py to include the above mapping, we now can go back to views.py and say the following:

def view_one_posting(request, post_id):
return HttpResponse("You asked for post '%s'" % post_id)

Then, we can go to http://69.55.232.87:8000/blog/5, and in our browser, we get the following response:

You asked for post '5'

Notice that in the view_one_posting method, we used %s (for strings) to render post_id, rather than %d (for integers). This is because parameters and URLs are passed as strings, rather than integers or other data types. We could, of course, get around this problem by converting the string to an integer, but for our purposes right now, this is enough.

Perhaps this goes without saying, but each method in our Django application is a Python method whose output just happens to be sent to the user's Web browser. You can use any number of Python libraries that you want, and even access databases, filesystems and remote computers. So long as the output is returned as a legitimate HTTP response, your method will work. The application we are building in these examples represents the tip of the iceberg, as far as implementation complexity is concerned.

Templates

If we wanted to, we could create a whole Web site using nothing more than the tools we've already seen. However, it quickly would become difficult, and even tedious, to do so, putting all of our output strings as parameters to HttpResponse. The real solution is to put the HTML in external files, interpolating variable values as necessary.

There are a lot of different templating solutions out there, and each has its advantages and disadvantages. The most common type is that used by ASP, PHP, JSP and ERb—the last of which is part of Ruby on Rails—in which the code is interspersed with the HTML. This type of template can be extremely easy for programmers to work with, but it can cause trouble when nonprogramming designers are involved, or if you just want to have a complete separation between code and design.

Such templates can be used by Django, but they are not the default. Rather, Django uses templates that are similar in many ways to the Smarty system for PHP, which avoids the use of actual code in the template by introducing its own, deliberately limited language. So long as a view can pass values to a template, there's no real need for a full-blown programming language inside of a template. It's probably enough to have if/then statements and some basic loops.

This is what Django's default template system provides. In order to use templates, we first modify the settings.py file that sits in the project's main directory. We want to set the TEMPLATE_DIRS variable, giving it a list of one or more directories in which our templates might be found. For example, we could set it to:

TEMPLATE_DIRS = (
"/opt/atf/mysite/templates"
)

With this in place, now we create the appropriate directories:

$ mkdir -p /opt/atf/mysite/templates/blog

And, then we create, in that blog subdirectory, a new HTML file, which we call view_one_posting.html:


<html>
<head>
    <title>One post</title>
</head>

<body>
    <p>This is the "view_one_posting" template.</p>
</body>
</html>

Now, we modify views.py, such that our view_one_posting method can invoke this template. We add the following import statement at the top of the file:

from django.template import Context, loader

Then, we modify view_one_posting to be:

def view_one_posting(request, post_id):
t = loader.get_template('blog/view_one_posting.html')
c = Context({})
return HttpResponse(t.render(c))

Our template is loaded into the variable t, and the context—that is, the variable values we want to pass to our template—is bound to the variable c. We then tell the template to render itself within the context c. In this particular example, we aren't passing any variables in the context, so it is represented by an empty dictionary.

And, if we reload the URL /blog/5 on our site, we should see the following in the browser window:

This is the "view_one_posting" template.

That's certainly better than what we had before, in that the contents of the template are easier to handle (by programmers and designers alike) than a string inside of a view method. But, how do we pass variables to be interpolated?

The answer is quite simple. In the view method, we can pass any variable we like to the template using the context dictionary, in which the keys are the passed variable names, and the values are the passed variable values. So, we can say:

def view_one_posting(request, post_id):
t = loader.get_template('blog/view_one_posting.html')
c = Context({'post_id': post_id})
return HttpResponse(t.render(c))

If we want to see this value in our template, we can view it in double curly braces:


<html>
<head>
    <title>One post</title>
</head>

<body>
    <p>This is the "view_one_posting" template.</p>
    <p>The post_id is {{post_id}}.</p>
</body>
</html>

And, sure enough, when we render this template, we get:

This is the "view_one_posting" template.

The post_id is 5.
Conclusion

Django, like Rails and many other Web frameworks, uses MVC to divide the work between models, views and templates. This month, we saw how to connect a URL to a view, how to pass one or more URL parameters to a view and how to invoke a template from a view.

Next month, we will see how to integrate databases and data models into a Django application.

Resources

The main Django site is at www.djangoproject.com. The site contains a great deal of documentation, including tutorials and pointers to mailing lists.

A prerelease copy of the forthcoming Django book (to be published by Apress) is at www.djangobook.com/en/beta, and although the book is still unfinished in many places, it is well written and includes many examples.

Reuven M. Lerner, a longtime Web/database consultant, is a PhD candidate in Learning Sciences at Northwestern University in Evanston, Illinois. He currently lives with his wife and three children in Skokie, Illinois. You can read his Weblog at altneuland.lerner.co.il.

Load Disqus comments