The Django project is a custom-built framework that originated with an online newspaper Website and was released as open source in July 2005. The core components of the Django framework are:
- Object-relational mapping for creating models
- Polished administrator interface designed for end users
- Elegant URL design
- Designer-friendly template language
- Caching system
This is the first article in a two-part series on Python Web frameworks. The secord article will introduce you to the TurboGears framework.
To use and understand the
code in this article, you need to have Python installed and know how to use it at a beginner level. Check to see if you have Python, and what version, by typing python -V. Django requires, at a minimum, version 2.3.5, which is available at the Python Web site (see the Resources section later in this article for a link). You should also be at least passingly familiar with the MVC architecture.
This article uses the development version of Django, to take advantage of the recent improvements to the Django framework. I recommend that you use this version until the 0.95 release. Check the Django Web site for the latest release (again, see Resources for a link).
Download and install Django as follows:
Listing 1. Downloading and installing Django
~/downloads# svn co http://code.djangoproject.com/svn/django/trunk/ django_src
~/downloads# cd django_src
~/downloads# python setup.py install
|
After installing Django, you should have the admin tool, django-admin.py, available on your path. Listing 2 shows some of the commands available to the admin tool:
Listing 2. Using the Django administration tool
~/dev$ django-admin.py
usage: django-admin.py action [options]
actions:
adminindex [modelmodule ...]
Prints the admin-index template snippet for the given model
module name(s).
... snip ...
startapp [appname]
Creates a Django app directory structure for the given app name
in the current directory.
startproject [projectname]
Creates a Django project directory structure for the given
project name in the current directory.
validate
Validates all installed models.
options:
-h, --help show this help message and exit
--settings=SETTINGS Python path to settings module, e.g.
"myproject.settings.main". If this isn't
provided, the DJANGO_SETTINGS_MODULE
environment variable will be used.
--pythonpath=PYTHONPATH
Lets you manually add a directory the Python
path, e.g. "/home/djangoprojects/myproject".
|
Django projects and applications
To begin a Django project, use the django-admin startproject command, like so:
Listing 3. Starting a project
~/dev$ django-admin.py startproject djproject
|
The above command creates a directory called djproject that contains the basic configuration files needed to run a Django project:
Listing 4. Contents of the djproject directory
__init__.py
manage.py
settings.py
urls.py
|
For this project, you will build a job-board application called "jobs." To create an application, use the manage.py script, which is a project-specific django-admin.py script where the settings.py file is automatically supplied:
Listing 5. Using manage.py startapp
~/dev$ cd djproject
~/dev/djproject$ python manage.py startapp jobs
|
This creates a barebone application with one Python module for your models and another for your views. The jobs directory will contain the following files:
Listing 6. Contents of the jobs application directory
__init__.py
models.py
views.py
|
The location of the application inside the project is purely a convention created for new Django developers, not a requirement. Once you start mixing and matching applications across several projects, you can put applications in their own module namespace and tie them together using settings and master URL files. For now, follow the steps as shown.
To make Django aware of a new application, you'll need to add an entry to the INSTALLED_APPS field in the settings.py file. For this job board
application, the string djproject.jobs must be added:
Listing 7. Adding an entry to settings.py
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'djproject.jobs',
)
|
Django comes with its own object-relational mapper (ORM) library that supports dynamic database access through a Python object interface. The Python interface is very usable and powerful, but you are also free to drop down and use SQL directly, if needed.
The ORM currently provides support for PostgreSQL, MySQL, SQLite, and Microsoft® SQL databases.
This example uses SQLite as the database backend. SQLite is
a lightweight database that requires no configuration and resides on
disk as a simple file. To use SQLite, simply install the pysqlite
library using setuptools (see Resources for more information on setuptools and the easy_install tool in particular, which you need to install separately):
easy_install pysqlite
Before working on the model, configure the database in the settings file. SQLite requires only that the database engine and name be specified.
Listing 8. Configuring the database in settings.py
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = '/path/to/dev/djproject/database.db'
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''
|
This job board application will have two types of objects, Locations and Jobs. A Location contains city, state (optional), and country fields. A Job has a location, title, description, and publish date.
Listing 9. The jobs/models.py module
from django.db import models
class Location(models.Model):
city = models.CharField(maxlength=50)
state = models.CharField(maxlength=50, null=True, blank=True)
country = models.CharField(maxlength=50)
def __str__(self):
if self.state:
return "%s, %s, %s" % (self.city, self.state, self.country)
else:
return "%s, %s" % (self.city, self.country)
class Job(models.Model):
pub_date = models.DateField()
job_title = models.CharField(maxlength=50)
job_description = models.TextField()
location = models.ForeignKey(Location)
def __str__(self):
return "%s (%s)" % (self.job_title, self.location)
|
The __str__ method is a special class method in Python that
returns the string representation of an object. Django uses
this method extensively when displaying objects in the Admin
tool.
To see the database schema for the model, run manage.py's sql
command. The schema won't be enacted yet.
Listing 10. Viewing the database schema using the manage.py sql command
~/dev/djproject$ python manage.py sql jobs
BEGIN;
CREATE TABLE "jobs_job" (
"id" integer NOT NULL PRIMARY KEY,
"pub_date" date NOT NULL,
"job_title" varchar(50) NOT NULL,
"job_description" text NOT NULL,
"location_id" integer NOT NULL
);
CREATE TABLE "jobs_location" (
"id" integer NOT NULL PRIMARY KEY,
"city" varchar(50) NOT NULL,
"state" varchar(50) NULL,
"country" varchar(50) NOT NULL
);
COMMIT;
|
To initialize and install the model, run the synchronize database
command, syncdb:
~/dev/djproject$ python manage.py syncdb
Note that the syncdb command asks you to create a superuser account.
This is because the django.contrib.auth application, which provides
basic user authentication functionality, is supplied by default in
your INSTALLED_APPS settings. The superuser name and password will be
used for logging into the admin tool described in the next section.
Remember that this is the Django superuser, not your system's.
Django models access the database through the default Manager class
called objects. For example, to print a list of all Jobs, you would
use the all method of the objects manager:
Listing 11. Printing all jobs
>>> from jobs.models import Job
>>> for job in Job.objects.all():
... print job
|
The Manager class also has filtering methods called filter and
exclude. Filtering gets all the objects that meet a condition, while
excluding gives all the objects that do not. The queries below should give
the same results ("gte"
means "greater than or equal," and "lt" means "less than").
Listing 12. Excluding and filtering jobs
>>> from jobs.models import Job
>>> from datetime import datetime
>>> q1 = Job.objects.filter(pub_date__gte=datetime(2006, 1, 1))
>>> q2 = Job.objects.exclude(pub_date__lt=datetime(2006, 1, 1))
|
The filter and exclude methods return QuerySet objects that can be
chained together and can even perform joins. The q4 query below will
find jobs posted since January 1st, 2006, in Cleveland, Ohio:
Listing 13. More excluding and filtering jobs
>>> from jobs.models import Job
>>> from datetime import datetime
>>> q3 = Job.objects.filter(pub_date__gte=datetime(2006, 1, 1))
>>> q4 = q3.filter(location__city__exact="Cleveland",
... location__state__exact="Ohio")
|
It's very nice that QuerySets are lazy. This means that they do not execute against the database until they are evaluated, and thus run much faster than immediate queries.
This laziness is handy with Python's slicing functionality. Rather than request all the records and then slice the records needed, the code below uses an OFFSET of 5 and a LIMIT of 10 in the actual SQL query, greatly improving performance.
Listing 14. Python slice
>>> from jobs.models import Job
>>> for job in Job.objects.all()[5:15]
... print job
|
Note: Use the count method to find out how many records are in a
QuerySet. The Python len method does a full evaluation and then
counts the rows returned as records, while the count method does an
actual SQL COUNT, which is much faster. Your database administrator
will thank you.
Listing 15. Counting records
>>> from jobs.models import Job
>>> print "Count = ", Job.objects.count() # GOOD!
>>> print "Count = ", len(Job.objects.all()) # BAD!
|
For more information, see the Resources section for a link to the Django "Database API reference."
One of the biggest selling points of Django is its well-polished admin interface. This tool was created with end users in mind. It gives your projects a great data entry tool.
The admin tool is an application that comes with Django. It must be
installed, like the jobs application, before you can use it. The
first step is to add the application's module (django.contrib.admin)
to the INSTALLED_APPS setting:
Listing 16. Modifying settings.py
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'djproject.jobs',
'django.contrib.admin',
)
|
To make the admin tool available from the /admin URL, simply uncomment the line provided in your project's urls.py file. The next section goes into URL configuration in greater detail.
Listing 17. Making the admin tool available via urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls.admin')),
)
|
The admin application has its own database model and needs to be
installed. Use the syncdb command again to
accomplish this:
python manage.py syncdb
To view the admin tool, you can use the test server that comes with Django.
Listing 18. Using the test server to view the admin tool
~/dev/djproject$ python manage.py runserver
Validating models...
0 errors found.
Django version 0.95 (post-magic-removal), using settings 'djproject.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C (Unix) or CTRL-BREAK (Windows).
|
You can now navigate to the admin tool at http://localhost:8000/admin and log in using the superuser account you created before. You will notice that none of your models are available for use.
To make a class accessible through the admin tool, create an Admin subclass to it. You can then customize how each class can be administered by adding class attributes to this subclass. Listing 19 shows how to add the Location class to the admin tool.
Listing 19. Adding the Location class using the admin tool
class Location(meta.Model):
...
class Admin:
list_display = ("city", "state", "country")
|
You can now create, update, and delete the Location records through the admin interface.
Figure 1. Editing locations with the admin tool
You can list and sort Records by city, state,
and country as specified by the list_display class attribute.
Figure 2. Listing locations with the admin tool
The admin tool has numerous options for managing each type of model class. Listing 20 shows several examples applied to the Job class:
Listing 20. Options for managing model classes
class Job(meta.Model):
...
class Admin:
list_display = ("job_title", "location", "pub_date")
ordering = ["-pub_date"]
search_fields = ("job_title", "job_description")
list_filter = ("location",)
|
According to the above settings, a job's title, location, and published data will be used when listing job records. The jobs will be ordered by when they were published, starting with the most recent (a minus sign indicates descending order). Users can find jobs by title and description, and administrators can filter records based on location.
Figure 3. Listing jobs with the admin tool
The Django URL dispatch system uses regular-expression configuration modules that map URL string patterns to Python methods called views. This system allows URLs to be completely decoupled from the underlying code, allowing for maximum control and flexibility.
A urls.py module is created and defined as the default starting
point for URL configuration (via the ROOT_URLCONF value in the
settings.py module). The only requirement for a URL configuration file
is that it must contain an object that defines the patterns called
urlpatterns.
The job board application will start with an index and detail view that are accessed through these URL mappings:
/jobsindex view: Displays the latest 10 jobs/jobs/1detail view: Displays jobs with an ID of 1
Both views (index and detail) will be implemented in a module called views.py in the jobs application. Implementing this configuration in the project's urls.py file would look like this:
Listing 21. Implementing the configuration of views in djproject/urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls.admin')),
(r'^jobs/$', 'djproject.jobs.views.index'),
(r'^jobs/(?P<job_id>\d+)/$', 'djproject.jobs.views.detail'),
)
|
Note the <job_id> piece. It's important later.
Best practice is to pull out application-specific URL patterns and place them in the application itself. This decouples the application from the project and allows for greater reuse. An application-level URL config file for jobs would look like this:
Listing 22. Application-level URL configuration file, urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^$', 'djproject.jobs.views.index'),
(r'^(?P<job_id>\d+)/$', 'djproject.jobs.views.detail'),
)
|
Since the view methods now all come from the same module, the first
argument can be used to specify djproject.jobs.views as the module's
root name, and Django will use it to look for the methods index and
detail:
Listing 23. jobs/urls.py: Looking for index and detail
from django.conf.urls.defaults import *
urlpatterns = patterns('djproject.jobs.views',
(r'^$', 'index'),
(r'^(?P<object_id>\d+)/$', 'detail'),
)
|
Tying the above jobs URLs back into the project as a whole is done
using the include function. The application level URLs are tied back
below the /jobs section:
Listing 24. djproject/urls.py: Tying URLs back into the project
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls.admin')),
(r'^jobs/', include('djproject.jobs.urls')),
)
|
If you try to access the index page (http://localhost:8000/jobs) at this point using your test server, you will get an error, since the view being called (djproject.jobs.views.index) does not exist yet.
A view is a simple Python method that accepts a request object and is responsible for:
- Any business logic (directly or indirectly)
- A context dictionary with data for the template
- Rendering the template with a context
- The response object that passes the rendered results back to the framework
In Django, the Python method called when a URL is requested is called a view, and the page loaded and rendered by the view is called a template. Because of this, the Django team refers to Django as an MVT (model-view-template) framework. TurboGears, on the other hand, calls its methods controllers and their rendered templates views so that they can fit squarely into the MVC acronym. The difference is largely semantic, as they accomplish the same things.
The simplest possible view returns an HttpResponse object initialized
with a string. Create the following method and make a /jobs HTTP
request to ensure your urls.py and views.py files are set up
correctly.
Listing 25. jobs/views.py (v1)
from django.http import HttpResponse
def index(request):
return HttpResponse("Job Index View")
|
The following code gets the latest 10 jobs, renders them through a template, and returns a response. It will not work without the template file from the next section.
Listing 26. jobs/views.py (v2)
from django.template import Context, loader
from django.http import HttpResponse
from jobs.models import Job
def index(request):
object_list = Job.objects.order_by('-pub_date')[:10]
t = loader.get_template('jobs/job_list.html')
c = Context({
'object_list': object_list,
})
return HttpResponse(t.render(c))
|
In the above code, the template is named by the jobs/job_list.html
string. The template is rendered with a context of the job list named
object_list. The rendered template string is then passed into an
HTTPResponse constructor, which is sent back to the request client via
the framework.
The steps of loading a template, creating a context, and returning a
new response object are replaced below with the convenience method
named render_to_response. Also new is the detail view method that
uses a convenience method called get_object_or_404 to retrieve a Job
object using the arguments supplied. If the object is not found, a 404
exception is thrown. These two methods remove a lot of boilerplate
code in most Web applications.
Listing 27. jobs/views.py (v3)
from django.shortcuts import get_object_or_404, render_to_response
from jobs.models import Job
def index(request):
object_list = Job.objects.order_by('-pub_date')[:10]
return render_to_response('jobs/job_list.html',
{'object_list': object_list})
def detail(request, object_id):
job = get_object_or_404(Job, pk=object_id)
return render_to_response('jobs/job_detail.html',
{'object': job})
|
Note that detail takes object_id as an argument. This is the
number mentioned earlier after the /jobs/ URL path in the jobs urls.py
file. It is passed further to the get_object_or_404 method as the
primary key (pk).
The above views will still fail because the templates that they load and render (jobs/job_list.html and jobs/job_detail.html) do not exist yet.
Django provides a simple templating language designed for fast rendering
and ease of use. Django templates are created with plain text embedded
with {{ variables }} and {% tags %}. Variables are evaluated and
replaced with the value they represent. Tags are used for basic control
logic. Templates can be used to generate any text-based format including
HTML, XML, CSV, and plain text.
The first step is to define where the templates are located. For simplicity's sake, create a templates directory under djproject and add its path to the TEMPLATE_DIRS settings.py entry:
Listing 28. Creating a templates directory in settings.py
TEMPLATE_DIRS = (
'/path/to/devdir/djproject/templates/',
)
|
Django templates support a concept called template inheritance,
which allows site designers to create a uniform look and feel without
repeating content in every template. You can use inheritance by
defining a skeleton, or base, document with block tags. These block
tags are filled by page templates with content. This example shows an
HTML skeleton with blocks called title, extrahead, and content:
Listing 29. Skeleton document, templates/base.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Company Site: {% block title %}Page{% endblock %}</title>
{% block extrahead %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
|
To keep the application decoupled from the project, use an intermediary base file as the base for all the Job application page files. For this example, put the application CSS in the base file for simplicity. In a real application, with a properly configured Web server, extract this CSS and put it in a static file served by the Web server.
Listing 30. Intermediary base file, templates/jobs/base.html
{% extends "base.html" %}
{% block extrahead %}
<style>
body {
font-style: arial;
}
h1 {
text-align: center;
}
.job .title {
font-size: 120%;
font-weight: bold;
}
.job .posted {
font-style: italic;
}
</style>
{% endblock %}
|
By default, the Django test server does not serve static files because that is the Web server's job. If, during development, you would like Django to serve images, style sheets, etc., then see the link in Resources on how to turn that feature on.
Now, create two page templates to be loaded and rendered by the
views. The jobs/job_list.html template simply iterates through the
object_list it gets through the context by the index view, and
displays a link to each record's detail page.
Listing 31. The templates/jobs/job_list.html template
{% extends "jobs/base.html" %}
{% block title %}Job List{% endblock %}
{% block content %}
<h1>Job List</h1>
<ul>
{% for job in object_list %}
<li><a href="{{ job.id }}">{{ job.job_title }}</a></li>
{% endfor %}
</ul>
{% endblock %}
|
The jobs/job_detail.html page shows one record, called job:
Listing 32. The templates/jobs/job_detail.html page
{% extends "jobs/base.html" %}
{% block title %}Job Detail{% endblock %}
{% block content %}
<h1>Job Detail</h1>
<div class="job">
<div class="title">
{{ job.job_title }}
-
{{ job.location }}
</div>
<div class="posted">
Posted: {{ job.pub_date|date:"d-M-Y" }}
</div>
<div class="description">
{{ job.job_description }}
</div>
</div>
{% endblock %}
|
The Django template language has been designed with limited functional capabilities. This limitation keeps templates simple for non-programmers and keeps programmers from putting business logic where it doesn't belong, the presentation layer. See the link to the template language documentation in Resources.
Django comes with four sets of generic views that let developers create applications that follow typical patterns:
- List/detail pages (like the above example)
- Date-based breakdown of records (useful for news or blog sites)
- Creation, update, and deletion (CRUD) of objects
- Simple direct template rendering or simple HTTP redirect
Instead of creating boilerplate view methods, all of the business logic is in the urls.py file and is handled by the generic view methods supplied by Django.
Listing 33. Generic views in jobs/urls.py
from django.conf.urls.defaults import *
from jobs.models import Job
info_dict = {
'queryset': Job.objects.all(),
}
urlpatterns = patterns('django.views.generic.list_detail',
(r'^$', 'object_list', info_dict),
(r'^(?P<object_id>\d+)/$', 'object_detail', info_dict),
)
|
Three major changes to this urls.py file are:
- An
info_dictmap object passes along a query set for the Jobs to be accessed. - It uses
django.views.generic.list_detailinstead ofdjproject.jobs.views. - The actual views called are
object_listandobject_detail.
This project follows some requirements to make the transition to generic views work automatically:
- The generic detail view expects an argument named
object_id. - The templates follow the naming pattern:
app_label/model_name_list.html (
jobs/job_list.html) app_label/model_name_detail.html (jobs/job_detail.html) - The list template handles a list named
object_list. - The detail template handles an object named
object.
More options can be passed through the info_dict,
including a paginate_by value that specifies the number of objects
per page.
The next article in this series will examine TurboGears, another Python Web framework, and compare it with Django.
Learn
-
Read an overview of the MVC architecture on Wikipedia.
-
"Fast-track your Web apps with Ruby on Rails"
(developerWorks, June 2005) shows how Ruby on Rails creates Web-based applications.
-
Python.org is the home of the Python
programming language, where you can find links for downloading the
Python interpreter and standard libraries.
-
The Python tutorial will get you started with Python.
-
DjangoProject.com is the
home page for the Django framework. The documentation includes:
- The How to install Django, which shows how to set up Django on a development machine
- The Database API reference, a guide to using the Django ORM library
- The Django template language, a simple guide for template authors
- How to serve static files, an illustration of how to set up Django to serve static files during development (do not do this in production)
- How to use Django with mod_python, a guide to combining Django with Apache using the mod_python module
- Generic views, which shows how to implement common Web application patterns even more quickly with Django's generic views
-
Building
and Distributing Packages with setuptools shows how to install
setuptools, along with
easy_install(part of the Python Eggs package). - Django performance tips shows how to handle lots of traffic with Django.
-
In the developerWorks Linux zone, find more resources for Linux developers.
-
Stay current with developerWorks technical events and Webcasts.
Get products and technologies
-
Order the SEK for Linux, a two-DVD set containing the latest IBM trial software for Linux from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
-
With IBM trial software, available for download directly from developerWorks, build your next development project on Linux.
Discuss
-
Check out developerWorks
blogs and get involved in the developerWorks community.

Ian Maurer is a senior consultant for Brulant, Inc., where he specializes in developing integrated e-commerce solutions using open source and IBM WebSphere technologies for various industries including consumer goods and retail. Ian resides in northeastern Ohio and is a member of the Cleveland Area Python Interest Group.





