Python Django Introduction

Recently, a colleague told me he want to build several new system with Python and Django and going to ask me questions. I think it's necessary to share a introduction.

Python and Django installation

Install Python

Google "download python", and install Python 2.x, but not Python 3.x .
(It's very important before Django 1.5 release and become popular.)

  • on windows, after installation, you'd better add the python executable path and "Script" path to PATH env.

Install Django

Google "download Django" and download a tarball, i.e. django 1.4.2 .

Extract files from the tarball and run

python setup.py

  • on linux, you need run setup.py under root or sudo

Project initialization

Start a new Django project

django-admin.py startproject mysite

  • on windows, after installation, you'd better add the python executable path and "Script" path to PATH env.

The following files will be generated.

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py

Startup server

python manage.py runserver

You will see

Validating models...

0 errors found
Django version 1.4.2, using settings 'mysite.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

Start a browser and open the url http://127.0.0.1:8000/

You will see

It worked!
Congratulations on your first Django-powered page.

And the following log will printed on the console

[date time] "GET / HTTP/1.1" 200 1957

Press Control+BREAK to end the test server.

Database configuration

Install and setup a database server is not the topic of this article, so we explain database configuration with the simplest SQLite3.

Edit mysite/settings.py and make DATABASE section like the following

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'C:/home/python/mysite/mysite.sqlite3.db',                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

Sync the configuration with Django

python manage.py syncdb

You will see the following output and you need answer the configuration questions.

Creating tables ...
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_user_permissions
Creating table auth_user_groups
Creating table auth_user
Creating table django_content_type
Creating table django_session
Creating table django_site

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (leave blank to use 'administrator'):
E-mail address:
Error: That e-mail address is invalid.
E-mail address: your@email.com
Password:
Password (again):
Superuser created successfully.
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)

Model and Database API

Create models

manage.py startapp polls

The following files will be created.

polls/
    __init__.py
    models.py
    tests.py
    views.py

edit models and make it like the following

from django.db import models
 
class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
 
class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    choice = models.CharField(max_length=200)
    votes = models.IntegerField()

Activate Models

We need tell Django pluggable configuration the model is active, so edit settings.py add 'polls'.

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    # 'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
    'polls',
)

Execute manage.py extract sql

python manage.py sql polls

The following sql will be output

BEGIN;
CREATE TABLE "polls_poll" (
    "id" serial NOT NULL PRIMARY KEY,
    "question" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
CREATE TABLE "polls_choice" (
    "id" serial NOT NULL PRIMARY KEY,
    "poll_id" integer NOT NULL REFERENCES "polls_poll" ("id") DEFERRABLE INITIALLY DEFERRED,
    "choice" varchar(200) NOT NULL,
    "votes" integer NOT NULL
);
COMMIT;

The following commands is useful too.

  • python manage.py validate — Checks for any errors in the construction of your models.
  • python manage.py sqlcustom polls — Outputs any custom SQL statements (such as table modifications or constraints) that are defined for the application.
  • python manage.py sqlclear polls — Outputs the necessary DROP TABLE statements for this app, according to which tables already exist in your database (if any).
  • python manage.py sqlindexes polls — Outputs the CREATE INDEX statements for this app.
  • python manage.py sqlall polls — A combination of all the SQL from the sql, sqlcustom, and sqlindexes commands.

Sync db

python manage.py syncdb

You will see

Creating tables ...
Creating table polls_poll
Creating table polls_choice
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)

Database API

After activate the models, we can access it with database API. Start shell

python manage.py shell

>>> from polls.models import Poll, Choice   # Import the model classes we just wrote.
 
# No polls are in the system yet.
>>> Poll.objects.all()
[]
 
# Create a new Poll.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> p = Poll(question="What's new?", pub_date=timezone.now())
 
# Save the object into the database. You have to call save() explicitly.
>>> p.save()
 
# Now it has an ID. Note that this might say "1L" instead of "1", depending
# on which database you're using. That's no biggie; it just means your
# database backend prefers to return integers as Python long integer
# objects.
>>> p.id
1
 
# Access database columns via Python attributes.
>>> p.question
"What's new?"
>>> p.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)
 
# Change values by changing the attributes, then calling save().
>>> p.question = "What's up?"
>>> p.save()
 
# objects.all() displays all the polls in the database.
>>> Poll.objects.all()
[<Poll: Poll object>]

Add string conversion method to make objects readable.

  • Because Django deal all string with unicode internally, we need add unicode to all models.
class Poll(models.Model):
    # ...
    def __unicode__(self):
        return self.question
 
class Choice(models.Model):
    # ...
    def __unicode__(self):
        return self.choice

Add more python methods

from datetime import datetime
from django.utils import timezone
# ...
class Poll(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

Continue with database API

>>> from polls.models import Poll, Choice
 
# Make sure our __unicode__() addition worked.
>>> Poll.objects.all()
[<Poll: What's up?>]
 
# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Poll.objects.filter(id=1)
[<Poll: What's up?>]
>>> Poll.objects.filter(question__startswith='What')
[<Poll: What's up?>]
 
# Get the poll whose year is 2012.
>>> Poll.objects.get(pub_date__year=2012)
<Poll: What's up?>
 
>>> Poll.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Poll matching query does not exist.
 
# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Poll.objects.get(id=1).
>>> Poll.objects.get(pk=1)
<Poll: What's up?>
 
# Make sure our custom method worked.
>>> p = Poll.objects.get(pk=1)
>>> p.was_published_recently()
True
 
# Give the Poll a couple of Choices. The create call constructs a new
# choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a poll's choices) which can be accessed via the API.
>>> p = Poll.objects.get(pk=1)
 
# Display any choices from the related object set -- none so far.
>>> p.choice_set.all()
[]
 
# Create three choices.
>>> p.choice_set.create(choice='Not much', votes=0)
<Choice: Not much>
>>> p.choice_set.create(choice='The sky', votes=0)
<Choice: The sky>
>>> c = p.choice_set.create(choice='Just hacking again', votes=0)
 
# Choice objects have API access to their related Poll objects.
>>> c.poll
<Poll: What's up?>
 
# And vice versa: Poll objects get access to Choice objects.
>>> p.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> p.choice_set.count()
3
 
# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any poll whose pub_date is in 2012.
>>> Choice.objects.filter(poll__pub_date__year=2012)
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
 
# Let's delete one of the choices. Use delete() for that.
>>> c = p.choice_set.filter(choice__startswith='Just hacking')
>>> c.delete()

Admin Site

Admin site activation

Edit settings.py and uncomment django.contrib.admin of INSTALLED_APPS section

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
    'polls',
)

Sync database to create admin tables

python manage.py syncdb

Creating tables ...
Creating table django_admin_log
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)

Edit mysite/utls.py

from django.conf.urls import patterns, include, url
 
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
 
urlpatterns = patterns('',
    # Examples:
    # url(r'^$', '{{ project_name }}.views.home', name='home'),
    # url(r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')),
 
    # Uncomment the admin/doc line below to enable admin documentation:
    # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
 
    # Uncomment the next line to enable the admin:
    url(r'^admin/', include(admin.site.urls)),
)

Start server

python manage.py runserver

Open http://localhost:8000/admin/ and enter admin user and password set by app creation steps, you will see admin site.

Create poll application admin page

Create poll/admin.py

from polls.models import Poll
from django.contrib import admin
 
admin.site.register(Poll)

Login into http://localhost:8000/admin/ again and test save date.

Customize admin form

You can customize polls form step by step

Add fields

from polls.models import Poll
from django.contrib import admin
 
class PollAdmin(admin.ModelAdmin):
    fields = ['pub_date', 'question']
 
admin.site.register(Poll, PollAdmin)

Conver to field sets

from polls.models import Poll
from django.contrib import admin
 
class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date']}),
    ]
 
# admin.site.register(Poll)
admin.site.register(Poll, PollAdmin)

Change display class

class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]

Add related models

Edit admin.py and register other models

from polls.models import Choice
 
admin.site.register(Choice)

Change to inline style

class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3
 
class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline]
 
admin.site.register(Poll, PollAdmin)

Change to tabular style

class ChoiceInline(admin.TabularInline):
    #...

Customize admin change list

Edit admin.py to add fields displayed on poll line

class PollAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question', 'pub_date', 'was_published_recently')

Edit models.py to improve the method new attributes

class Poll(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'

Filter and Search

Edit admin.py PollAdmin add filter, search box and date hieracy

list_filter = ['pub_date']
search_fields = ['question']
date_hierarchy = 'pub_date'

Customize look and feel

Create template dir and edit settings.py to add template dir

TEMPLATE_DIRS = (
    '/home/my_username/mytemplates', # Change this to your own directory.
)

Find django install path
On Linux
python -c "
import sys
sys.path = sys.path[1:]
import django
print(django.__path__)"

On Windows

>>> import sys
>>> sys.path = sys.path[1:]
>>> import django
>>> print(django.__path__)
['...python\\lib\\site-packages\\django']
>>> quit()

Copy django admin page to corresponding template sub dir.

copy <your python>\lib\site-packages\django\contrib/admin/templates/admin/base_site.htm

l mytemplates\admin

Application Pages

Page design

  • Poll “index” 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.

URLs design

ROOT_URLCONF of settings.py controls the url processing.

ROOT_URLCONF = 'mysite.urls'

It means mysite/urls.py contains the defination.

urls.py has several urlpatterns lines

(regular expression, Python callback function [, optional dictionary])

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    url(r'^polls/$', 'polls.views.index'),
    url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
    url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
    url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
    url(r'^admin/', include(admin.site.urls)),
)

A url request http://.../polls/23 will match line

url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),

and will call detail function of polls/views.py as

detail(request=<HttpRequest object>, poll_id='23')

Write views

Reference:
https://docs.djangoproject.com/en/1.4/intro