Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #6188, #6304, #6618, #6969, #8758, #8989, #10334, #11069, #1197…

…3 and #12403 -- Modified the syndication framework to use class-based views. Thanks to Ben Firshman for his work on this patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12338 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit c4c27d8a04c9125cfbc5c3611557d8e5d3845b0d 1 parent 3f68d25
Russell Keith-Magee authored January 28, 2010
1  AUTHORS
@@ -166,6 +166,7 @@ answer newbie questions, and generally made Django that much better:
166 166
     Afonso Fernández Nogueira <fonzzo.django@gmail.com>
167 167
     J. Pablo Fernandez <pupeno@pupeno.com>
168 168
     Maciej Fijalkowski
  169
+    Ben Firshman <ben@firshman.co.uk>
169 170
     Matthew Flanagan <http://wadofstuff.blogspot.com>
170 171
     Eric Floehr <eric@intellovations.com>
171 172
     Eric Florenzano <floguy@gmail.com>
4  django/contrib/comments/feeds.py
... ...
@@ -1,5 +1,5 @@
1 1
 from django.conf import settings
2  
-from django.contrib.syndication.feeds import Feed
  2
+from django.contrib.syndication.views import Feed
3 3
 from django.contrib.sites.models import Site
4 4
 from django.contrib import comments
5 5
 from django.utils.translation import ugettext as _
@@ -33,6 +33,6 @@ def items(self):
33 33
             params = [settings.COMMENTS_BANNED_USERS_GROUP]
34 34
             qs = qs.extra(where=where, params=params)
35 35
         return qs.order_by('-submit_date')[:40]
36  
-        
  36
+
37 37
     def item_pubdate(self, item):
38 38
         return item.submit_date
171  django/contrib/syndication/feeds.py
... ...
@@ -1,78 +1,22 @@
1  
-from datetime import datetime, timedelta
  1
+from django.contrib.syndication import views
  2
+from django.core.exceptions import ObjectDoesNotExist
  3
+import warnings
2 4
 
3  
-from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
4  
-from django.template import loader, Template, TemplateDoesNotExist
5  
-from django.contrib.sites.models import Site, RequestSite
6  
-from django.utils import feedgenerator
7  
-from django.utils.tzinfo import FixedOffset
8  
-from django.utils.encoding import smart_unicode, iri_to_uri
9  
-from django.conf import settings         
10  
-from django.template import RequestContext
11  
-
12  
-def add_domain(domain, url):
13  
-    if not (url.startswith('http://') or url.startswith('https://')):
14  
-        # 'url' must already be ASCII and URL-quoted, so no need for encoding
15  
-        # conversions here.
16  
-        url = iri_to_uri(u'http://%s%s' % (domain, url))
17  
-    return url
18  
-
19  
-class FeedDoesNotExist(ObjectDoesNotExist):
20  
-    pass
21  
-
22  
-class Feed(object):
23  
-    item_pubdate = None
24  
-    item_enclosure_url = None
25  
-    feed_type = feedgenerator.DefaultFeed
26  
-    feed_url = None
27  
-    title_template = None
28  
-    description_template = None
  5
+# This is part of the deprecated API
  6
+from django.contrib.syndication.views import FeedDoesNotExist, add_domain
29 7
 
  8
+class Feed(views.Feed):
  9
+    """Provided for backwards compatibility."""
30 10
     def __init__(self, slug, request):
  11
+        warnings.warn('The syndication feeds.Feed class is deprecated. Please '
  12
+                      'use the new class based view API.',
  13
+                      category=PendingDeprecationWarning)
  14
+
31 15
         self.slug = slug
32 16
         self.request = request
33  
-        self.feed_url = self.feed_url or request.path
34  
-        self.title_template_name = self.title_template or ('feeds/%s_title.html' % slug)
35  
-        self.description_template_name = self.description_template or ('feeds/%s_description.html' % slug)
36  
-
37  
-    def item_link(self, item):
38  
-        try:
39  
-            return item.get_absolute_url()
40  
-        except AttributeError:
41  
-            raise ImproperlyConfigured("Give your %s class a get_absolute_url() method, or define an item_link() method in your Feed class." % item.__class__.__name__)
42  
-
43  
-    def __get_dynamic_attr(self, attname, obj, default=None):
44  
-        try:
45  
-            attr = getattr(self, attname)
46  
-        except AttributeError:
47  
-            return default
48  
-        if callable(attr):
49  
-            # Check func_code.co_argcount rather than try/excepting the
50  
-            # function and catching the TypeError, because something inside
51  
-            # the function may raise the TypeError. This technique is more
52  
-            # accurate.
53  
-            if hasattr(attr, 'func_code'):
54  
-                argcount = attr.func_code.co_argcount
55  
-            else:
56  
-                argcount = attr.__call__.func_code.co_argcount
57  
-            if argcount == 2: # one argument is 'self'
58  
-                return attr(obj)
59  
-            else:
60  
-                return attr()
61  
-        return attr
62  
-
63  
-    def feed_extra_kwargs(self, obj):
64  
-        """
65  
-        Returns an extra keyword arguments dictionary that is used when
66  
-        initializing the feed generator.
67  
-        """
68  
-        return {}
69  
-
70  
-    def item_extra_kwargs(self, item):
71  
-        """
72  
-        Returns an extra keyword arguments dictionary that is used with
73  
-        the `add_item` call of the feed generator.
74  
-        """
75  
-        return {}
  17
+        self.feed_url = getattr(self, 'feed_url', None) or request.path
  18
+        self.title_template = self.title_template or ('feeds/%s_title.html' % slug)
  19
+        self.description_template = self.description_template or ('feeds/%s_description.html' % slug)
76 20
 
77 21
     def get_object(self, bits):
78 22
         return None
@@ -86,94 +30,9 @@ def get_feed(self, url=None):
86 30
             bits = url.split('/')
87 31
         else:
88 32
             bits = []
89  
-
90 33
         try:
91 34
             obj = self.get_object(bits)
92 35
         except ObjectDoesNotExist:
93 36
             raise FeedDoesNotExist
  37
+        return super(Feed, self).get_feed(obj, self.request)
94 38
 
