1. Skip to navigation
  2. Skip to content

Digging Into Django's Syndication Framework

Today there were two questions in a row on the #django IRC channel about getting Django’s Syndication Framework working. The syndication framework is used to add syndication feeds to your Django project. Unfortunately for the two individuals that I was attempting to help we got nowhere with resolving their issues. It struck me as odd because when I implemented syndication for the first time “it just worked.” I decided to dig into the source of the syndication framework contribution in an attempt to better understand exactly what is going on under the covers.

Setting Up Our URLConf

The first problem that we dealt with was an invalid URLConf specification for the feed. Although the documentation doesn’t state it explicitly, you are required to specify the url parameter in your URLConf definition. So in this case your best bet is to just follow the documentation exactly. A proper URLConf ends up looking like this:


(r'^feeds/(?P.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),

You can futz with the prefix if you want, for instance ’^/rss/feeds/’, but the rest of it should pretty much remain the same. The above URLConf specification will grab all url text after /feeds/ and pass it into the feed() method in the syndication view. The url parameter must at least contain one item after the /feeds/ part of the url. Without this you will get an error that states:

slug isn't registered. 

This error is a bit confusing, especially if you have a slug field on the model that your feed is based upon. The terminology is correct, but sometimes it can throw you off. The url parameter is made up of two parts: slug and params. The slug in this case is the first bits after the /feeds/ url. The params is everything else after that. So for instance if we have a url that looks like this:

/feeds/latest/

The slug will be ‘latest’ and the params will be empty. Likewise if you have a url that looks like this:

/feeds/category/django/

The slug is ‘category’ and the params is ‘django’.

The slug is the lookup value into your feed_dict that was specified in the URLConf. So in the two cases above you need to be sure to have a ‘latest’ and a ‘category’ key in your feed_dict. Continuing with our example, I should have a feed_dict that looks like the following:


feeds = {
    'latest':   LatestEntries,
    'category': LatestEntriesByCategory,
}

If you do not have these specified properly then you will receive an error message that says something like:

Slug 'category' isn't registered.

So if you get that message you know you have a mismatch between what’s in the url and what’s specified in your feed_dict. Ensuring that those two items are synced up will solve most of the problems.

Creating Our Feed Class

That next part of the implementation is creating your Feed class. Continuing with the example above I might implement my Feed classes like this:


from django.contrib.syndication.feeds import Feed
from models import Entry, Category

class LatestEntries(Feed):
    title_template = 'feeds/title.html'
    description_template = 'feeds/description.html'

    link = '/blog/'
    title = "My Blog" 
    description = "Latest entries in my blog." 

    def items(self):
        return Entry.objects.order_by('-created_at')[:5]

class LatestEntriesByCategory(Feed):
    title_template = 'feeds/title.html'
    description_template = 'feeds/description.html'

    def get_object(self, bits):
        if len(bits) < 1:
            raise ObjectDoesNotExist
        return Category.objects.get(slug=bits[-1])

    def title(self, obj):
        return "My blog for category '%s'" % obj.name

    def link(self, obj):
        return obj.get_absolute_url()

    def description(self, obj):
        return "Blog entries recently posted in category %s" % obj.name

    def items(self, obj):
        return Entry.objects.filter(category__slug=obj.slug).order_by('-created_at')[:10]

The first example is simplified by the fact that we only have a slug and no params. There are a couple of interesting things to note in the above code. First I’m specifying my title_template and description_template explicitly. If not specified the names for each of the above classes would be ‘latest_title.html’, ‘latest_description.html’ and ‘category_title.html’, ‘category_description.html’ respectively. Since the templates end up being fairly generic, I specify them in my Feed class so the same templates can be used for all of my feed classes.

Also notice that you can define the properties of your feed using either simple variables or defining methods. For instance, in the LatestEntries class I define description as a simple variable that has the description of my feed. In the LatestEntriesByCategory, instead I define description() as a method on my class, because I want to utilize the item object instance (obj) to provide a category relevant feed.

Another item of note is the get_object() method defined in the LatestEntriesByCategory Feed class. This method is responsible for returning the object that is being looked up. In other words if our url is /feeds/category/django/, the object returned should be an instance of the Category class that is for the django Category. We’re doing some basic checking to be sure that a category was specified, since a /feeds/category/ url doesn’t make sense without specifying the category we want. We then look up the category and return the Category instance object. If the object doesn’t exist or isn’t returned, you will receive a FeedDoesNotExist exception.

There are a lot of methods you can override in your Feed class definition to get the behavior you desire. For instance, if you want to specify an Atom feed instead of an RSS feed, that’s possible. All of the available additional methods are documented quite nicely in the Django Syndication Framework documentation. One you get a basic feed implementation working I encourage you to look into all the different possibilities.

You also may run across another error:

Give your Entry class a get_absolute_url() method, or define an item_link() method in your Feed class.

This message is pretty clear. If your model class, in this case Entry, doesn’t have a get_absolute_ur() method I suggest you add one. It’s pretty integral to a lot of things in Django so it’s best to have one defined. You may also define your own item_link() method in your Feed class if you want to do something funky with the links that are provide for each Feed item. So keep that in mind.

Sort of along these same lines, the Sites framework is required if you’re using a version of Django before changeset 5654. That changeset removes the requirement for the Sites framework. If the Sites framework is not installed the domain information is garnered from RequestSite, the current HttpRequest SERVERNAME information, instead.

Defining the Templates

In addition of the above we need one more piece to complete the puzzle, our templates. The templates are responsible for rendering the feed data, just like templates in your application are responsible for rendering the view data. As a result, the templates end up being very simple. In our case we will just use the defaults:

/feeds/title.html

{{ obj.title }}
/feeds/description.html

{{ obj.description_html }}

After doing all of the above test it out. Hopefully everything is in place and things will come up roses. If you’re using the Safari web browser and testing it against localhost, you might see the following error:

Safari can't open the page 'localhost:8000/blog/feeds/category/django/' because it cannot redirect to locations starting with 'localhost:'.    

As far as I can tell this is an issue with Safari when trying view a feed against localhost. On live sites it works fine. Also if you test in another browser like Firefox you should not have a problem.

Although the syndication framework is pretty simple to understand and implement there are pitfalls you need to watch out for. I hope that this information helps explain a few of the problems you might run into when implementing feeds on your site. I also hope that next time there’s a question on the #django IRC channel I can be a bit more helpful.

Even though you’re seeing all the ugly details in this post, I continue to be amazed at the simplicity and elegance of this implementation. As often heard in the Django community, “They make it stupidly simple.”


Discussion