A Django Primer - Creating a Simple Blog Application

Preface

A couple of months ago, Daniel and I began drafting ideas for a new website. When it came down to choosing a language to begin our work, we had a tough decision to make. We both traditionally stuck to PHP for web development in the past, but we wanted to use a web framework for rapid development. The decision to use Django came from three things: it’s written in Python, there are proven real world examples of large-scale websites using Django (the developers kept performance in mind and implemented a nice caching framework), and the documentation is good.

While this primer will mainly aim at those with no Django experience (or experience with a web framework at all), I am assuming that you have some understanding of Python. I will go over the details for creating a simple blog application. While Django is still not at 1.0 yet (as of this writing), there are things which may change. I will do my best to make sure to this information stays up to date. I am also assuming you are using the latest development version (0.96-dev as of this writing). Installation of the development version will require Subversion and is documented on the official Django project website here.

Table of Contents

  1. Introduction
  2. Getting Started
  3. Models
  4. The Admin Site
  5. Generic Views and URLs
  6. Templates
  7. Conclusion


Introduction

Django adopts the standard MVC (Model-View-Controller) design pattern, but instead the acronym is a little bit different. Instead, their naming convention is the MTV (Model-Template-View). Essentially, your code will be broken up into three main components. The model is an object relational mapping to your database schema. So each model is a class which represents a table in your database. This abstracts the underlying database management system and also generates the SQL statements based on the methods you call on your model. This is attractive to those who are afraid of touching SQL as virtually little to no SQL knowledge is required by using Django’s model API. The template is simply the HTML for your view (if you’ve used MVC web frameworks in the past, remember that a view is the controller in MVC). If you have used PHP in the past, you may have mixed PHP code and HTML together, which in turn became a mess to look at and maintain. In addition to HTML, Django’s template system allows you to use simple constructs for template-specific logic. For instance, you could display different messages depending on whether or not a user is logged in. The view can be thought of as a page in your application. It could be the homepage or a page to display a user’s information, for instance.

This primer will guide you through creating a simple blog, which will be extended upon throughout this primer. I will explain a concept, and then show an example of writing a blog application, but I encourage you to stray away from this if you have a different application you would like to implement.


Getting Started

I am assuming you have already installed Python, Django, and the database management system of your choice (PostgreSQL is recommended). Also, I will be using $PYTHON_DIR and $DJANGO_DIR as a shorthand for explaining the location of files. For example, $PYTHON_DIR in my case is C:/Python25 and my $DJANGO_DIR is C:/Python25/lib/site-packages/django.

First, we’ll need to create our project.

  • NOTE: Depending on the method you’ve installed Django and the platform you’re on, you may need to first create a symlink to the django-admin.py script. django-admin.py is located in $DJANGO_DIR/bin/django-admin.py. In Windows, I created a batch script, django-admin.bat, which I placed in my $PYTHON_DIR since it is in my environment’s PATH variable. The script is just:

    c:\python25\python.exe C:\Python25\lib\site-packages\django\bin\django-admin.py %1 %2 %3 %4 %5 %6 %7 %8 %9

    If you do end up using this batch script, run django-admin, not django-admin.py in all of the following examples.

To create your project, simply run django-admin.py startproject DjangoPrimer, where DjangoPrimer is the name of your project. This will create a directory named DjangoPrimer in your current working directory with the following files:

  • __init__.py — Blank module to initialize the package (your Django project)
  • manage.py — Wrapper to django-admin.py with your project’s settings module and project added to sys.path
  • settings.py — Django settings for your project
  • urls.py — URL regular expression mappings to views (this will be explained in greater detail shortly)

We’ll start by creating our first application — our blog. In the current working directory of your project, type python manage.py startapp blog. This will generate a new folder called blog with stubs (empty placeholders) for your views and models.

Next, we’ll need to make a few modifications to settings.py. I won’t be going through the entire file since the Django documentation for settings does a thorough job of that. Of interest to us are the database settings. You need to choose the engine of the database management system you are running. The Django installation documentation describes what you need to get your database running. The changes I have made to settings.py are as follows:

  • Change the database settings to reflect your database
    DATABASE_ENGINE = 'postgresql'
    DATABASE_NAME = 'DjangoPrimer'
    DATABASE_USER = 'bigheadlabs'
    DATABASE_PASSWORD = ''
    DATABASE_HOST = '' # Default: localhost
    DATABASE_PORT = '' # Default port
  • Add our new blog application, the Django authentication application (usually installed by default), and the Django automatic admin site. (The authentication application will automatically allow us to have user authentication, and the admin site will let us control all the applications of our site without having to design these normally tedious pages.)
    INSTALLED_APPS = (
      'django.contrib.auth',
      'django.contrib.contenttypes',
      'django.contrib.sessions',
      'django.contrib.sites',
      # The following are new.
      ’django.contrib.admin’,
      # DjangoPrimer is our project and blog is the application
      ’DjangoPrimer.blog’,
    )
  • Add a path to our templates for later. This has to be an absolute path, so your value will vary.
    TEMPLATE_DIRS = (
      'c:/Projects/DjangoPrimer/templates',
    )

