1. import datetime
    
  2. from xml.dom import minidom
    
  3. 
    
  4. from django.contrib.sites.models import Site
    
  5. from django.contrib.syndication import views
    
  6. from django.core.exceptions import ImproperlyConfigured
    
  7. from django.test import TestCase, override_settings
    
  8. from django.test.utils import requires_tz_support
    
  9. from django.utils import timezone
    
  10. from django.utils.feedgenerator import (
    
  11.     Atom1Feed,
    
  12.     Rss201rev2Feed,
    
  13.     rfc2822_date,
    
  14.     rfc3339_date,
    
  15. )
    
  16. 
    
  17. from .models import Article, Entry
    
  18. 
    
  19. TZ = timezone.get_default_timezone()
    
  20. 
    
  21. 
    
  22. class FeedTestCase(TestCase):
    
  23.     @classmethod
    
  24.     def setUpTestData(cls):
    
  25.         cls.e1 = Entry.objects.create(
    
  26.             title="My first entry",
    
  27.             updated=datetime.datetime(1980, 1, 1, 12, 30),
    
  28.             published=datetime.datetime(1986, 9, 25, 20, 15, 00),
    
  29.         )
    
  30.         cls.e2 = Entry.objects.create(
    
  31.             title="My second entry",
    
  32.             updated=datetime.datetime(2008, 1, 2, 12, 30),
    
  33.             published=datetime.datetime(2006, 3, 17, 18, 0),
    
  34.         )
    
  35.         cls.e3 = Entry.objects.create(
    
  36.             title="My third entry",
    
  37.             updated=datetime.datetime(2008, 1, 2, 13, 30),
    
  38.             published=datetime.datetime(2005, 6, 14, 10, 45),
    
  39.         )
    
  40.         cls.e4 = Entry.objects.create(
    
  41.             title="A & B < C > D",
    
  42.             updated=datetime.datetime(2008, 1, 3, 13, 30),
    
  43.             published=datetime.datetime(2005, 11, 25, 12, 11, 23),
    
  44.         )
    
  45.         cls.e5 = Entry.objects.create(
    
  46.             title="My last entry",
    
  47.             updated=datetime.datetime(2013, 1, 20, 0, 0),
    
  48.             published=datetime.datetime(2013, 3, 25, 20, 0),
    
  49.         )
    
  50.         cls.a1 = Article.objects.create(
    
  51.             title="My first article",
    
  52.             entry=cls.e1,
    
  53.             updated=datetime.datetime(1986, 11, 21, 9, 12, 18),
    
  54.             published=datetime.datetime(1986, 10, 21, 9, 12, 18),
    
  55.         )
    
  56. 
    
  57.     def assertChildNodes(self, elem, expected):
    
  58.         actual = {n.nodeName for n in elem.childNodes}
    
  59.         expected = set(expected)
    
  60.         self.assertEqual(actual, expected)
    
  61. 
    
  62.     def assertChildNodeContent(self, elem, expected):
    
  63.         for k, v in expected.items():
    
  64.             self.assertEqual(elem.getElementsByTagName(k)[0].firstChild.wholeText, v)
    
  65. 
    
  66.     def assertCategories(self, elem, expected):
    
  67.         self.assertEqual(
    
  68.             {
    
  69.                 i.firstChild.wholeText
    
  70.                 for i in elem.childNodes
    
  71.                 if i.nodeName == "category"
    
  72.             },
    
  73.             set(expected),
    
  74.         )
    
  75. 
    
  76. 
    
  77. @override_settings(ROOT_URLCONF="syndication_tests.urls")
    
  78. class SyndicationFeedTest(FeedTestCase):
    
  79.     """
    
  80.     Tests for the high-level syndication feed framework.
    
  81.     """
    
  82. 
    
  83.     @classmethod
    
  84.     def setUpClass(cls):
    
  85.         super().setUpClass()
    
  86.         # This cleanup is necessary because contrib.sites cache
    
  87.         # makes tests interfere with each other, see #11505
    
  88.         Site.objects.clear_cache()
    
  89. 
    
  90.     def test_rss2_feed(self):
    
  91.         """
    
  92.         Test the structure and content of feeds generated by Rss201rev2Feed.
    
  93.         """
    
  94.         response = self.client.get("/syndication/rss2/")
    
  95.         doc = minidom.parseString(response.content)
    
  96. 
    
  97.         # Making sure there's only 1 `rss` element and that the correct
    
  98.         # RSS version was specified.
    
  99.         feed_elem = doc.getElementsByTagName("rss")
    
  100.         self.assertEqual(len(feed_elem), 1)
    
  101.         feed = feed_elem[0]
    
  102.         self.assertEqual(feed.getAttribute("version"), "2.0")
    
  103.         self.assertEqual(
    
  104.             feed.getElementsByTagName("language")[0].firstChild.nodeValue, "en"
    
  105.         )
    
  106. 
    
  107.         # Making sure there's only one `channel` element w/in the
    
  108.         # `rss` element.
    
  109.         chan_elem = feed.getElementsByTagName("channel")
    
  110.         self.assertEqual(len(chan_elem), 1)
    
  111.         chan = chan_elem[0]
    
  112. 
    
  113.         # Find the last build date
    
  114.         d = Entry.objects.latest("published").published
    
  115.         last_build_date = rfc2822_date(timezone.make_aware(d, TZ))
    
  116. 
    
  117.         self.assertChildNodes(
    
  118.             chan,
    
  119.             [
    
  120.                 "title",
    
  121.                 "link",
    
  122.                 "description",
    
  123.                 "language",
    
  124.                 "lastBuildDate",
    
  125.                 "item",
    
  126.                 "atom:link",
    
  127.                 "ttl",
    
  128.                 "copyright",
    
  129.                 "category",
    
  130.             ],
    
  131.         )
    
  132.         self.assertChildNodeContent(
    
  133.             chan,
    
  134.             {
    
  135.                 "title": "My blog",
    
  136.                 "description": "A more thorough description of my blog.",
    
  137.                 "link": "http://example.com/blog/",
    
  138.                 "language": "en",
    
  139.                 "lastBuildDate": last_build_date,
    
  140.                 "ttl": "600",
    
  141.                 "copyright": "Copyright (c) 2007, Sally Smith",
    
  142.             },
    
  143.         )
    
  144.         self.assertCategories(chan, ["python", "django"])
    
  145. 
    
  146.         # Ensure the content of the channel is correct
    
  147.         self.assertChildNodeContent(
    
  148.             chan,
    
  149.             {
    
  150.                 "title": "My blog",
    
  151.                 "link": "http://example.com/blog/",
    
  152.             },
    
  153.         )
    
  154. 
    
  155.         # Check feed_url is passed
    
  156.         self.assertEqual(
    
  157.             chan.getElementsByTagName("atom:link")[0].getAttribute("href"),
    
  158.             "http://example.com/syndication/rss2/",
    
  159.         )
    
  160. 
    
  161.         # Find the pubdate of the first feed item
    
  162.         d = Entry.objects.get(pk=self.e1.pk).published
    
  163.         pub_date = rfc2822_date(timezone.make_aware(d, TZ))
    
  164. 
    
  165.         items = chan.getElementsByTagName("item")
    
  166.         self.assertEqual(len(items), Entry.objects.count())
    
  167.         self.assertChildNodeContent(
    
  168.             items[0],
    
  169.             {
    
  170.                 "title": "My first entry",
    
  171.                 "description": "Overridden description: My first entry",
    
  172.                 "link": "http://example.com/blog/%s/" % self.e1.pk,
    
  173.                 "guid": "http://example.com/blog/%s/" % self.e1.pk,
    
  174.                 "pubDate": pub_date,
    
  175.                 "author": "[email protected] (Sally Smith)",
    
  176.                 "comments": "/blog/%s/comments" % self.e1.pk,
    
  177.             },
    
  178.         )
    
  179.         self.assertCategories(items[0], ["python", "testing"])
    
  180.         for item in items:
    
  181.             self.assertChildNodes(
    
  182.                 item,
    
  183.                 [
    
  184.                     "title",
    
  185.                     "link",
    
  186.                     "description",
    
  187.                     "guid",
    
  188.                     "category",
    
  189.                     "pubDate",
    
  190.                     "author",
    
  191.                     "comments",
    
  192.                 ],
    
  193.             )
    
  194.             # Assert that <guid> does not have any 'isPermaLink' attribute
    
  195.             self.assertIsNone(
    
  196.                 item.getElementsByTagName("guid")[0].attributes.get("isPermaLink")
    
  197.             )
    
  198. 
    
  199.     def test_rss2_feed_with_decorated_methods(self):
    
  200.         response = self.client.get("/syndication/rss2/with-decorated-methods/")
    
  201.         doc = minidom.parseString(response.content)
    
  202.         chan = doc.getElementsByTagName("rss")[0].getElementsByTagName("channel")[0]
    
  203.         self.assertCategories(chan, ["javascript", "vue"])
    
  204.         self.assertChildNodeContent(
    
  205.             chan,
    
  206.             {
    
  207.                 "title": "Overridden title -- decorated by @wraps.",
    
  208.                 "description": "Overridden description -- decorated by @wraps.",
    
  209.                 "ttl": "800 -- decorated by @wraps.",
    
  210.                 "copyright": "Copyright (c) 2022, John Doe -- decorated by @wraps.",
    
  211.             },
    
  212.         )
    
  213.         items = chan.getElementsByTagName("item")
    
  214.         self.assertChildNodeContent(
    
  215.             items[0],
    
  216.             {
    
  217.                 "title": (
    
  218.                     f"Overridden item title: {self.e1.title} -- decorated by @wraps."
    
  219.                 ),
    
  220.                 "description": "Overridden item description -- decorated by @wraps.",
    
  221.             },
    
  222.         )
    
  223. 
    
  224.     def test_rss2_feed_with_wrong_decorated_methods(self):
    
  225.         msg = (
    
  226.             "Feed method 'item_description' decorated by 'wrapper' needs to use "
    
  227.             "@functools.wraps."
    
  228.         )
    
  229.         with self.assertRaisesMessage(ImproperlyConfigured, msg):
    
  230.             self.client.get("/syndication/rss2/with-wrong-decorated-methods/")
    
  231. 
    
  232.     def test_rss2_feed_guid_permalink_false(self):
    
  233.         """
    
  234.         Test if the 'isPermaLink' attribute of <guid> element of an item
    
  235.         in the RSS feed is 'false'.
    
  236.         """
    
  237.         response = self.client.get("/syndication/rss2/guid_ispermalink_false/")
    
  238.         doc = minidom.parseString(response.content)
    
  239.         chan = doc.getElementsByTagName("rss")[0].getElementsByTagName("channel")[0]
    
  240.         items = chan.getElementsByTagName("item")
    
  241.         for item in items:
    
  242.             self.assertEqual(
    
  243.                 item.getElementsByTagName("guid")[0]
    
  244.                 .attributes.get("isPermaLink")
    
  245.                 .value,
    
  246.                 "false",
    
  247.             )
    
  248. 
    
  249.     def test_rss2_feed_guid_permalink_true(self):
    
  250.         """
    
  251.         Test if the 'isPermaLink' attribute of <guid> element of an item
    
  252.         in the RSS feed is 'true'.
    
  253.         """
    
  254.         response = self.client.get("/syndication/rss2/guid_ispermalink_true/")
    
  255.         doc = minidom.parseString(response.content)
    
  256.         chan = doc.getElementsByTagName("rss")[0].getElementsByTagName("channel")[0]
    
  257.         items = chan.getElementsByTagName("item")
    
  258.         for item in items:
    
  259.             self.assertEqual(
    
  260.                 item.getElementsByTagName("guid")[0]
    
  261.                 .attributes.get("isPermaLink")
    
  262.                 .value,
    
  263.                 "true",
    
  264.             )
    
  265. 
    
  266.     def test_rss2_single_enclosure(self):
    
  267.         response = self.client.get("/syndication/rss2/single-enclosure/")
    
  268.         doc = minidom.parseString(response.content)
    
  269.         chan = doc.getElementsByTagName("rss")[0].getElementsByTagName("channel")[0]
    
  270.         items = chan.getElementsByTagName("item")
    
  271.         for item in items:
    
  272.             enclosures = item.getElementsByTagName("enclosure")
    
  273.             self.assertEqual(len(enclosures), 1)
    
  274. 
    
  275.     def test_rss2_multiple_enclosures(self):
    
  276.         with self.assertRaisesMessage(
    
  277.             ValueError,
    
  278.             "RSS feed items may only have one enclosure, see "
    
  279.             "http://www.rssboard.org/rss-profile#element-channel-item-enclosure",
    
  280.         ):
    
  281.             self.client.get("/syndication/rss2/multiple-enclosure/")
    
  282. 
    
  283.     def test_rss091_feed(self):
    
  284.         """
    
  285.         Test the structure and content of feeds generated by RssUserland091Feed.
    
  286.         """
    
  287.         response = self.client.get("/syndication/rss091/")
    
  288.         doc = minidom.parseString(response.content)
    
  289. 
    
  290.         # Making sure there's only 1 `rss` element and that the correct
    
  291.         # RSS version was specified.
    
  292.         feed_elem = doc.getElementsByTagName("rss")
    
  293.         self.assertEqual(len(feed_elem), 1)
    
  294.         feed = feed_elem[0]
    
  295.         self.assertEqual(feed.getAttribute("version"), "0.91")
    
  296. 
    
  297.         # Making sure there's only one `channel` element w/in the
    
  298.         # `rss` element.
    
  299.         chan_elem = feed.getElementsByTagName("channel")
    
  300.         self.assertEqual(len(chan_elem), 1)
    
  301.         chan = chan_elem[0]
    
  302.         self.assertChildNodes(
    
  303.             chan,
    
  304.             [
    
  305.                 "title",
    
  306.                 "link",
    
  307.                 "description",
    
  308.                 "language",
    
  309.                 "lastBuildDate",
    
  310.                 "item",
    
  311.                 "atom:link",
    
  312.                 "ttl",
    
  313.                 "copyright",
    
  314.                 "category",
    
  315.             ],
    
  316.         )
    
  317. 
    
  318.         # Ensure the content of the channel is correct
    
  319.         self.assertChildNodeContent(
    
  320.             chan,
    
  321.             {
    
  322.                 "title": "My blog",
    
  323.                 "link": "http://example.com/blog/",
    
  324.             },
    
  325.         )
    
  326.         self.assertCategories(chan, ["python", "django"])
    
  327. 
    
  328.         # Check feed_url is passed
    
  329.         self.assertEqual(
    
  330.             chan.getElementsByTagName("atom:link")[0].getAttribute("href"),
    
  331.             "http://example.com/syndication/rss091/",
    
  332.         )
    
  333. 
    
  334.         items = chan.getElementsByTagName("item")
    
  335.         self.assertEqual(len(items), Entry.objects.count())
    
  336.         self.assertChildNodeContent(
    
  337.             items[0],
    
  338.             {
    
  339.                 "title": "My first entry",
    
  340.                 "description": "Overridden description: My first entry",
    
  341.                 "link": "http://example.com/blog/%s/" % self.e1.pk,
    
  342.             },
    
  343.         )
    
  344.         for item in items:
    
  345.             self.assertChildNodes(item, ["title", "link", "description"])
    
  346.             self.assertCategories(item, [])
    
  347. 
    
  348.     def test_atom_feed(self):
    
  349.         """
    
  350.         Test the structure and content of feeds generated by Atom1Feed.
    
  351.         """
    
  352.         response = self.client.get("/syndication/atom/")
    
  353.         feed = minidom.parseString(response.content).firstChild
    
  354. 
    
  355.         self.assertEqual(feed.nodeName, "feed")
    
  356.         self.assertEqual(feed.getAttribute("xmlns"), "http://www.w3.org/2005/Atom")
    
  357.         self.assertChildNodes(
    
  358.             feed,
    
  359.             [
    
  360.                 "title",
    
  361.                 "subtitle",
    
  362.                 "link",
    
  363.                 "id",
    
  364.                 "updated",
    
  365.                 "entry",
    
  366.                 "rights",
    
  367.                 "category",
    
  368.                 "author",
    
  369.             ],
    
  370.         )
    
  371.         for link in feed.getElementsByTagName("link"):
    
  372.             if link.getAttribute("rel") == "self":
    
  373.                 self.assertEqual(
    
  374.                     link.getAttribute("href"), "http://example.com/syndication/atom/"
    
  375.                 )
    
  376. 
    
  377.         entries = feed.getElementsByTagName("entry")
    
  378.         self.assertEqual(len(entries), Entry.objects.count())
    
  379.         for entry in entries:
    
  380.             self.assertChildNodes(
    
  381.                 entry,
    
  382.                 [
    
  383.                     "title",
    
  384.                     "link",
    
  385.                     "id",
    
  386.                     "summary",
    
  387.                     "category",
    
  388.                     "updated",
    
  389.                     "published",
    
  390.                     "rights",
    
  391.                     "author",
    
  392.                 ],
    
  393.             )
    
  394.             summary = entry.getElementsByTagName("summary")[0]
    
  395.             self.assertEqual(summary.getAttribute("type"), "html")
    
  396. 
    
  397.     def test_atom_feed_published_and_updated_elements(self):
    
  398.         """
    
  399.         The published and updated elements are not
    
  400.         the same and now adhere to RFC 4287.
    
  401.         """
    
  402.         response = self.client.get("/syndication/atom/")
    
  403.         feed = minidom.parseString(response.content).firstChild
    
  404.         entries = feed.getElementsByTagName("entry")
    
  405. 
    
  406.         published = entries[0].getElementsByTagName("published")[0].firstChild.wholeText
    
  407.         updated = entries[0].getElementsByTagName("updated")[0].firstChild.wholeText
    
  408. 
    
  409.         self.assertNotEqual(published, updated)
    
  410. 
    
  411.     def test_atom_single_enclosure(self):
    
  412.         response = self.client.get("/syndication/atom/single-enclosure/")
    
  413.         feed = minidom.parseString(response.content).firstChild
    
  414.         items = feed.getElementsByTagName("entry")
    
  415.         for item in items:
    
  416.             links = item.getElementsByTagName("link")
    
  417.             links = [link for link in links if link.getAttribute("rel") == "enclosure"]
    
  418.             self.assertEqual(len(links), 1)
    
  419. 
    
  420.     def test_atom_multiple_enclosures(self):
    
  421.         response = self.client.get("/syndication/atom/multiple-enclosure/")
    
  422.         feed = minidom.parseString(response.content).firstChild
    
  423.         items = feed.getElementsByTagName("entry")
    
  424.         for item in items:
    
  425.             links = item.getElementsByTagName("link")
    
  426.             links = [link for link in links if link.getAttribute("rel") == "enclosure"]
    
  427.             self.assertEqual(len(links), 2)
    
  428. 
    
  429.     def test_latest_post_date(self):
    
  430.         """
    
  431.         Both the published and updated dates are
    
  432.         considered when determining the latest post date.
    
  433.         """
    
  434.         # this feed has a `published` element with the latest date
    
  435.         response = self.client.get("/syndication/atom/")
    
  436.         feed = minidom.parseString(response.content).firstChild
    
  437.         updated = feed.getElementsByTagName("updated")[0].firstChild.wholeText
    
  438. 
    
  439.         d = Entry.objects.latest("published").published
    
  440.         latest_published = rfc3339_date(timezone.make_aware(d, TZ))
    
  441. 
    
  442.         self.assertEqual(updated, latest_published)
    
  443. 
    
  444.         # this feed has an `updated` element with the latest date
    
  445.         response = self.client.get("/syndication/latest/")
    
  446.         feed = minidom.parseString(response.content).firstChild
    
  447.         updated = feed.getElementsByTagName("updated")[0].firstChild.wholeText
    
  448. 
    
  449.         d = Entry.objects.exclude(title="My last entry").latest("updated").updated
    
  450.         latest_updated = rfc3339_date(timezone.make_aware(d, TZ))
    
  451. 
    
  452.         self.assertEqual(updated, latest_updated)
    
  453. 
    
  454.     def test_custom_feed_generator(self):
    
  455.         response = self.client.get("/syndication/custom/")
    
  456.         feed = minidom.parseString(response.content).firstChild
    
  457. 
    
  458.         self.assertEqual(feed.nodeName, "feed")
    
  459.         self.assertEqual(feed.getAttribute("django"), "rocks")
    
  460.         self.assertChildNodes(
    
  461.             feed,
    
  462.             [
    
  463.                 "title",
    
  464.                 "subtitle",
    
  465.                 "link",
    
  466.                 "id",
    
  467.                 "updated",
    
  468.                 "entry",
    
  469.                 "spam",
    
  470.                 "rights",
    
  471.                 "category",
    
  472.                 "author",
    
  473.             ],
    
  474.         )
    
  475. 
    
  476.         entries = feed.getElementsByTagName("entry")
    
  477.         self.assertEqual(len(entries), Entry.objects.count())
    
  478.         for entry in entries:
    
  479.             self.assertEqual(entry.getAttribute("bacon"), "yum")
    
  480.             self.assertChildNodes(
    
  481.                 entry,
    
  482.                 [
    
  483.                     "title",
    
  484.                     "link",
    
  485.                     "id",
    
  486.                     "summary",
    
  487.                     "ministry",
    
  488.                     "rights",
    
  489.                     "author",
    
  490.                     "updated",
    
  491.                     "published",
    
  492.                     "category",
    
  493.                 ],
    
  494.             )
    
  495.             summary = entry.getElementsByTagName("summary")[0]
    
  496.             self.assertEqual(summary.getAttribute("type"), "html")
    
  497. 
    
  498.     def test_feed_generator_language_attribute(self):
    
  499.         response = self.client.get("/syndication/language/")
    
  500.         feed = minidom.parseString(response.content).firstChild
    
  501.         self.assertEqual(
    
  502.             feed.firstChild.getElementsByTagName("language")[0].firstChild.nodeValue,
    
  503.             "de",
    
  504.         )
    
  505. 
    
  506.     def test_title_escaping(self):
    
  507.         """
    
  508.         Titles are escaped correctly in RSS feeds.
    
  509.         """
    
  510.         response = self.client.get("/syndication/rss2/")
    
  511.         doc = minidom.parseString(response.content)
    
  512.         for item in doc.getElementsByTagName("item"):
    
  513.             link = item.getElementsByTagName("link")[0]
    
  514.             if link.firstChild.wholeText == "http://example.com/blog/4/":
    
  515.                 title = item.getElementsByTagName("title")[0]
    
  516.                 self.assertEqual(title.firstChild.wholeText, "A &amp; B &lt; C &gt; D")
    
  517. 
    
  518.     def test_naive_datetime_conversion(self):
    
  519.         """
    
  520.         Datetimes are correctly converted to the local time zone.
    
  521.         """
    
  522.         # Naive date times passed in get converted to the local time zone, so
    
  523.         # check the received zone offset against the local offset.
    
  524.         response = self.client.get("/syndication/naive-dates/")
    
  525.         doc = minidom.parseString(response.content)
    
  526.         updated = doc.getElementsByTagName("updated")[0].firstChild.wholeText
    
  527. 
    
  528.         d = Entry.objects.latest("published").published
    
  529.         latest = rfc3339_date(timezone.make_aware(d, TZ))
    
  530. 
    
  531.         self.assertEqual(updated, latest)
    
  532. 
    
  533.     def test_aware_datetime_conversion(self):
    
  534.         """
    
  535.         Datetimes with timezones don't get trodden on.
    
  536.         """
    
  537.         response = self.client.get("/syndication/aware-dates/")
    
  538.         doc = minidom.parseString(response.content)
    
  539.         published = doc.getElementsByTagName("published")[0].firstChild.wholeText
    
  540.         self.assertEqual(published[-6:], "+00:42")
    
  541. 
    
  542.     def test_feed_no_content_self_closing_tag(self):
    
  543.         tests = [
    
  544.             (Atom1Feed, "link"),
    
  545.             (Rss201rev2Feed, "atom:link"),
    
  546.         ]
    
  547.         for feedgenerator, tag in tests:
    
  548.             with self.subTest(feedgenerator=feedgenerator.__name__):
    
  549.                 feed = feedgenerator(
    
  550.                     title="title",
    
  551.                     link="https://example.com",
    
  552.                     description="self closing tags test",
    
  553.                     feed_url="https://feed.url.com",
    
  554.                 )
    
  555.                 doc = feed.writeString("utf-8")
    
  556.                 self.assertIn(f'<{tag} href="https://feed.url.com" rel="self"/>', doc)
    
  557. 
    
  558.     @requires_tz_support
    
  559.     def test_feed_last_modified_time_naive_date(self):
    
  560.         """
    
  561.         Tests the Last-Modified header with naive publication dates.
    
  562.         """
    
  563.         response = self.client.get("/syndication/naive-dates/")
    
  564.         self.assertEqual(
    
  565.             response.headers["Last-Modified"], "Tue, 26 Mar 2013 01:00:00 GMT"
    
  566.         )
    
  567. 
    
  568.     def test_feed_last_modified_time(self):
    
  569.         """
    
  570.         Tests the Last-Modified header with aware publication dates.
    
  571.         """
    
  572.         response = self.client.get("/syndication/aware-dates/")
    
  573.         self.assertEqual(
    
  574.             response.headers["Last-Modified"], "Mon, 25 Mar 2013 19:18:00 GMT"
    
  575.         )
    
  576. 
    
  577.         # No last-modified when feed has no item_pubdate
    
  578.         response = self.client.get("/syndication/no_pubdate/")
    
  579.         self.assertFalse(response.has_header("Last-Modified"))
    
  580. 
    
  581.     def test_feed_url(self):
    
  582.         """
    
  583.         The feed_url can be overridden.
    
  584.         """
    
  585.         response = self.client.get("/syndication/feedurl/")
    
  586.         doc = minidom.parseString(response.content)
    
  587.         for link in doc.getElementsByTagName("link"):
    
  588.             if link.getAttribute("rel") == "self":
    
  589.                 self.assertEqual(
    
  590.                     link.getAttribute("href"), "http://example.com/customfeedurl/"
    
  591.                 )
    
  592. 
    
  593.     def test_secure_urls(self):
    
  594.         """
    
  595.         Test URLs are prefixed with https:// when feed is requested over HTTPS.
    
  596.         """
    
  597.         response = self.client.get(
    
  598.             "/syndication/rss2/",
    
  599.             **{
    
  600.                 "wsgi.url_scheme": "https",
    
  601.             },
    
  602.         )
    
  603.         doc = minidom.parseString(response.content)
    
  604.         chan = doc.getElementsByTagName("channel")[0]
    
  605.         self.assertEqual(
    
  606.             chan.getElementsByTagName("link")[0].firstChild.wholeText[0:5], "https"
    
  607.         )
    
  608.         atom_link = chan.getElementsByTagName("atom:link")[0]
    
  609.         self.assertEqual(atom_link.getAttribute("href")[0:5], "https")
    
  610.         for link in doc.getElementsByTagName("link"):
    
  611.             if link.getAttribute("rel") == "self":
    
  612.                 self.assertEqual(link.getAttribute("href")[0:5], "https")
    
  613. 
    
  614.     def test_item_link_error(self):
    
  615.         """
    
  616.         An ImproperlyConfigured is raised if no link could be found for the
    
  617.         item(s).
    
  618.         """
    
  619.         msg = (
    
  620.             "Give your Article class a get_absolute_url() method, or define "
    
  621.             "an item_link() method in your Feed class."
    
  622.         )
    
  623.         with self.assertRaisesMessage(ImproperlyConfigured, msg):
    
  624.             self.client.get("/syndication/articles/")
    
  625. 
    
  626.     def test_template_feed(self):
    
  627.         """
    
  628.         The item title and description can be overridden with templates.
    
  629.         """
    
  630.         response = self.client.get("/syndication/template/")
    
  631.         doc = minidom.parseString(response.content)
    
  632.         feed = doc.getElementsByTagName("rss")[0]
    
  633.         chan = feed.getElementsByTagName("channel")[0]
    
  634.         items = chan.getElementsByTagName("item")
    
  635. 
    
  636.         self.assertChildNodeContent(
    
  637.             items[0],
    
  638.             {
    
  639.                 "title": "Title in your templates: My first entry\n",
    
  640.                 "description": "Description in your templates: My first entry\n",
    
  641.                 "link": "http://example.com/blog/%s/" % self.e1.pk,
    
  642.             },
    
  643.         )
    
  644. 
    
  645.     def test_template_context_feed(self):
    
  646.         """
    
  647.         Custom context data can be passed to templates for title
    
  648.         and description.
    
  649.         """
    
  650.         response = self.client.get("/syndication/template_context/")
    
  651.         doc = minidom.parseString(response.content)
    
  652.         feed = doc.getElementsByTagName("rss")[0]
    
  653.         chan = feed.getElementsByTagName("channel")[0]
    
  654.         items = chan.getElementsByTagName("item")
    
  655. 
    
  656.         self.assertChildNodeContent(
    
  657.             items[0],
    
  658.             {
    
  659.                 "title": "My first entry (foo is bar)\n",
    
  660.                 "description": "My first entry (foo is bar)\n",
    
  661.             },
    
  662.         )
    
  663. 
    
  664.     def test_add_domain(self):
    
  665.         """
    
  666.         add_domain() prefixes domains onto the correct URLs.
    
  667.         """
    
  668.         prefix_domain_mapping = (
    
  669.             (("example.com", "/foo/?arg=value"), "http://example.com/foo/?arg=value"),
    
  670.             (
    
  671.                 ("example.com", "/foo/?arg=value", True),
    
  672.                 "https://example.com/foo/?arg=value",
    
  673.             ),
    
  674.             (
    
  675.                 ("example.com", "http://djangoproject.com/doc/"),
    
  676.                 "http://djangoproject.com/doc/",
    
  677.             ),
    
  678.             (
    
  679.                 ("example.com", "https://djangoproject.com/doc/"),
    
  680.                 "https://djangoproject.com/doc/",
    
  681.             ),
    
  682.             (
    
  683.                 ("example.com", "mailto:[email protected]"),
    
  684.                 "mailto:[email protected]",
    
  685.             ),
    
  686.             (
    
  687.                 ("example.com", "//example.com/foo/?arg=value"),
    
  688.                 "http://example.com/foo/?arg=value",
    
  689.             ),
    
  690.         )
    
  691.         for prefix in prefix_domain_mapping:
    
  692.             with self.subTest(prefix=prefix):
    
  693.                 self.assertEqual(views.add_domain(*prefix[0]), prefix[1])
    
  694. 
    
  695.     def test_get_object(self):
    
  696.         response = self.client.get("/syndication/rss2/articles/%s/" % self.e1.pk)
    
  697.         doc = minidom.parseString(response.content)
    
  698.         feed = doc.getElementsByTagName("rss")[0]
    
  699.         chan = feed.getElementsByTagName("channel")[0]
    
  700.         items = chan.getElementsByTagName("item")
    
  701. 
    
  702.         self.assertChildNodeContent(
    
  703.             items[0],
    
  704.             {
    
  705.                 "comments": "/blog/%s/article/%s/comments" % (self.e1.pk, self.a1.pk),
    
  706.                 "description": "Article description: My first article",
    
  707.                 "link": "http://example.com/blog/%s/article/%s/"
    
  708.                 % (self.e1.pk, self.a1.pk),
    
  709.                 "title": "Title: My first article",
    
  710.                 "pubDate": rfc2822_date(timezone.make_aware(self.a1.published, TZ)),
    
  711.             },
    
  712.         )
    
  713. 
    
  714.     def test_get_non_existent_object(self):
    
  715.         response = self.client.get("/syndication/rss2/articles/0/")
    
  716.         self.assertEqual(response.status_code, 404)