The basics of creating a tumblelog with Django

On my new homepage, a combined list of my tweets, bookmarks, and user comments on my site appear underneath my latest blog entries. I use a fun bit of Django code to pull these various items together, sorted by publication date, and I’d like to share how this bit of tumblelog-like functionality works. The basic concept is this:

Every time an object of type A, B, or C is created, create an object of type D that does nothing but point to an A, B, or C and keep track of its publication date.

In my case, A, B, and C are Bookmark (from Del.icio.us), Status (from Twitter), and FreeComment(from Django). D is an object I call a Stream Item. The basic Django model for this is below:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 from django.db import models from django.contrib.contenttypes import generic from django.template.loader import render_to_string class StreamItem ( models . Model ): content_type = models . ForeignKey ( ContentType ) object_id = models . PositiveIntegerField () pub_date = models . DateTimeField () content_object = generic . GenericForeignKey ( 'content_type' , 'object_id' ) def get_rendered_html ( self ): template_name = 'blog/includes/stream_item_ %s .html' % ( self . content_type . name ) return render_to_string ( template_name , { 'object' : self . content_object })

Django’s ContentTypes framework allows us to choose what other model we are pointing to, and what the ID of the specific instance of that model is. The GenericForeignKey allows us to retrieve that object just like we would with a foreign key to a known model. We store the pub_date in the StreamItem as well, since objects we point to may use a different field name and we want to have a consistent field by which to sort. The get_rendered_html method simply passes the retrieved object to a template with that object’s name.

Now that the model is defined, a StreamItem has to be created whenever a Bookmark, Status, or FreeComment is created. Conveniently, Django sends a ‘signal’ after saving any object, known as post_save, along with an argument ‘created’ that returns True if the object was just created for the first time. (Signals are sent at other times, as well. See signals documentation). The idea is this:

Create a function that saves a new StreamItem. Invoke this function whenever the post_save signal is sent from a Bookmark, Status, or FreeComment object. The basic function looks like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from django.db.models import signals from django.contrib.contenttypes.models import ContentType from django.dispatch import dispatcher from appname.models import Bookmark , Status from comments.models import FreeComment def create_stream_item ( sender , instance , signal , * args , ** kwargs ): # Check to see if the object was just created for the first time if 'created' in kwargs : if kwargs [ 'created' ]: create = True # Get the instance's content type ctype = ContentType . objects . get_for_model ( instance ) # Special cases for different date fields if ctype . name == 'free comment' : pub_date = instance . submit_date elif ctype . name == 'bookmark' : pub_date = instance . time else : pub_date = instance . pub_date # Special case for FreeComments to ensure the comment is public # This prevents comments in moderation or thought to be spam from appearing if ctype . name == 'free comment' : if instance . is_public == False : create = False if create : si = StreamItem . objects . get_or_create ( content_type = ctype , object_id = instance . id , pub_date = pub_date ) # Send a signal on post_save for each of these models for modelname in [ Status , Bookmark , FreeComment ]: dispatcher . connect ( create_stream_item , signal = signals . post_save , sender = modelname )

First, check to see if the object was just created for the first time. If so, indicate in which field that object’s model stores its publication date. Perform any other checks to make sure you want to create the StreamItem. Then create the StreamItem.

Now, a list of all these objects can be accessed by any view by using StreamItem.objects.all().

Djangosnippets can be of assistance in helping you pull in feeds from Del.icio.us, Twitter, or any other service you’d like to use, as can the Python libraries pydelicious and Python Twitter.

This is my first attempt at a code walk-thru, so please leave a comment if anything is not explained clearly. I don’t doubt there are more efficient ways to accomplish this same goal, so feel free to share those, too.

There is now a second part“) that explains how these items actually show up on the homepage.