By adding items to the INSTALLED_APPS tuple, we’re telling Django where the models and views we want to use are located. We can now create our database, which is DjangoPrimer, the value we specified for DATABASE_NAME in our settings. (In the interactive shell for your database, you can use “CREATE DATABASE DjangoPrimer;“.) At this point, we can test our configuration by typing python manage.py syncdb. This will create the database tables for the models for all of our installed applications. Since we’re using Django’s authentication application, we’ll be able to create our personal superuser account. If you happened to enter no when it asked you to create the superuser during syncdb, you can create the superuser account by doing python manage.py shell and typing these commands:

from django.contrib.auth.create_superuser import createsuperuser
createsuperuser()


Models

Models are where we begin to define what our data looks like. If you’ve had any experience with object-oriented programming languages, then you already know how to model data. Normally, one would have to create the relational schema and write SQL to define the desired data. Then, more SQL queries would be needed to grab data from the database. With Django’s model API, you only need to create a class to model your data, and Django’s object-relational mappings will do all the SQL in the background. (But don’t worry. Even if you’re an SQL expert, you can still write your own, more powerful queries. Django does not limit you.)

For our purposes, the main component of a blog is a Post, so we’ll use that to start with our first model. When you first open models.py in the blog project, you’ll see one line importing the Django model API. There isn’t too much code we need to write here to begin, but let’s just stop and think about what a simple post usually includes. Each post has an author, a date/time of post, a title, and the post itself. The author is a User defined by Django’s authentication application, the date/time post could be a Python datetime object, and the title and post are just strings. Let’s see how we can write a Django model to map to the different data types.

from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
  author = models.ForeignKey(User)
  date = models.DateTimeField()
  title = models.CharField(maxlength=100)
  post = models.TextField()

I’ve imported User model from the Django authentication application (in the django.contrib.auth package) so that we can reference a User. When I defined the class, I subclass models.Model, which handles the low-level database methods (such as saving instances of models). Django’s model API uses fields to map Python types to database fields (column types). For instance, Python datetime objects are different from SQL’s DATATIME data type. By using the model API’s fields, the difference between the two are abstracted away. I will go through each attribute in the model line by line.

  • author = models.ForeignKey(User)
    Each post has an author. Since we’re using Django’s authentication application, we’ve already got the User model defined for us. By stating that author is a ForeignKey, we are defining a Many-to-One relationship between the Post and User models. That is, we are saying that the author attribute references the User model, and a post can have one User as an author, and a User may be the author of any number of Posts.
  • date = models.DateTimeField()
    As stated earlier, we want to map Python datetime objects to the SQL DATETIME data type.
  • title = models.CharField(maxlength=50)
    The title should be a small string. Imagine a single-line input text box, as opposed to a multi-line textarea. We pass the argument maxlength=100 to prevent the input from being too large (and thus no longer a title).
  • post = models.TextField()
    A post should be a large amount of text. Even if we didn’t specify a maxlength for a CharField, there is still a limitation at the database level for CHAR types. So we use a TextField so our post is not limited in size.

If you’re familiar with SQL, you might be wondering why I haven’t defined a primary key. A primary key is simply a unique identifier for each instance of our model (or a row in a table), i.e. to differentiate Post instances from each other. Django automatically creates a primary key field named id if you do not specify any field in your model as a primary key. Django uses AutoField, an auto-incrementing integer, as the default primary key. To specify a different field as a primary key, you can pass in primary_key=True to a field in your model. (Django currently does not support composite keys, a primary key made from multiple attributes, by specifying multiple primary key attributes)

Now, like we did earlier, we can use python manage.py syncdb to have Django create our new model as a table in the database. Keep in mind, however, that syncdb will only create tables for new models. It will not update existing tables if we have changed the model. In order to update an existing table, you will have to manually alter the table or use python manage.py sqlreset [appname] to drop and re-create the table (which will result in a loss of all data for the models in the application).

