Fixed #13110 -- Added support for multiple enclosures in Atom feeds.
The ``item_enclosures`` hook returns a list of ``Enclosure`` objects which is then used by the feed builder. If the feed is a RSS feed, an exception is raised as RSS feeds don't allow multiple enclosures per feed item. The ``item_enclosures`` hook defaults to an empty list or, if the ``item_enclosure_url`` hook is defined, to a list with a single ``Enclosure`` built from the ``item_enclosure_url``, ``item_enclosure_length``, and ``item_enclosure_mime_type`` hooks.
This commit is contained in:
parent
71ebcb85b9
commit
aac2a2d2ae
|
@ -64,6 +64,17 @@ class Feed(object):
|
||||||
'item_link() method in your Feed class.' % item.__class__.__name__
|
'item_link() method in your Feed class.' % item.__class__.__name__
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def item_enclosures(self, item):
|
||||||
|
enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
|
||||||
|
if enc_url:
|
||||||
|
enc = feedgenerator.Enclosure(
|
||||||
|
url=smart_text(enc_url),
|
||||||
|
length=smart_text(self.__get_dynamic_attr('item_enclosure_length', item)),
|
||||||
|
mime_type=smart_text(self.__get_dynamic_attr('item_enclosure_mime_type', item)),
|
||||||
|
)
|
||||||
|
return [enc]
|
||||||
|
return []
|
||||||
|
|
||||||
def __get_dynamic_attr(self, attname, obj, default=None):
|
def __get_dynamic_attr(self, attname, obj, default=None):
|
||||||
try:
|
try:
|
||||||
attr = getattr(self, attname)
|
attr = getattr(self, attname)
|
||||||
|
@ -171,14 +182,7 @@ class Feed(object):
|
||||||
self.__get_dynamic_attr('item_link', item),
|
self.__get_dynamic_attr('item_link', item),
|
||||||
request.is_secure(),
|
request.is_secure(),
|
||||||
)
|
)
|
||||||
enc = None
|
enclosures = self.__get_dynamic_attr('item_enclosures', item)
|
||||||
enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
|
|
||||||
if enc_url:
|
|
||||||
enc = feedgenerator.Enclosure(
|
|
||||||
url=smart_text(enc_url),
|
|
||||||
length=smart_text(self.__get_dynamic_attr('item_enclosure_length', item)),
|
|
||||||
mime_type=smart_text(self.__get_dynamic_attr('item_enclosure_mime_type', item))
|
|
||||||
)
|
|
||||||
author_name = self.__get_dynamic_attr('item_author_name', item)
|
author_name = self.__get_dynamic_attr('item_author_name', item)
|
||||||
if author_name is not None:
|
if author_name is not None:
|
||||||
author_email = self.__get_dynamic_attr('item_author_email', item)
|
author_email = self.__get_dynamic_attr('item_author_email', item)
|
||||||
|
@ -203,7 +207,7 @@ class Feed(object):
|
||||||
unique_id=self.__get_dynamic_attr('item_guid', item, link),
|
unique_id=self.__get_dynamic_attr('item_guid', item, link),
|
||||||
unique_id_is_permalink=self.__get_dynamic_attr(
|
unique_id_is_permalink=self.__get_dynamic_attr(
|
||||||
'item_guid_is_permalink', item),
|
'item_guid_is_permalink', item),
|
||||||
enclosure=enc,
|
enclosures=enclosures,
|
||||||
pubdate=pubdate,
|
pubdate=pubdate,
|
||||||
updateddate=updateddate,
|
updateddate=updateddate,
|
||||||
author_name=author_name,
|
author_name=author_name,
|
||||||
|
|
|
@ -118,11 +118,13 @@ class SyndicationFeed(object):
|
||||||
def add_item(self, title, link, description, author_email=None,
|
def add_item(self, title, link, description, author_email=None,
|
||||||
author_name=None, author_link=None, pubdate=None, comments=None,
|
author_name=None, author_link=None, pubdate=None, comments=None,
|
||||||
unique_id=None, unique_id_is_permalink=None, enclosure=None,
|
unique_id=None, unique_id_is_permalink=None, enclosure=None,
|
||||||
categories=(), item_copyright=None, ttl=None, updateddate=None, **kwargs):
|
categories=(), item_copyright=None, ttl=None, updateddate=None,
|
||||||
|
enclosures=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Adds an item to the feed. All args are expected to be Python Unicode
|
Adds an item to the feed. All args are expected to be Python Unicode
|
||||||
objects except pubdate and updateddate, which are datetime.datetime
|
objects except pubdate and updateddate, which are datetime.datetime
|
||||||
objects, and enclosure, which is an instance of the Enclosure class.
|
objects, and enclosures, which is an iterable of instances of the
|
||||||
|
Enclosure class.
|
||||||
"""
|
"""
|
||||||
to_unicode = lambda s: force_text(s, strings_only=True)
|
to_unicode = lambda s: force_text(s, strings_only=True)
|
||||||
if categories:
|
if categories:
|
||||||
|
@ -130,6 +132,16 @@ class SyndicationFeed(object):
|
||||||
if ttl is not None:
|
if ttl is not None:
|
||||||
# Force ints to unicode
|
# Force ints to unicode
|
||||||
ttl = force_text(ttl)
|
ttl = force_text(ttl)
|
||||||
|
if enclosure is None:
|
||||||
|
enclosures = [] if enclosures is None else enclosures
|
||||||
|
else:
|
||||||
|
warnings.warn(
|
||||||
|
"The enclosure keyword argument is deprecated, "
|
||||||
|
"use enclosures instead.",
|
||||||
|
RemovedInDjango20Warning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
enclosures = [enclosure]
|
||||||
item = {
|
item = {
|
||||||
'title': to_unicode(title),
|
'title': to_unicode(title),
|
||||||
'link': iri_to_uri(link),
|
'link': iri_to_uri(link),
|
||||||
|
@ -142,7 +154,7 @@ class SyndicationFeed(object):
|
||||||
'comments': to_unicode(comments),
|
'comments': to_unicode(comments),
|
||||||
'unique_id': to_unicode(unique_id),
|
'unique_id': to_unicode(unique_id),
|
||||||
'unique_id_is_permalink': unique_id_is_permalink,
|
'unique_id_is_permalink': unique_id_is_permalink,
|
||||||
'enclosure': enclosure,
|
'enclosures': enclosures,
|
||||||
'categories': categories or (),
|
'categories': categories or (),
|
||||||
'item_copyright': to_unicode(item_copyright),
|
'item_copyright': to_unicode(item_copyright),
|
||||||
'ttl': ttl,
|
'ttl': ttl,
|
||||||
|
@ -317,10 +329,19 @@ class Rss201rev2Feed(RssFeed):
|
||||||
handler.addQuickElement("ttl", item['ttl'])
|
handler.addQuickElement("ttl", item['ttl'])
|
||||||
|
|
||||||
# Enclosure.
|
# Enclosure.
|
||||||
if item['enclosure'] is not None:
|
if item['enclosures']:
|
||||||
handler.addQuickElement("enclosure", '',
|
enclosures = list(item['enclosures'])
|
||||||
{"url": item['enclosure'].url, "length": item['enclosure'].length,
|
if len(enclosures) > 1:
|
||||||
"type": item['enclosure'].mime_type})
|
raise ValueError(
|
||||||
|
"RSS feed items may only have one enclosure, see "
|
||||||
|
"http://www.rssboard.org/rss-profile#element-channel-item-enclosure"
|
||||||
|
)
|
||||||
|
enclosure = enclosures[0]
|
||||||
|
handler.addQuickElement('enclosure', '', {
|
||||||
|
'url': enclosure.url,
|
||||||
|
'length': enclosure.length,
|
||||||
|
'type': enclosure.mime_type,
|
||||||
|
})
|
||||||
|
|
||||||
# Categories.
|
# Categories.
|
||||||
for cat in item['categories']:
|
for cat in item['categories']:
|
||||||
|
@ -328,7 +349,7 @@ class Rss201rev2Feed(RssFeed):
|
||||||
|
|
||||||
|
|
||||||
class Atom1Feed(SyndicationFeed):
|
class Atom1Feed(SyndicationFeed):
|
||||||
# Spec: http://atompub.org/2005/07/11/draft-ietf-atompub-format-10.html
|
# Spec: https://tools.ietf.org/html/rfc4287
|
||||||
content_type = 'application/atom+xml; charset=utf-8'
|
content_type = 'application/atom+xml; charset=utf-8'
|
||||||
ns = "http://www.w3.org/2005/Atom"
|
ns = "http://www.w3.org/2005/Atom"
|
||||||
|
|
||||||
|
@ -405,13 +426,14 @@ class Atom1Feed(SyndicationFeed):
|
||||||
if item['description'] is not None:
|
if item['description'] is not None:
|
||||||
handler.addQuickElement("summary", item['description'], {"type": "html"})
|
handler.addQuickElement("summary", item['description'], {"type": "html"})
|
||||||
|
|
||||||
# Enclosure.
|
# Enclosures.
|
||||||
if item['enclosure'] is not None:
|
for enclosure in item.get('enclosures') or []:
|
||||||
handler.addQuickElement("link", '',
|
handler.addQuickElement('link', '', {
|
||||||
{"rel": "enclosure",
|
'rel': 'enclosure',
|
||||||
"href": item['enclosure'].url,
|
'href': enclosure.url,
|
||||||
"length": item['enclosure'].length,
|
'length': enclosure.length,
|
||||||
"type": item['enclosure'].mime_type})
|
'type': enclosure.mime_type,
|
||||||
|
})
|
||||||
|
|
||||||
# Categories.
|
# Categories.
|
||||||
for cat in item['categories']:
|
for cat in item['categories']:
|
||||||
|
|
|
@ -94,6 +94,9 @@ details on these changes.
|
||||||
* The ``callable_obj`` keyword argument to
|
* The ``callable_obj`` keyword argument to
|
||||||
``SimpleTestCase.assertRaisesMessage()`` will be removed.
|
``SimpleTestCase.assertRaisesMessage()`` will be removed.
|
||||||
|
|
||||||
|
* The ``enclosure`` keyword argument to ``SyndicationFeed.add_item()`` will be
|
||||||
|
removed.
|
||||||
|
|
||||||
.. _deprecation-removed-in-1.10:
|
.. _deprecation-removed-in-1.10:
|
||||||
|
|
||||||
1.10
|
1.10
|
||||||
|
|
|
@ -298,10 +298,16 @@ Enclosures
|
||||||
----------
|
----------
|
||||||
|
|
||||||
To specify enclosures, such as those used in creating podcast feeds, use the
|
To specify enclosures, such as those used in creating podcast feeds, use the
|
||||||
``item_enclosure_url``, ``item_enclosure_length`` and
|
``item_enclosures`` hook or, alternatively and if you only have a single
|
||||||
|
enclosure per item, the ``item_enclosure_url``, ``item_enclosure_length``, and
|
||||||
``item_enclosure_mime_type`` hooks. See the ``ExampleFeed`` class below for
|
``item_enclosure_mime_type`` hooks. See the ``ExampleFeed`` class below for
|
||||||
usage examples.
|
usage examples.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.9
|
||||||
|
|
||||||
|
Support for multiple enclosures per feed item was added through the
|
||||||
|
``item_enclosures`` hook.
|
||||||
|
|
||||||
Language
|
Language
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -742,8 +748,28 @@ This example illustrates all possible attributes and methods for a
|
||||||
|
|
||||||
item_author_link = 'http://www.example.com/' # Hard-coded author URL.
|
item_author_link = 'http://www.example.com/' # Hard-coded author URL.
|
||||||
|
|
||||||
|
# ITEM ENCLOSURES -- One of the following three is optional. The
|
||||||
|
# framework looks for them in this order. If one of them is defined,
|
||||||
|
# ``item_enclosure_url``, ``item_enclosure_length``, and
|
||||||
|
# ``item_enclosure_mime_type`` will have no effect.
|
||||||
|
|
||||||
|
def item_enclosures(self, item):
|
||||||
|
"""
|
||||||
|
Takes an item, as returned by items(), and returns a list of
|
||||||
|
``django.utils.feedgenerator.Enclosure`` objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def item_enclosure_url(self):
|
||||||
|
"""
|
||||||
|
Returns the ``django.utils.feedgenerator.Enclosure`` list for every
|
||||||
|
item in the feed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
item_enclosures = [] # Hard-coded enclosure list
|
||||||
|
|
||||||
# ITEM ENCLOSURE URL -- One of these three is required if you're
|
# ITEM ENCLOSURE URL -- One of these three is required if you're
|
||||||
# publishing enclosures. The framework looks for them in this order.
|
# publishing enclosures and you're not using ``item_enclosures``. The
|
||||||
|
# framework looks for them in this order.
|
||||||
|
|
||||||
def item_enclosure_url(self, item):
|
def item_enclosure_url(self, item):
|
||||||
"""
|
"""
|
||||||
|
@ -759,9 +785,10 @@ This example illustrates all possible attributes and methods for a
|
||||||
item_enclosure_url = "/foo/bar.mp3" # Hard-coded enclosure link.
|
item_enclosure_url = "/foo/bar.mp3" # Hard-coded enclosure link.
|
||||||
|
|
||||||
# ITEM ENCLOSURE LENGTH -- One of these three is required if you're
|
# ITEM ENCLOSURE LENGTH -- One of these three is required if you're
|
||||||
# publishing enclosures. The framework looks for them in this order.
|
# publishing enclosures and you're not using ``item_enclosures``. The
|
||||||
# In each case, the returned value should be either an integer, or a
|
# framework looks for them in this order. In each case, the returned
|
||||||
# string representation of the integer, in bytes.
|
# value should be either an integer, or a string representation of the
|
||||||
|
# integer, in bytes.
|
||||||
|
|
||||||
def item_enclosure_length(self, item):
|
def item_enclosure_length(self, item):
|
||||||
"""
|
"""
|
||||||
|
@ -777,7 +804,8 @@ This example illustrates all possible attributes and methods for a
|
||||||
item_enclosure_length = 32000 # Hard-coded enclosure length.
|
item_enclosure_length = 32000 # Hard-coded enclosure length.
|
||||||
|
|
||||||
# ITEM ENCLOSURE MIME TYPE -- One of these three is required if you're
|
# ITEM ENCLOSURE MIME TYPE -- One of these three is required if you're
|
||||||
# publishing enclosures. The framework looks for them in this order.
|
# publishing enclosures and you're not using ``item_enclosures``. The
|
||||||
|
# framework looks for them in this order.
|
||||||
|
|
||||||
def item_enclosure_mime_type(self, item):
|
def item_enclosure_mime_type(self, item):
|
||||||
"""
|
"""
|
||||||
|
@ -941,6 +969,7 @@ They share this interface:
|
||||||
* ``comments``
|
* ``comments``
|
||||||
* ``unique_id``
|
* ``unique_id``
|
||||||
* ``enclosure``
|
* ``enclosure``
|
||||||
|
* ``enclosures``
|
||||||
* ``categories``
|
* ``categories``
|
||||||
* ``item_copyright``
|
* ``item_copyright``
|
||||||
* ``ttl``
|
* ``ttl``
|
||||||
|
@ -954,8 +983,15 @@ They share this interface:
|
||||||
* ``updateddate`` should be a Python :class:`~datetime.datetime` object.
|
* ``updateddate`` should be a Python :class:`~datetime.datetime` object.
|
||||||
* ``enclosure`` should be an instance of
|
* ``enclosure`` should be an instance of
|
||||||
:class:`django.utils.feedgenerator.Enclosure`.
|
:class:`django.utils.feedgenerator.Enclosure`.
|
||||||
|
* ``enclosures`` should be a list of
|
||||||
|
:class:`django.utils.feedgenerator.Enclosure` instances.
|
||||||
* ``categories`` should be a sequence of Unicode objects.
|
* ``categories`` should be a sequence of Unicode objects.
|
||||||
|
|
||||||
|
.. deprecated:: 1.9
|
||||||
|
|
||||||
|
The ``enclosure`` keyword argument is deprecated in favor of the
|
||||||
|
``enclosures`` keyword argument.
|
||||||
|
|
||||||
:meth:`.SyndicationFeed.write`
|
:meth:`.SyndicationFeed.write`
|
||||||
Outputs the feed in the given encoding to outfile, which is a file-like object.
|
Outputs the feed in the given encoding to outfile, which is a file-like object.
|
||||||
|
|
||||||
|
|
|
@ -351,11 +351,18 @@ SyndicationFeed
|
||||||
All parameters should be Unicode objects, except ``categories``, which
|
All parameters should be Unicode objects, except ``categories``, which
|
||||||
should be a sequence of Unicode objects.
|
should be a sequence of Unicode objects.
|
||||||
|
|
||||||
.. method:: add_item(title, link, description, author_email=None, author_name=None, author_link=None, pubdate=None, comments=None, unique_id=None, enclosure=None, categories=(), item_copyright=None, ttl=None, updateddate=None, **kwargs)
|
.. method:: add_item(title, link, description, author_email=None, author_name=None, author_link=None, pubdate=None, comments=None, unique_id=None, enclosure=None, categories=(), item_copyright=None, ttl=None, updateddate=None, enclosures=None, **kwargs)
|
||||||
|
|
||||||
Adds an item to the feed. All args are expected to be Python ``unicode``
|
Adds an item to the feed. All args are expected to be Python ``unicode``
|
||||||
objects except ``pubdate`` and ``updateddate``, which are ``datetime.datetime``
|
objects except ``pubdate`` and ``updateddate``, which are ``datetime.datetime``
|
||||||
objects, and ``enclosure``, which is an instance of the ``Enclosure`` class.
|
objects, ``enclosure``, which is an ``Enclosure`` instance, and
|
||||||
|
``enclosures``, which is a list of ``Enclosure`` instances.
|
||||||
|
|
||||||
|
.. deprecated:: 1.9
|
||||||
|
|
||||||
|
The ``enclosure`` keyword argument is deprecated in favor of the
|
||||||
|
new ``enclosures`` keyword argument which accepts a list of
|
||||||
|
``Enclosure`` objects.
|
||||||
|
|
||||||
.. method:: num_items()
|
.. method:: num_items()
|
||||||
|
|
||||||
|
|
|
@ -303,7 +303,9 @@ Minor features
|
||||||
:mod:`django.contrib.syndication`
|
:mod:`django.contrib.syndication`
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* ...
|
* Support for multiple enclosures per feed item has been added. If multiple
|
||||||
|
enclosures are defined on a RSS feed, an exception is raised as RSS feeds,
|
||||||
|
unlike Atom feeds, do not support multiple enclosures per feed item.
|
||||||
|
|
||||||
Cache
|
Cache
|
||||||
^^^^^
|
^^^^^
|
||||||
|
@ -1265,6 +1267,10 @@ Miscellaneous
|
||||||
:func:`~django.utils.safestring.mark_safe` when constructing the method's
|
:func:`~django.utils.safestring.mark_safe` when constructing the method's
|
||||||
return value instead.
|
return value instead.
|
||||||
|
|
||||||
|
* The ``enclosure`` keyword argument to ``SyndicationFeed.add_item()`` is
|
||||||
|
deprecated. Use the new ``enclosures`` argument which accepts a list of
|
||||||
|
``Enclosure`` objects instead of a single one.
|
||||||
|
|
||||||
.. removed-features-1.9:
|
.. removed-features-1.9:
|
||||||
|
|
||||||
Features removed in 1.9
|
Features removed in 1.9
|
||||||
|
|
|
@ -88,8 +88,29 @@ class ArticlesFeed(TestRss2Feed):
|
||||||
return Article.objects.all()
|
return Article.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class TestEnclosureFeed(TestRss2Feed):
|
class TestSingleEnclosureRSSFeed(TestRss2Feed):
|
||||||
pass
|
"""
|
||||||
|
A feed to test that RSS feeds work with a single enclosure.
|
||||||
|
"""
|
||||||
|
def item_enclosure_url(self, item):
|
||||||
|
return 'http://example.com'
|
||||||
|
|
||||||
|
def item_enclosure_size(self, item):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def item_mime_type(self, item):
|
||||||
|
return 'image/png'
|
||||||
|
|
||||||
|
|
||||||
|
class TestMultipleEnclosureRSSFeed(TestRss2Feed):
|
||||||
|
"""
|
||||||
|
A feed to test that RSS feeds raise an exception with multiple enclosures.
|
||||||
|
"""
|
||||||
|
def item_enclosures(self, item):
|
||||||
|
return [
|
||||||
|
feedgenerator.Enclosure('http://example.com/hello.png', 0, 'image/png'),
|
||||||
|
feedgenerator.Enclosure('http://example.com/goodbye.png', 0, 'image/png'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class TemplateFeed(TestRss2Feed):
|
class TemplateFeed(TestRss2Feed):
|
||||||
|
@ -165,3 +186,28 @@ class MyCustomAtom1Feed(feedgenerator.Atom1Feed):
|
||||||
|
|
||||||
class TestCustomFeed(TestAtomFeed):
|
class TestCustomFeed(TestAtomFeed):
|
||||||
feed_type = MyCustomAtom1Feed
|
feed_type = MyCustomAtom1Feed
|
||||||
|
|
||||||
|
|
||||||
|
class TestSingleEnclosureAtomFeed(TestAtomFeed):
|
||||||
|
"""
|
||||||
|
A feed to test that Atom feeds work with a single enclosure.
|
||||||
|
"""
|
||||||
|
def item_enclosure_url(self, item):
|
||||||
|
return 'http://example.com'
|
||||||
|
|
||||||
|
def item_enclosure_size(self, item):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def item_mime_type(self, item):
|
||||||
|
return 'image/png'
|
||||||
|
|
||||||
|
|
||||||
|
class TestMultipleEnclosureAtomFeed(TestAtomFeed):
|
||||||
|
"""
|
||||||
|
A feed to test that Atom feeds work with multiple enclosures.
|
||||||
|
"""
|
||||||
|
def item_enclosures(self, item):
|
||||||
|
return [
|
||||||
|
feedgenerator.Enclosure('http://example.com/hello.png', 0, 'image/png'),
|
||||||
|
feedgenerator.Enclosure('http://example.com/goodbye.png', 0, 'image/png'),
|
||||||
|
]
|
||||||
|
|
|
@ -9,7 +9,10 @@ from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from django.test.utils import requires_tz_support
|
from django.test.utils import requires_tz_support
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.feedgenerator import rfc2822_date, rfc3339_date
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
|
from django.utils.feedgenerator import (
|
||||||
|
Enclosure, SyndicationFeed, rfc2822_date, rfc3339_date,
|
||||||
|
)
|
||||||
|
|
||||||
from .models import Article, Entry
|
from .models import Article, Entry
|
||||||
|
|
||||||
|
@ -63,10 +66,6 @@ class FeedTestCase(TestCase):
|
||||||
set(expected)
|
set(expected)
|
||||||
)
|
)
|
||||||
|
|
||||||
######################################
|
|
||||||
# Feed view
|
|
||||||
######################################
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='syndication_tests.urls')
|
@override_settings(ROOT_URLCONF='syndication_tests.urls')
|
||||||
class SyndicationFeedTest(FeedTestCase):
|
class SyndicationFeedTest(FeedTestCase):
|
||||||
|
@ -186,6 +185,22 @@ class SyndicationFeedTest(FeedTestCase):
|
||||||
item.getElementsByTagName('guid')[0].attributes.get(
|
item.getElementsByTagName('guid')[0].attributes.get(
|
||||||
'isPermaLink').value, "true")
|
'isPermaLink').value, "true")
|
||||||
|
|
||||||
|
def test_rss2_single_enclosure(self):
|
||||||
|
response = self.client.get('/syndication/rss2/single-enclosure/')
|
||||||
|
doc = minidom.parseString(response.content)
|
||||||
|
chan = doc.getElementsByTagName('rss')[0].getElementsByTagName('channel')[0]
|
||||||
|
items = chan.getElementsByTagName('item')
|
||||||
|
for item in items:
|
||||||
|
enclosures = item.getElementsByTagName('enclosure')
|
||||||
|
self.assertEqual(len(enclosures), 1)
|
||||||
|
|
||||||
|
def test_rss2_multiple_enclosures(self):
|
||||||
|
with self.assertRaisesMessage(ValueError, (
|
||||||
|
"RSS feed items may only have one enclosure, see "
|
||||||
|
"http://www.rssboard.org/rss-profile#element-channel-item-enclosure"
|
||||||
|
)):
|
||||||
|
self.client.get('/syndication/rss2/multiple-enclosure/')
|
||||||
|
|
||||||
def test_rss091_feed(self):
|
def test_rss091_feed(self):
|
||||||
"""
|
"""
|
||||||
Test the structure and content of feeds generated by RssUserland091Feed.
|
Test the structure and content of feeds generated by RssUserland091Feed.
|
||||||
|
@ -284,6 +299,24 @@ class SyndicationFeedTest(FeedTestCase):
|
||||||
|
|
||||||
self.assertNotEqual(published, updated)
|
self.assertNotEqual(published, updated)
|
||||||
|
|
||||||
|
def test_atom_single_enclosure(self):
|
||||||
|
response = self.client.get('/syndication/rss2/single-enclosure/')
|
||||||
|
feed = minidom.parseString(response.content).firstChild
|
||||||
|
items = feed.getElementsByTagName('entry')
|
||||||
|
for item in items:
|
||||||
|
links = item.getElementsByTagName('link')
|
||||||
|
links = [link for link in links if link.getAttribute('rel') == 'enclosure']
|
||||||
|
self.assertEqual(len(links), 1)
|
||||||
|
|
||||||
|
def test_atom_multiple_enclosures(self):
|
||||||
|
response = self.client.get('/syndication/rss2/single-enclosure/')
|
||||||
|
feed = minidom.parseString(response.content).firstChild
|
||||||
|
items = feed.getElementsByTagName('entry')
|
||||||
|
for item in items:
|
||||||
|
links = item.getElementsByTagName('link')
|
||||||
|
links = [link for link in links if link.getAttribute('rel') == 'enclosure']
|
||||||
|
self.assertEqual(len(links), 2)
|
||||||
|
|
||||||
def test_latest_post_date(self):
|
def test_latest_post_date(self):
|
||||||
"""
|
"""
|
||||||
Test that both the published and updated dates are
|
Test that both the published and updated dates are
|
||||||
|
@ -493,3 +526,17 @@ class SyndicationFeedTest(FeedTestCase):
|
||||||
views.add_domain('example.com', '//example.com/foo/?arg=value'),
|
views.add_domain('example.com', '//example.com/foo/?arg=value'),
|
||||||
'http://example.com/foo/?arg=value'
|
'http://example.com/foo/?arg=value'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FeedgeneratorTestCase(TestCase):
|
||||||
|
def test_add_item_warns_when_enclosure_kwarg_is_used(self):
|
||||||
|
feed = SyndicationFeed(title='Example', link='http://example.com', description='Foo')
|
||||||
|
with self.assertRaisesMessage(RemovedInDjango20Warning, (
|
||||||
|
'The enclosure keyword argument is deprecated, use enclosures instead.'
|
||||||
|
)):
|
||||||
|
feed.add_item(
|
||||||
|
title='Example Item',
|
||||||
|
link='https://example.com/item',
|
||||||
|
description='bar',
|
||||||
|
enclosure=Enclosure('http://example.com/favicon.ico', 0, 'image/png'),
|
||||||
|
)
|
||||||
|
|
|
@ -19,4 +19,8 @@ urlpatterns = [
|
||||||
url(r'^syndication/articles/$', feeds.ArticlesFeed()),
|
url(r'^syndication/articles/$', feeds.ArticlesFeed()),
|
||||||
url(r'^syndication/template/$', feeds.TemplateFeed()),
|
url(r'^syndication/template/$', feeds.TemplateFeed()),
|
||||||
url(r'^syndication/template_context/$', feeds.TemplateContextFeed()),
|
url(r'^syndication/template_context/$', feeds.TemplateContextFeed()),
|
||||||
|
url(r'^syndication/rss2/single-enclosure/$', feeds.TestSingleEnclosureRSSFeed()),
|
||||||
|
url(r'^syndication/rss2/multiple-enclosure/$', feeds.TestMultipleEnclosureRSSFeed()),
|
||||||
|
url(r'^syndication/atom/single-enclosure/$', feeds.TestSingleEnclosureAtomFeed()),
|
||||||
|
url(r'^syndication/atom/multiple-enclosure/$', feeds.TestMultipleEnclosureAtomFeed()),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue