Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added a number of callbacks to SyndicationFeed for adding custom attr…

…ibutes and elements to feeds. Refs #6547.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@8311 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 942e5246accda3d953b4634a010402ef5786c2e0 1 parent 899ca54
Jacob Kaplan-Moss authored August 11, 2008
246  django/utils/feedgenerator.py
@@ -19,9 +19,10 @@
19 19
 http://diveintomark.org/archives/2004/02/04/incompatible-rss
20 20
 """
21 21
 
  22
+import re
  23
+import datetime
22 24
 from django.utils.xmlutils import SimplerXMLGenerator
23 25
 from django.utils.encoding import force_unicode, iri_to_uri
24  
-import datetime, re, time
25 26
 
26 27
 def rfc2822_date(date):
27 28
     # We do this ourselves to be timezone aware, email.Utils is not tz aware.
@@ -56,7 +57,7 @@ class SyndicationFeed(object):
56 57
     "Base class for all syndication feeds. Subclasses should provide write()"
57 58
     def __init__(self, title, link, description, language=None, author_email=None,
58 59
             author_name=None, author_link=None, subtitle=None, categories=None,
59  
-            feed_url=None, feed_copyright=None, feed_guid=None, ttl=None):
  60
+            feed_url=None, feed_copyright=None, feed_guid=None, ttl=None, **kwargs):
60 61
         to_unicode = lambda s: force_unicode(s, strings_only=True)
61 62
         if categories:
62 63
             categories = [force_unicode(c) for c in categories]
@@ -75,11 +76,13 @@ def __init__(self, title, link, description, language=None, author_email=None,
75 76
             'id': feed_guid or link,
76 77
             'ttl': ttl,
77 78
         }
  79
+        self.feed.update(kwargs)
78 80
         self.items = []
79 81
 
80 82
     def add_item(self, title, link, description, author_email=None,
81 83
         author_name=None, author_link=None, pubdate=None, comments=None,
82  
-        unique_id=None, enclosure=None, categories=(), item_copyright=None, ttl=None):
  84
+        unique_id=None, enclosure=None, categories=(), item_copyright=None, 
  85
+        ttl=None, **kwargs):
83 86
         """
84 87
         Adds an item to the feed. All args are expected to be Python Unicode
85 88
         objects except pubdate, which is a datetime.datetime object, and
