feeds.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. from functools import wraps
  2. from django.contrib.syndication import views
  3. from django.utils import feedgenerator
  4. from django.utils.timezone import get_fixed_timezone
  5. from .models import Article, Entry
  6. def wraps_decorator(f):
  7. @wraps(f)
  8. def wrapper(*args, **kwargs):
  9. value = f(*args, **kwargs)
  10. return f"{value} -- decorated by @wraps."
  11. return wrapper
  12. def common_decorator(f):
  13. def wrapper(*args, **kwargs):
  14. value = f(*args, **kwargs)
  15. return f"{value} -- common decorated."
  16. return wrapper
  17. class TestRss2Feed(views.Feed):
  18. title = "My blog"
  19. description = "A more thorough description of my blog."
  20. link = "/blog/"
  21. feed_guid = "/foo/bar/1234"
  22. author_name = "Sally Smith"
  23. author_email = "test@example.com"
  24. author_link = "http://www.example.com/"
  25. categories = ("python", "django")
  26. feed_copyright = "Copyright (c) 2007, Sally Smith"
  27. ttl = 600
  28. def items(self):
  29. return Entry.objects.all()
  30. def item_description(self, item):
  31. return "Overridden description: %s" % item
  32. def item_pubdate(self, item):
  33. return item.published
  34. def item_updateddate(self, item):
  35. return item.updated
  36. def item_comments(self, item):
  37. return "%scomments" % item.get_absolute_url()
  38. item_author_name = "Sally Smith"
  39. item_author_email = "test@example.com"
  40. item_author_link = "http://www.example.com/"
  41. item_categories = ("python", "testing")
  42. item_copyright = "Copyright (c) 2007, Sally Smith"
  43. class TestRss2FeedWithCallableObject(TestRss2Feed):
  44. class TimeToLive:
  45. def __call__(self):
  46. return 700
  47. ttl = TimeToLive()
  48. class TestRss2FeedWithDecoratedMethod(TestRss2Feed):
  49. class TimeToLive:
  50. @wraps_decorator
  51. def __call__(self):
  52. return 800
  53. @staticmethod
  54. @wraps_decorator
  55. def feed_copyright():
  56. return "Copyright (c) 2022, John Doe"
  57. ttl = TimeToLive()
  58. @staticmethod
  59. def categories():
  60. return ("javascript", "vue")
  61. @wraps_decorator
  62. def title(self):
  63. return "Overridden title"
  64. @wraps_decorator
  65. def item_title(self, item):
  66. return f"Overridden item title: {item.title}"
  67. @wraps_decorator
  68. def description(self, obj):
  69. return "Overridden description"
  70. @wraps_decorator
  71. def item_description(self):
  72. return "Overridden item description"
  73. class TestRss2FeedWithWrongDecoratedMethod(TestRss2Feed):
  74. @common_decorator
  75. def item_description(self, item):
  76. return f"Overridden item description: {item.title}"
  77. class TestRss2FeedWithGuidIsPermaLinkTrue(TestRss2Feed):
  78. def item_guid_is_permalink(self, item):
  79. return True
  80. class TestRss2FeedWithGuidIsPermaLinkFalse(TestRss2Feed):
  81. def item_guid(self, item):
  82. return str(item.pk)
  83. def item_guid_is_permalink(self, item):
  84. return False
  85. class TestRss091Feed(TestRss2Feed):
  86. feed_type = feedgenerator.RssUserland091Feed
  87. class TestNoPubdateFeed(views.Feed):
  88. title = "Test feed"
  89. link = "/feed/"
  90. def items(self):
  91. return Entry.objects.all()
  92. class TestAtomFeed(TestRss2Feed):
  93. feed_type = feedgenerator.Atom1Feed
  94. subtitle = TestRss2Feed.description
  95. class TestLatestFeed(TestRss2Feed):
  96. """
  97. A feed where the latest entry date is an `updated` element.
  98. """
  99. feed_type = feedgenerator.Atom1Feed
  100. subtitle = TestRss2Feed.description
  101. def items(self):
  102. return Entry.objects.exclude(title="My last entry")
  103. class ArticlesFeed(TestRss2Feed):
  104. """
  105. A feed to test no link being defined. Articles have no get_absolute_url()
  106. method, and item_link() is not defined.
  107. """
  108. def items(self):
  109. return Article.objects.all()
  110. class TestSingleEnclosureRSSFeed(TestRss2Feed):
  111. """
  112. A feed to test that RSS feeds work with a single enclosure.
  113. """
  114. def item_enclosure_url(self, item):
  115. return "http://example.com"
  116. def item_enclosure_size(self, item):
  117. return 0
  118. def item_mime_type(self, item):
  119. return "image/png"
  120. class TestMultipleEnclosureRSSFeed(TestRss2Feed):
  121. """
  122. A feed to test that RSS feeds raise an exception with multiple enclosures.
  123. """
  124. def item_enclosures(self, item):
  125. return [
  126. feedgenerator.Enclosure("http://example.com/hello.png", 0, "image/png"),
  127. feedgenerator.Enclosure("http://example.com/goodbye.png", 0, "image/png"),
  128. ]
  129. class TemplateFeed(TestRss2Feed):
  130. """
  131. A feed to test defining item titles and descriptions with templates.
  132. """
  133. title_template = "syndication/title.html"
  134. description_template = "syndication/description.html"
  135. # Defining a template overrides any item_title definition
  136. def item_title(self):
  137. return "Not in a template"
  138. class TemplateContextFeed(TestRss2Feed):
  139. """
  140. A feed to test custom context data in templates for title or description.
  141. """
  142. title_template = "syndication/title_context.html"
  143. description_template = "syndication/description_context.html"
  144. def get_context_data(self, **kwargs):
  145. context = super().get_context_data(**kwargs)
  146. context["foo"] = "bar"
  147. return context
  148. class TestLanguageFeed(TestRss2Feed):
  149. language = "de"
  150. class TestGetObjectFeed(TestRss2Feed):
  151. def get_object(self, request, entry_id):
  152. return Entry.objects.get(pk=entry_id)
  153. def items(self, obj):
  154. return Article.objects.filter(entry=obj)
  155. def item_link(self, item):
  156. return "%sarticle/%s/" % (item.entry.get_absolute_url(), item.pk)
  157. def item_comments(self, item):
  158. return "%scomments" % self.item_link(item)
  159. def item_description(self, item):
  160. return "Article description: %s" % item.title
  161. def item_title(self, item):
  162. return "Title: %s" % item.title
  163. class TestFeedWithStylesheets(TestRss2Feed):
  164. stylesheets = [
  165. "/stylesheet1.xsl",
  166. feedgenerator.Stylesheet("/stylesheet2.xsl"),
  167. ]
  168. class NaiveDatesFeed(TestAtomFeed):
  169. """
  170. A feed with naive (non-timezone-aware) dates.
  171. """
  172. def item_pubdate(self, item):
  173. return item.published
  174. class TZAwareDatesFeed(TestAtomFeed):
  175. """
  176. A feed with timezone-aware dates.
  177. """
  178. def item_pubdate(self, item):
  179. # Provide a weird offset so that the test can know it's getting this
  180. # specific offset and not accidentally getting on from
  181. # settings.TIME_ZONE.
  182. return item.published.replace(tzinfo=get_fixed_timezone(42))
  183. class TestFeedUrlFeed(TestAtomFeed):
  184. feed_url = "http://example.com/customfeedurl/"
  185. class MyCustomAtom1Feed(feedgenerator.Atom1Feed):
  186. """
  187. Test of a custom feed generator class.
  188. """
  189. def root_attributes(self):
  190. attrs = super().root_attributes()
  191. attrs["django"] = "rocks"
  192. return attrs
  193. def add_root_elements(self, handler):
  194. super().add_root_elements(handler)
  195. handler.addQuickElement("spam", "eggs")
  196. def item_attributes(self, item):
  197. attrs = super().item_attributes(item)
  198. attrs["bacon"] = "yum"
  199. return attrs
  200. def add_item_elements(self, handler, item):
  201. super().add_item_elements(handler, item)
  202. handler.addQuickElement("ministry", "silly walks")
  203. class TestCustomFeed(TestAtomFeed):
  204. feed_type = MyCustomAtom1Feed
  205. class TestSingleEnclosureAtomFeed(TestAtomFeed):
  206. """
  207. A feed to test that Atom feeds work with a single enclosure.
  208. """
  209. def item_enclosure_url(self, item):
  210. return "http://example.com"
  211. def item_enclosure_size(self, item):
  212. return 0
  213. def item_mime_type(self, item):
  214. return "image/png"
  215. class TestMultipleEnclosureAtomFeed(TestAtomFeed):
  216. """
  217. A feed to test that Atom feeds work with multiple enclosures.
  218. """
  219. def item_enclosures(self, item):
  220. return [
  221. feedgenerator.Enclosure("http://example.com/hello.png", "0", "image/png"),
  222. feedgenerator.Enclosure("http://example.com/goodbye.png", "0", "image/png"),
  223. ]