django@zerokspot: The syndication framework

Welcome to the 2nd installment of my little series where I write about what I learnt today when trying to implement zerokspot.com using Django. This time it will be all about Django’s contributed syndication framework


First of all: What is it? django.contrib.syndication is a framework consisting of a single view as well as a couple of helper classes and functions that allow developers to easily extend their websites with RSS and Atom feeds.

So what you’d normally have to do is just write an URL pattern pointing to the framework’s feed view and pass to it a Feed class that actually fetches the data you want to have in your feed. For detail on the general usage of this module take a look at the official documentation :)

Coming from Drupal I now want to have a remotely similar URL scheme for feeds. Something like /weblog/tags/lala/feed/atom/ would for example return a feed of the latest weblog entries tagged with “lala”.

At first I thought, Django couldn’t do it and would require some format like /weblog/feed/tags/lala/atom, basically with the relevant part of the content selection after an URL separator like “feed” or “feeds” (or whatever you prefer). So I thought about it a little bit and started writing a wrapper for the syndication.view feed in order to pass additional arguments like the tag and the feed type (“atom” in this case).

The URL configuration for this looked now somewhat like this:

urlpatterns = patterns('',
(r'^tags/(?P<tag>.+)/feeds/(?P<url>.*)/$',
	'zerokspot.weblog.views.tag_feed'),
)

with the actual view now looking this way:

def tag_feed(request,url, tag, feed_dict={}):
	"""
	Wraps django's feed generating view in order to pass additional stuff
	to the feed class.
	"""
	# Prepare classes for the given tag and add them to the feed_dict
	rss = feeds.createTagFeed(tag,'rss')
	atom = feeds.createTagFeed(tag,'atom')
	feed_dict['rss']=rss
	feed_dict['atom']=atom
	return django.contrib.syndication.views.feed(request,url,feed_dict)

As you can see here, I’m using a custom factory method that creates a Feed class for every tag and (‘rss’,‘atom’,) combination out there on the fly.

def createTagFeed(_tag,_type):
	# Extract the tag from the request_path
	_types = {'rss':Rss201rev2Feed,'atom':Atom1Feed}
	tag = get_object_or_404(Tag,name=_tag)

	class klass(Feed):
		title = "tag:%s"%(tag.name,)
		link = "/weblog/tags/%s/"%(tag.name,)
		description_template = 'feeds/latest_description.html'
		feed_type = _types[_type]
		def items(self):
			return TaggedItem.objects.get_by_model(Entry,tag).order_by('-datetime')[:10]
	return klass

While perhaps a funny idea, it’s … strange and quite obscure. Esp. since I consider stuff like that a quite common use-case for feeds and therefor couldn’t really believe than some quite flexible URL handling wasn’t somewhere in the syndication framework.

So I kept looking and eventually found it (I have to admit that I was blind when I looked for the first time and missed it ;-))

Then I went looking a little bit more and found a hint in the cab-project’s source code that indicated some get_object method in order to handle more complicated URLs. And thanks to some input from jdunck_ on #django I finally found my way around that Feed class ;)

I guess the easiest way now to achieve feeds behind such an URL-scheme would be something like this:

urlpatterns = patterns('',
(r'(?P<url>tags/[^/]+)/feed/',feed,
	{'feed_dict': {'tags':TagFeed}}),
)

This does no longer require a custom view (the feed function referenced here is the one offered by the syndication framework) but just need the TagFeed class:

	class TagFeed(Feed):
		description_template = 'feeds/latest_description.html'
		feed_type = Atom1Feed
		def get_object(self,bits):
			tagname = bits[0]
			tag = get_object_or_404(Tag,name=tagname)
			return tag
		def title(self,obj):
			return "tag:%s"%(obj.name,)
		def link(self,obj):
			return "/weblog/tags/%s/"%(obj.name,)
		def items(self,obj):
			return TaggedItem.objects.get_by_model(Entry,obj)[:10]

Much better, isn’t it? ;-) This one just offers Atom1.0 feeds for now, but you should get the idea. Thanks jdunck_ for saving me from a breakdown :)