Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, PHP, and whatever else I happen to feel like hacking at.

Fixing Django’s feed generator without hacking Django

Jerry Stratton, August 6, 2012

I installed security update 1.4.1 for Django yesterday, and when I went to hack feedgenerator.py I thought I’d take another look at somehow subclassing or otherwise overriding the offending code. It’s been a long time since I wrote that hack and maybe I’ve learned enough about Django and/or Python to stop having to hack Django’s source every time I upgrade.

The offending code is in add_item_elements in django.utils.feedgenerator.Rss201rev2Feed. When creating a feed, however, I don’t subclass Rss201rev2Feed, I subclass django.contrib.syndication.views.Feed. In fact, all of my feeds inherit from a base subclass called NSFeed.1

Feed uses Rss201rev2Feed by way of DefaultGenerator. It’s just a property, feed_type, on the Feed class. So I overrode the feed_type property with my own subclass of Rss201rev2Feed and was able to override add_item_elements. I tested it by just putting in one line, “pass”, and checking the feed contents; it was just a bunch of empty items, as hoped for. Replacing “pass” with a “super” call to get the parent method’s functionality restored the feed.

Unfortunately, add_item_elements does a lot of work—it adds everything via a series of if/then statements. It uses an XMLGenerator subclass—the “handler” variable—to add elements to itself depending only on the dict entries in the “item” variable. My first thought was to let the parent add_item_elements do its work and then just add the isPermaLink attribute to the newly-added guid element. As far as I can tell, however, XMLGenerator is focused purely on XML generation, with no methods for XML modifications.

Fortunately guid is an optional element. If it doesn’t exist in the item dict, add_item_elements doesn’t create one. So I can modify handler before passing it through to the parent and then set guid to None. The element already has a guid element with isPermaLink=False and the parent doesn’t add another.

Note that as far as I can tell, none of these classes are documented beyond their signature, so they’re likely subject to change in any Django revision.

[toggle code]

  • from django.contrib.syndication.views import Feed
  • from django.utils.feedgenerator import Rss201rev2Feed
  • import hashlib
  • class NSFeedGenerator(Rss201rev2Feed):
    • #in my feeds, no guid is used as a link
    • def add_item_elements(self, handler, item):
      • if item['unique_id'] is not None:
        • handler.addQuickElement(u"guid", item['unique_id'], {u"isPermaLink": "false"})
        • item['unique_id'] = None
      • super(NSFeedGenerator, self).add_item_elements(handler, item)
  • class NSFeed(Feed):
    • feed_type = NSFeedGenerator
    • type = "application/rss+xml"
    • description_template = "feeds/latest_description.html"
    • def item_pubdate(self, obj):
      • return max(obj.edited, obj.added)
    • def item_guid(self, obj):
      • hasher = hashlib.md5()
      • url = unicode(obj.get_absolute_url())
      • edited = unicode(obj.edited)
      • hashString = url + edited
      • hashString = hashString.encode('utf-8')
      • hasher.update(hashString)
      • hash = hasher.hexdigest()
      • return hash

This simple subclass of Feed ensures that all objects get a unique id from a hash of when they were last changed and their URL. The NSFeedGenerator subclass of Rss201rev2Feed ensures that no guids are treated as permalinks, because all of my feeds can have the same URL come up multiple times; using the URL as the guid would mean that browsers would ignore subsequent listings. Your own logic may vary, so you would need to replace my logic (if unique_id exists, make isPermaLink false) with your own.

In response to Django 1.0 feedgenerator and unique IDs: The new Django now supports unique_id, but still doesn’t check to see if it’s a permalink or not.

  1. For Negative Space, not for Apple’s NextStep frameworks.

  1. <- feedgenerator potentially improved