Python Web Development with Django

Ian Maurer
Xteric Technology Group

What is Python?

References:
  • Python: http://www.python.org
  • Jython: http://www.jython.org
  • IronPython: http://www.ironpython.org
  • Comparison to other languages: http://python.org/doc/essays/comparisons.html
  • Wikipedia Entry: http://en.wikipedia.org/wiki/Python_programming_language

The Zen of Python

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
		

The Speed Issue

Since python is interpreted it cannot run as fast as compiled languages.

While it is true that Python cannot run as fast natively compiled languages, its main driver is developer productivity not processing speed.

For those cases where performance is critical, Python does allow for the creation of performance critical modules in C. Also, the psyco project provides python code a significant performance boost.

The Whitespace Issue

Python code is developed with significant whitespace, that's dumb.

Whitespace encourages uniform development standards and this uniformity makes code much more readable, thus making it easier to maintain someone else's code.

Most python developers consider this to be a very positive feature of the language.

The Scalability Issue

Python doesn't "scale".

The biggest sticking point for a lot of people is Python's "global interpreter lock" that prevents single processes from spanning multiple processors, even with threads. The below article talks about the alternatives to a thread programming model and some of the benefits of avoiding it through the use of multiple processes and asynchronous IO:

Needled by Threads
http://cleverdevil.org/computing/30/needled-by-threads

LAMP

In the world of open-source web development has emerged 'LAMP' which is a stack of open source tools and programs used in conjunction to build websites.

LAMP is an acronym for the names of the different components:

  • Linux (operating system)
  • Apache (web server)
  • MySQL, MyPostgreSQL, MyFirebird (database)
  • Perl, PHP, Python, and P'Ruby (programming language)

Some also refer to using the above stack on Windows as "WAMP".

Python Web Development

After several years of too many choices and no leading web frameworks, finally a few projecs have begun gaining critical mass:

  • Zope (Plone)
  • CherryPy (TurboGears)
  • Django

While Zope has been powerful framework for some time, it is not a lightweight, LAMP-styled choice. This presentation is on Django, which I hope will become a mainstream development framework with a community size that rivals frameworks such as Ruby on Rails, Spring, and Struts.

History of Django

Django was created for the development of the Lawrence Journal-World website along with other projects for the Lawrence, Kansas area by the World Company.

The key deveopers of Django are Adrian Holovaty, Simon Willison, Jacob Kaplan-Moss, and Wilson Miner whose profiles, along with more history of the project, can be found on the Django FAQ page: http://www.djangoproject.com/documentation/faq/

There is an interesting speech by the former leader of LJWorld here: http://www.itconversations.com/shows/detail550.html

Pronouncing "Django"

If I continue to pronounce it 'da-zhane-go', please correct me. It is just 'zhane-go'.

Django is named after Django Reinhardt, a gypsy jazz guitarist from the 1930s to early 1950s.

To this day, he's considered one of the best guitarists of all time. Adrian, one of the creators of the framework, likes to play the guitar, so I believe that is the influence for the name of the project. If you want to be a true Django officianado, then you must check out this list of other names they considered for Django:
jacobian.org : Private Dancer?

Getting Involved

The main site for the Django project is: http://djangoproject.com

You can get the information about the SVN repository and how to submit patches at the code site: http://code.djangoproject.com/

There are 2 google groups. The 'users' group for web developers that are using Django to build websites and the 'developers' group for those actively working on the Django code:
http://groups.google.com/group/django-users
http://groups.google.com/group/django-developers

Installation

The Django Installation guide can be found here: http://www.djangoproject.com/documentation/install/

The steps included are:

  • Install Apache and mod_python
  • Install a Database: MySQL, PostgreSQL, or SQLite
  • Install a Database Library: MySQLdb, psycopg, or pysqlite
  • Install Django
    • Download, unzip the tarball, and run 'python setup.py install'
    • Check it out of the Subversion repository

Apache Configuration

Got Apache 2 and mod_python working. For Windows, info and mod_python 3.2.2b download link from this website: http://www.lehuen.com/nicolas/index.php/2005/02/21/39-win32-build-of-mod_python-314-for-python-24

Information configuring httpd.conf for Django can be found here: http://www.djangoproject.com/documentation/modpython/

httpd.conf example

# For Testing Purposes Only:
KeepAlive off
MaxRequestsPerChild  1

<VirtualHost *:8080>
    ServerAdmin webmaster@clepy.org
    DocumentRoot /dev/clepy/htdocs
    ServerName clepy
    ErrorLog logs/clepy-error_log
    CustomLog logs/clepy-access_log common

    SetHandler python-program
    PythonHandler django.core.handlers.modpython
    SetEnv DJANGO_SETTINGS_MODULE clepy.settings.main
    PythonDebug On

    <Location "/media/">
        SetHandler None
    </Location>
