views.py 8.6 KB

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