├── feedutil ├── __init__.py ├── templatetags │ ├── __init__.py │ └── feedutil.py └── templates │ └── feedutil │ └── feed.html └── setup.py /feedutil/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /feedutil/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /feedutil/templates/feedutil/feed.html: -------------------------------------------------------------------------------- 1 | {% for post in posts %}
2 |

{{ post.title|escape }}

3 | {{ post.author|escape }}

4 |
5 | {{ post.content }} 6 |

Published: {{ post.published|date:"r" }}

7 |
8 | {% endfor %} 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='django-feedutil', 5 | version='0.1.0', 6 | description='RSS+ATOM Feed replication for django sites', 7 | author='Doug Napoleone', 8 | author_email='doug.napoleone@gmail.com', 9 | url='http://code.google.com/p/django-feedutil/', 10 | packages=find_packages(), 11 | classifiers=[ 12 | 'Development Status :: 3 - Alpha', 13 | 'Environment :: Web Environment', 14 | 'Intended Audience :: Developers', 15 | 'License :: OSI Approved :: MIT License', 16 | 'Operating System :: OS Independent', 17 | 'Programming Language :: Python', 18 | 'Framework :: Django', 19 | ], 20 | include_package_data=True, 21 | zip_safe=False, 22 | install_requires=['setuptools'], 23 | setup_requires=['setuptools_git'], 24 | ) 25 | -------------------------------------------------------------------------------- /feedutil/templatetags/feedutil.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | from django.core.cache import cache 4 | from cPickle import loads, dumps, HIGHEST_PROTOCOL 5 | import datetime 6 | import feedparser 7 | import re 8 | import time 9 | 10 | def _getdefault(name, default=None): 11 | try: 12 | default = getattr(settings, name) 13 | except: pass 14 | return default 15 | 16 | FEEDUTIL_NUM_POSTS = _getdefault('FEEDUTIL_NUM_POSTS', -1) 17 | FEEDUTIL_CACHE_MIN = _getdefault('FEEDUTIL_CACHE_MIN', 30) 18 | FEEDUTIL_SUMMARY_LEN = _getdefault('FEEDUTIL_SUMMARY_LEN', 150) 19 | FEEDUTIL_SUMMARY_HTML_WORDS = _getdefault('FEEDUTIL_SUMMARY_HTML_WORDS', 25) 20 | 21 | register = template.Library() 22 | 23 | def summarize(text): 24 | cleaned = template.defaultfilters.striptags(text) 25 | l = len(cleaned) 26 | if len(cleaned) > FEEDUTIL_SUMMARY_LEN: 27 | cleaned = cleaned[:FEEDUTIL_SUMMARY_LEN] + '...' 28 | return cleaned 29 | 30 | def summarize_html(text): 31 | return template.defaultfilters.truncatewords_html(text, 32 | FEEDUTIL_SUMMARY_HTML_WORDS) + ' ...' 33 | 34 | def pull_feed(feed_url, posts_to_show=None, cache_expires=None): 35 | if posts_to_show is None: posts_to_show = FEEDUTIL_NUM_POSTS 36 | if cache_expires is None: cache_expires = FEEDUTIL_CACHE_MIN 37 | cachename = 'feed_cache_' + template.defaultfilters.slugify(feed_url) 38 | posts = [] 39 | data = None 40 | if cache_expires > 0: 41 | data = cache.get(cachename) 42 | if data is None: 43 | # load feed 44 | try: 45 | feed = feedparser.parse(feed_url) 46 | entries = feed['entries'] 47 | #if posts_to_show > 0 and len(entries) > posts_to_show: 48 | # entries = entries[:posts_to_show] 49 | posts = [ { 50 | 'title': entry.title, 51 | 'author': entry.author if entry.has_key('author') else '', 52 | 'summary': summarize(entry.summary if entry.has_key('summary') else entry.content[0]['value']), 53 | 'summary_html': summarize_html(entry.description if entry.has_key('description') else entry.content[0]['value']), 54 | 'content': entry.description if entry.has_key('description') else entry.content[0]['value'], 55 | 'url': entry.link, 56 | 'comments': entry.comments if entry.has_key('comments') else '', 57 | 'published': datetime.datetime.fromtimestamp(time.mktime(entry.updated_parsed)) if entry.has_key('updated_parsed') else '', } 58 | for entry in entries ] 59 | except: 60 | if settings.DEBUG: 61 | raise 62 | return [] 63 | if cache_expires > 0: 64 | cache.set(cachename, posts, cache_expires*60) 65 | else: 66 | #load feed from cache 67 | posts = data 68 | 69 | if posts_to_show > 0 and len(posts) > posts_to_show: 70 | posts = posts[:posts_to_show] 71 | 72 | return posts 73 | 74 | @register.inclusion_tag('feedutil/feed.html') 75 | def feed(feed_url, posts_to_show=None, cache_expires=None): 76 | """ 77 | Render an RSS/Atom feed using the 'feedutil/feed.html' template. 78 | 79 | :: 80 | {% feed feed_url [posts_to_show] [cache_expires] %} 81 | {% feed "https://foo.net/timeline?max=5&format=rss" 5 60 %} 82 | 83 | 84 | feed_url: full url to the feed (required) 85 | posts_to_show: Number of posts to pull. <=0 for all 86 | (default: settings.FEEDUTIL_NUM_POSTS or -1) 87 | cache_expired: Number of minuites for the cache. <=0 for no cache. 88 | (default: settings.FEEDUTIL_CACHE_MIN or 30) 89 | 90 | """ 91 | return { 'posts': pull_feed(feed_url, posts_to_show, cache_expires) } 92 | 93 | class GetFeedNode(template.Node): 94 | def __init__(self, var_name, feed_url, posts_to_show=None, 95 | cache_expires=None): 96 | self.var_name = var_name 97 | self.feed_url = feed_url 98 | self.posts_to_show = posts_to_show 99 | self.cache_expires = cache_expires 100 | def render(self, context): 101 | posts_to_show = cache_expires = None 102 | try: 103 | feed_url = template.resolve_variable(self.feed_url, context) 104 | except: 105 | if settings.DEBUG: 106 | raise 107 | context[self.var_name] = [] 108 | return '' 109 | if self.posts_to_show is not None: 110 | try: 111 | posts_to_show = template.resolve_variable(self.posts_to_show, 112 | context) 113 | except template.VariableDoesNotExist: 114 | if settings.DEBUG: 115 | raise 116 | if self.cache_expires is not None: 117 | try: 118 | cache_expires = template.resolve_variable(self.cache_expires, 119 | context) 120 | except template.VariableDoesNotExist: 121 | if settings.DEBUG: 122 | raise 123 | context[self.var_name] = pull_feed(feed_url, 124 | posts_to_show, 125 | cache_expires) 126 | return '' 127 | 128 | @register.tag 129 | def get_feed(parser, token): 130 | """ 131 | Pull a RSS or Atom feed into the context as the supplied variable. 132 | 133 | :: 134 | {% get_feed feed_url [posts_to_show] [cache_expires] as var %} 135 | {% get_feed "https://foo.net/timeline?max=5&format=rss" 5 60 as myfeed %} 136 | 137 | feed_url: full url to the feed (required) 138 | posts_to_show: Number of posts to pull. <=0 for all 139 | (default: settings.FEEDUTIL_NUM_POSTS or -1) 140 | cache_expired: Number of minuites for the cache. <=0 for no cache. 141 | (default: settings.FEEDUTIL_CACHE_MIN or 30) 142 | var: Name of variable to set the feed to in the current context 143 | 144 | 145 | Format of var: 146 | 147 | :: 148 | 149 | [ { 'title': "A title" , 'summary': "The summary", 150 | 'url': "http://foo.net/a-title", 151 | 'published': datetime(10, 20, 2007) }, 152 | ... ] 153 | 154 | 155 | """ 156 | args = token.split_contents() 157 | args.pop(0) 158 | if len(args) < 3 or len(args) > 5 or args[-2] != 'as': 159 | raise template.TemplateSyntaxError("Malformed arguments to get_feed tag.") 160 | nargs = [args[-1]] + args[:-2] 161 | return GetFeedNode(*nargs) 162 | return { 'posts': pull_feed(feed_url, posts_to_show, cache_expires) } 163 | --------------------------------------------------------------------------------