95  
-        if Site._meta.installed:
96  
-            current_site = Site.objects.get_current()
97  
-        else:
98  
-            current_site = RequestSite(self.request)
99  
-        
100  
-        link = self.__get_dynamic_attr('link', obj)
101  
-        link = add_domain(current_site.domain, link)
102  
-
103  
-        feed = self.feed_type(
104  
-            title = self.__get_dynamic_attr('title', obj),
105  
-            subtitle = self.__get_dynamic_attr('subtitle', obj),
106  
-            link = link,
107  
-            description = self.__get_dynamic_attr('description', obj),
108  
-            language = settings.LANGUAGE_CODE.decode(),
109  
-            feed_url = add_domain(current_site.domain,
110  
-                                  self.__get_dynamic_attr('feed_url', obj)),
111  
-            author_name = self.__get_dynamic_attr('author_name', obj),
112  
-            author_link = self.__get_dynamic_attr('author_link', obj),
113  
-            author_email = self.__get_dynamic_attr('author_email', obj),
114  
-            categories = self.__get_dynamic_attr('categories', obj),
115  
-            feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
116  
-            feed_guid = self.__get_dynamic_attr('feed_guid', obj),
117  
-            ttl = self.__get_dynamic_attr('ttl', obj),
118  
-            **self.feed_extra_kwargs(obj)
119  
-        )
120  
-
121  
-        try:
122  
-            title_tmp = loader.get_template(self.title_template_name)
123  
-        except TemplateDoesNotExist:
124  
-            title_tmp = Template('{{ obj }}')
125  
-        try:
126  
-            description_tmp = loader.get_template(self.description_template_name)
127  
-        except TemplateDoesNotExist:
128  
-            description_tmp = Template('{{ obj }}')
129  
-
130  
-        for item in self.__get_dynamic_attr('items', obj):
131  
-            link = add_domain(current_site.domain, self.__get_dynamic_attr('item_link', item))
132  
-            enc = None
133  
-            enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
134  
-            if enc_url:
135  
-                enc = feedgenerator.Enclosure(
136  
-                    url = smart_unicode(enc_url),
137  
-                    length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
138  
-                    mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
139  
-                )
140  
-            author_name = self.__get_dynamic_attr('item_author_name', item)
141  
-            if author_name is not None:
142  
-                author_email = self.__get_dynamic_attr('item_author_email', item)
143  
-                author_link = self.__get_dynamic_attr('item_author_link', item)
144  
-            else:
145  
-                author_email = author_link = None
146  
-
147  
-            pubdate = self.__get_dynamic_attr('item_pubdate', item)
148  
-            if pubdate and not pubdate.tzinfo:
149  
-                now = datetime.now()
150  
-                utcnow = datetime.utcnow()
151  
-
152  
-                # Must always subtract smaller time from larger time here.
153  
-                if utcnow > now:
154  
-                    sign = -1
155  
-                    tzDifference = (utcnow - now)
156  
-                else:
157  
-                    sign = 1
158  
-                    tzDifference = (now - utcnow)
159  
-
160  
-                # Round the timezone offset to the nearest half hour.
161  
-                tzOffsetMinutes = sign * ((tzDifference.seconds / 60 + 15) / 30) * 30
162  
-                tzOffset = timedelta(minutes=tzOffsetMinutes)
163  
-                pubdate = pubdate.replace(tzinfo=FixedOffset(tzOffset))
164  
-
165  
-            feed.add_item(
166  
-                title = title_tmp.render(RequestContext(self.request, {'obj': item, 'site': current_site})),
167  
-                link = link,
168  
-                description = description_tmp.render(RequestContext(self.request, {'obj': item, 'site': current_site})),
169  
-                unique_id = self.__get_dynamic_attr('item_guid', item, link),
170  
-                enclosure = enc,
171  
-                pubdate = pubdate,
172  
-                author_name = author_name,
173  
-                author_email = author_email,
174  
-                author_link = author_link,
175  
-                categories = self.__get_dynamic_attr('item_categories', item),
176  
-                item_copyright = self.__get_dynamic_attr('item_copyright', item),
177  
-                **self.item_extra_kwargs(item)
178  
-            )
179  
-        return feed
201  django/contrib/syndication/views.py
... ...
@@ -1,7 +1,203 @@
1  
-from django.contrib.syndication import feeds
  1
+import datetime
  2
+from django.conf import settings
  3
+from django.contrib.sites.models import Site, RequestSite
  4
+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
2 5
 from django.http import HttpResponse, Http404
  6
+from django.template import loader, Template, TemplateDoesNotExist, RequestContext
  7
+from django.utils import feedgenerator, tzinfo
  8
+from django.utils.encoding import force_unicode, iri_to_uri, smart_unicode
  9
+from django.utils.html import escape
  10
+
  11
+def add_domain(domain, url):
  12
+    if not (url.startswith('http://')
  13
+            or url.startswith('https://')
  14
+            or url.startswith('mailto:')):
  15
+        # 'url' must already be ASCII and URL-quoted, so no need for encoding
  16
+        # conversions here.
  17
+        url = iri_to_uri(u'http://%s%s' % (domain, url))
  18
+    return url
  19
+
  20
+class FeedDoesNotExist(ObjectDoesNotExist):
  21
+    pass
  22
+
  23
+
  24
+class Feed(object):
  25
+    feed_type = feedgenerator.DefaultFeed
  26
+    title_template = None
  27
+    description_template = None
  28
+
  29
+    def __call__(self, request, *args, **kwargs):
  30
+        try:
  31
+            obj = self.get_object(request, *args, **kwargs)
  32
+        except ObjectDoesNotExist:
  33
+            raise Http404('Feed object does not exist.')
  34
+        feedgen = self.get_feed(obj, request)
  35
+        response = HttpResponse(mimetype=feedgen.mime_type)
  36
+        feedgen.write(response, 'utf-8')
  37
+        return response
  38
+
  39
+    def item_title(self, item):
  40
+        # Titles should be double escaped by default (see #6533)
  41
+        return escape(force_unicode(item))
  42
+
  43
+    def item_description(self, item):
  44
+        return force_unicode(item)
  45
+
  46
+    def item_link(self, item):
  47
+        try:
  48
+            return item.get_absolute_url()
  49
+        except AttributeError:
  50
+            raise ImproperlyConfigured('Give your %s class a get_absolute_url() method, or define an item_link() method in your Feed class.' % item.__class__.__name__)
  51
+
  52
+    def __get_dynamic_attr(self, attname, obj, default=None):
  53
+        try:
  54
+            attr = getattr(self, attname)
  55
+        except AttributeError:
  56
+            return default
  57
+        if callable(attr):
  58
+            # Check func_code.co_argcount rather than try/excepting the
  59
+            # function and catching the TypeError, because something inside
  60
+            # the function may raise the TypeError. This technique is more
  61
+            # accurate.
  62
+            if hasattr(attr, 'func_code'):
  63
+                argcount = attr.func_code.co_argcount
  64
+            else:
  65
+                argcount = attr.__call__.func_code.co_argcount
  66
+            if argcount == 2: # one argument is 'self'
  67
+                return attr(obj)
  68
+            else:
  69
+                return attr()
  70
+        return attr
  71
+
  72
+    def feed_extra_kwargs(self, obj):
  73
+        """
  74
+        Returns an extra keyword arguments dictionary that is used when
  75
+        initializing the feed generator.
  76
+        """
  77