Let’s test out our new model and create our first post. Since we haven’t defined any web interfaces (the admin site) to interact with our model yet, we will use the Python console. python manage.py shell will open the Python console with our project’s settings already loaded, so we can begin to use it without having to worry about paths or manually loading the settings.

  • First we need to import our Post model
    from DjangoPrimer.blog.models import Post
  • And we also need to import the User model so we can point the author to ourselves
    from django.contrib.auth.models import User
  • We need a datetime object to set the date of our post
    from datetime import datetime
  • We can use the Database API to query for the user account we created when we first issued a syncdb.
    me = User.objects.get(id=1)
    or
    me = User.objects.get(username='Jason')
  • We can then just create a new instance of a Post and use the object to create our first post.
    first_post = Post()
  • Now let’s set the attributes of our first post.
    first_post.author = me
    first_post.date = datetime.now()
    first_post.title = 'My first post!'
    first_post.post = 'How exciting. That is all!'
  • Since Django’s database API includes transaction management, the data is not automatically saved in the database until we explicitly tell it to save.
    first_post.save()

To see that it actually worked, you can look in your database under the blog_post table to see the data you just saved. You can also use the database API to retrieve all the Post objects. Post.objects.all() will return a list of all Post objects, which we should see one. Since it is a list, you can do something like Post.objects.all()[0].title to see that it is indeed our post. You might notice if you have more than one post, that the string displayed for each Post in the list is Post: Post object, which isn’t very informative. If we wanted to, we could write the __str__() method for our Post class to give our objects a string representation.

def __str__(self):
  return self.title

Now, when you do Post.objects.all(), the list should show you the title of each Post object.


The Admin Site

The models play an important role in a website, but we still have yet to create any web pages with Django. Normally after we’ve finished creating the database schema, we would have to create the tools to maintain the tables. This usually involves implementing the repetitive CRUD (Create, Read, Update, Delete) operations for all of our database tables. The admin site that is provided by Django automatically gives you this for free. We’ve already installed the admin application earlier, so let’s see how much work we have to put in to get the admin site up and running.

Open the generated urls.py for the project. Uncomment the line below “# Uncomment this for admin:”. If you’ve misplaced it, here it is again:
(r'^admin/', include('django.contrib.admin.urls')),

In our blog models.py, we need to create a nested class within our Post class called Admin which will provide metadata to the admin site about our model. We can provide attributes for the metadata, but for now we can just make an empty class so the admin site will display our model.

class Post:
  ...
  class Admin:
    pass

So if you were counting, we wrote two lines of code and uncommented one line of code. Let’s see what that got us. We can now start a webserver with python manage.py runserver. Once it’s up and running, point your browser to http://localhost:8000/admin/ and login. From here, we can create, read, update, and delete data for all of our installed applications. We can also see the post we created earlier under Posts. Amazing.

Now let’s extend the admin metaclass. You may have noticed that when you listed the blog posts, it showed a list of the titles, and that was it. If we hadn’t written the __str__() method earlier, we would have just seen a list of Post objects. With the list_display attribute, we can choose which attributes of our model to display. This will also allow us to sort columns in the table by that attribute. If we wanted to add a search function, we could add the search_fields attribute with a list of fields we wish to search by. And as a last example, we could and a column which would filter the results by an object. For instance, if we had multiple authors and we wanted to be able to filter all posts by a particular author, we could use the list_filter object. Here’s how our admin metaclass will look now.

class Admin:
  list_display = ('author', 'date', 'title')
  search_fields = ('title', 'post')
  list_filter = ('author', 'date')

Django Primer - Admin site
NOTE: The author filter will not show up unless you have posts by more than one author.


(Generic) Views and URLs

With most of the backend work done, we can now begin doing frontend work — and that’s what a user’s going to see. As I stated in the introduction, the view is the backend where all the logic is written and the template is the frontend that is visible to the user. If we wanted our front page to show the five latest posts, we would create a view which would grab posts from the Post model and pass it into the template. The template then takes that data and turns it into a presentable web page.

But, like much of what I’ve already shown, Django takes away the work of writing your own views for redundant tasks by providing generic views to do these tasks for you. As of this writing, Django provides generic views for:

  1. Rendering a template
  2. Redirecting to another URL
  3. Querying data from a model based on dates
  4. Displaying lists of data from a model
  5. Creating, updating, and deleting objects in the models (the remainder of the CRUD)

It’s worth describing the layout of the urls.py module now. Earlier, we added the admin site to the urls, but we didn’t really go over what was going on. The urlpatterns variable being declared is returned from the patterns function.

