Stripped-down Django Tutorial

Django LogoThe django official tutorial consists of 4 parts, unnecessarily long, mixes commands with text, and omit some important details. Here is an attempt at providing a more complete and more condensed version of the tutorial.

  1. Install Django. The latest tutorial uses a newer version of Django that is not backward compatible. Therefore, it is best to install Django from svn:
    svn co django-trunk
    cd django-trunk
    sudo python install
  2. Create a Project. Create a new project under the current directory called mysite: startproject mysite
  3. Configure Database. For this example, we will use an sqlite3 database.
    mkdir db
    chmod a+rwx db
    chmod g+s db

    The Web server needs access and write permission. The g+s ensures that group ownership of created files in db remains the same that of db. Edit mysite/ as follows:

    DATABASE_ENGINE = 'sqlite3'
    DATABASE_NAME = '/path/to/mysite/db/mysite.db'

    Create the database and the tables:

    python syncdb
  4. Create a Polls Application. From the mysite directory, run the command:
    python startapp polls
  5. Create the Polls Data Model. Edit the file mysite/polls/ as follows:
    from django.db import models
    class Poll(models.Model):
        question = models.CharField(max_length=200)
        pub_date = models.DateTimeField('date published')
        def __unicode__(self):
            return self.question
    class Choice(models.Model):
        poll = models.ForeignKey(Poll)
        choice = models.CharField(max_length=200)
        votes = models.IntegerField()
        def __unicode__(self):
            return self.choice

    And synchronize the database:

    python syncdb
  6. Enable the Admin Interface Enable the admin application in mysite/

    Then enable the admin URLs in mysite/

    from django.conf.urls.defaults import *
    from django.contrib import admin
    urlpatterns = patterns('',
        (r'^admin/', include(,

    And synchronize the database:

    python syncdb

    Note that at this point we only enabled the admin URL. The home page will no longer work until we add another pattern to match it.

  7. Test the admin Interface. Either configure your Web server according to the docs, or use the development built-in server from the mysite direcotry:
    python runserver

    Visit the admin interface from a Web browser at:
  8. Enable admin in Polls. Create a file mysite/polls/ with:
    from mysite.polls.models import Poll
    from django.contrib import admin

    You should now see the Polls in the admin interface.

    Note: don’t forget to restart the Web server every time you make changes to your python code.

  9. Customize the Polls Admin. Edit mysite/polls/ to be:
    from mysite.polls.models import Poll
    from mysite.polls.models import Choice
    from django.contrib import admin
    class ChoiceInline(admin.TabularInline):
        model = Choice
        extra = 3
    class PollAdmin(admin.ModelAdmin):
        fieldsets = [
            (None,               {'fields': ['question']}),
            ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
        inlines = [ChoiceInline]
        list_display = ('question', 'pub_date', 'was_published_today')
        list_filter = ['pub_date']
        search_fields = ['question']
        date_hierarchy = 'pub_date', PollAdmin)

    Each line adds a new functionality to the admin interface, and the functions are independent of each other. Most should self-evident. Feel free to experiment.

    To customize the how the “was_published_today” header is displayed, change mysite/polls/ to:

        def was_published_today(self):
                return ==
        was_published_today.short_description = 'Published today?'
  10. Setup URLs. Create the polls urls in mysite/polls/
    from django.conf.urls.defaults import *
    urlpatterns = patterns('mysite.polls.views',
        (r'^$', 'index'),
        (r'^(?P<poll_id>\d+)/$', 'detail'),
        (r'^(?P<poll_id>\d+)/results/$', 'results'),
        (r'^(?P<poll_id>\d+)/vote/$', 'vote'),

    Add polls to the site urls in mysite/ by including the pattern:

        (r'^polls/', include('mysite.polls.urls')),
  11. Setup a Templates Directory.Create a directory mysite/templates. Update mysite/ to use this direcotyr:
  12. Create the Index View and Templates. Create a file mysite/polls/
    from django.shortcuts import render_to_response, get_object_or_404
    from mysite.polls.models import Poll
    def index(request):
        latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
        return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})

    And the index template mysite/templates/polls/index.html:

    {% if latest_poll_list %}
        {% for poll in latest_poll_list %}
            <li>{{ poll.question }}</li>
        {% endfor %}
    {% else %}
        <p>No polls are available.</p>
    {% endif %}
  13. Create the Detail View and Template. Add to mysite/polls/
    def detail(request, poll_id):
        p = get_object_or_404(Poll, pk=poll_id)
        return render_to_response('polls/detail.html', {'poll': p})

    And create the template mysite/templates/detail.html:

    <h1>{{ poll.question }}</h1>
    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
    <form action="/polls/{{ }}/vote/" method="post">
    {% for choice in poll.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ }}" />
        <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
    {% endfor %}
    <input type="submit" value="Vote" />
  14. Create the Vote Handler. Edit mysite/polls/ to include:
    from django.shortcuts import get_object_or_404, render_to_response
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    from mysite.polls.models import Choice, Poll
    # ...
    def vote(request, poll_id):
        p = get_object_or_404(Poll, pk=poll_id)
            selected_choice = p.choice_set.get(pk=request.POST['choice'])
        except (KeyError, Choice.DoesNotExist):
            # Redisplay the poll voting form.
            return render_to_response('polls/detail.html', {
                'poll': p,
                'error_message': "You didn't select a choice.",
            selected_choice.votes += 1
            # Always return an HttpResponseRedirect after successfully dealing
            # with POST data. This prevents data from being posted twice if a
            # user hits the Back button.
            return HttpResponseRedirect(reverse('mysite.polls.views.results', args=(,)))
  15. Create the Results View and Template. Edit mysite/polls/ to include the results view:
    def results(request, poll_id):
        p = get_object_or_404(Poll, pk=poll_id)
        return render_to_response('polls/results.html', {'poll': p})

    Create a results templates file mysite/templates/polls/results.html with:

    <h1>{{ poll.question }}</h1>
    {% for choice in poll.choice_set.all %}
        <li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
    {% endfor %}

Et Voilà! These steps cover most of the 4-part tutorial, excluding templating the admin area and using the generic views. For completeness, I include a sample apache config file that uses virtual hosts so you can go through this tutorial with apache.

<VirtualHost *>
    ServerAdmin webmaster@localhost
    DocumentRoot /path/to/django-projects/mysite

    <Directory /path/to/django-projects/mysite >
        Options Indexes FollowSymLinks MultiViews
        AllowOverride None
        Order allow,deny
        allow from all

    ErrorLog /var/log/apache2/error.log

    # Possible values include: debug, info, notice, warn, error, crit,
    # alert, emerg.
    LogLevel warn

    CustomLog /var/log/apache2/access.log combined
    ServerSignature On

<LocationMatch "/mysite/*">
    SetHandler mod_python
    PythonHandler django.core.handlers.modpython
    SetEnv DJANGO_SETTINGS_MODULE mysite.settings
    PythonOption django.root /mysite
    PythonDebug On
    PythonPath "['/path/to/django-projects'] + sys.path"

<Location "/media">
    SetHandler None

<LocationMatch "\.(jpg|gif|png)$">
    SetHandler None


If you find any omissions or inaccuracies, please report them privately or in the comments and I’ll update the tutorial accordingly.

13 thoughts on “Stripped-down Django Tutorial

  1. RAZAFY Lerina Jean-Yves

    You have made a wonderful summary of the official tutorial. Its Ideal to refresh the memory and quickly explain to new djangonauts why Django Rocks 🙂

    you need to syncdb before step.7. After adding “django.contrib.admin”,
    the table django_admin_log must be created.

  2. George Georgalis

    Great revision! Here’s a couple comments.

    Step 5 : Should read mysite/polls/ not

    Step 6: FYI, url admin in 1.0.2 is:

    Step 7: This may be easier for people that need to change it:
    alternate: python runserver

    Step 13: Incorrect URL, line should read:


  3. George Georgalis

    oops, it dropped my html looking correction…
    in Step 13, the “form” line from the code should not contain “/mysite”

  4. mark

    thanks for taking the time to do this its useful.

    for a real beginner i think too much to take in, but useful in conjunction with the full tutorial.


  5. Jeezuz Kraist

    from django.conf.urls.defaults import *
    has been replaced with:
    from django.conf.urls import patterns, url, include
    In Django 1.6 and above.

Leave a Reply

Your email address will not be published. Required fields are marked *