From 5449240c548bb6877923791d02e800c6b25393f5 Mon Sep 17 00:00:00 2001
From: Simon Charette <charette.s@gmail.com>
Date: Wed, 6 Feb 2013 05:25:35 -0500
Subject: [PATCH] Fixed #9800 -- Allow "isPermaLink" attribute in <guid>
 element of an RSS item.

Thanks @rtnpro for the patch!
---
 django/contrib/syndication/views.py        |  2 ++
 django/utils/feedgenerator.py              | 11 +++++--
 docs/ref/contrib/syndication.txt           | 12 ++++++++
 tests/regressiontests/syndication/feeds.py | 13 ++++++++
 tests/regressiontests/syndication/tests.py | 36 +++++++++++++++++++++-
 tests/regressiontests/syndication/urls.py  |  4 +++
 6 files changed, 74 insertions(+), 4 deletions(-)

diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py
index 996b7dfb40..a80b9d1fae 100644
--- a/django/contrib/syndication/views.py
+++ b/django/contrib/syndication/views.py
@@ -184,6 +184,8 @@ class Feed(object):
                 link = link,
                 description = description,
                 unique_id = self.__get_dynamic_attr('item_guid', item, link),
+                unique_id_is_permalink = self.__get_dynamic_attr(
+                    'item_guid_is_permalink', item),
                 enclosure = enc,
                 pubdate = pubdate,
                 author_name = author_name,
diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py
index f9126a6782..7eba842a89 100644
--- a/django/utils/feedgenerator.py
+++ b/django/utils/feedgenerator.py
@@ -113,8 +113,8 @@ class SyndicationFeed(object):
 
     def add_item(self, title, link, description, author_email=None,
         author_name=None, author_link=None, pubdate=None, comments=None,
-        unique_id=None, enclosure=None, categories=(), item_copyright=None,
-        ttl=None, **kwargs):
+        unique_id=None, unique_id_is_permalink=None, enclosure=None,
+        categories=(), item_copyright=None, ttl=None, **kwargs):
         """
         Adds an item to the feed. All args are expected to be Python Unicode
         objects except pubdate, which is a datetime.datetime object, and
@@ -136,6 +136,7 @@ class SyndicationFeed(object):
             'pubdate': pubdate,
             'comments': to_unicode(comments),
             'unique_id': to_unicode(unique_id),
+            'unique_id_is_permalink': unique_id_is_permalink,
             'enclosure': enclosure,
             'categories': categories or (),
             'item_copyright': to_unicode(item_copyright),
@@ -280,7 +281,11 @@ class Rss201rev2Feed(RssFeed):
         if item['comments'] is not None:
             handler.addQuickElement("comments", item['comments'])
         if item['unique_id'] is not None:
-            handler.addQuickElement("guid", item['unique_id'])
+            guid_attrs = {}
+            if isinstance(item.get('unique_id_is_permalink'), bool):
+                guid_attrs['isPermaLink'] = str(
+                    item['unique_id_is_permalink']).lower()
+            handler.addQuickElement("guid", item['unique_id'], guid_attrs)
         if item['ttl'] is not None:
             handler.addQuickElement("ttl", item['ttl'])
 
diff --git a/docs/ref/contrib/syndication.txt b/docs/ref/contrib/syndication.txt
index 2955d7dad3..65aa7b57b4 100644
--- a/docs/ref/contrib/syndication.txt
+++ b/docs/ref/contrib/syndication.txt
@@ -624,6 +624,18 @@ This example illustrates all possible attributes and methods for a
             Takes an item, as return by items(), and returns the item's ID.
             """
 
+        # ITEM_GUID_IS_PERMALINK -- The following method is optional. If
+        # provided, it sets the 'isPermaLink' attribute of an item's
+        # GUID element. This method is used only when 'item_guid' is
+        # specified.
+
+        def item_guid_is_permalink(self, obj):
+            """
+            Takes an item, as returned by items(), and returns a boolean.
+            """
+
+        item_guid_is_permalink = False  # Hard coded value
+
         # ITEM AUTHOR NAME -- One of the following three is optional. The
         # framework looks for them in this order.
 
