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.
There are no comments.