├── 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 |
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 |
--------------------------------------------------------------------------------