You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

221 lines
8.6 KiB

  1. from __future__ import unicode_literals
  2. from calendar import timegm
  3. from django.conf import settings
  4. from django.contrib.sites.shortcuts import get_current_site
  5. from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
  6. from django.http import Http404, HttpResponse
  7. from django.template import TemplateDoesNotExist, loader
  8. from django.utils import feedgenerator, six
  9. from django.utils.encoding import force_text, iri_to_uri, smart_text
  10. from django.utils.html import escape
  11. from django.utils.http import http_date
  12. from django.utils.timezone import get_default_timezone, is_naive, make_aware
  13. def add_domain(domain, url, secure=False):
  14. protocol = 'https' if secure else 'http'
  15. if url.startswith('//'):
  16. # Support network-path reference (see #16753) - RSS requires a protocol
  17. url = '%s:%s' % (protocol, url)
  18. elif not url.startswith(('http://', 'https://', 'mailto:')):
  19. url = iri_to_uri('%s://%s%s' % (protocol, domain, url))
  20. return url
  21. class FeedDoesNotExist(ObjectDoesNotExist):
  22. pass
  23. class Feed(object):
  24. feed_type = feedgenerator.DefaultFeed
  25. title_template = None
  26. description_template = None
  27. def __call__(self, request, *args, **kwargs):
  28. try:
  29. obj = self.get_object(request, *args, **kwargs)
  30. except ObjectDoesNotExist:
  31. raise Http404('Feed object does not exist.')
  32. feedgen = self.get_feed(obj, request)
  33. response = HttpResponse(content_type=feedgen.content_type)
  34. if hasattr(self, 'item_pubdate') or hasattr(self, 'item_updateddate'):
  35. # if item_pubdate or item_updateddate is defined for the feed, set
  36. # header so as ConditionalGetMiddleware is able to send 304 NOT MODIFIED
  37. response['Last-Modified'] = http_date(
  38. timegm(feedgen.latest_post_date().utctimetuple()))
  39. feedgen.write(response, 'utf-8')
  40. return response
  41. def item_title(self, item):
  42. # Titles should be double escaped by default (see #6533)
  43. return escape(force_text(item))
  44. def item_description(self, item):
  45. return force_text(item)
  46. def item_link(self, item):
  47. try:
  48. return item.get_absolute_url()
  49. except AttributeError:
  50. raise ImproperlyConfigured(
  51. 'Give your %s class a get_absolute_url() method, or define an '
  52. 'item_link() method in your Feed class.' % item.__class__.__name__
  53. )
  54. def item_enclosures(self, item):
  55. enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
  56. if enc_url:
  57. enc = feedgenerator.Enclosure(
  58. url=smart_text(enc_url),
  59. length=smart_text(self.__get_dynamic_attr('item_enclosure_length', item)),
  60. mime_type=smart_text(self.__get_dynamic_attr('item_enclosure_mime_type', item)),
  61. )
  62. return [enc]
  63. return []
  64. def __get_dynamic_attr(self, attname, obj, default=None):
  65. try:
  66. attr = getattr(self, attname)
  67. except AttributeError:
  68. return default
  69. if callable(attr):
  70. # Check co_argcount rather than try/excepting the function and
  71. # catching the TypeError, because something inside the function
  72. # may raise the TypeError. This technique is more accurate.
  73. try:
  74. code = six.get_function_code(attr)
  75. except AttributeError:
  76. code = six.get_function_code(attr.__call__)
  77. if code.co_argcount == 2: # one argument is 'self'
  78. return attr(obj)
  79. else:
  80. return attr()
  81. return attr
  82. def feed_extra_kwargs(self, obj):
  83. """
  84. Returns an extra keyword arguments dictionary that is used when
  85. initializing the feed generator.
  86. """
  87. return {}
  88. def item_extra_kwargs(self, item):
  89. """
  90. Returns an extra keyword arguments dictionary that is used with
  91. the `add_item` call of the feed generator.
  92. """
  93. return {}
  94. def get_object(self, request, *args, **kwargs):
  95. return None
  96. def get_context_data(self, **kwargs):
  97. """
  98. Returns a dictionary to use as extra context if either
  99. ``self.description_template`` or ``self.item_template`` are used.
  100. Default implementation preserves the old behavior
  101. of using {'obj': item, 'site': current_site} as the context.
  102. """
  103. return {'obj': kwargs.get('item'), 'site': kwargs.get('site')}
  104. def get_feed(self, obj, request):
  105. """
  106. Returns a feedgenerator.DefaultFeed object, fully populated, for
  107. this feed. Raises FeedDoesNotExist for invalid parameters.
  108. """
  109. current_site = get_current_site(request)
  110. link = self.__get_dynamic_attr('link', obj)
  111. link = add_domain(current_site.domain, link, request.is_secure())
  112. feed = self.feed_type(
  113. title=self.__get_dynamic_attr('title', obj),
  114. subtitle=self.__get_dynamic_attr('subtitle', obj),
  115. link=link,
  116. description=self.__get_dynamic_attr('description', obj),
  117. language=settings.LANGUAGE_CODE,
  118. feed_url=add_domain(
  119. current_site.domain,
  120. self.__get_dynamic_attr('feed_url', obj) or request.path,
  121. request.is_secure(),
  122. ),
  123. author_name=self.__get_dynamic_attr('author_name', obj),
  124. author_link=self.__get_dynamic_attr('author_link', obj),
  125. author_email=self.__get_dynamic_attr('author_email', obj),
  126. categories=self.__get_dynamic_attr('categories', obj),
  127. feed_copyright=self.__get_dynamic_attr('feed_copyright', obj),
  128. feed_guid=self.__get_dynamic_attr('feed_guid', obj),
  129. ttl=self.__get_dynamic_attr('ttl', obj),
  130. **self.feed_extra_kwargs(obj)
  131. )
  132. title_tmp = None
  133. if self.title_template is not None:
  134. try:
  135. title_tmp = loader.get_template(self.title_template)
  136. except TemplateDoesNotExist:
  137. pass
  138. description_tmp = None
  139. if self.description_template is not None:
  140. try:
  141. description_tmp = loader.get_template(self.description_template)
  142. except TemplateDoesNotExist:
  143. pass
  144. for item in self.__get_dynamic_attr('items', obj):
  145. context = self.get_context_data(item=item, site=current_site,
  146. obj=obj, request=request)
  147. if title_tmp is not None:
  148. title = title_tmp.render(context, request)
  149. else:
  150. title = self.__get_dynamic_attr('item_title', item)
  151. if description_tmp is not None:
  152. description = description_tmp.render(context, request)
  153. else:
  154. description = self.__get_dynamic_attr('item_description', item)
  155. link = add_domain(
  156. current_site.domain,
  157. self.__get_dynamic_attr('item_link', item),
  158. request.is_secure(),
  159. )
  160. enclosures = self.__get_dynamic_attr('item_enclosures', item)
  161. author_name = self.__get_dynamic_attr('item_author_name', item)
  162. if author_name is not None:
  163. author_email = self.__get_dynamic_attr('item_author_email', item)
  164. author_link = self.__get_dynamic_attr('item_author_link', item)
  165. else:
  166. author_email = author_link = None
  167. tz = get_default_timezone()
  168. pubdate = self.__get_dynamic_attr('item_pubdate', item)
  169. if pubdate and is_naive(pubdate):
  170. pubdate = make_aware(pubdate, tz)
  171. updateddate = self.__get_dynamic_attr('item_updateddate', item)
  172. if updateddate and is_naive(updateddate):
  173. updateddate = make_aware(updateddate, tz)
  174. feed.add_item(
  175. title=title,
  176. link=link,
  177. description=description,
  178. unique_id=self.__get_dynamic_attr('item_guid', item, link),
  179. unique_id_is_permalink=self.__get_dynamic_attr(
  180. 'item_guid_is_permalink', item),
  181. enclosures=enclosures,
  182. pubdate=pubdate,
  183. updateddate=updateddate,
  184. author_name=author_name,
  185. author_email=author_email,
  186. author_link=author_link,
  187. categories=self.__get_dynamic_attr('item_categories', item),
  188. item_copyright=self.__get_dynamic_attr('item_copyright', item),
  189. **self.item_extra_kwargs(item)
  190. )
  191. return feed