+        return {}
  78
+
  79
+    def item_extra_kwargs(self, item):
  80
+        """
  81
+        Returns an extra keyword arguments dictionary that is used with
  82
+        the `add_item` call of the feed generator.
  83
+        """
  84
+        return {}
  85
+
  86
+    def get_object(self, request, *args, **kwargs):
  87
+        return None
  88
+
  89
+    def get_feed(self, obj, request):
  90
+        """
  91
+        Returns a feedgenerator.DefaultFeed object, fully populated, for
  92
+        this feed. Raises FeedDoesNotExist for invalid parameters.
  93
+        """
  94
+        if Site._meta.installed:
  95
+            current_site = Site.objects.get_current()
  96
+        else:
  97
+            current_site = RequestSite(request)
  98
+
  99
+        link = self.__get_dynamic_attr('link', obj)
  100
+        link = add_domain(current_site.domain, link)
  101
+
  102
+        feed = self.feed_type(
  103
+            title = self.__get_dynamic_attr('title', obj),
  104
+            subtitle = self.__get_dynamic_attr('subtitle', obj),
  105
+            link = link,
  106
+            description = self.__get_dynamic_attr('description', obj),
  107
+            language = settings.LANGUAGE_CODE.decode(),
  108
+            feed_url = add_domain(current_site.domain,
  109
+                    self.__get_dynamic_attr('feed_url', obj) or request.path),
  110
+            author_name = self.__get_dynamic_attr('author_name', obj),
  111
+            author_link = self.__get_dynamic_attr('author_link', obj),
  112
+            author_email = self.__get_dynamic_attr('author_email', obj),
  113
+            categories = self.__get_dynamic_attr('categories', obj),
  114
+            feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
  115
+            feed_guid = self.__get_dynamic_attr('feed_guid', obj),
  116
+            ttl = self.__get_dynamic_attr('ttl', obj),
  117
+            **self.feed_extra_kwargs(obj)
  118
+        )
  119
+
  120
+        title_tmp = None
  121
+        if self.title_template is not None:
  122
+            try:
  123
+                title_tmp = loader.get_template(self.title_template)
  124
+            except TemplateDoesNotExist:
  125
+                pass
  126
+
  127
+        description_tmp = None
  128
+        if self.description_template is not None:
  129
+            try:
  130
+                description_tmp = loader.get_template(self.description_template)
  131
+            except TemplateDoesNotExist:
  132
+                pass
  133
+
  134
+        for item in self.__get_dynamic_attr('items', obj):
  135
+            if title_tmp is not None:
  136
+                title = title_tmp.render(RequestContext(request, {'obj': item, 'site': current_site}))
  137
+            else:
  138
+                title = self.__get_dynamic_attr('item_title', item)
  139
+            if description_tmp is not None:
  140
+                description = description_tmp.render(RequestContext(request, {'obj': item, 'site': current_site}))
  141
+            else:
  142
+                description = self.__get_dynamic_attr('item_description', item)
  143
+            link = add_domain(current_site.domain, self.__get_dynamic_attr('item_link', item))
  144
+            enc = None
  145
+            enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
  146
+            if enc_url:
  147
+                enc = feedgenerator.Enclosure(
  148
+                    url = smart_unicode(enc_url),
  149
+                    length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
  150
+                    mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
  151
+                )
  152
+            author_name = self.__get_dynamic_attr('item_author_name', item)
  153
+            if author_name is not None:
  154
+                author_email = self.__get_dynamic_attr('item_author_email', item)
  155
+                author_link = self.__get_dynamic_attr('item_author_link', item)
  156
+            else:
  157
+                author_email = author_link = None
  158
+
  159
+            pubdate = self.__get_dynamic_attr('item_pubdate', item)
  160
+            if pubdate and not pubdate.tzinfo:
  161
+                now = datetime.datetime.now()
  162
+                utcnow = datetime.datetime.utcnow()
  163
+
  164
+                # Must always subtract smaller time from larger time here.
  165
+                if utcnow > now:
  166
+                    sign = -1
  167
+                    tzDifference = (utcnow - now)
  168
+                else:
  169
+                    sign = 1
  170
+                    tzDifference = (now - utcnow)
  171
+
  172
+                # Round the timezone offset to the nearest half hour.
  173
+                tzOffsetMinutes = sign * ((tzDifference.seconds / 60 + 15) / 30) * 30
  174
+                tzOffset = datetime.timedelta(minutes=tzOffsetMinutes)
  175
+                pubdate = pubdate.replace(tzinfo=tzinfo.FixedOffset(tzOffset))
  176
+
  177
+            feed.add_item(
  178
+                title = title,
  179
+                link = link,
  180
+                description = description,
  181
+                unique_id = self.__get_dynamic_attr('item_guid', item, link),
  182
+                enclosure = enc,
  183
+                pubdate = pubdate,
  184
+                author_name = author_name,
  185
+                author_email = author_email,
  186
+                author_link = author_link,
  187
+                categories = self.__get_dynamic_attr('item_categories', item),
  188
+                item_copyright = self.__get_dynamic_attr('item_copyright', item),
  189
+                **self.item_extra_kwargs(item)
  190
+            )
  191
+        return feed
  192
+
3 193
 
4 194
 def feed(request, url, feed_dict=None):
  195
+    """Provided for backwards compatibility."""
  196
+    import warnings
  197
+    warnings.warn('The syndication feed() view is deprecated. Please use the '
  198
+                  'new class based view API.',
  199
+                  category=PendingDeprecationWarning)
  200
+
5 201
     if not feed_dict:
6 202
         raise Http404("No feeds are registered.")
7 203
 
@@ -17,9 +213,10 @@ def feed(request, url, feed_dict=None):
17 213
 
18 214
     try:
19 215
         feedgen = f(slug, request).get_feed(param)
20  
-    except feeds.FeedDoesNotExist:
  216
+    except FeedDoesNotExist:
21 217
         raise Http404("Invalid feed parameters. Slug %r is valid, but other parameters, or lack thereof, are not." % slug)
22 218
 
23 219
     response = HttpResponse(mimetype=feedgen.mime_type)
24 220
     feedgen.write(response, 'utf-8')
25 221
     return response
  222
+
28  django/utils/feedgenerator.py
@@ -19,8 +19,8 @@
19 19
 http://diveintomark.org/archives/2004/02/04/incompatible-rss
20 20
 """
21 21
 
22  
-import re
23 22
 import datetime
  23
+import urlparse
24 24
 from django.utils.xmlutils import SimplerXMLGenerator
25 25
 from django.utils.encoding import force_unicode, iri_to_uri
26 26
 
@@ -46,12 +46,16 @@ def rfc3339_date(date):
46 46
         return date.strftime('%Y-%m-%dT%H:%M:%SZ')
47 47
 
48 48
 def get_tag_uri(url, date):
49  
-    "Creates a TagURI. See http://diveintomark.org/archives/2004/05/28/howto-atom-id"
50  
-    tag = re.sub('^http://', '', url)
  49
+    """
  50
+    Creates a TagURI.
  51
+
  52
+    See http://diveintomark.org/archives/2004/05/28/howto-atom-id
  53
+    """
  54
+    url_split = urlparse.urlparse(url)
  55
+    d = ''
51 56
     if date is not None:
52  
-        tag = re.sub('/', ',%s:/' % date.strftime('%Y-%m-%d'), tag, 1)
53  
-    tag = re.sub('#', '/', tag)
54  
-    return u'tag:' + tag
  57
+        d = ',%s' % date.strftime('%Y-%m-%d')
  58
+    return u'tag:%s%s:%s/%s' % (url_split.hostname, d, url_split.path, url_split.fragment)
55 59
 
56 60
 class SyndicationFeed(object):
57 61
     "Base class for all syndication feeds. Subclasses should provide write()"
@@ -61,6 +65,9 @@ def __init__(self, title, link, description, language=None, author_email=None,
61 65
         to_unicode = lambda s: force_unicode(s, strings_only=True)
62 66
         if categories:
63 67
             categories = [force_unicode(c) for c in categories]
  68
+        if ttl is not None:
  69
+            # Force ints to unicode
  70
+            ttl = force_unicode(ttl)
64 71
         self.feed = {
65 72
             'title': to_unicode(title),
66 73
             'link': iri_to_uri(link),
@@ -91,6 +98,9 @@ def add_item(self, title, link, description, author_email=None,
91 98
         to_unicode = lambda s: force_unicode(s, strings_only=True)
92 99
         if categories:
93 100
             categories = [to_unicode(c) for c in categories]
  101
+        if ttl is not None:
  102
+            # Force ints to unicode
  103
+            ttl = force_unicode(ttl)
94 104
         item = {
95 105
             'title': to_unicode(title),
96 106
             'link': iri_to_uri(link),
@@ -186,7 +196,8 @@ def write(self, outfile, encoding):
186 196
         handler.endElement(u"rss")
187 197
 
188 198
     def rss_attributes(self):
189  
-        return {u"version": self._version}
  199
+        return {u"version": self._version,
  200
+                u"xmlns:atom": u"http://www.w3.org/2005/Atom"}
190 201
 
191 202
     def write_items(self, handler):
192 203
         for item in self.items:
@@ -198,6 +209,7 @@ def add_root_elements(self, handler):
198 209
         handler.addQuickElement(u"title", self.feed['title'])
199 210
         handler.addQuickElement(u"link", self.feed['link'])
200 211
         handler.addQuickElement(u"description", self.feed['description'])
  212
+        handler.addQuickElement(u"atom:link", None, {u"rel": u"self", u"href": self.feed['feed_url']})
201 213
         if self.feed['language'] is not None:
202 214
             handler.addQuickElement(u"language", self.feed['language'])
203 215
         for cat in self.feed['categories']:
@@ -235,7 +247,7 @@ def add_item_elements(self, handler, item):
235 247
         elif item["author_email"]:
236 248
             handler.addQuickElement(u"author", item["author_email"])
237 249
         elif item["author_name"]:
238  
-            handler.addQuickElement(u"dc:creator", item["author_name"], {"xmlns:dc": u"http://purl.org/dc/elements/1.1/"})
  250
+            handler.addQuickElement(u"dc:creator", item["author_name"], {u"xmlns:dc": u"http://purl.org/dc/elements/1.1/"})
239 251
 
240 252
         if item['pubdate'] is not None:
241 253
             handler.addQuickElement(u"pubDate", rfc2822_date(item['pubdate']).decode('utf-8'))
4  docs/internals/deprecation.txt
@@ -82,6 +82,10 @@ their deprecation, as per the :ref:`Django deprecation policy
82 82
         * The ability to use a function-based test runners will be removed,
83 83
           along with the ``django.test.simple.run_tests()`` test runner.
84 84
 
  85
+        * The ``views.feed()`` view and ``feeds.Feed`` class in
  86
+          ``django.contrib.syndication`` have been deprecated since the 1.2
  87
+          release. The class-based view ``views.Feed`` should be used instead.
  88
+
85 89
     * 2.0
86 90
         * ``django.views.defaults.shortcut()``. This function has been moved
87 91
           to ``django.contrib.contenttypes.views.shortcut()`` as part of the
421  docs/ref/contrib/syndication.txt
@@ -8,14 +8,15 @@ The syndication feed framework
8 8
    :synopsis: A framework for generating syndication feeds, in RSS and Atom,
9 9
               quite easily.
10 10
 
11  
-Django comes with a high-level syndication-feed-generating framework that makes
12  
-creating RSS_ and Atom_ feeds easy.
  11
+Django comes with a high-level syndication-feed-generating framework
  12
+that makes creating RSS_ and Atom_ feeds easy.
13 13
 
14  
-To create any syndication feed, all you have to do is write a short Python
15  
-class. You can create as many feeds as you want.
  14
+To create any syndication feed, all you have to do is write a short
  15
+Python class. You can create as many feeds as you want.
16 16
 
17  
-Django also comes with a lower-level feed-generating API. Use this if you want
18  
-to generate feeds outside of a Web context, or in some other lower-level way.
  17
+Django also comes with a lower-level feed-generating API. Use this if
  18
+you want to generate feeds outside of a Web context, or in some other
  19
+lower-level way.
19 20
 
20 21
 .. _RSS: http://www.whatisrss.com/
21 22
 .. _Atom: http://www.atomenabled.org/
@@ -23,74 +24,37 @@ to generate feeds outside of a Web context, or in some other lower-level way.
23 24
 The high-level framework
24 25
 ========================
25 26
 
  27
+.. versionchanged:: 1.2
  28
+   The high-level feeds framework was refactored in Django 1.2. The
  29
+   pre-1.2 interface still exists, but it has been deprecated, and
  30
+   will be removed in Django 1.4. If you need to maintain an old-style
  31
+   Django feed, please consult the Django 1.1 documentation. For
  32
+   details on updating to use the new high-level feed framework, see
  33
+   the :ref:`Django 1.2 release notes <1.2-updating-feeds>`.
  34
+
26 35
 Overview
27 36
 --------
28 37
 
29  
-The high-level feed-generating framework is a view that's hooked to ``/feeds/``
30  
-by default. Django uses the remainder of the URL (everything after ``/feeds/``)
31  
-to determine which feed to output.
32  
-
33  
-To create a feed, just write a :class:`~django.contrib.syndication.feeds.Feed`
34  
-class and point to it in your :ref:`URLconf <topics-http-urls>`.
35  
-
36  
-Initialization
37  
---------------
38  
-
39  
-To activate syndication feeds on your Django site, add this line to your
40  
-:ref:`URLconf <topics-http-urls>`::
41  
-
42  
-   (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),
43  
-
44  
-This tells Django to use the RSS framework to handle all URLs starting with
45  
-:file:`"feeds/"`. (You can change that :file:`"feeds/"` prefix to fit your own
46  
-needs.)
47  
-
48  
-This URLconf line has an extra argument: ``{'feed_dict': feeds}``. Use this
49  
-extra argument to pass the syndication framework the feeds that should be
50  
-published under that URL.
51  
-
52  
-Specifically, :data:`feed_dict` should be a dictionary that maps a feed's slug
53  
-(short URL label) to its :class:`~django.contrib.syndication.feeds.Feed` class.
54  
-
55  
-You can define the ``feed_dict`` in the URLconf itself. Here's a full example
56  
-URLconf::
57  
-
58  
-    from django.conf.urls.defaults import *
59  
-    from myproject.feeds import LatestEntries, LatestEntriesByCategory
60  
-
61  
-    feeds = {
62  
-        'latest': LatestEntries,
63  
-        'categories': LatestEntriesByCategory,
64  
-    }
65  
-
66  
-    urlpatterns = patterns('',
67  
-        # ...
68  
-        (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
69  
-            {'feed_dict': feeds}),
70  
-        # ...
71  
-    )
72  
-
73  
-The above example registers two feeds:
74  
-
75  
-    * The feed represented by ``LatestEntries`` will live at ``feeds/latest/``.
76  
-    * The feed represented by ``LatestEntriesByCategory`` will live at
77  
-      ``feeds/categories/``.
78  
-
79  
-Once that's set up, you just need to define the
80  
-:class:`~django.contrib.syndication.feeds.Feed` classes themselves.
  38
+The high-level feed-generating framework is supplied by the
  39
+:class:`~django.contrib.syndication.views.Feed` class. To create a
  40
+feed, write a :class:`~django.contrib.syndication.views.Feed` class
  41
+and point to an instance of it in your :ref:`URLconf
  42
+<topics-http-urls>`.
81 43
 
82 44
 Feed classes
83 45
 ------------
84 46
 
85  
-A :class:`~django.contrib.syndication.feeds.Feed` class is a simple Python class
86  
-that represents a syndication feed. A feed can be simple (e.g., a "site news"
87  
-feed, or a basic feed displaying the latest entries of a blog) or more complex
88  
-(e.g., a feed displaying all the blog entries in a particular category, where
89  
-the category is variable).
  47
+A :class:`~django.contrib.syndication.views.Feed` class is a Python
  48
+class that represents a syndication feed. A feed can be simple (e.g.,
  49
+a "site news" feed, or a basic feed displaying the latest entries of a
  50
+blog) or more complex (e.g., a feed displaying all the blog entries in
  51
+a particular category, where the category is variable).
90 52
 
91  
-:class:`~django.contrib.syndication.feeds.Feed` classes must subclass
92  
-``django.contrib.syndication.feeds.Feed``. They can live anywhere in your
93  
-codebase.
  53
+Feed classes subclass :class:`django.contrib.syndication.views.Feed`.
  54
+They can live anywhere in your codebase.
  55
+
  56
+Instances of :class:`~django.contrib.syndication.views.Feed` classes
  57
+are views which can be used in your :ref:`URLconf <topics-http-urls>`.
94 58
 
95 59
 A simple example
96 60
 ----------------
@@ -98,10 +62,10 @@ A simple example
98 62
 This simple example, taken from `chicagocrime.org`_, describes a feed of the
99 63
 latest five news items::
100 64
 
101  
-    from django.contrib.syndication.feeds import Feed
  65
+    from django.contrib.syndication.views import Feed
102 66
     from chicagocrime.models import NewsItem
103 67
 
104  
-    class LatestEntries(Feed):
  68
+    class LatestEntriesFeed(Feed):
105 69
         title = "Chicagocrime.org site news"
106 70
         link = "/sitenews/"
107 71
         description = "Updates on changes and additions to chicagocrime.org."
@@ -109,9 +73,27 @@ latest five news items::
109 73
         def items(self):
110 74
             return NewsItem.objects.order_by('-pub_date')[:5]
111 75
 
  76
+        def item_title(self, item):
  77
+            return item.title
  78
+
  79
+        def item_description(self, item):
  80
+            return item.description
  81
+
  82
+To connect a URL to this feed, put an instance of the Feed object in
  83
+your :ref:`URLconf <topics-http-urls>`. For example::
  84
+
  85
+    from django.conf.urls.defaults import *
  86
+    from myproject.feeds import LatestEntriesFeed
  87
+
  88
+    urlpatterns = patterns('',
  89
+        # ...
  90
+        (r'^latest/feed/$', LatestEntriesFeed()),
  91
+        # ...
  92
+    )
  93
+
112 94
 Note:
113 95
 
114  
-* The class subclasses ``django.contrib.syndication.feeds.Feed``.
  96
+* The Feed class subclasses :class:`django.contrib.syndication.views.Feed`.
115 97
 
116 98
 * :attr:`title`, :attr:`link` and :attr:`description` correspond to the
117 99
   standard RSS ``<title>``, ``<link>`` and ``<description>`` elements,
@@ -129,17 +111,23 @@ Note:
129 111
   :attr:`subtitle` attribute instead of the :attr:`description` attribute.
130 112
   See `Publishing Atom and RSS feeds in tandem`_, later, for an example.
131 113
 
132  
-One thing's left to do. In an RSS feed, each ``<item>`` has a ``<title>``,
  114
+One thing is left to do. In an RSS feed, each ``<item>`` has a ``<title>``,
133 115
 ``<link>`` and ``<description>``. We need to tell the framework what data to put
134 116
 into those elements.
135 117
 
136  
-    * To specify the contents of ``<title>`` and ``<description>``, create
137  
-      :ref:`Django templates <topics-templates>` called
138  
-      :file:`feeds/latest_title.html` and
139  
-      :file:`feeds/latest_description.html`, where :attr:`latest` is the
140  
-      :attr:`slug` specified in the URLconf for the given feed. Note the
141  
-      ``.html`` extension is required. The RSS system renders that template for
142  
-      each item, passing it two template context variables:
  118
+    * For the contents of ``<title>`` and ``<description>``, Django tries
  119
+      calling the methods :meth:`item_title()` and :meth:`item_description()` on
  120
+      the :class:`~django.contrib.syndication.views.Feed` class. They are passed
  121
+      a single parameter, :attr:`item`, which is the object itself. These are
  122
+      optional; by default, the unicode representation of the object is used for
  123
+      both.
  124
+
  125
+      If you want to do any special formatting for either the title or
  126
+      description, :ref:`Django templates <topics-templates>` can be used
  127
+      instead. Their paths can be specified with the ``title_template`` and
  128
+      ``description_template`` attributes on the
  129
+      :class:`~django.contrib.syndication.views.Feed` class. The templates are
  130
+      rendered for each item and are passed two template context variables:
143 131
 
144 132
          * ``{{ obj }}`` -- The current object (one of whichever objects you
145 133
            returned in :meth:`items()`).
@@ -152,152 +140,102 @@ into those elements.
152 140
            :ref:`RequestSite section of the sites framework documentation
153 141
            <requestsite-objects>` for more.
154 142
 
155  
-      If you don't create a template for either the title or description, the
156  
-      framework will use the template ``"{{ obj }}"`` by default -- that is, the
157  
-      normal string representation of the object. You can also change the names
158  
-      of these two templates by specifying ``title_template`` and
159  
-      ``description_template`` as attributes of your
160  
-      :class:`~django.contrib.syndication.feeds.Feed` class.
  143
+      See `a complex example`_ below that uses a description template.
161 144
 
162 145
     * To specify the contents of ``<link>``, you have two options. For each item
163  
-      in :meth:`items()`, Django first tries calling a method
164  
-      :meth:`item_link()` in the :class:`~django.contrib.syndication.feeds.Feed`
165  
-      class, passing it a single parameter, :attr:`item`, which is the object
166  
-      itself. If that method doesn't exist, Django tries executing a
167  
-      ``get_absolute_url()`` method on that object. . Both
168  
-      ``get_absolute_url()`` and :meth:`item_link()` should return the item's
169  
-      URL as a normal Python string. As with ``get_absolute_url()``, the result
170  
-      of :meth:`item_link()` will be included directly in the URL, so you are
171  
-      responsible for doing all necessary URL quoting and conversion to ASCII
172  
-      inside the method itself.
173  
-
174  
-    * For the LatestEntries example above, we could have very simple feed
175  
-      templates:
176  
-
177  
-      * latest_title.html:
178  
-
179  
-        .. code-block:: html+django
180  
-
181  
-            {{ obj.title }}
182  
-
183  
-      * latest_description.html:
184  
-
185  
-        .. code-block:: html+django
186  
-
187  
-            {{ obj.description }}
  146
+      in :meth:`items()`, Django first tries calling the
  147
+      :meth:`item_link()` method on the
  148
+      :class:`~django.contrib.syndication.views.Feed` class. In a similar way to
  149
+      the title and description, it is passed it a single parameter,
  150
+      :attr:`item`. If that method doesn't exist, Django tries executing a
  151
+      ``get_absolute_url()`` method on that object. Both
  152
+      :meth:`get_absolute_url()` and :meth:`item_link()` should return the
  153
+      item's URL as a normal Python string. As with ``get_absolute_url()``, the
  154
+      result of :meth:`item_link()` will be included directly in the URL, so you
  155
+      are responsible for doing all necessary URL quoting and conversion to
  156
+      ASCII inside the method itself.
188 157
 
189 158
 .. _chicagocrime.org: http://www.chicagocrime.org/
190 159
 
191 160
 A complex example
192 161
 -----------------
193 162
 
194  
-The framework also supports more complex feeds, via parameters.
  163
+The framework also supports more complex feeds, via arguments.
195 164
 
196 165
 For example, `chicagocrime.org`_ offers an RSS feed of recent crimes for every
197 166
 police beat in Chicago. It'd be silly to create a separate
198  
-:class:`~django.contrib.syndication.feeds.Feed` class for each police beat; that
  167
+:class:`~django.contrib.syndication.views.Feed` class for each police beat; that
199 168
 would violate the :ref:`DRY principle <dry>` and would couple data to
200  
-programming logic. Instead, the syndication framework lets you make generic
201  
-feeds that output items based on information in the feed's URL.
  169
+programming logic. Instead, the syndication framework lets you access the
  170
+arguments passed from your :ref:`URLconf <topics-http-urls>` so feeds can output
  171
+items based on information in the feed's URL.
202 172
 
203 173
 On chicagocrime.org, the police-beat feeds are accessible via URLs like this:
204 174
 
205  
-    * :file:`/rss/beats/0613/` -- Returns recent crimes for beat 0613.
206  
-    * :file:`/rss/beats/1424/` -- Returns recent crimes for beat 1424.
  175
+    * :file:`/beats/613/rss/` -- Returns recent crimes for beat 613.
  176
+    * :file:`/beats/1424/rss/` -- Returns recent crimes for beat 1424.
  177
+
  178
+These can be matched with a :ref:`URLconf <topics-http-urls>` line such as::
207 179
 
208  
-The slug here is ``"beats"``. The syndication framework sees the extra URL bits
209  
-after the slug -- ``0613`` and ``1424`` -- and gives you a hook to tell it what
210  
-those URL bits mean, and how they should influence which items get published in
211  
-the feed.
  180
+    (r'^beats/(?P<beat_id>\d+)/rss/$', BeatFeed()),
212 181
 
213  
-An example makes this clear. Here's the code for these beat-specific feeds::
  182
+Like a view, the arguments in the URL are passed to the :meth:`get_object()`
  183
+method along with the request object.
214 184
 
215  
-    from django.contrib.syndication.feeds import FeedDoesNotExist
216  
-    from django.core.exceptions import ObjectDoesNotExist
  185
+.. versionchanged:: 1.2
  186
+   Prior to version 1.2, ``get_object()`` only accepted a ``bits`` argument.
  187
+
  188
+Here's the code for these beat-specific feeds::
  189
+
  190
+    from django.contrib.syndication.views import FeedDoesNotExist
  191
+    from django.shortcuts import get_object_or_404
217 192
 
218 193
     class BeatFeed(Feed):
219  
-        def get_object(self, bits):
220  
-            # In case of "/rss/beats/0613/foo/bar/baz/", or other such clutter,
221  
-            # check that bits has only one member.
222  
-            if len(bits) != 1:
223  
-                raise ObjectDoesNotExist
224  
-            return Beat.objects.get(beat__exact=bits[0])
  194
+        description_template = 'feeds/beat_description.html'
  195
+
  196
+        def get_object(self, request, beat_id):
  197
+            return get_object_or_404(Beat, pk=beat_id)
225 198
 
226 199
         def title(self, obj):
227 200
             return "Chicagocrime.org: Crimes for beat %s" % obj.beat
228 201
 
229 202
         def link(self, obj):
230  
-            if not obj:
231  
-                raise FeedDoesNotExist
232 203
             return obj.get_absolute_url()
233 204
 
234 205
         def description(self, obj):
235 206
             return "Crimes recently reported in police beat %s" % obj.beat
236 207
 
237 208
         def items(self, obj):
238  
-           return Crime.objects.filter(beat__id__exact=obj.id).order_by('-crime_date')[:30]
239  
-
240  
-Here's the basic algorithm the RSS framework follows, given this class and a
241  
-request to the URL :file:`/rss/beats/0613/`:
242  
-
243  
-    * The framework gets the URL :file:`/rss/beats/0613/` and notices there's an
244  
-      extra bit of URL after the slug. It splits that remaining string by the
245  
-      slash character (``"/"``) and calls the
246  
-      :class:`~django.contrib.syndication.feeds.Feed` class'
247  
-      :meth:`get_object()` method, passing it the bits. In this case, bits is
248  
-      ``['0613']``. For a request to :file:`/rss/beats/0613/foo/bar/`, bits
249  
-      would be ``['0613', 'foo', 'bar']``.
250  
-
251  
-    * :meth:`get_object()` is responsible for retrieving the given beat, from
252  
-      the given ``bits``. In this case, it uses the Django database API to
253  
-      retrieve the beat. Note that :meth:`get_object()` should raise
254  
-      :exc:`django.core.exceptions.ObjectDoesNotExist` if given invalid
255  
-      parameters. There's no ``try``/``except`` around the
256  
-      ``Beat.objects.get()`` call, because it's not necessary; that function
257  
-      raises :exc:`Beat.DoesNotExist` on failure, and :exc:`Beat.DoesNotExist`
258  
-      is a subclass of :exc:`ObjectDoesNotExist`. Raising
259  
-      :exc:`ObjectDoesNotExist` in :meth:`get_object()` tells Django to produce
260  
-      a 404 error for that request.
261  
-
262  
-      .. versionadded:: 1.0
263  
-         :meth:`get_object()` can handle the :file:`/rss/beats/` url.
264  
-
265  
-      The :meth:`get_object()` method also has a chance to handle the
266  
-      :file:`/rss/beats/` url. In this case, :data:`bits` will be an
267  
-      empty list. In our example, ``len(bits) != 1`` and an
268  
-      :exc:`ObjectDoesNotExist` exception will be raised, so
269  
-      :file:`/rss/beats/` will generate a 404 page. But you can handle this case
270  
-      however you like. For example, you could generate a combined feed for all
271  
-      beats.
272  
-
273  
-    * To generate the feed's ``<title>``, ``<link>`` and ``<description>``,
274  
-      Django uses the :meth:`title()`, :meth:`link()` and :meth:`description()`
275  
-      methods. In the previous example, they were simple string class
276  
-      attributes, but this example illustrates that they can be either strings
277  
-      *or* methods. For each of :attr:`title`, :attr:`link` and
278  
-      :attr:`description`, Django follows this algorithm:
279  
-
280  
-        * First, it tries to call a method, passing the ``obj`` argument, where
281  
-          ``obj`` is the object returned by :meth:`get_object()`.
282  
-
283  
-        * Failing that, it tries to call a method with no arguments.
284  
-
285  
-        * Failing that, it uses the class attribute.
286  
-
287  
-      Inside the :meth:`link()` method, we handle the possibility that ``obj``
288  
-      might be ``None``, which can occur when the URL isn't fully specified. In
289  
-      some cases, you might want to do something else in this case, which would
290  
-      mean you'd need to check for ``obj`` existing in other methods as well.
291  
-      (The :meth:`link()` method is called very early in the feed generation
292  
-      process, so it's a good place to bail out early.)
293  
-
294  
-    * Finally, note that :meth:`items()` in this example also takes the ``obj``
295  
-      argument. The algorithm for :attr:`items` is the same as described in the
296  
-      previous step -- first, it tries :meth:`items(obj)`, then :meth:`items()`,
297  
-      then finally an :attr:`items` class attribute (which should be a list).
  209
+            return Crime.objects.filter(beat=obj).order_by('-crime_date')[:30]
  210
+
  211
+To generate the feed's ``<title>``, ``<link>`` and ``<description>``, Django
  212
+uses the :meth:`title()`, :meth:`link()` and :meth:`description()` methods. In
  213
+the previous example, they were simple string class attributes, but this example
  214
+illustrates that they can be either strings *or* methods. For each of
  215
+:attr:`title`, :attr:`link` and :attr:`description`, Django follows this
  216
+algorithm:
  217
+
  218
+    * First, it tries to call a method, passing the ``obj`` argument, where
  219
+      ``obj`` is the object returned by :meth:`get_object()`.
  220
+
  221
+    * Failing that, it tries to call a method with no arguments.
  222
+
  223
+    * Failing that, it uses the class attribute.
  224
+
  225
+Also note that :meth:`items()` also follows the same algorithm -- first, it
  226
+tries :meth:`items(obj)`, then :meth:`items()`, then finally an :attr:`items`
  227
+class attribute (which should be a list).
  228
+
  229
+We are using a template for the item descriptions. It can be very simple:
  230
+
  231
+.. code-block:: html+django
  232
+
  233
+    {{ obj.description }}
  234
+
  235
+However, you are free to add formatting as desired.
298 236
 
299 237
 The ``ExampleFeed`` class below gives full documentation on methods and
300  
-attributes of :class:`~django.contrib.syndication.feeds.Feed` classes.
  238
+attributes of :class:`~django.contrib.syndication.views.Feed` classes.
301 239
 
302 240
 Specifying the type of feed
303 241
 ---------------------------
@@ -305,7 +243,7 @@ Specifying the type of feed
305 243
 By default, feeds produced in this framework use RSS 2.0.
306 244
 
307 245
 To change that, add a ``feed_type`` attribute to your
308  
-:class:`~django.contrib.syndication.feeds.Feed` class, like so::
  246
+:class:`~django.contrib.syndication.views.Feed` class, like so::
309 247
 
310 248
     from django.utils.feedgenerator import Atom1Feed
311 249
 
@@ -353,13 +291,13 @@ Publishing Atom and RSS feeds in tandem
353 291
 
354 292
 Some developers like to make available both Atom *and* RSS versions of their
355 293
 feeds. That's easy to do with Django: Just create a subclass of your
356  
-:class:`~django.contrib.syndication.feeds.Feed`
  294
+:class:`~django.contrib.syndication.views.Feed`
357 295
 class and set the :attr:`feed_type` to something different. Then update your
358 296
 URLconf to add the extra versions.
359 297
 
360 298
 Here's a full example::
361 299
 
362  
-    from django.contrib.syndication.feeds import Feed
  300
+    from django.contrib.syndication.views import Feed
363 301
     from chicagocrime.models import NewsItem
364 302
     from django.utils.feedgenerator import Atom1Feed
365 303
 
@@ -381,7 +319,7 @@ Here's a full example::
381 319
     a feed-level "description," but they *do* provide for a "subtitle."
382 320
 
383 321
     If you provide a :attr:`description` in your
384  
-    :class:`~django.contrib.syndication.feeds.Feed` class, Django will *not*
  322
+    :class:`~django.contrib.syndication.views.Feed` class, Django will *not*
385 323
     automatically put that into the :attr:`subtitle` element, because a
386 324
     subtitle and description are not necessarily the same thing. Instead, you
387 325
     should define a :attr:`subtitle` attribute.
@@ -394,56 +332,50 @@ And the accompanying URLconf::
394 332
     from django.conf.urls.defaults import *
395 333
     from myproject.feeds import RssSiteNewsFeed, AtomSiteNewsFeed
396 334
 
397  
-    feeds = {
398  
-        'rss': RssSiteNewsFeed,
399  
-        'atom': AtomSiteNewsFeed,
400  
-    }
401  
-
402 335
     urlpatterns = patterns('',
403 336
         # ...
404  
-        (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
405  
-            {'feed_dict': feeds}),
  337
+        (r'^sitenews/rss/$', RssSiteNewsFeed()),
  338
+        (r'^sitenews/atom/$', AtomSiteNewsFeed()),
406 339
         # ...
407 340
     )
408 341
 
409 342
 Feed class reference
410 343
 --------------------
411 344
 
412  
-.. class:: django.contrib.syndication.feeds.Feed
  345
+.. class:: django.contrib.syndication.views.Feed
413 346
 
414 347
 This example illustrates all possible attributes and methods for a
415  
-:class:`~django.contrib.syndication.feeds.Feed` class::
  348
+:class:`~django.contrib.syndication.views.Feed` class::
416 349
 
417  
-    from django.contrib.syndication.feeds import Feed
  350
+    from django.contrib.syndication.views import Feed
418 351
     from django.utils import feedgenerator
419 352
 
420 353
     class ExampleFeed(Feed):
421 354
 
422 355
         # FEED TYPE -- Optional. This should be a class that subclasses
423  
-        # django.utils.feedgenerator.SyndicationFeed. This designates which
424  
-        # type of feed this should be: RSS 2.0, Atom 1.0, etc.
425  
-        # If you don't specify feed_type, your feed will be RSS 2.0.
426  
-        # This should be a class, not an instance of the class.
  356
+        # django.utils.feedgenerator.SyndicationFeed. This designates
  357
+        # which type of feed this should be: RSS 2.0, Atom 1.0, etc. If
  358
+        # you don't specify feed_type, your feed will be RSS 2.0. This
  359
+        # should be a class, not an instance of the class.
427 360
 
428 361
         feed_type = feedgenerator.Rss201rev2Feed
429 362
 
430  
-        # TEMPLATE NAMES -- Optional. These should be strings representing
431  
-        # names of Django templates that the system should use in rendering the
432  
-        # title and description of your feed items. Both are optional.
433  
-        # If you don't specify one, or either, Django will use the template
434  
-        # 'feeds/SLUG_title.html' and 'feeds/SLUG_description.html', where SLUG
435  
-        # is the slug you specify in the URL.
  363
+        # TEMPLATE NAMES -- Optional. These should be strings
  364
+        # representing names of Django templates that the system should
  365
+        # use in rendering the title and description of your feed items.
  366
+        # Both are optional. If a template is not specified, the
  367
+        # item_title() or item_description() methods are used instead.
436 368
 
437 369
         title_template = None
438 370
         description_template = None
439 371
 
440  
-        # TITLE -- One of the following three is required. The framework looks
441  
-        # for them in this order.
  372
+        # TITLE -- One of the following three is required. The framework
  373
+        # looks for them in this order.
442 374
 
443 375
         def title(self, obj):
444 376
             """