</VirtualHost>

Starting a Project

django-admin.py, which is in the django/bin directory, is used to create projects, applications, as well as perform other administrative commands. On my windows machine, I created a batch file in my path (django-admin.bat) that runs the python intepreter passing the python script to it and giving it any arguments supplied.

I created the clepy project in my workspace, with the following command:
django-admin startproject clepy

Project Layout

The startproject command creates this structure:

  • clepy/
    • __init__.py
    • apps/
      • __init__.py
    • settings/
      • __init__.py
      • admin.py
      • main.py
      • urls/
        • __init__.py
        • admin.py
        • main.py

Initialize a Project

You need to make sure your project is in your PYTHONPATH. You can either copy your project's directory to your site-packages directory, or you can add that directory's parent to the PYTHONPATH. I added my workspace (C:\dev) to my PYTHONPATH.

To initialize a project's database use the 'init' command:
django-admin init --settings=clepy.settings.main

The django-admin script usually needs the settings. You can get around this by setting the DJANGO_SETTINGS_MODULE environment variable.

Creating an Application

After creating and initializing your project, you can now start adding applications. Each project can contain any number of applications.

The simple application that I am creating is called 'forums' and it will be a basic message board application. To create the application, from within my clepy/apps directory, I ran this command:
django-admin startapp forums

Application Layout

The startapp command creates this directory structure under the clepy/apps directory:

  • forums/
    • __init__.py
    • models/
      • __init__.py
      • forums.py
    • views/
      • __init__.py

The forums/models/forums.py is the default location for the forums model classes. Model classess can be broken into multiple files if so desired.

Model (forums.py): Category

from django.core import meta

class Category(meta.Model):
    parent = meta.ForeignKey("self", null=True, blank=True, related_name="subcategory")
    name = meta.CharField(maxlength=255)
    updated = meta.DateTimeField(null=True, auto_now=True)
    created = meta.DateTimeField(null=True, auto_now_add=True)

    class META:
        module_name = 'categories'
        verbose_name_plural = 'categories'
        ordering = ['name']
        admin = meta.Admin()

    def __repr__(self):
        return self.name			
			

Model (forums.py): Forum

from django.core import meta

class Forum(meta.Model):
    category = meta.ForeignKey(Category, null=True)
    name = meta.CharField(maxlength=255)
    updated = meta.DateTimeField(null=True, auto_now=True)
    created = meta.DateTimeField(null=True, auto_now_add=True)

    class META:
        ordering = ['name']
        admin = meta.Admin()

    def __repr__(self):
        return self.name
			

Model (forums.py): Topic

from django.core import meta
from django.models.auth import User

class Topic(meta.Model):
    forums = meta.ManyToManyField(Forum)
    subject = meta.CharField(maxlength=255)
    summary = meta.TextField(null=True, blank=True)
    owner = meta.ForeignKey(User, null=True, blank=True)
    updated = meta.DateTimeField(null=True, auto_now=True)
    created = meta.DateTimeField(null=True, auto_now_add=True)

    class META:
        ordering = ['created']
        admin = meta.Admin()

    def __repr__(self):
        return self.subject
			

Model (forums.py): Post

from django.core import meta
from django.models.auth import User

class Post(meta.Model):
    topics = meta.ForeignKey(Topic)
    subject = meta.CharField(maxlength=255)
    body = meta.TextField()
    owner = meta.ForeignKey(User, null=True, blank=True)
    updated = meta.DateTimeField(null=True, auto_now=True)
    created = meta.DateTimeField(null=True, auto_now_add=True)

    class META:
        ordering = ['created']
        admin = meta.Admin()

    def __repr__(self):
        return self.subject
			

Activating Models

Before activating your model, you need to add your application to the INSTALLED_APPS setting in the settings/main.py file.

INSTALLED_APPS = (
    'clepy.apps.forums',
)

Then you use the django-admin utility to install the model which runs the necessary SQL statements to create and register the appropriate tables and classes.
django-admin install forums

Database Tables

mysql> show tables;
+-----------------------------+
| Tables_in_clepy             |
+-----------------------------+
| auth_admin_log              |
| auth_groups                 |
| auth_groups_permissions     |
| auth_messages               |
| auth_permissions            |
| auth_users                  |
| auth_users_groups           |
| auth_users_user_permissions |
| content_types               |
| core_sessions               |
| flatfiles                   |
| flatfiles_sites             |
| forums_categories           |
| forums_forums               |
| forums_posts                |
| forums_topics               |
| forums_topics_forums        |
| packages                    |
| redirects                   |
| sites                       |
+-----------------------------+
20 rows in set (0.09 sec)

Playing with the API

>>> from django.models.forums import *
>>> parent = Category(name="Parent Category")
>>> parent.save()
>>> parent, parent.id
(Parent Category, 1L)


>>> category = parent.add_subcategory(name="A Subcategory")
>>> category.save()
>>> category, category.id
(A Subcategory, 2L)


>>> parent.get_subcategory_list()
[A Subcategory]


>>> forum = category.add_forum(name="My Forum")
>>> forum.save()
>>> forum, forum.id, forum.get_category()
(My Forum, 1L, A Subcategory)

Admin Tool

In order to use the Admin tool, you will need to create a superuser account:

django-admin.py createsuperuser
Username (only letters, digits and underscores): webmaster
E-mail address: webmaster@clepy.org
Password:
Password (again):
User created successfully.

The admin server is a pure python server that is at port 8000 by default:
http://localhost:8000/admin/

Admin Login

Admin META Class

class Topic(meta.Model):
    forums = meta.ManyToManyField(Forum)
    subject = meta.CharField(maxlength=255)
    summary = meta.TextField(null=True, blank=True)
    owner = meta.ForeignKey(User, null=True, blank=True)
    updated = meta.DateTimeField(null=True, auto_now=True)
    created = meta.DateTimeField(null=True, auto_now_add=True)

    class META:
        ordering = ['created']
        admin = meta.Admin(
            list_display = ('subject', 'owner', 'updated', 'created'),
            search_fields = ['subject', 'summary'],
            list_filter = ['created'],
            date_hierarchy = 'created',
        )

Admin List View

Admin Data Entry

Creating a Website

The Admin tool is meant to be used strictly as a "behind-the-scenes" administration site, it is not meant to be the basis for your public website. This next section discusses how the steps for creating your "public interface" using Django.

To create a website that leverages our model, we are going to have 3 steps:

  1. Planning a URL Structure
  2. Creating a View
  3. Creating a Template

Planning a URL Structure

What makes a good URL design is very subjective. The most important thing is that you actually put some thought into your URL design because it is essentially an "interface" to your web application.

In my forum application, I have decided to follow this pattern since it will make it very easy to map URLs to my implementation, while at the same time I have been careful not to paint myself into any corners with regards to future functionality:

top level : /forums/
category  : /forums/category/(category_id)/
forum     : /forums/forum/(forum_id)/
forum page: /forums/forum/(forum_id)/(page_num)/
topic     : /forums/topic/(forum_id)/(topic_id)/
topic page: /forums/topic/(forum_id)/(topic_id)/(page_num)/

Project-Level URL Configuration

URLs are mapped in their own separate modules using Regular Expressions. There is a setting that allows you to specify the "root" URL configuration file:

ROOT_URLCONF = 'clepy.settings.urls.main'

The tutorial on the Django website shows you the refactoring process for pulling your URL configuration into your application. I am going to do that by default in my example. My main URL config file looks like this:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^community/', include('clepy.apps.forums.urls.forums')),
)

Application-Level URL Configuration

Here is the URL configuration for my forums app that is stored in the clepy.apps.forums.urls.forums module:

from django.conf.urls.defaults import *

urlpatterns = patterns('clepy.apps.forums.views.forums',
    (r'^$', 'toplevel_display'),
    (r'^category/(?P<category_id>\d+)/$', 'category_display'),
    (r'^forum/(?P<forum_id>\d+)/$', 'forum_display'),
    (r'^forum/(?P<forum_id>\d+)/(?P<page_num>\d+)/$', 'forum_display'),
    (r'^topic/(?P<forum_id>\d+)/(?P<topic_id>\d+)/$', 'topic_display'),
    (r'^topic/(?P<forum_id>\d+)/(?P<topic_id>\d+)/(?P<page_num>\d+)/$', 'topic_display'),
)

Examinining the URL Configuration

Let's break down the longest URL example. Here is our original plan:

topic page: /forums/topic/(forum_id)/(topic_id)/(page_num)/

Which was converted into a URL configuration line:

(r'^topic/(?P<forum_id>\d+)/(?P<topic_id>\d+)/(?P<page_num>\d+)/$', 'topic_display'),

Which tells Django to call this method:

# module: clepy.apps.forums.views.forums

def topic_page(forum_id, topic_id, page_num=1):
	# handle topic page here...

View

A view is a function whose job is to prepare model objects and execute business logic. It is very similar to Struts actions.

A view receives a request object and a set of arguments that are parsed from the URL string using the URL configuration.

