Safari Feeds from Localhost
In the Django Screencasts – Episode 002 I mentioned that there is a bug in Safari which prevents testing syndication feeds from localhost. Thankfully, the new Safari 3 in Mac OSX Leopard corrects this problem.
In the Django Screencasts – Episode 002 I mentioned that there is a bug in Safari which prevents testing syndication feeds from localhost. Thankfully, the new Safari 3 in Mac OSX Leopard corrects this problem.
In this screencast I cover implementing model based feeds using the syndication framework.
We’ll start where we left off last time and enhance our simple blogging application by adding feeds for individual sections of the website. We will walk through modifications to the urls.py, implementation of a model based Feed class, and changes to our base template to expose the feeds. Finally I will briefly cover how you can use Atom feeds in place of RSS.
Source code for the example application is provided below. Please be sure to read the README for information about requirements.
Regular (51.6 MB, 33:06, H.264, MP4)
IPod (28.6MB, 33:06, H.264, MP4)
Source Code (124 KB)
Requires the latest version of QuickTime, VLC Media Player, or comparable software capable of playing MP4 container format and the H.264 codec.
In this screencast I cover implementing Django’s syndication framework into an existing application. We’ll start with a simple blogging application and walk through what it takes to get syndication (rss feeds) implemented, covering modifications to the urls.py, implementation of a Feed class, creation of the templates, as well as adding syndication auto-detection in the browser.
Source code for the example application is provided below. Please be sure to read the README for information about requirements.
Full Quality (135.8 MB, 15:11, Apple Animation)
Medium Quality (19.7 MB, 15:11, H.264)
IPod & Apple TV (24.4MB, 15:11, Apple Animation)
Source Code (112 KB)
Requires QuickTime
Note, I apologize for the large file sizes. I’m working on getting the compression settings correct, while still preserving the quality of the screencast. I didn’t want to hold off posting this any longer. The next one should be much improved.
I appreciate any and all feedback, both positive and negative.
Special thanks to Ryan Bates of Railscasts for giving me some pointers and help along the way. If you do Rails development you definitely need to check out Ryan’s work as well as support what he’s doing.
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.
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.
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.
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.”