445  
-            Takes the object returned by get_object() and returns the feed's
446  
-            title as a normal Python string.
  377
+            Takes the object returned by get_object() and returns the
  378
+            feed's title as a normal Python string.
447 379
             """
448 380
 
449 381
         def title(self):
@@ -453,13 +385,13 @@ This example illustrates all possible attributes and methods for a
453 385
 
454 386
         title = 'foo' # Hard-coded title.
455 387
 
456  
-        # LINK -- One of the following three is required. The framework looks
457  
-        # for them in this order.
  388
+        # LINK -- One of the following three is required. The framework
  389
+        # looks for them in this order.
458 390
 
459 391
         def link(self, obj):
460 392
             """
461  
-            Takes the object returned by get_object() and returns the feed's
462  
-            link as a normal Python string.
  393
+            # Takes the object returned by get_object() and returns the feed's
  394
+            # link as a normal Python string.
463 395
             """
464 396
 
465 397
         def link(self):
@@ -572,18 +504,18 @@ This example illustrates all possible attributes and methods for a
572 504
         # COPYRIGHT NOTICE -- One of the following three is optional. The
573 505
         # framework looks for them in this order.
574 506
 
575  
-        def copyright(self, obj):
  507
+        def feed_copyright(self, obj):
576 508
             """