diff --git a/tests/regressiontests/syndication/feeds.py b/tests/regressiontests/syndication/feeds.py
index 04a67f4bdb..25757057b9 100644
--- a/tests/regressiontests/syndication/feeds.py
+++ b/tests/regressiontests/syndication/feeds.py
@@ -42,6 +42,19 @@ class TestRss2Feed(views.Feed):
     item_copyright = 'Copyright (c) 2007, Sally Smith'
 
 
+class TestRss2FeedWithGuidIsPermaLinkTrue(TestRss2Feed):
+    def item_guid_is_permalink(self, item):
+        return True
+
+
+class TestRss2FeedWithGuidIsPermaLinkFalse(TestRss2Feed):
+    def item_guid(self, item):
+        return str(item.pk)
+
+    def item_guid_is_permalink(self, item):
+        return False
+
+
 class TestRss091Feed(TestRss2Feed):
     feed_type = feedgenerator.RssUserland091Feed
 
diff --git a/tests/regressiontests/syndication/tests.py b/tests/regressiontests/syndication/tests.py
index 10413b4ddd..8885dc28c0 100644
--- a/tests/regressiontests/syndication/tests.py
+++ b/tests/regressiontests/syndication/tests.py
@@ -103,9 +103,43 @@ class SyndicationFeedTest(FeedTestCase):
             'author': 'test@example.com (Sally Smith)',
         })
         self.assertCategories(items[0], ['python', 'testing'])
-
         for item in items:
             self.assertChildNodes(item, ['title', 'link', 'description', 'guid', 'category', 'pubDate', 'author'])
+            # Assert that <guid> does not have any 'isPermaLink' attribute
+            self.assertIsNone(item.getElementsByTagName(
+                'guid')[0].attributes.get('isPermaLink'))
+
+    def test_rss2_feed_guid_permalink_false(self):
+        """
+        Test if the 'isPermaLink' attribute of <guid> element of an item
+        in the RSS feed is 'false'.
+        """
+        response = self.client.get(
+            '/syndication/rss2/guid_ispermalink_false/')
+        doc = minidom.parseString(response.content)
+        chan = doc.getElementsByTagName(
+            'rss')[0].getElementsByTagName('channel')[0]
+        items = chan.getElementsByTagName('item')
+        for item in items:
+            self.assertEqual(
+                item.getElementsByTagName('guid')[0].attributes.get(
+                    'isPermaLink').value, "false")
+
+    def test_rss2_feed_guid_permalink_true(self):
+        """
+        Test if the 'isPermaLink' attribute of <guid> element of an item
+        in the RSS feed is 'true'.
+        """
+        response = self.client.get(
+            '/syndication/rss2/guid_ispermalink_true/')
+        doc = minidom.parseString(response.content)
+        chan = doc.getElementsByTagName(
+            'rss')[0].getElementsByTagName('channel')[0]
+        items = chan.getElementsByTagName('item')
+        for item in items:
+            self.assertEqual(
+                item.getElementsByTagName('guid')[0].attributes.get(
+                    'isPermaLink').value, "true")
 
     def test_rss091_feed(self):
         """
diff --git a/tests/regressiontests/syndication/urls.py b/tests/regressiontests/syndication/urls.py
index 57f9d81a73..ec3c8cc596 100644
--- a/tests/regressiontests/syndication/urls.py
+++ b/tests/regressiontests/syndication/urls.py
@@ -8,6 +8,10 @@ from . import feeds
 urlpatterns = patterns('django.contrib.syndication.views',
     (r'^syndication/complex/(?P<foo>.*)/$', feeds.ComplexFeed()),
     (r'^syndication/rss2/$', feeds.TestRss2Feed()),
+    (r'^syndication/rss2/guid_ispermalink_true/$',
+        feeds.TestRss2FeedWithGuidIsPermaLinkTrue()),
+    (r'^syndication/rss2/guid_ispermalink_false/$',
+        feeds.TestRss2FeedWithGuidIsPermaLinkFalse()),
     (r'^syndication/rss091/$', feeds.TestRss091Feed()),
     (r'^syndication/no_pubdate/$', feeds.TestNoPubdateFeed()),
     (r'^syndication/atom/$', feeds.TestAtomFeed()),