##// END OF EJS Templates
feedgenerator: fixed missing utc definition.
marcink -
r4274:10b9ffbb default
parent child Browse files
Show More
@@ -1,444 +1,446 b''
1 # Copyright (c) Django Software Foundation and individual contributors.
1 # Copyright (c) Django Software Foundation and individual contributors.
2 # All rights reserved.
2 # All rights reserved.
3 #
3 #
4 # Redistribution and use in source and binary forms, with or without modification,
4 # Redistribution and use in source and binary forms, with or without modification,
5 # are permitted provided that the following conditions are met:
5 # are permitted provided that the following conditions are met:
6 #
6 #
7 # 1. Redistributions of source code must retain the above copyright notice,
7 # 1. Redistributions of source code must retain the above copyright notice,
8 # this list of conditions and the following disclaimer.
8 # this list of conditions and the following disclaimer.
9 #
9 #
10 # 2. Redistributions in binary form must reproduce the above copyright
10 # 2. Redistributions in binary form must reproduce the above copyright
11 # notice, this list of conditions and the following disclaimer in the
11 # notice, this list of conditions and the following disclaimer in the
12 # documentation and/or other materials provided with the distribution.
12 # documentation and/or other materials provided with the distribution.
13 #
13 #
14 # 3. Neither the name of Django nor the names of its contributors may be used
14 # 3. Neither the name of Django nor the names of its contributors may be used
15 # to endorse or promote products derived from this software without
15 # to endorse or promote products derived from this software without
16 # specific prior written permission.
16 # specific prior written permission.
17 #
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
21 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
28
29 """
29 """
30 For definitions of the different versions of RSS, see:
30 For definitions of the different versions of RSS, see:
31 http://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004/02/04/incompatible-rss
31 http://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004/02/04/incompatible-rss
32 """
32 """
33 from __future__ import unicode_literals
33 from __future__ import unicode_literals
34
34
35 import datetime
35 import datetime
36 from StringIO import StringIO
36 from StringIO import StringIO
37
38 import pytz
37 from six.moves.urllib import parse as urlparse
39 from six.moves.urllib import parse as urlparse
38
40
39 from rhodecode.lib.feedgenerator import datetime_safe
41 from rhodecode.lib.feedgenerator import datetime_safe
40 from rhodecode.lib.feedgenerator.utils import SimplerXMLGenerator, iri_to_uri, force_text
42 from rhodecode.lib.feedgenerator.utils import SimplerXMLGenerator, iri_to_uri, force_text
41
43
42
44
43 #### The following code comes from ``django.utils.feedgenerator`` ####
45 #### The following code comes from ``django.utils.feedgenerator`` ####
44
46
45
47
46 def rfc2822_date(date):
48 def rfc2822_date(date):
47 # We can't use strftime() because it produces locale-dependent results, so
49 # We can't use strftime() because it produces locale-dependent results, so
48 # we have to map english month and day names manually
50 # we have to map english month and day names manually
49 months = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',)
51 months = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',)
50 days = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')
52 days = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')
51 # Support datetime objects older than 1900
53 # Support datetime objects older than 1900
52 date = datetime_safe.new_datetime(date)
54 date = datetime_safe.new_datetime(date)
53 # We do this ourselves to be timezone aware, email.Utils is not tz aware.
55 # We do this ourselves to be timezone aware, email.Utils is not tz aware.
54 dow = days[date.weekday()]
56 dow = days[date.weekday()]
55 month = months[date.month - 1]
57 month = months[date.month - 1]
56 time_str = date.strftime('%s, %%d %s %%Y %%H:%%M:%%S ' % (dow, month))
58 time_str = date.strftime('%s, %%d %s %%Y %%H:%%M:%%S ' % (dow, month))
57
59
58 time_str = time_str.decode('utf-8')
60 time_str = time_str.decode('utf-8')
59 offset = date.utcoffset()
61 offset = date.utcoffset()
60 # Historically, this function assumes that naive datetimes are in UTC.
62 # Historically, this function assumes that naive datetimes are in UTC.
61 if offset is None:
63 if offset is None:
62 return time_str + '-0000'
64 return time_str + '-0000'
63 else:
65 else:
64 timezone = (offset.days * 24 * 60) + (offset.seconds // 60)
66 timezone = (offset.days * 24 * 60) + (offset.seconds // 60)
65 hour, minute = divmod(timezone, 60)
67 hour, minute = divmod(timezone, 60)
66 return time_str + '%+03d%02d' % (hour, minute)
68 return time_str + '%+03d%02d' % (hour, minute)
67
69
68
70
69 def rfc3339_date(date):
71 def rfc3339_date(date):
70 # Support datetime objects older than 1900
72 # Support datetime objects older than 1900
71 date = datetime_safe.new_datetime(date)
73 date = datetime_safe.new_datetime(date)
72 time_str = date.strftime('%Y-%m-%dT%H:%M:%S')
74 time_str = date.strftime('%Y-%m-%dT%H:%M:%S')
73
75
74 time_str = time_str.decode('utf-8')
76 time_str = time_str.decode('utf-8')
75 offset = date.utcoffset()
77 offset = date.utcoffset()
76 # Historically, this function assumes that naive datetimes are in UTC.
78 # Historically, this function assumes that naive datetimes are in UTC.
77 if offset is None:
79 if offset is None:
78 return time_str + 'Z'
80 return time_str + 'Z'
79 else:
81 else:
80 timezone = (offset.days * 24 * 60) + (offset.seconds // 60)
82 timezone = (offset.days * 24 * 60) + (offset.seconds // 60)
81 hour, minute = divmod(timezone, 60)
83 hour, minute = divmod(timezone, 60)
82 return time_str + '%+03d:%02d' % (hour, minute)
84 return time_str + '%+03d:%02d' % (hour, minute)
83
85
84
86
85 def get_tag_uri(url, date):
87 def get_tag_uri(url, date):
86 """
88 """
87 Creates a TagURI.
89 Creates a TagURI.
88
90
89 See http://web.archive.org/web/20110514113830/http://diveintomark.org/archives/2004/05/28/howto-atom-id
91 See http://web.archive.org/web/20110514113830/http://diveintomark.org/archives/2004/05/28/howto-atom-id
90 """
92 """
91 bits = urlparse(url)
93 bits = urlparse(url)
92 d = ''
94 d = ''
93 if date is not None:
95 if date is not None:
94 d = ',%s' % datetime_safe.new_datetime(date).strftime('%Y-%m-%d')
96 d = ',%s' % datetime_safe.new_datetime(date).strftime('%Y-%m-%d')
95 return 'tag:%s%s:%s/%s' % (bits.hostname, d, bits.path, bits.fragment)
97 return 'tag:%s%s:%s/%s' % (bits.hostname, d, bits.path, bits.fragment)
96
98
97
99
98 class SyndicationFeed(object):
100 class SyndicationFeed(object):
99 """Base class for all syndication feeds. Subclasses should provide write()"""
101 """Base class for all syndication feeds. Subclasses should provide write()"""
100
102
101 def __init__(self, title, link, description, language=None, author_email=None,
103 def __init__(self, title, link, description, language=None, author_email=None,
102 author_name=None, author_link=None, subtitle=None, categories=None,
104 author_name=None, author_link=None, subtitle=None, categories=None,
103 feed_url=None, feed_copyright=None, feed_guid=None, ttl=None, **kwargs):
105 feed_url=None, feed_copyright=None, feed_guid=None, ttl=None, **kwargs):
104 def to_unicode(s):
106 def to_unicode(s):
105 return force_text(s, strings_only=True)
107 return force_text(s, strings_only=True)
106 if categories:
108 if categories:
107 categories = [force_text(c) for c in categories]
109 categories = [force_text(c) for c in categories]
108 if ttl is not None:
110 if ttl is not None:
109 # Force ints to unicode
111 # Force ints to unicode
110 ttl = force_text(ttl)
112 ttl = force_text(ttl)
111 self.feed = {
113 self.feed = {
112 'title': to_unicode(title),
114 'title': to_unicode(title),
113 'link': iri_to_uri(link),
115 'link': iri_to_uri(link),
114 'description': to_unicode(description),
116 'description': to_unicode(description),
115 'language': to_unicode(language),
117 'language': to_unicode(language),
116 'author_email': to_unicode(author_email),
118 'author_email': to_unicode(author_email),
117 'author_name': to_unicode(author_name),
119 'author_name': to_unicode(author_name),
118 'author_link': iri_to_uri(author_link),
120 'author_link': iri_to_uri(author_link),
119 'subtitle': to_unicode(subtitle),
121 'subtitle': to_unicode(subtitle),
120 'categories': categories or (),
122 'categories': categories or (),
121 'feed_url': iri_to_uri(feed_url),
123 'feed_url': iri_to_uri(feed_url),
122 'feed_copyright': to_unicode(feed_copyright),
124 'feed_copyright': to_unicode(feed_copyright),
123 'id': feed_guid or link,
125 'id': feed_guid or link,
124 'ttl': ttl,
126 'ttl': ttl,
125 }
127 }
126 self.feed.update(kwargs)
128 self.feed.update(kwargs)
127 self.items = []
129 self.items = []
128
130
129 def add_item(self, title, link, description, author_email=None,
131 def add_item(self, title, link, description, author_email=None,
130 author_name=None, author_link=None, pubdate=None, comments=None,
132 author_name=None, author_link=None, pubdate=None, comments=None,
131 unique_id=None, unique_id_is_permalink=None, enclosure=None,
133 unique_id=None, unique_id_is_permalink=None, enclosure=None,
132 categories=(), item_copyright=None, ttl=None, updateddate=None,
134 categories=(), item_copyright=None, ttl=None, updateddate=None,
133 enclosures=None, **kwargs):
135 enclosures=None, **kwargs):
134 """
136 """
135 Adds an item to the feed. All args are expected to be Python Unicode
137 Adds an item to the feed. All args are expected to be Python Unicode
136 objects except pubdate and updateddate, which are datetime.datetime
138 objects except pubdate and updateddate, which are datetime.datetime
137 objects, and enclosures, which is an iterable of instances of the
139 objects, and enclosures, which is an iterable of instances of the
138 Enclosure class.
140 Enclosure class.
139 """
141 """
140 def to_unicode(s):
142 def to_unicode(s):
141 return force_text(s, strings_only=True)
143 return force_text(s, strings_only=True)
142 if categories:
144 if categories:
143 categories = [to_unicode(c) for c in categories]
145 categories = [to_unicode(c) for c in categories]
144 if ttl is not None:
146 if ttl is not None:
145 # Force ints to unicode
147 # Force ints to unicode
146 ttl = force_text(ttl)
148 ttl = force_text(ttl)
147 if enclosure is None:
149 if enclosure is None:
148 enclosures = [] if enclosures is None else enclosures
150 enclosures = [] if enclosures is None else enclosures
149
151
150 item = {
152 item = {
151 'title': to_unicode(title),
153 'title': to_unicode(title),
152 'link': iri_to_uri(link),
154 'link': iri_to_uri(link),
153 'description': to_unicode(description),
155 'description': to_unicode(description),
154 'author_email': to_unicode(author_email),
156 'author_email': to_unicode(author_email),
155 'author_name': to_unicode(author_name),
157 'author_name': to_unicode(author_name),
156 'author_link': iri_to_uri(author_link),
158 'author_link': iri_to_uri(author_link),
157 'pubdate': pubdate,
159 'pubdate': pubdate,
158 'updateddate': updateddate,
160 'updateddate': updateddate,
159 'comments': to_unicode(comments),
161 'comments': to_unicode(comments),
160 'unique_id': to_unicode(unique_id),
162 'unique_id': to_unicode(unique_id),
161 'unique_id_is_permalink': unique_id_is_permalink,
163 'unique_id_is_permalink': unique_id_is_permalink,
162 'enclosures': enclosures,
164 'enclosures': enclosures,
163 'categories': categories or (),
165 'categories': categories or (),
164 'item_copyright': to_unicode(item_copyright),
166 'item_copyright': to_unicode(item_copyright),
165 'ttl': ttl,
167 'ttl': ttl,
166 }
168 }
167 item.update(kwargs)
169 item.update(kwargs)
168 self.items.append(item)
170 self.items.append(item)
169
171
170 def num_items(self):
172 def num_items(self):
171 return len(self.items)
173 return len(self.items)
172
174
173 def root_attributes(self):
175 def root_attributes(self):
174 """
176 """
175 Return extra attributes to place on the root (i.e. feed/channel) element.
177 Return extra attributes to place on the root (i.e. feed/channel) element.
176 Called from write().
178 Called from write().
177 """
179 """
178 return {}
180 return {}
179
181
180 def add_root_elements(self, handler):
182 def add_root_elements(self, handler):
181 """
183 """
182 Add elements in the root (i.e. feed/channel) element. Called
184 Add elements in the root (i.e. feed/channel) element. Called
183 from write().
185 from write().
184 """
186 """
185 pass
187 pass
186
188
187 def item_attributes(self, item):
189 def item_attributes(self, item):
188 """
190 """
189 Return extra attributes to place on each item (i.e. item/entry) element.
191 Return extra attributes to place on each item (i.e. item/entry) element.
190 """
192 """
191 return {}
193 return {}
192
194
193 def add_item_elements(self, handler, item):
195 def add_item_elements(self, handler, item):
194 """
196 """
195 Add elements on each item (i.e. item/entry) element.
197 Add elements on each item (i.e. item/entry) element.
196 """
198 """
197 pass
199 pass
198
200
199 def write(self, outfile, encoding):
201 def write(self, outfile, encoding):
200 """
202 """
201 Outputs the feed in the given encoding to outfile, which is a file-like
203 Outputs the feed in the given encoding to outfile, which is a file-like
202 object. Subclasses should override this.
204 object. Subclasses should override this.
203 """
205 """
204 raise NotImplementedError('subclasses of SyndicationFeed must provide a write() method')
206 raise NotImplementedError('subclasses of SyndicationFeed must provide a write() method')
205
207
206 def writeString(self, encoding):
208 def writeString(self, encoding):
207 """
209 """
208 Returns the feed in the given encoding as a string.
210 Returns the feed in the given encoding as a string.
209 """
211 """
210 s = StringIO()
212 s = StringIO()
211 self.write(s, encoding)
213 self.write(s, encoding)
212 return s.getvalue()
214 return s.getvalue()
213
215
214 def latest_post_date(self):
216 def latest_post_date(self):
215 """
217 """
216 Returns the latest item's pubdate or updateddate. If no items
218 Returns the latest item's pubdate or updateddate. If no items
217 have either of these attributes this returns the current UTC date/time.
219 have either of these attributes this returns the current UTC date/time.
218 """
220 """
219 latest_date = None
221 latest_date = None
220 date_keys = ('updateddate', 'pubdate')
222 date_keys = ('updateddate', 'pubdate')
221
223
222 for item in self.items:
224 for item in self.items:
223 for date_key in date_keys:
225 for date_key in date_keys:
224 item_date = item.get(date_key)
226 item_date = item.get(date_key)
225 if item_date:
227 if item_date:
226 if latest_date is None or item_date > latest_date:
228 if latest_date is None or item_date > latest_date:
227 latest_date = item_date
229 latest_date = item_date
228
230
229 # datetime.now(tz=utc) is slower, as documented in django.utils.timezone.now
231 # datetime.now(tz=utc) is slower, as documented in django.utils.timezone.now
230 return latest_date or datetime.datetime.utcnow().replace(tzinfo=utc)
232 return latest_date or datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
231
233
232
234
233 class Enclosure(object):
235 class Enclosure(object):
234 "Represents an RSS enclosure"
236 """Represents an RSS enclosure"""
235 def __init__(self, url, length, mime_type):
237 def __init__(self, url, length, mime_type):
236 "All args are expected to be Python Unicode objects"
238 """All args are expected to be Python Unicode objects"""
237 self.length, self.mime_type = length, mime_type
239 self.length, self.mime_type = length, mime_type
238 self.url = iri_to_uri(url)
240 self.url = iri_to_uri(url)
239
241
240
242
241 class RssFeed(SyndicationFeed):
243 class RssFeed(SyndicationFeed):
242 content_type = 'application/rss+xml; charset=utf-8'
244 content_type = 'application/rss+xml; charset=utf-8'
243
245
244 def write(self, outfile, encoding):
246 def write(self, outfile, encoding):
245 handler = SimplerXMLGenerator(outfile, encoding)
247 handler = SimplerXMLGenerator(outfile, encoding)
246 handler.startDocument()
248 handler.startDocument()
247 handler.startElement("rss", self.rss_attributes())
249 handler.startElement("rss", self.rss_attributes())
248 handler.startElement("channel", self.root_attributes())
250 handler.startElement("channel", self.root_attributes())
249 self.add_root_elements(handler)
251 self.add_root_elements(handler)
250 self.write_items(handler)
252 self.write_items(handler)
251 self.endChannelElement(handler)
253 self.endChannelElement(handler)
252 handler.endElement("rss")
254 handler.endElement("rss")
253
255
254 def rss_attributes(self):
256 def rss_attributes(self):
255 return {"version": self._version,
257 return {"version": self._version,
256 "xmlns:atom": "http://www.w3.org/2005/Atom"}
258 "xmlns:atom": "http://www.w3.org/2005/Atom"}
257
259
258 def write_items(self, handler):
260 def write_items(self, handler):
259 for item in self.items:
261 for item in self.items:
260 handler.startElement('item', self.item_attributes(item))
262 handler.startElement('item', self.item_attributes(item))
261 self.add_item_elements(handler, item)
263 self.add_item_elements(handler, item)
262 handler.endElement("item")
264 handler.endElement("item")
263
265
264 def add_root_elements(self, handler):
266 def add_root_elements(self, handler):
265 handler.addQuickElement("title", self.feed['title'])
267 handler.addQuickElement("title", self.feed['title'])
266 handler.addQuickElement("link", self.feed['link'])
268 handler.addQuickElement("link", self.feed['link'])
267 handler.addQuickElement("description", self.feed['description'])
269 handler.addQuickElement("description", self.feed['description'])
268 if self.feed['feed_url'] is not None:
270 if self.feed['feed_url'] is not None:
269 handler.addQuickElement("atom:link", None, {"rel": "self", "href": self.feed['feed_url']})
271 handler.addQuickElement("atom:link", None, {"rel": "self", "href": self.feed['feed_url']})
270 if self.feed['language'] is not None:
272 if self.feed['language'] is not None:
271 handler.addQuickElement("language", self.feed['language'])
273 handler.addQuickElement("language", self.feed['language'])
272 for cat in self.feed['categories']:
274 for cat in self.feed['categories']:
273 handler.addQuickElement("category", cat)
275 handler.addQuickElement("category", cat)
274 if self.feed['feed_copyright'] is not None:
276 if self.feed['feed_copyright'] is not None:
275 handler.addQuickElement("copyright", self.feed['feed_copyright'])
277 handler.addQuickElement("copyright", self.feed['feed_copyright'])
276 handler.addQuickElement("lastBuildDate", rfc2822_date(self.latest_post_date()))
278 handler.addQuickElement("lastBuildDate", rfc2822_date(self.latest_post_date()))
277 if self.feed['ttl'] is not None:
279 if self.feed['ttl'] is not None:
278 handler.addQuickElement("ttl", self.feed['ttl'])
280 handler.addQuickElement("ttl", self.feed['ttl'])
279
281
280 def endChannelElement(self, handler):
282 def endChannelElement(self, handler):
281 handler.endElement("channel")
283 handler.endElement("channel")
282
284
283
285
284 class RssUserland091Feed(RssFeed):
286 class RssUserland091Feed(RssFeed):
285 _version = "0.91"
287 _version = "0.91"
286
288
287 def add_item_elements(self, handler, item):
289 def add_item_elements(self, handler, item):
288 handler.addQuickElement("title", item['title'])
290 handler.addQuickElement("title", item['title'])
289 handler.addQuickElement("link", item['link'])
291 handler.addQuickElement("link", item['link'])
290 if item['description'] is not None:
292 if item['description'] is not None:
291 handler.addQuickElement("description", item['description'])
293 handler.addQuickElement("description", item['description'])
292
294
293
295
294 class Rss201rev2Feed(RssFeed):
296 class Rss201rev2Feed(RssFeed):
295 # Spec: http://blogs.law.harvard.edu/tech/rss
297 # Spec: http://blogs.law.harvard.edu/tech/rss
296 _version = "2.0"
298 _version = "2.0"
297
299
298 def add_item_elements(self, handler, item):
300 def add_item_elements(self, handler, item):
299 handler.addQuickElement("title", item['title'])
301 handler.addQuickElement("title", item['title'])
300 handler.addQuickElement("link", item['link'])
302 handler.addQuickElement("link", item['link'])
301 if item['description'] is not None:
303 if item['description'] is not None:
302 handler.addQuickElement("description", item['description'])
304 handler.addQuickElement("description", item['description'])
303
305
304 # Author information.
306 # Author information.
305 if item["author_name"] and item["author_email"]:
307 if item["author_name"] and item["author_email"]:
306 handler.addQuickElement("author", "%s (%s)" % (item['author_email'], item['author_name']))
308 handler.addQuickElement("author", "%s (%s)" % (item['author_email'], item['author_name']))
307 elif item["author_email"]:
309 elif item["author_email"]:
308 handler.addQuickElement("author", item["author_email"])
310 handler.addQuickElement("author", item["author_email"])
309 elif item["author_name"]:
311 elif item["author_name"]:
310 handler.addQuickElement(
312 handler.addQuickElement(
311 "dc:creator", item["author_name"], {"xmlns:dc": "http://purl.org/dc/elements/1.1/"}
313 "dc:creator", item["author_name"], {"xmlns:dc": "http://purl.org/dc/elements/1.1/"}
312 )
314 )
313
315
314 if item['pubdate'] is not None:
316 if item['pubdate'] is not None:
315 handler.addQuickElement("pubDate", rfc2822_date(item['pubdate']))
317 handler.addQuickElement("pubDate", rfc2822_date(item['pubdate']))
316 if item['comments'] is not None:
318 if item['comments'] is not None:
317 handler.addQuickElement("comments", item['comments'])
319 handler.addQuickElement("comments", item['comments'])
318 if item['unique_id'] is not None:
320 if item['unique_id'] is not None:
319 guid_attrs = {}
321 guid_attrs = {}
320 if isinstance(item.get('unique_id_is_permalink'), bool):
322 if isinstance(item.get('unique_id_is_permalink'), bool):
321 guid_attrs['isPermaLink'] = str(item['unique_id_is_permalink']).lower()
323 guid_attrs['isPermaLink'] = str(item['unique_id_is_permalink']).lower()
322 handler.addQuickElement("guid", item['unique_id'], guid_attrs)
324 handler.addQuickElement("guid", item['unique_id'], guid_attrs)
323 if item['ttl'] is not None:
325 if item['ttl'] is not None:
324 handler.addQuickElement("ttl", item['ttl'])
326 handler.addQuickElement("ttl", item['ttl'])
325
327
326 # Enclosure.
328 # Enclosure.
327 if item['enclosures']:
329 if item['enclosures']:
328 enclosures = list(item['enclosures'])
330 enclosures = list(item['enclosures'])
329 if len(enclosures) > 1:
331 if len(enclosures) > 1:
330 raise ValueError(
332 raise ValueError(
331 "RSS feed items may only have one enclosure, see "
333 "RSS feed items may only have one enclosure, see "
332 "http://www.rssboard.org/rss-profile#element-channel-item-enclosure"
334 "http://www.rssboard.org/rss-profile#element-channel-item-enclosure"
333 )
335 )
334 enclosure = enclosures[0]
336 enclosure = enclosures[0]
335 handler.addQuickElement('enclosure', '', {
337 handler.addQuickElement('enclosure', '', {
336 'url': enclosure.url,
338 'url': enclosure.url,
337 'length': enclosure.length,
339 'length': enclosure.length,
338 'type': enclosure.mime_type,
340 'type': enclosure.mime_type,
339 })
341 })
340
342
341 # Categories.
343 # Categories.
342 for cat in item['categories']:
344 for cat in item['categories']:
343 handler.addQuickElement("category", cat)
345 handler.addQuickElement("category", cat)
344
346
345
347
346 class Atom1Feed(SyndicationFeed):
348 class Atom1Feed(SyndicationFeed):
347 # Spec: https://tools.ietf.org/html/rfc4287
349 # Spec: https://tools.ietf.org/html/rfc4287
348 content_type = 'application/atom+xml; charset=utf-8'
350 content_type = 'application/atom+xml; charset=utf-8'
349 ns = "http://www.w3.org/2005/Atom"
351 ns = "http://www.w3.org/2005/Atom"
350
352
351 def write(self, outfile, encoding):
353 def write(self, outfile, encoding):
352 handler = SimplerXMLGenerator(outfile, encoding)
354 handler = SimplerXMLGenerator(outfile, encoding)
353 handler.startDocument()
355 handler.startDocument()
354 handler.startElement('feed', self.root_attributes())
356 handler.startElement('feed', self.root_attributes())
355 self.add_root_elements(handler)
357 self.add_root_elements(handler)
356 self.write_items(handler)
358 self.write_items(handler)
357 handler.endElement("feed")
359 handler.endElement("feed")
358
360
359 def root_attributes(self):
361 def root_attributes(self):
360 if self.feed['language'] is not None:
362 if self.feed['language'] is not None:
361 return {"xmlns": self.ns, "xml:lang": self.feed['language']}
363 return {"xmlns": self.ns, "xml:lang": self.feed['language']}
362 else:
364 else:
363 return {"xmlns": self.ns}
365 return {"xmlns": self.ns}
364
366
365 def add_root_elements(self, handler):
367 def add_root_elements(self, handler):
366 handler.addQuickElement("title", self.feed['title'])
368 handler.addQuickElement("title", self.feed['title'])
367 handler.addQuickElement("link", "", {"rel": "alternate", "href": self.feed['link']})
369 handler.addQuickElement("link", "", {"rel": "alternate", "href": self.feed['link']})
368 if self.feed['feed_url'] is not None:
370 if self.feed['feed_url'] is not None:
369 handler.addQuickElement("link", "", {"rel": "self", "href": self.feed['feed_url']})
371 handler.addQuickElement("link", "", {"rel": "self", "href": self.feed['feed_url']})
370 handler.addQuickElement("id", self.feed['id'])
372 handler.addQuickElement("id", self.feed['id'])
371 handler.addQuickElement("updated", rfc3339_date(self.latest_post_date()))
373 handler.addQuickElement("updated", rfc3339_date(self.latest_post_date()))
372 if self.feed['author_name'] is not None:
374 if self.feed['author_name'] is not None:
373 handler.startElement("author", {})
375 handler.startElement("author", {})
374 handler.addQuickElement("name", self.feed['author_name'])
376 handler.addQuickElement("name", self.feed['author_name'])
375 if self.feed['author_email'] is not None:
377 if self.feed['author_email'] is not None:
376 handler.addQuickElement("email", self.feed['author_email'])
378 handler.addQuickElement("email", self.feed['author_email'])
377 if self.feed['author_link'] is not None:
379 if self.feed['author_link'] is not None:
378 handler.addQuickElement("uri", self.feed['author_link'])
380 handler.addQuickElement("uri", self.feed['author_link'])
379 handler.endElement("author")
381 handler.endElement("author")
380 if self.feed['subtitle'] is not None:
382 if self.feed['subtitle'] is not None:
381 handler.addQuickElement("subtitle", self.feed['subtitle'])
383 handler.addQuickElement("subtitle", self.feed['subtitle'])
382 for cat in self.feed['categories']:
384 for cat in self.feed['categories']:
383 handler.addQuickElement("category", "", {"term": cat})
385 handler.addQuickElement("category", "", {"term": cat})
384 if self.feed['feed_copyright'] is not None:
386 if self.feed['feed_copyright'] is not None:
385 handler.addQuickElement("rights", self.feed['feed_copyright'])
387 handler.addQuickElement("rights", self.feed['feed_copyright'])
386
388
387 def write_items(self, handler):
389 def write_items(self, handler):
388 for item in self.items:
390 for item in self.items:
389 handler.startElement("entry", self.item_attributes(item))
391 handler.startElement("entry", self.item_attributes(item))
390 self.add_item_elements(handler, item)
392 self.add_item_elements(handler, item)
391 handler.endElement("entry")
393 handler.endElement("entry")
392
394
393 def add_item_elements(self, handler, item):
395 def add_item_elements(self, handler, item):
394 handler.addQuickElement("title", item['title'])
396 handler.addQuickElement("title", item['title'])
395 handler.addQuickElement("link", "", {"href": item['link'], "rel": "alternate"})
397 handler.addQuickElement("link", "", {"href": item['link'], "rel": "alternate"})
396
398
397 if item['pubdate'] is not None:
399 if item['pubdate'] is not None:
398 handler.addQuickElement('published', rfc3339_date(item['pubdate']))
400 handler.addQuickElement('published', rfc3339_date(item['pubdate']))
399
401
400 if item['updateddate'] is not None:
402 if item['updateddate'] is not None:
401 handler.addQuickElement('updated', rfc3339_date(item['updateddate']))
403 handler.addQuickElement('updated', rfc3339_date(item['updateddate']))
402
404
403 # Author information.
405 # Author information.
404 if item['author_name'] is not None:
406 if item['author_name'] is not None:
405 handler.startElement("author", {})
407 handler.startElement("author", {})
406 handler.addQuickElement("name", item['author_name'])
408 handler.addQuickElement("name", item['author_name'])
407 if item['author_email'] is not None:
409 if item['author_email'] is not None:
408 handler.addQuickElement("email", item['author_email'])
410 handler.addQuickElement("email", item['author_email'])
409 if item['author_link'] is not None:
411 if item['author_link'] is not None:
410 handler.addQuickElement("uri", item['author_link'])
412 handler.addQuickElement("uri", item['author_link'])
411 handler.endElement("author")
413 handler.endElement("author")
412
414
413 # Unique ID.
415 # Unique ID.
414 if item['unique_id'] is not None:
416 if item['unique_id'] is not None:
415 unique_id = item['unique_id']
417 unique_id = item['unique_id']
416 else:
418 else:
417 unique_id = get_tag_uri(item['link'], item['pubdate'])
419 unique_id = get_tag_uri(item['link'], item['pubdate'])
418 handler.addQuickElement("id", unique_id)
420 handler.addQuickElement("id", unique_id)
419
421
420 # Summary.
422 # Summary.
421 if item['description'] is not None:
423 if item['description'] is not None:
422 handler.addQuickElement("summary", item['description'], {"type": "html"})
424 handler.addQuickElement("summary", item['description'], {"type": "html"})
423
425
424 # Enclosures.
426 # Enclosures.
425 for enclosure in item['enclosures']:
427 for enclosure in item['enclosures']:
426 handler.addQuickElement('link', '', {
428 handler.addQuickElement('link', '', {
427 'rel': 'enclosure',
429 'rel': 'enclosure',
428 'href': enclosure.url,
430 'href': enclosure.url,
429 'length': enclosure.length,
431 'length': enclosure.length,
430 'type': enclosure.mime_type,
432 'type': enclosure.mime_type,
431 })
433 })
432
434
433 # Categories.
435 # Categories.
434 for cat in item['categories']:
436 for cat in item['categories']:
435 handler.addQuickElement("category", "", {"term": cat})
437 handler.addQuickElement("category", "", {"term": cat})
436
438
437 # Rights.
439 # Rights.
438 if item['item_copyright'] is not None:
440 if item['item_copyright'] is not None:
439 handler.addQuickElement("rights", item['item_copyright'])
441 handler.addQuickElement("rights", item['item_copyright'])
440
442
441
443
442 # This isolates the decision of what the system default is, so calling code can
444 # This isolates the decision of what the system default is, so calling code can
443 # do "feedgenerator.DefaultFeed" instead of "feedgenerator.Rss201rev2Feed".
445 # do "feedgenerator.DefaultFeed" instead of "feedgenerator.Rss201rev2Feed".
444 DefaultFeed = Rss201rev2Feed No newline at end of file
446 DefaultFeed = Rss201rev2Feed
General Comments 0
You need to be logged in to leave comments. Login now