577 509
             Takes the object returned by get_object() and returns the feed's
578 510
             copyright notice as a normal Python string.
579 511
             """
580 512
 
581  
-        def copyright(self):
  513
+        def feed_copyright(self):
582 514
             """
583 515
             Returns the feed's copyright notice as a normal Python string.
584 516
             """
585 517
 
586  
-        copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
  518
+        feed_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
587 519
 
588 520
         # TTL -- One of the following three is optional. The framework looks
589 521
         # for them in this order. Ignored for Atom feeds.
@@ -620,13 +552,44 @@ This example illustrates all possible attributes and methods for a
620 552
         # GET_OBJECT -- This is required for feeds that publish different data
621 553
         # for different URL parameters. (See "A complex example" above.)
622 554
 
623  
-        def get_object(self, bits):
  555
+        def get_object(self, request, *args, **kwargs):
624 556
             """
625  
-            Takes a list of strings gleaned from the URL and returns an object
626  
-            represented by this feed. Raises
  557
+            Takes the current request and the arguments from the URL, and
  558
+            returns an object represented by this feed. Raises
627 559
             django.core.exceptions.ObjectDoesNotExist on error.
628 560
             """
629 561
 
  562
+        # ITEM TITLE AND DESCRIPTION -- If title_template or
  563
+        # description_template are not defined, these are used instead. Both are
  564
+        # optional, by default they will use the unicode representation of the
  565
+        # item.
  566
+
  567
+        def item_title(self, item):
  568
+            """
  569
+            Takes an item, as returned by items(), and returns the item's
  570
+            title as a normal Python string.
  571
+            """
  572
+
  573
+        def item_title(self):
  574
+            """
  575
+            Returns the title for every item in the feed.
  576
+            """
  577
+