SaltyCrane Blog — Notes on JavaScript and web development

Django Blog Project #4: Adding post metadata

I've created a basic blog using SQLite as a backend, deployed it at a shared hosting provider, Webfaction, and added a little CSS for style. Now what? Well, while I say I've created a basic blog, I should say it's a not-quite-yet-basic blog. All it has is some text, which I call Posts, separated by <hr> tags. The next step is to add some post metadata, such as a title, date of creation and tags.

Modify the Post model

To create this new metadata, I first modified the Post model in ~/src/django/myblogsite/myblogapp/

from django.db import models

class Post(models.Model):
    title = models.CharField(maxlength=500)
    date_created = models.DateField()
    tags = models.CharField(maxlength=200)
    body = models.TextField()

    def __str__(self):
        return self.title

    class Meta:
        ordering = ["-id"]

    class Admin:

I added new attributes: title, date_created, and tags. The __str__ method is used to identify a Post instance by its title. This is useful when working in the Administration page. The class Meta: is used to reverse sort the Posts by "id".

Correction 7/6/2008: For the Post's body field, I previously used the line: body = models.CharField(maxlength=999999). However, thanks to Myles's comment below, I've changed this to use the more appropriate TextField.

python syncdb

Whoops, running python syncdb doesn't actually sync the model with the database. It is only used to initially create the database tables. To add the new columns to my table, I would have to enter the SQL commands directly. Though it is not super difficult, and would be good experience since all those hiring managers seem to want SQL experience, I took the lazy route and just replaced my database. (Not like I had much in there anyways.) For your reference, the section Making Changes to a Database Schema in Chapter 5 of The Django Book describes how to solve my problem without replacing my database.

$ cd ~/src/django/myblogsite
$ rm mydatabase.sqlite3 
$ python syncdb 
Creating table auth_message
Creating table auth_group
Creating table auth_user
Creating table auth_permission
Creating table django_content_type
Creating table django_session
Creating table django_site
Creating table django_admin_log
Creating table myblogapp_post

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 sofeng'): sofeng
E-mail address: [email protected]
Password (again): 
Superuser created successfully.
Installing index for auth.Message model
Installing index for auth.Permission model
Installing index for admin.LogEntry model
Loading 'initial_data' fixtures...
No fixtures found.

Oh yeah, I forgot gotta recreate my superuser.

Modify slightly

In versions 0.0.1 and 0.0.2 of my new blog, I passed a list of post bodies to my template. But actually, I could have just passed a list of the Post objects and accessed the body attribute of each post in the template. This saves a line of unecessary code, and provides accessiblity to all the attributes of the Post object. (I also factored out my blog version number from the base.html template.)

from django.shortcuts import render_to_response
from myblogsite.myblogapp.models import Post

def frontpage(request):
    posts = Post.objects.all()
    return render_to_response('frontpage.html', 
                              {'post_list': posts,
                               'VERSION': '0.0.3'})
Correction 7/6/2008: I previously had from myblogapp.models import Post on the second line. This works, but is inconsistent with my below and can (and did for me) cause subtle errors in the future. I corrected the line to read: from myblogsite.myblogapp.models import Post.

Modify my frontpage.html template

This is pretty self-explanatory. I access the the Post object's attributes using the "." (dot).

{% extends "base.html" %}

{% block main %}
  {% for post in post_list %}
    <h2>{{ post.title }}</h2>
    {{ post.body }}
    <div class="post_footer">Created: {{ post.date_created }} | Tags: {{ post.tags }}</div>
  {% endfor %}
{% endblock %}

A little CSS

In between the <style> tags in ~/src/django/myblogsite/templates/base.html:

.post_footer {
  font-family: Verdana, Arial, sans-serif;
hr {
  border: 0;
  color: gray;
  background-color: gray;
  height: 1px;

Start development server and add a couple posts

I started the development server and added a couple of posts in the Admin site:

  • $ cd ~/src/django/myblogsite
    $ python runserver
  • I visited, logged in, and added a couple posts.

Upload to Webfaction server

pushwebf is my alias for hg push --remotecmd /home/sofeng/bin/hg ssh://[email protected]/webapps/django/myblogsite

$ pushwebf
[email protected]'s password:
pushing to ssh://[email protected]/webapps/django/myblogsite
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 7 changesets with 20 changes to 10 files

Logged into Webfaction:
$ cd ~/webapps/django/myblogsite
$ hg update -C
9 files updated, 0 files merged, 4 files removed, 0 files unresolved
$ ~/webapps/django/apache2/bin/restart

Here is a snapshot screenshot of version 0.0.3

The live site can be viewed at

Related posts:
  Django Blog Project #1: Creating a basic blog
  Django Blog Project #2: Deploying at Webfaction
  Django Blog Project #3: Using CSS and Template Inheritance
  Django Blog Project #5: YUI CSS and serving static media


#1 Myles commented on :

Why use: models.CharField(maxlength=999999) when you could use models.TextField().

#2 sofeng commented on :

Myles, Because I didn't know about models.TextField()! Thanks for the tip!

#3 Yulong commented on :

Hi, I am just reading your tutorials; they are great for django noob like me. Just one thing, to jump from #3 to #4, which adding post function in the site, at least in my situation, would cause an error saying "no such column blogapp_post.title" when trying to get on the running server. I googled a solution: use python reset [blog app name], then syncdb, would solve the problem. Hope it helps.

#4 Stephane commented on :

Hello, I'm a beginner in Django-python, but it seems that there is a mistake in the news For me it doesn't work with "maxlength" but it works with "max_length". Can you explain me? Thanks for this course. Stephane