@@ -88,7 +91,7 @@ def add_item(self, title, link, description, author_email=None,
88 91
         to_unicode = lambda s: force_unicode(s, strings_only=True)
89 92
         if categories:
90 93
             categories = [to_unicode(c) for c in categories]
91  
-        self.items.append({
  94
+        item = {
92 95
             'title': to_unicode(title),
93 96
             'link': iri_to_uri(link),
94 97
             'description': to_unicode(description),
@@ -102,11 +105,39 @@ def add_item(self, title, link, description, author_email=None,
102 105
             'categories': categories or (),
103 106
             'item_copyright': to_unicode(item_copyright),
104 107
             'ttl': ttl,
105  
-        })
  108
+        }
  109
+        item.update(kwargs)
  110
+        self.items.append(item)
106 111
 
107 112
     def num_items(self):
108 113
         return len(self.items)
109 114
 
  115
+    def root_attributes(self):
  116
+        """
  117
+        Return extra attributes to place on the root (i.e. feed/channel) element.
  118
+        Called from write().
  119
+        """
  120
+        return {}
  121
+        
  122
+    def add_root_elements(self, handler):
  123
+        """
  124
+        Add elements in the the root (i.e. feed/channel) element. Called 
  125
+        from write().
  126
+        """
  127
+        pass
  128
+        
  129
+    def item_attributes(self, item):
  130
+        """
  131
+        Return extra attributes to place on each item (i.e. item/entry) element.
  132
+        """
  133
+        return {}
  134
+        
  135
+    def add_item_elements(self, handler, item):
  136
+        """
  137
+        Add elements on each item (i.e. item/entry) element.
  138
+        """
  139
+        pass
  140
+        
110 141
     def write(self, outfile, encoding):
111 142
         """
112 143
         Outputs the feed in the given encoding to outfile, which is a file-like
@@ -148,7 +179,19 @@ def write(self, outfile, encoding):
148 179
         handler = SimplerXMLGenerator(outfile, encoding)
149 180
         handler.startDocument()
150 181
         handler.startElement(u"rss", {u"version": self._version})
151  
-        handler.startElement(u"channel", {})
  182
+        handler.startElement(u"channel", self.root_attributes())
  183
+        self.add_root_elements(handler)
  184
+        self.write_items(handler)
  185
+        self.endChannelElement(handler)
  186
+        handler.endElement(u"rss")
  187
+
  188
+    def write_items(self, handler):
  189
+        for item in self.items:
  190
+            handler.startElement(u'item', self.item_attributes(item))
  191
+            self.add_item_elements(handler, item)
  192
+            handler.endElement(u"item")
  193
+
  194
+    def add_root_elements(self, handler):
152 195
         handler.addQuickElement(u"title", self.feed['title'])
153 196
         handler.addQuickElement(u"link", self.feed['link'])
154 197
         handler.addQuickElement(u"description", self.feed['description'])
@@ -161,76 +204,75 @@ def write(self, outfile, encoding):
161 204
         handler.addQuickElement(u"lastBuildDate", rfc2822_date(self.latest_post_date()).decode('ascii'))
162 205
         if self.feed['ttl'] is not None:
163 206
             handler.addQuickElement(u"ttl", self.feed['ttl'])
164  
-        self.write_items(handler)
165  
-        self.endChannelElement(handler)
166  
-        handler.endElement(u"rss")
167 207
 
168 208
     def endChannelElement(self, handler):
169 209
         handler.endElement(u"channel")
170 210
 
171 211
 class RssUserland091Feed(RssFeed):
172 212
     _version = u"0.91"
173  
-    def write_items(self, handler):
174  
-        for item in self.items:
175  
-            handler.startElement(u"item", {})
176  
-            handler.addQuickElement(u"title", item['title'])
177  
-            handler.addQuickElement(u"link", item['link'])
178  
-            if item['description'] is not None:
179  
-                handler.addQuickElement(u"description", item['description'])
180  
-            handler.endElement(u"item")
  213
+    def add_item_elements(self, handler, item):
  214
+        handler.addQuickElement(u"title", item['title'])
  215
+        handler.addQuickElement(u"link", item['link'])
  216
+        if item['description'] is not None:
  217
+            handler.addQuickElement(u"description", item['description'])
181 218
 
182 219
 class Rss201rev2Feed(RssFeed):
183 220
     # Spec: http://blogs.law.harvard.edu/tech/rss
184 221
     _version = u"2.0"
185  
-    def write_items(self, handler):
186  
-        for item in self.items:
187  
-            handler.startElement(u"item", {})
188  
-            handler.addQuickElement(u"title", item['title'])
189  
-            handler.addQuickElement(u"link", item['link'])
190  
-            if item['description'] is not None:
191  
-                handler.addQuickElement(u"description", item['description'])
192  
-
193  
-            # Author information.
194  
-            if item["author_name"] and item["author_email"]:
195  
-                handler.addQuickElement(u"author", "%s (%s)" % \
196  
-                    (item['author_email'], item['author_name']))
197  
-            elif item["author_email"]:
198  
-                handler.addQuickElement(u"author", item["author_email"])
199  
-            elif item["author_name"]:
200  
-                handler.addQuickElement(u"dc:creator", item["author_name"], {"xmlns:dc": u"http://purl.org/dc/elements/1.1/"})
201  
-
202  
-            if item['pubdate'] is not None:
203  
-                handler.addQuickElement(u"pubDate", rfc2822_date(item['pubdate']).decode('ascii'))
204  
-            if item['comments'] is not None:
205  
-                handler.addQuickElement(u"comments", item['comments'])
206  
-            if item['unique_id'] is not None:
207  
-                handler.addQuickElement(u"guid", item['unique_id'])
208  
-            if item['ttl'] is not None:
209  
-                handler.addQuickElement(u"ttl", item['ttl'])
210  
-
211  
-            # Enclosure.
212  
-            if item['enclosure'] is not None:
213  
-                handler.addQuickElement(u"enclosure", '',
214  
-                    {u"url": item['enclosure'].url, u"length": item['enclosure'].length,
215  
-                        u"type": item['enclosure'].mime_type})
216  
-
217  
-            # Categories.
218  
-            for cat in item['categories']:
219  
-                handler.addQuickElement(u"category", cat)
220  
-
221  
-            handler.endElement(u"item")
  222
+    def add_item_elements(self, handler, item):
  223
+        handler.addQuickElement(u"title", item['title'])
  224
+        handler.addQuickElement(u"link", item['link'])
  225
+        if item['description'] is not None:
  226
+            handler.addQuickElement(u"description", item['description'])
  227
+
  228
+        # Author information.
  229
+        if item["author_name"] and item["author_email"]:
  230
+            handler.addQuickElement(u"author", "%s (%s)" % \
  231
+                (item['author_email'], item['author_name']))
  232
+        elif item["author_email"]:
  233
+            handler.addQuickElement(u"author", item["author_email"])
  234
+        elif item["author_name"]:
  235
+            handler.addQuickElement(u"dc:creator", item["author_name"], {"xmlns:dc": u"http://purl.org/dc/elements/1.1/"})
  236
+
  237
+        if item['pubdate'] is not None:
  238
+            handler.addQuickElement(u"pubDate", rfc2822_date(item['pubdate']).decode('ascii'))
  239
+        if item['comments'] is not None:
  240
+            handler.addQuickElement(u"comments", item['comments'])
  241
+        if item['unique_id'] is not None:
  242
+            handler.addQuickElement(u"guid", item['unique_id'])
  243
+        if item['ttl'] is not None:
  244
+            handler.addQuickElement(u"ttl", item['ttl'])
  245
+
  246
+        # Enclosure.
  247
+        if item['enclosure'] is not None:
  248
+            handler.addQuickElement(u"enclosure", '',
  249
+                {u"url": item['enclosure'].url, u"length": item['enclosure'].length,
  250
+                    u"type": item['enclosure'].mime_type})
  251
+
  252
+        # Categories.
  253
+        for cat in item['categories']:
  254
+            handler.addQuickElement(u"category", cat)
222 255
 
223 256
 class Atom1Feed(SyndicationFeed):
224 257
     # Spec: http://atompub.org/2005/07/11/draft-ietf-atompub-format-10.html
225 258
     mime_type = 'application/atom+xml'
226 259
     ns = u"http://www.w3.org/2005/Atom"
  260
+
227 261
     def write(self, outfile, encoding):
228 262
         handler = SimplerXMLGenerator(outfile, encoding)
229 263
         handler.startDocument()
  264
+        handler.startElement(u'feed', self.root_attributes())
  265
+        self.add_root_elements(handler)
  266
+        self.write_items(handler)
  267
+        handler.endElement(u"feed")
  268
+
  269
+    def root_element_attributes(self):
230 270
         if self.feed['language'] is not None:
231  
-            handler.startElement(u"feed", {u"xmlns": self.ns, u"xml:lang": self.feed['language']})
  271
+            return {u"xmlns": self.ns, u"xml:lang": self.feed['language']}
232 272
         else:
233  
-            handler.startElement(u"feed", {u"xmlns": self.ns})
  273
+            return {u"xmlns": self.ns}
  274
+
  275
+    def add_root_elements(self, handler):
234 276
         handler.addQuickElement(u"title", self.feed['title'])
235 277
         handler.addQuickElement(u"link", "", {u"rel": u"alternate", u"href": self.feed['link']})
236 278
         if self.feed['feed_url'] is not None:
@@ -251,55 +293,55 @@ def write(self, outfile, encoding):
251 293
             handler.addQuickElement(u"category", "", {u"term": cat})
252 294
         if self.feed['feed_copyright'] is not None:
253 295
             handler.addQuickElement(u"rights", self.feed['feed_copyright'])
254  
-        self.write_items(handler)
255  
-        handler.endElement(u"feed")
256  
-
  296
+        
257 297
     def write_items(self, handler):
258 298
         for item in self.items:
259  
-            handler.startElement(u"entry", {})
260  
-            handler.addQuickElement(u"title", item['title'])
261  
-            handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"alternate"})
262  
-            if item['pubdate'] is not None:
263  
-                handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('ascii'))
264  
-
265  
-            # Author information.
266  
-            if item['author_name'] is not None:
267  
-                handler.startElement(u"author", {})
268  
-                handler.addQuickElement(u"name", item['author_name'])
269  
-                if item['author_email'] is not None:
270  
-                    handler.addQuickElement(u"email", item['author_email'])
271  
-                if item['author_link'] is not None:
272  
-                    handler.addQuickElement(u"uri", item['author_link'])
273  
-                handler.endElement(u"author")
274  
-
275  
-            # Unique ID.
276  
-            if item['unique_id'] is not None:
277  
-                unique_id = item['unique_id']
278  
-            else:
279  
-                unique_id = get_tag_uri(item['link'], item['pubdate'])
280  
-            handler.addQuickElement(u"id", unique_id)
281  
-
282  
-            # Summary.
283  
-            if item['description'] is not None:
284  
-                handler.addQuickElement(u"summary", item['description'], {u"type": u"html"})
285  
-
286  
-            # Enclosure.
287  
-            if item['enclosure'] is not None:
288  
-                handler.addQuickElement(u"link", '',
289  
-                    {u"rel": u"enclosure",
290  
-                     u"href": item['enclosure'].url,
291  
-                     u"length": item['enclosure'].length,
292  
-                     u"type": item['enclosure'].mime_type})
293  
-
294  
-            # Categories.
295  
-            for cat in item['categories']:
296  
-                handler.addQuickElement(u"category", u"", {u"term": cat})
297  
-
298  
-            # Rights.
299  
-            if item['item_copyright'] is not None:
300  
-                handler.addQuickElement(u"rights", item['item_copyright'])
301  
-
  299
+            handler.startElement(u"entry", self.item_attributes(item))
  300
+            self.add_item_elements(handler, item)
302 301
             handler.endElement(u"entry")
  302
+            
  303
+    def add_item_elements(self, handler, item):
  304
+        handler.addQuickElement(u"title", item['title'])
  305
+        handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"alternate"})
  306
+        if item['pubdate'] is not None:
  307
+            handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('ascii'))
  308
+
  309
+        # Author information.
  310
+        if item['author_name'] is not None:
  311
+            handler.startElement(u"author", {})
  312
+            handler.addQuickElement(u"name", item['author_name'])
  313
+            if item['author_email'] is not None:
  314
+                handler.addQuickElement(u"email", item['author_email'])
  315
+            if item['author_link'] is not None:
  316
+                handler.addQuickElement(u"uri", item['author_link'])
  317
+            handler.endElement(u"author")
  318
+
  319
+        # Unique ID.
  320
+        if item['unique_id'] is not None:
  321
+            unique_id = item['unique_id']
  322
+        else:
  323
+            unique_id = get_tag_uri(item['link'], item['pubdate'])
  324
+        handler.addQuickElement(u"id", unique_id)
  325
+
  326
+        # Summary.
  327
+        if item['description'] is not None:
  328
+            handler.addQuickElement(u"summary", item['description'], {u"type": u"html"})
  329
+
  330
+        # Enclosure.
  331
+        if item['enclosure'] is not None:
  332
+            handler.addQuickElement(u"link", '',
  333
+                {u"rel": u"enclosure",
  334
+                 u"href": item['enclosure'].url,
  335
+                 u"length": item['enclosure'].length,
  336
+                 u"type": item['enclosure'].mime_type})
  337
+
  338
+        # Categories.
  339
+        for cat in item['categories']:
  340
+            handler.addQuickElement(u"category", u"", {u"term": cat})
  341
+
  342
+        # Rights.
  343
+        if item['item_copyright'] is not None:
  344
+            handler.addQuickElement(u"rights", item['item_copyright'])
303 345
 
304 346
 # This isolates the decision of what the system default is, so calling code can
305 347
 # do "feedgenerator.DefaultFeed" instead of "feedgenerator.Rss201rev2Feed".
173  docs/syndication_feeds.txt
@@ -801,7 +801,12 @@ Behind the scenes, the high-level RSS framework uses a lower-level framework
801 801
 for generating feeds' XML. This framework lives in a single module:
802 802
 `django/utils/feedgenerator.py`_.
803 803
 
804  
-Feel free to use this framework on your own, for lower-level tasks.
  804
+You use this framework on your own, for lower-level feed generation. You can
  805
+also create custom feed generator subclasses for use with the ``feed_type``
  806
+``Feed`` option.
  807
+
  808
+``SyndicationFeed`` classes
  809
+---------------------------
805 810
 
806 811
 The ``feedgenerator`` module contains a base class ``SyndicationFeed`` and
807 812
 several subclasses:
@@ -813,38 +818,71 @@ several subclasses:
813 818
 Each of these three classes knows how to render a certain type of feed as XML.
814 819
 They share this interface:
815 820
 
816  
-``__init__(title, link, description, language=None, author_email=None,``
817  
-``author_name=None, author_link=None, subtitle=None, categories=None,``
818  
-``feed_url=None)``
819  
-
820  
-Initializes the feed with the given metadata, which applies to the entire feed
821  
-(i.e., not just to a specific item in the feed).
822  
-
823  
-All parameters, if given, should be Unicode objects, except ``categories``,
824  
-which should be a sequence of Unicode objects.
825  
-
826  
-``add_item(title, link, description, author_email=None, author_name=None,``
827  
-``pubdate=None, comments=None, unique_id=None, enclosure=None, categories=())``
828  
-
829  
-Add an item to the feed with the given parameters. All parameters, if given,
830  
-should be Unicode objects, except:
831  
-
832  
-    * ``pubdate`` should be a `Python datetime object`_.
833  
-    * ``enclosure`` should be an instance of ``feedgenerator.Enclosure``.
834  
-    * ``categories`` should be a sequence of Unicode objects.
835  
-
836  
-``write(outfile, encoding)``
837  
-
838  
-Outputs the feed in the given encoding to outfile, which is a file-like object.
839  
-
840  
-``writeString(encoding)``
841  
-
842  
-Returns the feed as a string in the given encoding.
843  
-
844  
-Example usage
845  
--------------
846  
-
847  
-This example creates an Atom 1.0 feed and prints it to standard output::
  821
+``SyndicationFeed.__init__(**kwargs)``
  822
+    Initialize the feed with the given dictionary of metadata, which applies to
  823
+    the entire feed. Required keyword arguments are:
  824
+    
  825
+        * ``title``
  826
+        * ``link``
  827
+        * ``description``
  828
+        
  829
+    There's also a bunch of other optional keywords:
  830
+    
  831
+        * ``language``
  832
+        * ``author_email``
  833
+        * ``author_name``
  834
+        * ``author_link``
  835
+        * ``subtitle``
  836
+        * ``categories``
  837
+        * ``feed_url``
  838
+        * ``feed_copyright``
  839
+        * ``feed_guid``
  840
+        * ``ttl``
  841
+        
  842
+    Any extra keyword arguments you pass to ``__init__`` will be stored in
  843
+    ``self.feed`` for use with `custom feed generators`_.
  844
+
  845
+    All parameters should be Unicode objects, except ``categories``, which
  846
+    should be a sequence of Unicode objects.
  847
+
  848
+``SyndicationFeed.add_item(**kwargs)``
  849
+    Add an item to the feed with the given parameters. 
  850
+
  851
+    Required keyword arguments are:
  852
+    
  853
+        * ``title``
  854
+        * ``link``
  855
+        * ``description``
  856
+
  857
+    Optional keyword arguments are:
  858
+
  859
+        * ``author_email``
  860
+        * ``author_name``
  861
+        * ``author_link``
  862
+        * ``pubdate``
  863
+        * ``comments``
  864
+        * ``unique_id``
  865
+        * ``enclosure``
  866
+        * ``categories``
  867
+        * ``item_copyright``
  868
+        * ``ttl``
  869
+
  870
+    Extra keyword arguments will be stored for `custom feed generators`_.
  871
+
  872
+    All parameters, if given, should be Unicode objects, except:
  873
+
  874
+        * ``pubdate`` should be a `Python datetime object`_.
  875
+        * ``enclosure`` should be an instance of ``feedgenerator.Enclosure``.
  876
+        * ``categories`` should be a sequence of Unicode objects.
  877
+        
  878
+``SyndicationFeed.write(outfile, encoding)``
  879
+    Outputs the feed in the given ``encoding`` to ``outfile``, which must be a
  880
+    file-like object.
  881
+
  882
+``SyndicationFeed.writeString(encoding)``
  883
+    Returns the feed as a string in the given ``encoding``.
  884
+
  885
+For example, to create an Atom 1.0 feed and print it to standard output::
848 886
 
849 887
     >>> from django.utils import feedgenerator
850 888
     >>> f = feedgenerator.Atom1Feed(
@@ -857,12 +895,69 @@ This example creates an Atom 1.0 feed and prints it to standard output::
857 895
     ...     description=u"<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>")
858 896
     >>> print f.writeString('utf8')
859 897
     <?xml version="1.0" encoding="utf8"?>
860  
-    <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title>My Weblog</title>
861  
-    <link href="http://www.example.com/"></link><id>http://www.example.com/</id>
862  
-    <updated>Sat, 12 Nov 2005 00:28:43 -0000</updated><entry><title>Hot dog today</title>
863  
-    <link>http://www.example.com/entries/1/</link><id>tag:www.example.com/entries/1/</id>
864  
-    <summary type="html">&lt;p&gt;Today I had a Vienna Beef hot dog. It was pink, plump and perfect.&lt;/p&gt;</summary>
865  
-    </entry></feed>
  898
+    <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  899
+    ...
  900
+    </feed>
866 901
 
867 902
 .. _django/utils/feedgenerator.py: http://code.djangoproject.com/browser/django/trunk/django/utils/feedgenerator.py
868 903
 .. _Python datetime object: http://www.python.org/doc/current/lib/module-datetime.html
  904
+
  905
+Custom feed generators
  906
+----------------------
  907
+
  908
+If you need to produce a custom feed format, you've got a couple of options.
  909
+    
  910
+If the feed format is totally custom, you'll want to subclass
  911
+``SyndicationFeed`` and completely replace the ``write()`` and
  912
+``writeString()`` methods.
  913
+
  914
+However, if the feed format is a spin-off of RSS or Atom (i.e. GeoRSS_, Apple's
  915
+`iTunes podcast format`_, etc.), you've got a better choice. These types of
  916
+feeds typically add extra elements and/or attributes to the underlying format,
  917
+and there are a set of methods that ``SyndicationFeed`` calls to get these extra
  918
+attributes. Thus, you can subclass the appropriate feed generator class
  919
+(``Atom1Feed`` or ``Rss201rev2Feed``) and extend these callbacks. They are:
  920
+      
  921
+.. _georss: http://georss.org/
  922
+.. _itunes podcast format: http://www.apple.com/itunes/store/podcaststechspecs.html
  923
+
  924
+``SyndicationFeed.root_attributes(self, )``
  925
+    Return a ``dict`` of attributes to add to the root feed element
  926
+    (``feed``/``channel``).
  927
+    
  928
+``SyndicationFeed.add_root_elements(self, handler)``
  929
+    Callback to add elements inside the root feed element
  930
+    (``feed``/``channel``). ``handler`` is an `XMLGenerator`_ from Python's
  931
+    built-in SAX library; you'll call methods on it to add to the XML
  932
+    document in process.
  933
+    
  934
+``SyndicationFeed.item_attributes(self, item)``
  935
+    Return a ``dict`` of attributes to add to each item (``item``/``entry``)
  936
+    element. The argument, ``item``, is a dictionary of all the data passed to
  937
+    ``SyndicationFeed.add_item()``.
  938
+    
  939
+``SyndicationFeed.add_item_elements(self, handler, item)``
  940
+    Callback to add elements to each item (``item``/``entry``) element.
  941
+    ``handler`` and ``item`` are as above.
  942
+
  943
+.. warning::
  944
+
  945
+    If you override any of these methods, be sure to call the superclass methods
  946
+    since they add the required elements for each feed format.
  947
+
  948
+For example, you might start implementing an iTunes RSS feed generator like so::
  949
+
  950
+    class iTunesFeed(Rss201rev2Feed):
  951
+        def root_attibutes(self):
  952
+            attrs = super(iTunesFeed, self).root_attibutes()
  953
+            attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd
  954
+            return attrs
  955
+            
  956
+        def add_root_elements(self, handler):
  957
+            super(iTunesFeed, self).add_root_elements(handler)
  958
+            handler.addQuickElement('itunes:explicit', 'clean')
  959
+
  960
+Obviously there's a lot more work to be done for a complete custom feed class,
  961
+but the above example should demonstrate the basic idea.
  962
+
  963
+.. _XMLGenerator: http://docs.python.org/dev/library/xml.sax.utils.html#xml.sax.saxutils.XMLGenerator
25  tests/regressiontests/syndication/feeds.py
@@ -21,3 +21,28 @@ def item_link(self, item):
21 21
 
22 22
 class TestAtomFeed(TestRssFeed):
23 23
     feed_type = Atom1Feed
  24
+
  25
+class MyCustomAtom1Feed(Atom1Feed):
  26
+    """
  27
+    Test of a custom feed generator class.
  28
+    """    
  29
+    def root_attributes(self):
  30
+        attrs = super(MyCustomAtom1Feed, self).root_attributes()
  31
+        attrs[u'django'] = u'rocks'
  32
+        return attrs
  33
+        
  34
+    def add_root_elements(self, handler):
  35
+        super(MyCustomAtom1Feed, self).add_root_elements(handler)
  36
+        handler.addQuickElement(u'spam', u'eggs')
  37
+        
  38
+    def item_attributes(self, item):
  39
+        attrs = super(MyCustomAtom1Feed, self).item_attributes(item)
  40
+        attrs[u'bacon'] = u'yum'
  41
+        return attrs
  42
+        
  43
+    def add_item_elements(self, handler, item):
  44
+        super(MyCustomAtom1Feed, self).add_item_elements(handler, item)
  45
+        handler.addQuickElement(u'ministry', u'silly walks')
  46
+    
  47
+class TestCustomFeed(TestAtomFeed):
  48
+    feed_type = MyCustomAtom1Feed
48  tests/regressiontests/syndication/tests.py
@@ -4,22 +4,64 @@
4 4
 from django.test import TestCase
5 5
 from django.test.client import Client
6 6
 from models import Entry
  7
+try:
  8
+    set
  9
+except NameError:
  10
+    from sets import Set as set
7 11
 
8 12
 class SyndicationFeedTest(TestCase):
9 13
     fixtures = ['feeddata.json']
10 14
 
  15
+    def assertChildNodes(self, elem, expected):
  16
+        actual = set([n.nodeName for n in elem.childNodes])
  17
+        expected = set(expected)
  18
+        self.assertEqual(actual, expected)
  19
+
11 20
     def test_rss_feed(self):
12 21
         response = self.client.get('/syndication/feeds/rss/')
13 22
         doc = minidom.parseString(response.content)
14 23
         self.assertEqual(len(doc.getElementsByTagName('channel')), 1)
15  
-        self.assertEqual(len(doc.getElementsByTagName('item')), Entry.objects.count())
  24
+
  25
+        chan = doc.getElementsByTagName('channel')[0]
  26
+        self.assertChildNodes(chan, ['title', 'link', 'description', 'language', 'lastBuildDate', 'item'])
  27
+    
  28
+        items = chan.getElementsByTagName('item')
  29
+        self.assertEqual(len(items), Entry.objects.count())
  30
+        for item in items:
  31
+            self.assertChildNodes(item, ['title', 'link', 'description', 'guid'])
16 32
     
17 33
     def test_atom_feed(self):
18 34
         response = self.client.get('/syndication/feeds/atom/')
19 35
         doc = minidom.parseString(response.content)
20  
-        self.assertEqual(len(doc.getElementsByTagName('feed')), 1)
21  
-        self.assertEqual(len(doc.getElementsByTagName('entry')), Entry.objects.count())
  36
+        
  37
+        feed = doc.firstChild
  38
+        self.assertEqual(feed.nodeName, 'feed')
  39
+        self.assertChildNodes(feed, ['title', 'link', 'id', 'updated', 'entry'])        
  40
+        
  41
+        entries = feed.getElementsByTagName('entry')
  42
+        self.assertEqual(len(entries), Entry.objects.count())
  43
+        for entry in entries:
  44
+            self.assertChildNodes(entry, ['title', 'link', 'id', 'summary'])
  45
+            summary = entry.getElementsByTagName('summary')[0]
  46
+            self.assertEqual(summary.getAttribute('type'), 'html')
22 47
     
  48
+    def test_custom_feed_generator(self):
  49
+        response = self.client.get('/syndication/feeds/custom/')
  50
+        doc = minidom.parseString(response.content)
  51
+        
  52
+        feed = doc.firstChild
  53
+        self.assertEqual(feed.nodeName, 'feed')
  54
+        self.assertEqual(feed.getAttribute('django'), 'rocks')
  55
+        self.assertChildNodes(feed, ['title', 'link', 'id', 'updated', 'entry', 'spam'])        
  56
+        
  57
+        entries = feed.getElementsByTagName('entry')
  58
+        self.assertEqual(len(entries), Entry.objects.count())
  59
+        for entry in entries:
  60
+            self.assertEqual(entry.getAttribute('bacon'), 'yum')
  61
+            self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'ministry'])
  62
+            summary = entry.getElementsByTagName('summary')[0]
  63
+            self.assertEqual(summary.getAttribute('type'), 'html')
  64
+        
23 65
     def test_complex_base_url(self):
24 66
         """
25 67
         Tests that that the base url for a complex feed doesn't raise a 500
3  tests/regressiontests/syndication/urls.py
... ...
@@ -1,10 +1,11 @@
1  
-from feeds import TestRssFeed, TestAtomFeed, ComplexFeed
  1
+from feeds import TestRssFeed, TestAtomFeed, TestCustomFeed, ComplexFeed
2 2
 from django.conf.urls.defaults import patterns
3 3
 
4 4
 feed_dict = {
5 5
     'complex': ComplexFeed,
6 6
     'rss': TestRssFeed,
7 7
     'atom': TestAtomFeed,
  8
+    'custom': TestCustomFeed,
8 9
     
9 10
 }
10 11
 urlpatterns = patterns('',

0 notes on commit 942e524

Please sign in to comment.
Something went wrong with that request. Please try again.