kennethkam.com

A Simple Django Blog

I developed a simple Django application that powers kennethkam.com. This entry will describe the workflow I used, from using git as my repository, using fabric for deployment, and various Django apps that can be used to plug straight into your project. This first entry will focus on using virtualenv, git, and knocking up a simple blog while the second one will focus on Django contrib packages, more git topics, and Fabric.

virtualenv is your friend

virtualenv will save you so much time. If you haven’t got it, do this right now.

$ easy_install pip
$ pip install --no-site-packages virtualenv
$ virtualenv django-1.1
New python executable in test-env/bin/python
Installing setuptools............done.

The no-site-packages flag means your environment will not attempt to load packages in your Python’s site-packages. Activate the environment and install the necessary packages into this folder.

$ source django-1.1/bin/activate
(django-1.1)$ pip install django markdown psycopg2

psycopg2 is the Python PostgreSQL driver and markdown is a text-to-HTML conversion tool. Django has support for Markdown but you must have the package for it to work.

There is a cool feature with pip. You can freeze your environment’s site-packages so you can recreate the same environment elsewhere, say, on your production box.

(django-1.1)$ pip freeze > requirements.txt
# Install packages on server
$ pip install -f requirements.txt

To exit the environment, use the deactivate command.

(django-1.1)$ deactivate

git – The Stupid Content Tracker

git is a distributed version control system, and it is extremely easy to use with any project. I used subversion before, but I found that having to run a daemon or a service to maintain access was cumbersome. Git, on the other hand, deals away with this overhead. First, initiate your repository:

(django-1.1)$ mkdir blog_project
(django-1.1)$ cd blog_project
(django-1.1)$ git init
Initialized empty Git repository in /home/kenneth/blog_project

That was easy. I continued to start a Django project.

(django-1.1)$ django-admin.py startproject blog_project

Then I made my first commit.

$ git add .
$ git commit -m "Committing default project"
Created initial commit 8f3eaee: Committing default project
 3 files changed, 107 insertions(+), 0 deletions(-)
 create mode 100644 blog_project/__init__.py
 create mode 100755 blog_project/manage.py
 create mode 100644 blog_project/settings.py
 create mode 100644 blog_project/urls.py

The git add and git commit commands were the most used. You will work on a feature, add it to the staging area, commit it to the repository, repeat. There are other useful commands to do with branches, undoing a commit, unstaging files, viewing diffs, etc. The git docs are a great place to start.

How do you model this

At this time I only have the default Django project files. I wanted a blog, so I started with a new Django app.

(django-1.1)$ cd blog_project
(django-1.1)$ django-admin.py startapp blog

My model for the blog is called Entry. Here it is.

from django.db import models
from tagging.fields import TagField

class Entry(models.Model):
    text = models.TextField()
    title = models.CharField(max_length=100)
    slug = models.SlugField()
    excerpt = models.TextField()
    date = models.DateTimeField()
    draft = models.BooleanField()
    enable_comments = models.BooleanField()
    tags = TagField()

    class Meta:
        verbose_name_plural = "entries"
        ordering = ('-date',)

I said it was simple. The only thing that might raise an eyebrow is TagField, which comes from the django-tagging package. django-tagging gives you generic tagging functionality that works in a similar way to Django’s generic comments framework. You hook your tagging functionality using templatetags in your templates.

There are two interesting things on settings.py I learnt from the djangoproject.com code that I want to share.

# templates are in ./templates relative to settings.py
TEMPLATE_DIRS = [os.path.join(os.path.dirname(__file__), "templates")]

# debug mode if server not run on the production box
import platform
DEBUG = (platform.node() != "web58.webfaction.com")

I had settings.py setup to use a PostgreSQL database on my Ubuntu box. I then ran python manage.py syncdb to create the tables and to setup the admin username and password.

(django-1.1)$ python manage.py syncdb

Routing the traffic

There are no URLs setup at this stage. Before I wrote any code to urls.py, I did a rough sketch of the sitemap. It helped me see what urls I needed to write. Luckily, there weren’t many.

from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()

blog_data = (
    ("queryset", Entry.objects.exclude(draft=True)),
    ("template_name", "blog/index.html"),
)

blog_info = dict(blog_data + (
    ("paginate_by", 10),
))

single_blog_info = dict(blog_data + (
    ("date_field", "date"),
    ("month_format", "%m"),
    ("template_name", "blog/single.html"),
    ("slug_field", "slug"),
))

urlpatterns = patterns('',
    (r'^admin/', include(admin.site.urls,)),
    (r'^$', 'django.views.generic.list_detail.object_list', blog_info),
    url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d+)/(?P<slug>[A-Za-z0-9_-]+)/$', \ 
        'django.views.generic.date_based.object_detail', single_blog_info, name="single"),
)

I have no need for my own custom views, so I helped myself to Django’s excellent generic views. I also uncommented the admin lines to give me free admin. I created two quick templates at blog/index.html and blog/single.html. I fired up the development server and, sure enough, everything works.

Playing nice with Django

Having configured my urls.py, I thought I would play nice with Django and implement the get_absolute_url method for my Entry model, which is all documented very nicely.

class Entry(models.Model):
    ...
    @property
    def year(self):
        return self.date.strftime("%Y")

    @property
    def month(self):
        return self.date.strftime("%m")

    @property
    def day(self):
        return self.date.strftime("%d")

    @models.permalink
    def get_absolute_url(self):
        return ('single', (), \
            {'year': self.year, 'month': self.month, 'day': self.day, 'slug': self.slug})

Implementing it means that your code adheres to the DRY principles, and you can do this in your templates:

<a href="{{ object.get_absolute_url }}">...</a>

which is much better than:

<a href="/object/{{ object.id }}">...</a>

A good start

At this stage, I had a working blog, a free admin system, a version tracked project using git, and a Python environment using virtualenv and pip. This only took a few hours, if not less. I will talk about how I added more features using nothing but Django’s contrib packages, my git experiences, and how to use Fabric to automatically deploy the project to your server in my next entry.

Comments

  • There are no comments.

Add a Comment