173.2.3
by Holger Rapp
Made the site compatible to django 1.1 and all the various packages |
1 |
#
|
2 |
# django-atompub by James Tauber <http://jtauber.com/>
|
|
3 |
# http://code.google.com/p/django-atompub/
|
|
4 |
# An implementation of the Atom format and protocol for Django
|
|
5 |
#
|
|
6 |
# For instructions on how to use this module to generate Atom feeds,
|
|
7 |
# see http://code.google.com/p/django-atompub/wiki/UserGuide
|
|
8 |
#
|
|
9 |
#
|
|
10 |
# Copyright (c) 2007, James Tauber
|
|
11 |
#
|
|
12 |
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13 |
# of this software and associated documentation files (the "Software"), to deal
|
|
14 |
# in the Software without restriction, including without limitation the rights
|
|
15 |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16 |
# copies of the Software, and to permit persons to whom the Software is
|
|
17 |
# furnished to do so, subject to the following conditions:
|
|
18 |
#
|
|
19 |
# The above copyright notice and this permission notice shall be included in
|
|
20 |
# all copies or substantial portions of the Software.
|
|
21 |
#
|
|
22 |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23 |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24 |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25 |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26 |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27 |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
28 |
# THE SOFTWARE.
|
|
29 |
#
|
|
30 |
||
31 |
from xml.sax.saxutils import XMLGenerator |
|
32 |
from datetime import datetime |
|
33 |
||
34 |
||
35 |
GENERATOR_TEXT = 'django-atompub' |
|
36 |
GENERATOR_ATTR = { |
|
37 |
'uri': 'http://code.google.com/p/django-atompub/', |
|
38 |
'version': 'r33' |
|
39 |
}
|
|
40 |
||
41 |
||
42 |
||
43 |
## based on django.utils.xmlutils.SimplerXMLGenerator
|
|
44 |
class SimplerXMLGenerator(XMLGenerator): |
|
45 |
def addQuickElement(self, name, contents=None, attrs=None): |
|
46 |
"Convenience method for adding an element with no children"
|
|
47 |
if attrs is None: attrs = {} |
|
48 |
self.startElement(name, attrs) |
|
49 |
if contents is not None: |
|
50 |
self.characters(contents) |
|
51 |
self.endElement(name) |
|
52 |
||
53 |
||
54 |
||
55 |
## based on django.utils.feedgenerator.rfc3339_date
|
|
56 |
def rfc3339_date(date): |
|
57 |
return date.strftime('%Y-%m-%dT%H:%M:%SZ') |
|
58 |
||
59 |
||
60 |
||
61 |
## based on django.utils.feedgenerator.get_tag_uri
|
|
62 |
def get_tag_uri(url, date): |
|
63 |
"Creates a TagURI. See http://diveintomark.org/archives/2004/05/28/howto-atom-id"
|
|
64 |
tag = re.sub('^http://', '', url) |
|
65 |
if date is not None: |
|
66 |
tag = re.sub('/', ',%s:/' % date.strftime('%Y-%m-%d'), tag, 1) |
|
67 |
tag = re.sub('#', '/', tag) |
|
68 |
return 'tag:' + tag |
|
69 |
||
70 |
||
71 |
||
72 |
## based on django.contrib.syndication.feeds.Feed
|
|
73 |
class Feed(object): |
|
74 |
||
75 |
||
76 |
VALIDATE = True |
|
77 |
||
78 |
||
79 |
def __init__(self, slug, feed_url): |
|
80 |
# @@@ slug and feed_url are not used yet
|
|
81 |
pass
|
|
82 |
||
83 |
||
84 |
def __get_dynamic_attr(self, attname, obj, default=None): |
|
85 |
try: |
|
86 |
attr = getattr(self, attname) |
|
87 |
except AttributeError: |
|
88 |
return default |
|
89 |
if callable(attr): |
|
90 |
# Check func_code.co_argcount rather than try/excepting the
|
|
91 |
# function and catching the TypeError, because something inside
|
|
92 |
# the function may raise the TypeError. This technique is more
|
|
93 |
# accurate.
|
|
94 |
if hasattr(attr, 'func_code'): |
|
95 |
argcount = attr.func_code.co_argcount |
|
96 |
else: |
|
97 |
argcount = attr.__call__.func_code.co_argcount |
|
98 |
if argcount == 2: # one argument is 'self' |
|
99 |
return attr(obj) |
|
100 |
else: |
|
101 |
return attr() |
|
102 |
return attr |
|
103 |
||
104 |
||
105 |
def get_feed(self, extra_params=None): |
|
106 |
||
107 |
if extra_params: |
|
108 |
try: |
|
109 |
obj = self.get_object(extra_params.split('/')) |
|
110 |
except (AttributeError, LookupError): |
|
111 |
raise LookupError('Feed does not exist') |
|
112 |
else: |
|
113 |
obj = None |
|
114 |
||
115 |
feed = AtomFeed( |
|
116 |
atom_id = self.__get_dynamic_attr('feed_id', obj), |
|
117 |
title = self.__get_dynamic_attr('feed_title', obj), |
|
118 |
updated = self.__get_dynamic_attr('feed_updated', obj), |
|
119 |
icon = self.__get_dynamic_attr('feed_icon', obj), |
|
120 |
logo = self.__get_dynamic_attr('feed_logo', obj), |
|
121 |
rights = self.__get_dynamic_attr('feed_rights', obj), |
|
122 |
subtitle = self.__get_dynamic_attr('feed_subtitle', obj), |
|
123 |
authors = self.__get_dynamic_attr('feed_authors', obj, default=[]), |
|
124 |
categories = self.__get_dynamic_attr('feed_categories', obj, default=[]), |
|
125 |
contributors = self.__get_dynamic_attr('feed_contributors', obj, default=[]), |
|
126 |
links = self.__get_dynamic_attr('feed_links', obj, default=[]), |
|
127 |
extra_attrs = self.__get_dynamic_attr('feed_extra_attrs', obj), |
|
128 |
hide_generator = self.__get_dynamic_attr('hide_generator', obj, default=False) |
|
129 |
)
|
|
130 |
||
131 |
items = self.__get_dynamic_attr('items', obj) |
|
132 |
if items is None: |
|
133 |
raise LookupError('Feed has no items field') |
|
134 |
||
135 |
for item in items: |
|
136 |
feed.add_item( |
|
137 |
atom_id = self.__get_dynamic_attr('item_id', item), |
|
138 |
title = self.__get_dynamic_attr('item_title', item), |
|
139 |
updated = self.__get_dynamic_attr('item_updated', item), |
|
140 |
content = self.__get_dynamic_attr('item_content', item), |
|
141 |
published = self.__get_dynamic_attr('item_published', item), |
|
142 |
rights = self.__get_dynamic_attr('item_rights', item), |
|
143 |
source = self.__get_dynamic_attr('item_source', item), |
|
144 |
summary = self.__get_dynamic_attr('item_summary', item), |
|
145 |
authors = self.__get_dynamic_attr('item_authors', item, default=[]), |
|
146 |
categories = self.__get_dynamic_attr('item_categories', item, default=[]), |
|
147 |
contributors = self.__get_dynamic_attr('item_contributors', item, default=[]), |
|
148 |
links = self.__get_dynamic_attr('item_links', item, default=[]), |
|
149 |
extra_attrs = self.__get_dynamic_attr('item_extra_attrs', None, default={}), |
|
150 |
)
|
|
151 |
||
152 |
if self.VALIDATE: |
|
153 |
feed.validate() |
|
154 |
return feed |
|
155 |
||
156 |
||
157 |
||
158 |
class ValidationError(Exception): |
|
159 |
pass
|
|
160 |
||
161 |
||
162 |
||
163 |
## based on django.utils.feedgenerator.SyndicationFeed and django.utils.feedgenerator.Atom1Feed
|
|
164 |
class AtomFeed(object): |
|
165 |
||
166 |
||
167 |
mime_type = 'application/atom+xml' |
|
168 |
ns = u'http://www.w3.org/2005/Atom' |
|
169 |
||
170 |
||
171 |
def __init__(self, atom_id, title, updated=None, icon=None, logo=None, rights=None, subtitle=None, |
|
172 |
authors=[], categories=[], contributors=[], links=[], extra_attrs={}, hide_generator=False): |
|
173 |
if atom_id is None: |
|
174 |
raise LookupError('Feed has no feed_id field') |
|
175 |
if title is None: |
|
176 |
raise LookupError('Feed has no feed_title field') |
|
177 |
# if updated == None, we'll calculate it
|
|
178 |
self.feed = { |
|
179 |
'id': atom_id, |
|
180 |
'title': title, |
|
181 |
'updated': updated, |
|
182 |
'icon': icon, |
|
183 |
'logo': logo, |
|
184 |
'rights': rights, |
|
185 |
'subtitle': subtitle, |
|
186 |
'authors': authors, |
|
187 |
'categories': categories, |
|
188 |
'contributors': contributors, |
|
189 |
'links': links, |
|
190 |
'extra_attrs': extra_attrs, |
|
191 |
'hide_generator': hide_generator, |
|
192 |
}
|
|
193 |
self.items = [] |
|
194 |
||
195 |
||
196 |
def add_item(self, atom_id, title, updated, content=None, published=None, rights=None, source=None, summary=None, |
|
197 |
authors=[], categories=[], contributors=[], links=[], extra_attrs={}): |
|
198 |
if atom_id is None: |
|
199 |
raise LookupError('Feed has no item_id method') |
|
200 |
if title is None: |
|
201 |
raise LookupError('Feed has no item_title method') |
|
202 |
if updated is None: |
|
203 |
raise LookupError('Feed has no item_updated method') |
|
204 |
self.items.append({ |
|
205 |
'id': atom_id, |
|
206 |
'title': title, |
|
207 |
'updated': updated, |
|
208 |
'content': content, |
|
209 |
'published': published, |
|
210 |
'rights': rights, |
|
211 |
'source': source, |
|
212 |
'summary': summary, |
|
213 |
'authors': authors, |
|
214 |
'categories': categories, |
|
215 |
'contributors': contributors, |
|
216 |
'links': links, |
|
217 |
'extra_attrs': extra_attrs, |
|
218 |
})
|
|
219 |
||
220 |
||
221 |
def latest_updated(self): |
|
222 |
"""
|
|
223 |
Returns the latest item's updated or the current time if there are no items.
|
|
224 |
"""
|
|
225 |
updates = [item['updated'] for item in self.items] |
|
226 |
if len(updates) > 0: |
|
227 |
updates.sort() |
|
228 |
return updates[-1] |
|
229 |
else: |
|
230 |
return datetime.now() # @@@ really we should allow a feed to define its "start" for this case |
|
231 |
||
232 |
||
233 |
def write_text_construct(self, handler, element_name, data): |
|
234 |
if isinstance(data, tuple): |
|
235 |
text_type, text = data |
|
236 |
if text_type == 'xhtml': |
|
237 |
handler.startElement(element_name, {'type': text_type}) |
|
238 |
handler._write(text) # write unescaped -- it had better be well-formed XML |
|
239 |
handler.endElement(element_name) |
|
240 |
else: |
|
241 |
handler.addQuickElement(element_name, text, {'type': text_type}) |
|
242 |
else: |
|
243 |
handler.addQuickElement(element_name, data) |
|
244 |
||
245 |
||
246 |
def write_person_construct(self, handler, element_name, person): |
|
247 |
handler.startElement(element_name, {}) |
|
248 |
handler.addQuickElement(u'name', person['name']) |
|
249 |
if 'uri' in person: |
|
250 |
handler.addQuickElement(u'uri', person['uri']) |
|
251 |
if 'email' in person: |
|
252 |
handler.addQuickElement(u'email', person['email']) |
|
253 |
handler.endElement(element_name) |
|
254 |
||
255 |
||
256 |
def write_link_construct(self, handler, link): |
|
257 |
if 'length' in link: |
|
258 |
link['length'] = str(link['length']) |
|
259 |
handler.addQuickElement(u'link', None, link) |
|
260 |
||
261 |
||
262 |
def write_category_construct(self, handler, category): |
|
263 |
handler.addQuickElement(u'category', None, category) |
|
264 |
||
265 |
||
266 |
def write_source(self, handler, data): |
|
267 |
handler.startElement(u'source', {}) |
|
268 |
if data.get('id'): |
|
269 |
handler.addQuickElement(u'id', data['id']) |
|
270 |
if data.get('title'): |
|
271 |
self.write_text_construct(handler, u'title', data['title']) |
|
272 |
if data.get('subtitle'): |
|
273 |
self.write_text_construct(handler, u'subtitle', data['subtitle']) |
|
274 |
if data.get('icon'): |
|
275 |
handler.addQuickElement(u'icon', data['icon']) |
|
276 |
if data.get('logo'): |
|
277 |
handler.addQuickElement(u'logo', data['logo']) |
|
278 |
if data.get('updated'): |
|
279 |
handler.addQuickElement(u'updated', rfc3339_date(data['updated'])) |
|
280 |
for category in data.get('categories', []): |
|
281 |
self.write_category_construct(handler, category) |
|
282 |
for link in data.get('links', []): |
|
283 |
self.write_link_construct(handler, link) |
|
284 |
for author in data.get('authors', []): |
|
285 |
self.write_person_construct(handler, u'author', author) |
|
286 |
for contributor in data.get('contributors', []): |
|
287 |
self.write_person_construct(handler, u'contributor', contributor) |
|
288 |
if data.get('rights'): |
|
289 |
self.write_text_construct(handler, u'rights', data['rights']) |
|
290 |
handler.endElement(u'source') |
|
291 |
||
292 |
||
293 |
def write_content(self, handler, data): |
|
294 |
if isinstance(data, tuple): |
|
295 |
content_dict, text = data |
|
296 |
if content_dict.get('type') == 'xhtml': |
|
297 |
handler.startElement(u'content', content_dict) |
|
298 |
handler._write(text) # write unescaped -- it had better be well-formed XML |
|
299 |
handler.endElement(u'content') |
|
300 |
else: |
|
301 |
handler.addQuickElement(u'content', text, content_dict) |
|
302 |
else: |
|
303 |
handler.addQuickElement(u'content', data) |
|
304 |
||
305 |
||
306 |
def write(self, outfile, encoding): |
|
307 |
handler = SimplerXMLGenerator(outfile, encoding) |
|
308 |
handler.startDocument() |
|
309 |
feed_attrs = {u'xmlns': self.ns} |
|
310 |
if self.feed.get('extra_attrs'): |
|
311 |
feed_attrs.update(self.feed['extra_attrs']) |
|
312 |
handler.startElement(u'feed', feed_attrs) |
|
313 |
handler.addQuickElement(u'id', self.feed['id']) |
|
314 |
self.write_text_construct(handler, u'title', self.feed['title']) |
|
315 |
if self.feed.get('subtitle'): |
|
316 |
self.write_text_construct(handler, u'subtitle', self.feed['subtitle']) |
|
317 |
if self.feed.get('icon'): |
|
318 |
handler.addQuickElement(u'icon', self.feed['icon']) |
|
319 |
if self.feed.get('logo'): |
|
320 |
handler.addQuickElement(u'logo', self.feed['logo']) |
|
321 |
if self.feed['updated']: |
|
322 |
handler.addQuickElement(u'updated', rfc3339_date(self.feed['updated'])) |
|
323 |
else: |
|
324 |
handler.addQuickElement(u'updated', rfc3339_date(self.latest_updated())) |
|
325 |
for category in self.feed['categories']: |
|
326 |
self.write_category_construct(handler, category) |
|
327 |
for link in self.feed['links']: |
|
328 |
self.write_link_construct(handler, link) |
|
329 |
for author in self.feed['authors']: |
|
330 |
self.write_person_construct(handler, u'author', author) |
|
331 |
for contributor in self.feed['contributors']: |
|
332 |
self.write_person_construct(handler, u'contributor', contributor) |
|
333 |
if self.feed.get('rights'): |
|
334 |
self.write_text_construct(handler, u'rights', self.feed['rights']) |
|
335 |
if not self.feed.get('hide_generator'): |
|
336 |
handler.addQuickElement(u'generator', GENERATOR_TEXT, GENERATOR_ATTR) |
|
337 |
||
338 |
self.write_items(handler) |
|
339 |
||
340 |
handler.endElement(u'feed') |
|
341 |
||
342 |
||
343 |
def write_items(self, handler): |
|
344 |
for item in self.items: |
|
345 |
entry_attrs = item.get('extra_attrs', {}) |
|
346 |
handler.startElement(u'entry', entry_attrs) |
|
347 |
||
348 |
handler.addQuickElement(u'id', item['id']) |
|
349 |
self.write_text_construct(handler, u'title', item['title']) |
|
350 |
handler.addQuickElement(u'updated', rfc3339_date(item['updated'])) |
|
351 |
if item.get('published'): |
|
352 |
handler.addQuickElement(u'published', rfc3339_date(item['published'])) |
|
353 |
if item.get('rights'): |
|
354 |
self.write_text_construct(handler, u'rights', item['rights']) |
|
355 |
if item.get('source'): |
|
356 |
self.write_source(handler, item['source']) |
|
357 |
||
358 |
for author in item['authors']: |
|
359 |
self.write_person_construct(handler, u'author', author) |
|
360 |
for contributor in item['contributors']: |
|
361 |
self.write_person_construct(handler, u'contributor', contributor) |
|
362 |
for category in item['categories']: |
|
363 |
self.write_category_construct(handler, category) |
|
364 |
for link in item['links']: |
|
365 |
self.write_link_construct(handler, link) |
|
366 |
if item.get('summary'): |
|
367 |
self.write_text_construct(handler, u'summary', item['summary']) |
|
368 |
if item.get('content'): |
|
369 |
self.write_content(handler, item['content']) |
|
370 |
||
371 |
handler.endElement(u'entry') |
|
372 |
||
373 |
||
374 |
def validate(self): |
|
375 |
||
376 |
def validate_text_construct(obj): |
|
377 |
if isinstance(obj, tuple): |
|
378 |
if obj[0] not in ['text', 'html', 'xhtml']: |
|
379 |
return False |
|
380 |
# @@@ no validation is done that 'html' text constructs are valid HTML
|
|
381 |
# @@@ no validation is done that 'xhtml' text constructs are well-formed XML or valid XHTML
|
|
382 |
||
383 |
return True |
|
384 |
||
385 |
if not validate_text_construct(self.feed['title']): |
|
386 |
raise ValidationError('feed title has invalid type') |
|
387 |
if self.feed.get('subtitle'): |
|
388 |
if not validate_text_construct(self.feed['subtitle']): |
|
389 |
raise ValidationError('feed subtitle has invalid type') |
|
390 |
if self.feed.get('rights'): |
|
391 |
if not validate_text_construct(self.feed['rights']): |
|
392 |
raise ValidationError('feed rights has invalid type') |
|
393 |
||
394 |
alternate_links = {} |
|
395 |
for link in self.feed.get('links'): |
|
396 |
if link.get('rel') == 'alternate' or link.get('rel') == None: |
|
397 |
key = (link.get('type'), link.get('hreflang')) |
|
398 |
if key in alternate_links: |
|
399 |
raise ValidationError('alternate links must have unique type/hreflang') |
|
400 |
alternate_links[key] = link |
|
401 |
||
402 |
if self.feed.get('authors'): |
|
403 |
feed_author = True |
|
404 |
else: |
|
405 |
feed_author = False |
|
406 |
||
407 |
for item in self.items: |
|
408 |
if not feed_author and not item.get('authors'): |
|
409 |
if item.get('source') and item['source'].get('authors'): |
|
410 |
pass
|
|
411 |
else: |
|
412 |
raise ValidationError('if no feed author, all entries must have author (possibly in source)') |
|
413 |
||
414 |
if not validate_text_construct(item['title']): |
|
415 |
raise ValidationError('entry title has invalid type') |
|
416 |
if item.get('rights'): |
|
417 |
if not validate_text_construct(item['rights']): |
|
418 |
raise ValidationError('entry rights has invalid type') |
|
419 |
if item.get('summary'): |
|
420 |
if not validate_text_construct(item['summary']): |
|
421 |
raise ValidationError('entry summary has invalid type') |
|
422 |
source = item.get('source') |
|
423 |
if source: |
|
424 |
if source.get('title'): |
|
425 |
if not validate_text_construct(source['title']): |
|
426 |
raise ValidationError('source title has invalid type') |
|
427 |
if source.get('subtitle'): |
|
428 |
if not validate_text_construct(source['subtitle']): |
|
429 |
raise ValidationError('source subtitle has invalid type') |
|
430 |
if source.get('rights'): |
|
431 |
if not validate_text_construct(source['rights']): |
|
432 |
raise ValidationError('source rights has invalid type') |
|
433 |
||
434 |
alternate_links = {} |
|
435 |
for link in item.get('links'): |
|
436 |
if link.get('rel') == 'alternate' or link.get('rel') == None: |
|
437 |
key = (link.get('type'), link.get('hreflang')) |
|
438 |
if key in alternate_links: |
|
439 |
raise ValidationError('alternate links must have unique type/hreflang') |
|
440 |
alternate_links[key] = link |
|
441 |
||
442 |
if not item.get('content'): |
|
443 |
if not alternate_links: |
|
444 |
raise ValidationError('if no content, entry must have alternate link') |
|
445 |
||
446 |
if item.get('content') and isinstance(item.get('content'), tuple): |
|
447 |
content_type = item.get('content')[0].get('type') |
|
448 |
if item.get('content')[0].get('src'): |
|
449 |
if item.get('content')[1]: |
|
450 |
raise ValidationError('content with src should be empty') |
|
451 |
if not item.get('summary'): |
|
452 |
raise ValidationError('content with src requires a summary too') |
|
453 |
if content_type in ['text', 'html', 'xhtml']: |
|
454 |
raise ValidationError('content with src cannot have type of text, html or xhtml') |
|
455 |
if content_type: |
|
456 |
if '/' in content_type and \ |
|
457 |
not content_type.startswith('text/') and \ |
|
458 |
not content_type.endswith('/xml') and not content_type.endswith('+xml') and \ |
|
459 |
not content_type in ['application/xml-external-parsed-entity', 'application/xml-dtd']: |
|
460 |
# @@@ check content is Base64
|
|
461 |
if not item.get('summary'): |
|
462 |
raise ValidationError('content in Base64 requires a summary too') |
|
463 |
if content_type not in ['text', 'html', 'xhtml'] and '/' not in content_type: |
|
464 |
raise ValidationError('content type does not appear to be valid') |
|
465 |
||
466 |
# @@@ no validation is done that 'html' text constructs are valid HTML
|
|
467 |
# @@@ no validation is done that 'xhtml' text constructs are well-formed XML or valid XHTML
|
|
468 |
||
469 |
return
|
|
470 |
||
471 |
return
|
|
472 |
||
473 |
||
474 |
||
475 |
class LegacySyndicationFeed(AtomFeed): |
|
476 |
"""
|
|
477 |
Provides an SyndicationFeed-compatible interface in its __init__ and
|
|
478 |
add_item but is really a new AtomFeed object.
|
|
479 |
"""
|
|
480 |
||
481 |
def __init__(self, title, link, description, language=None, author_email=None, |
|
482 |
author_name=None, author_link=None, subtitle=None, categories=[], |
|
483 |
feed_url=None, feed_copyright=None): |
|
484 |
||
485 |
atom_id = link |
|
486 |
title = title |
|
487 |
updated = None # will be calculated |
|
488 |
rights = feed_copyright |
|
489 |
subtitle = subtitle |
|
490 |
author_dict = {'name': author_name} |
|
491 |
if author_link: |
|
492 |
author_dict['uri'] = author_uri |
|
493 |
if author_email: |
|
494 |
author_dict['email'] = author_email |
|
495 |
authors = [author_dict] |
|
496 |
if categories: |
|
497 |
categories = [{'term': term} for term in categories] |
|
498 |
links = [{'rel': 'alternate', 'href': link}] |
|
499 |
if feed_url: |
|
500 |
links.append({'rel': 'self', 'href': feed_url}) |
|
501 |
if language: |
|
502 |
extra_attrs = {'xml:lang': language} |
|
503 |
else: |
|
504 |
extra_attrs = {} |
|
505 |
||
506 |
# description ignored (as with Atom1Feed)
|
|
507 |
||
508 |
AtomFeed.__init__(self, atom_id, title, updated, rights=rights, subtitle=subtitle, |
|
509 |
authors=authors, categories=categories, links=links, extra_attrs=extra_attrs) |
|
510 |
||
511 |
||
512 |
def add_item(self, title, link, description, author_email=None, |
|
513 |
author_name=None, author_link=None, pubdate=None, comments=None, |
|
514 |
unique_id=None, enclosure=None, categories=[], item_copyright=None): |
|
515 |
||
516 |
if unique_id: |
|
517 |
atom_id = unique_id |
|
518 |
else: |
|
519 |
atom_id = get_tag_uri(link, pubdate) |
|
520 |
title = title |
|
521 |
updated = pubdate |
|
522 |
if item_copyright: |
|
523 |
rights = item_copyright |
|
524 |
else: |
|
525 |
rights = None |
|
526 |
if description: |
|
527 |
summary = 'html', description |
|
528 |
else: |
|
529 |
summary = None |
|
530 |
author_dict = {'name': author_name} |
|
531 |
if author_link: |
|
532 |
author_dict['uri'] = author_uri |
|
533 |
if author_email: |
|
534 |
author_dict['email'] = author_email |
|
535 |
authors = [author_dict] |
|
536 |
categories = [{'term': term} for term in categories] |
|
537 |
links = [{'rel': 'alternate', 'href': link}] |
|
538 |
if enclosure: |
|
539 |
links.append({'rel': 'enclosure', 'href': enclosure.url, 'length': enclosure.length, 'type': enclosure.mime_type}) |
|
540 |
||
541 |
AtomFeed.add_item(self, atom_id, title, updated, rights=rights, summary=summary, |
|
542 |
authors=authors, categories=categories, links=links) |