/django/contrib/syndication/views.py
Python | 229 lines | 185 code | 24 blank | 20 comment | 28 complexity | 68bd7d8ccded8a794ed613fe82f6abac MD5 | raw file
1from django.conf import settings 2from django.contrib.sites.models import get_current_site 3from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist 4from django.http import HttpResponse, Http404 5from django.template import loader, TemplateDoesNotExist, RequestContext 6from django.utils import feedgenerator, tzinfo 7from django.utils.encoding import force_unicode, iri_to_uri, smart_unicode 8from django.utils.html import escape 9 10def add_domain(domain, url, secure=False): 11 if not (url.startswith('http://') 12 or url.startswith('https://') 13 or url.startswith('mailto:')): 14 # 'url' must already be ASCII and URL-quoted, so no need for encoding 15 # conversions here. 16 if secure: 17 protocol = 'https' 18 else: 19 protocol = 'http' 20 url = iri_to_uri(u'%s://%s%s' % (protocol, domain, url)) 21 return url 22 23class FeedDoesNotExist(ObjectDoesNotExist): 24 pass 25 26 27class Feed(object): 28 feed_type = feedgenerator.DefaultFeed 29 title_template = None 30 description_template = None 31 32 def __call__(self, request, *args, **kwargs): 33 try: 34 obj = self.get_object(request, *args, **kwargs) 35 except ObjectDoesNotExist: 36 raise Http404('Feed object does not exist.') 37 feedgen = self.get_feed(obj, request) 38 response = HttpResponse(mimetype=feedgen.mime_type) 39 feedgen.write(response, 'utf-8') 40 return response 41 42 def item_title(self, item): 43 # Titles should be double escaped by default (see #6533) 44 return escape(force_unicode(item)) 45 46 def item_description(self, item): 47 return force_unicode(item) 48 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 55 def __get_dynamic_attr(self, attname, obj, default=None): 56 try: 57 attr = getattr(self, attname) 58 except AttributeError: 59 return default 60 if callable(attr): 61 # Check func_code.co_argcount rather than try/excepting the 62 # function and catching the TypeError, because something inside 63 # the function may raise the TypeError. This technique is more 64 # accurate. 65 if hasattr(attr, 'func_code'): 66 argcount = attr.func_code.co_argcount 67 else: 68 argcount = attr.__call__.func_code.co_argcount 69 if argcount == 2: # one argument is 'self' 70 return attr(obj) 71 else: 72 return attr() 73 return attr 74 75 def feed_extra_kwargs(self, obj): 76 """ 77 Returns an extra keyword arguments dictionary that is used when 78 initializing the feed generator. 79 """ 80 return {} 81 82 def item_extra_kwargs(self, item): 83 """ 84 Returns an extra keyword arguments dictionary that is used with 85 the `add_item` call of the feed generator. 86 """ 87 return {} 88 89 def get_object(self, request, *args, **kwargs): 90 return None 91 92 def get_feed(self, obj, request): 93 """ 94 Returns a feedgenerator.DefaultFeed object, fully populated, for 95 this feed. Raises FeedDoesNotExist for invalid parameters. 96 """ 97 current_site = get_current_site(request) 98 99 link = self.__get_dynamic_attr('link', obj) 100 link = add_domain(current_site.domain, link, request.is_secure()) 101 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.decode(), 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 123 title_tmp = None 124 if self.title_template is not None: 125 try: 126 title_tmp = loader.get_template(self.title_template) 127 except TemplateDoesNotExist: 128 pass 129 130 description_tmp = None 131 if self.description_template is not None: 132 try: 133 description_tmp = loader.get_template(self.description_template) 134 except TemplateDoesNotExist: 135 pass 136 137 for item in self.__get_dynamic_attr('items', obj): 138 if title_tmp is not None: 139 title = title_tmp.render(RequestContext(request, {'obj': item, 'site': current_site})) 140 else: 141 title = self.__get_dynamic_attr('item_title', item) 142 if description_tmp is not None: 143 description = description_tmp.render(RequestContext(request, {'obj': item, 'site': current_site})) 144 else: 145 description = self.__get_dynamic_attr('item_description', item) 146 link = add_domain( 147 current_site.domain, 148 self.__get_dynamic_attr('item_link', item), 149 request.is_secure(), 150 ) 151 enc = None 152 enc_url = self.__get_dynamic_attr('item_enclosure_url', item) 153 if enc_url: 154 enc = feedgenerator.Enclosure( 155 url = smart_unicode(enc_url), 156 length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)), 157 mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item)) 158 ) 159 author_name = self.__get_dynamic_attr('item_author_name', item) 160 if author_name is not None: 161 author_email = self.__get_dynamic_attr('item_author_email', item) 162 author_link = self.__get_dynamic_attr('item_author_link', item) 163 else: 164 author_email = author_link = None 165 166 pubdate = self.__get_dynamic_attr('item_pubdate', item) 167 if pubdate and not pubdate.tzinfo: 168 ltz = tzinfo.LocalTimezone(pubdate) 169 pubdate = pubdate.replace(tzinfo=ltz) 170 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 enclosure = enc, 177 pubdate = pubdate, 178 author_name = author_name, 179 author_email = author_email, 180 author_link = author_link, 181 categories = self.__get_dynamic_attr('item_categories', item), 182 item_copyright = self.__get_dynamic_attr('item_copyright', item), 183 **self.item_extra_kwargs(item) 184 ) 185 return feed 186 187 188def feed(request, url, feed_dict=None): 189 """Provided for backwards compatibility.""" 190 from django.contrib.syndication.feeds import Feed as LegacyFeed 191 import warnings 192 warnings.warn('The syndication feed() view is deprecated. Please use the ' 193 'new class based view API.', 194 category=DeprecationWarning) 195 196 if not feed_dict: 197 raise Http404("No feeds are registered.") 198 199 try: 200 slug, param = url.split('/', 1) 201 except ValueError: 202 slug, param = url, '' 203 204 try: 205 f = feed_dict[slug] 206 except KeyError: 207 raise Http404("Slug %r isn't registered." % slug) 208 209 # Backwards compatibility within the backwards compatibility; 210 # Feeds can be updated to be class-based, but still be deployed 211 # using the legacy feed view. This only works if the feed takes 212 # no arguments (i.e., get_object returns None). Refs #14176. 213 if not issubclass(f, LegacyFeed): 214 instance = f() 215 instance.feed_url = getattr(f, 'feed_url', None) or request.path 216 instance.title_template = f.title_template or ('feeds/%s_title.html' % slug) 217 instance.description_template = f.description_template or ('feeds/%s_description.html' % slug) 218 219 return instance(request) 220 221 try: 222 feedgen = f(slug, request).get_feed(param) 223 except FeedDoesNotExist: 224 raise Http404("Invalid feed parameters. Slug %r is valid, but other parameters, or lack thereof, are not." % slug) 225 226 response = HttpResponse(mimetype=feedgen.mime_type) 227 feedgen.write(response, 'utf-8') 228 return response 229