~widelands-dev/widelands-website/django_staticfiles

« back to all changes in this revision

Viewing changes to notification/atomformat.py

  • Committer: franku
  • Date: 2016-12-13 18:28:51 UTC
  • mto: This revision was merged to the branch mainline in revision 443.
  • Revision ID: somal@arcor.de-20161213182851-bo5ebf8pdvw5beua
run the script

Show diffs side-by-side

added added

removed removed

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