urlpatterns = patterns('prefix‘,
  ( r’regexp‘, ‘view or include‘, { additional parameters … } ),
  …
)

The first argument to patterns is a common prefix to prepend all views with. For instance, all of the generic views reside in the django.views.generic package. For each view provided, we would have to type out django.views.generic.simple.direct_to_template, django.views.generic.date_based.archive_index, and so on. Using a prefix of django.views.generic would reduce the name of the views we have to pass to simple.direct_to_template and date_based.archive_index, respectively.

The remaining number of arbitrary arguments are tuples which include a regular expression to match URLs and the view to render for the aforementioned URL. The tuple takes an optional third item, a dictionary, with key-value pairs for additional parameters. The view may be an include statement instead, which tells Django to look in the module specified for additional URLs. Django will also prefix all the URLs in the included module with the URL pattern for the included module. This can be best explained with an example. We will create a URL for our blog’s front page to display the latest posts.

  • Our blog will be located at “/blog/”, and all URLs defined in our blog application will be prefixed by “/blog/”.
    (r'^blog/', include('DjangoPrimer.blog.urls')),
  • We can then create a urls.py in our blog application (package).
    from django.conf.urls.defaults import *
    from DjangoPrimer.blog.models import Post
    urlpatterns = patterns('django.views.generic.date_based',
      (r'^$', 'archive_index',
        { 'queryset' : Post.objects.all(), 'date_field' : 'date', }
      ),
    )

The tuple we created for the URL pattern is of most interest. The regular expression ‘^$’ matches nothing more, so when a user visits “/blog/”, this is the pattern that will be matched. archive_index is a generic view which will display the latest objects by date. archive_index also has required parameters, queryset which is the QuerySet for our model and date_field which is the name of the field in our model to use for the date. (The QuerySet is obtained by using our model’s Manager, which is named objects by default. Using the manager, we can query the database for certain data from our model. In this case, we are getting all objects in the Post model.)


Templates

We finally get to write our first template. Django provides a template language which allows you to add design logic into your pages. Create a new directory in the template directory you set in the settings earlier named “blog”. Name the template “post_archive.html”.

{% for post in latest %}
  <h2>{{ post }}</h2>
  <p>{{ post.post }}</p>
  <b>{{ post.author }}</b> wrote {{ post.post|wordcount }} words on {{ post.date|date }} at {{ post.date|time }}
{% endfor %}

{{ variable }} will evaluate a variable in the context of the template, which is provided by the view. If it encounters a dot (like above), Django will attempt the following lookups in this order: Dictionary key lookup, attribute lookup, method call, or list-index lookup. Since post is an object of our Post class, we can use an attribute lookup to get the values of the post.

{% tag %} is more complex in that it allows for logic, written in Python, to be executed inside of your template. We are passed a variable, latest, into the context of our template from the view. latest is a list of the num_latest (default value of 15 if not specified in the URL pattern) newest objects. In order to get the individual Post objects from this list in our template, we must iterate it using the {% for %} tag, which mimics Python’s for-loop.

When using the pipe after a variable ({{ variable|filter }}), the variable is passed into a filter which will do work on the variable and return a new string. We’ve used this in the template above to get the number of words in the post, and to format the date and time from the datetime object.


Conclusion

What?! Already? I hope I’ve covered enough for you to extend your application or for you to create new applications. I haven’t covered creating your own views yet, and I barely touched upon using the database API. I have plans for articles to discuss these topics in greater detail than I wish to write in this primer.

As an exercise, I recommend extending the blog to support listing posts by year/month (there are more generic views which will allow you to do this easily). For instance, add a URL “/blogs/2007/” to list all months with entries in 2007, and “/blogs/2007/03/” to list entries in March 2007. You can also create a new model for comments and use the generic view for creating objects to add support for comments on your blog.

If you want to get a better understanding of the settings file, the official documentation on Django settings is a comprehensive list of all the options.

Use Django’s documentation on Generic Views to see all the parameters the generic views accept.

There are a ton of built-in template tags and filters that I didn’t get to show in the simple template example. The template guide is a must-read for any template author. If you’re looking at writing your own templates or filters, the template guide for Python programmers is your source.

Best of all, the developers of Django have put a beta version of their book online at djangobook.com.

Feel free to ask any questions you may have in the comments. I would also appreciate any other comments you may have, whether it be criticism, pointing out errata, or if there is a specific topic that you would like to be covered in the future. Thanks for reading.

Jason on March 11th 2007 in Django, Python