A view needs to return an HTTPResponse object that wraps your content which can be HTML, XML, PDF, etc. etc. If it doesn't return an HTTPResponse, it should throw an exception.

View Stubs

from django.utils.httpwrappers import HttpResponse

def toplevel_display(request):
    return HttpResponse("toplevel_display: %s" % locals())

def category_display(request, category_id):
    return HttpResponse("category_display: %s" % locals())

def forum_display(request, forum_id, page_num=1):
    return HttpResponse("forum_display: %s" % locals())

def topic_display(request, forum_id, topic_id, page_num=1):
    return HttpResponse("topic_display: %s" % locals())

View Stub Display

View Implementation

from django.core import template_loader
from django.core.exceptions import Http404
from django.utils.httpwrappers import HttpResponse
from django.core.template import Context
from django.models.forums import categories

def category_display(request, category_id):
    try:
        category = categories.get_object(pk=category_id)
    except categories.CategoryDoesNotExist:
        raise Http404
        
    subcategory_list = category.get_subcategory_list()
    forum_list = category.get_forum_list()

    t = template_loader.get_template('forums/category_display')
    c = Context({
        'category': category,
        'subcategory_list': subcategory_list,
        'forum_list': forum_list
    })
    
    return HttpResponse(t.render(c))

View Implementation (Shortcuts)

from django.core.extensions import get_object_or_404, render_to_response
from django.models.forums import categories

def category_display(request, category_id):
    category = get_object_or_404(categories, pk=category_id)
    subcategory_list = category.get_subcategory_list()
    forum_list = category.get_forum_list()
    return render_to_response('forums/category_display', locals())

Template Configuration

We now need to create template files that will be used for the rendering of web pages. We are going to use Django's built-in templating system, although there is nothing from stopping you from using a different template system like Cheetah or ZPT. You just cannot use the built-in facilities for retrieving and rendering results.

The first step to using the Django template system is to configure the TEMPLATE_DIRS setting within the main settings file:

TEMPLATE_DIRS = (
    "/dev/clepy/templates",
)

Template Overview

I am going to give just a brief overview of the Django template language. For a complete guide to the language, check out this link:

HTML Author's Template Guide
http://www.djangoproject.com/documentation/templates/

For more information about using the template API or extending it with your own custom features, you can check out the Python developer's guide:

Python Programmer's Template Guide
http://www.djangoproject.com/documentation/templates_python/

Variables

A variable in a template document looks like {{ category.name }}

Note the use of double curly-braces and the standard python dot-notation.

Variables come from the context and the dot tells the system to do one of the following (in order):

  • Dictionary lookup
  • Attribute lookup
  • Method call
  • List-index lookup

Filters

A filter, which is specified using a pipe (|), is a function that modifies the value of the variable it is acting upon. The following example displays a category name in upper case:

{{ category.name|upper }}

Filters can be "chained." The output of one filter applied to the next:
{{ text|escape|linebreaks }} is a common idiom for escaping text contents and then converting line breaks to <p> tags.

A list of filters can be found at the bottom of the author's guide.

Tags

Tags are denoted using this format {% tag %}. Tags are used for control flow logic, importing of libraries, and other basic programming functionality.

The template language has been designed to allow for minimum functionality on purpose, to help drive business logic down to the views and the models and out of the templates.

The most common tags that used are the {% if %} and {% for %} tags. For a complete list of tags and their functionality, see the bottom author's guide, right above the filters section.

Template Inheritence

Django provides a powerful inheritence mechanism that allows you do define a "parent" template that provides a skeleton document that "child" templates can inherit from and fill in the defined blocks.

The key tags that implement this functionality are {% extends %} and {% block %}.

A parent template should define your basic page structure for your entire website with {% block %} tags defining the sections that should be customized by each page. Child templates use the {% extends %} tag to declare the inheritence and then uses the same {% block %} tag to fill in the blocks defined by the parent.

Parent Template Example

<html>
<head>
    <link rel="stylesheet" href="style.css" />
    <title>{% block title %}My amazing site{% endblock %}</title>
</head>

<body>
    <div id="sidebar">
        {% block sidebar %}
        <ul>
            <li><a href="/">Home</a></li>
            <li><a href="/blog/">Blog</a></li>
        </ul>
        {% endblock %}
    </div>
    <div id="content">
        {% block content %}{% endblock %}
    </div>
</body>

Child Template Example

{% extends "base" %}

{% block title %}My amazing blog{% endblock %}

{% block content %}
{% for entry in blog_entries %}
    <h2>{{ entry.title }}</h2>
    <p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}

Additional Topics

Here are a list of other topics that are worth proper examination before starting a Django project:

Q & A

Are there any questions?