Permalink
Browse files

Add RSS support to the bmark views.

- Add routes, view and template
- Use dynamic route URLs in RSS template
- Add tests for parsing out the rss feed.
- Updates to the factory to prevent Tag issues
- Make sure we can parse it with a few important properties
- Add Alexandre Gravel-Raymond to the credits file
  • Loading branch information...
1 parent 776546a commit 56c911d94e3810baa9667ea27fb0d16aaad1806a @ilesinge ilesinge committed with Oct 16, 2012
View
@@ -10,3 +10,4 @@ Thanks to these people that have contributed code/assistance to bookie
- Jay Wren
- mikelietz
- Stéphane Klein
+- Alexandre Gravel-Raymond
View
@@ -125,7 +125,7 @@ smtp:
.PHONY: test
test:
- $(NOSE) --with-id -xv -s bookie/tests
+ $(NOSE) --with-id -vx -s bookie/tests
.PHONY: clean_testdb
clean_testdb:
@@ -134,7 +134,7 @@ clean_testdb:
.PHONY: builder_test
builder_test: clean_testdb test_bookie.db
# $(NOSE) -vx --with-id 61 bookie/tests
- $(NOSE) --with-coverage --cover-package=bookie --cover-erase --with-xunit bookie/tests
+ $(NOSE) -v --with-coverage --with-id --cover-package=bookie --cover-erase --with-xunit bookie/tests
.PHONY: mysql_test
mysql_test:
View
@@ -27,12 +27,18 @@ def build_routes(config):
config.add_route("bmark_recent", "recent")
config.add_route("bmark_recent_tags", "recent/*tags")
+ config.add_route("bmark_recent_rss", "rss")
+ config.add_route("bmark_recent_rss_tags", "rss/*tags")
+
config.add_route("bmark_readable", "bmark/readable/{hash_id}")
# user based bmark routes
config.add_route("user_bmark_recent", "{username}/recent")
config.add_route("user_bmark_recent_tags", "{username}/recent/*tags")
+ config.add_route("user_bmark_rss", "{username}/rss")
+ config.add_route("user_bmark_rss_tags", "{username}/rss/*tags")
+
config.add_route("user_bmark_edit", "{username}/edit/{hash_id}")
config.add_route("user_bmark_edit_error",
"{username}/edit_error/{hash_id}")
@@ -1,6 +1,10 @@
<%inherit file="/main_wrap.mako" />
<%namespace file="func.mako" import="api_setup, pager_setup"/>
+<%namespace file="rss.mako" import="rss_title"/>
<%def name="title()">Recent Bookmarks</%def>
+<%def name="header()">
+ <link href="${rss_url}" rel="alternate" title="${rss_title()}" type="application/rss+xml" />
+</%def>
<div class="bmarks"></div>
<%include file="../jstpl.mako"/>
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<%
+
+ import time
+ time_format = '%Y-%m-%d %H:%M:%S'
+
+%>
+<%def name="rss_title()">
+ <%
+ rss_title = 'Latest bookmarks'
+ if username:
+ rss_title += ' from ' + username
+ if tags:
+ rss_title += ' tagged with ' + ", ".join(tags)
+ %>${rss_title}
+</%def>
+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
+ <channel>
+ <title>Bookie: ${rss_title()}</title>
+ <link>${request.route_url('home')}</link>
+ <atom:link href="${request.current_route_url()}" rel="self" type="application/rss+xml" />
+ <description>bookmark your web</description>
+ % for bmark in bmarks:
+ <item>
+ <title><![CDATA[${bmark['description']}]]></title>
+ <description><![CDATA[${bmark['extended']}]]></description>
+ <%
+ local_timestamp = time.mktime(time.strptime(bmark['stored'], time_format))
+ gmt_date_string = time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime(local_timestamp))
+ %><pubDate>${gmt_date_string} GMT</pubDate>
+ <link>${request.route_url('redirect', hash_id=bmark['hash_id'])}</link>
+ <guid isPermaLink="false">${request.route_url('home')}#${bmark['bid']}</guid>
+ % for tag in bmark['tags']:
+ <category>${tag['name']}</category>
+ % endfor
+ </item>
+ % endfor
+ </channel>
+</rss>
@@ -42,6 +42,7 @@
% if hasattr(self, 'header'):
${self.header()}
% endif
+
<script type="text/javascript" charset="utf-8">
<%
app_url = request.route_url('home').rstrip('/')
View
@@ -3,6 +3,8 @@
import random
import string
+from bookie.models import DBSession
+from bookie.models import Bmark
from bookie.models import Tag
@@ -21,11 +23,29 @@ def random_string(length=None):
"""
chars = string.ascii_uppercase + string.digits
str_length = length if length is not None else random_int()
- return ''.join(random.choice(chars) for x in range(str_length))
+ return u''.join(random.choice(chars) for x in range(str_length))
+
+
+def random_url():
+ """Generate a random url that is totally bogus."""
+ url = "http://{0}.com".format(random_string())
+ return url
def make_tag(name=None):
if not name:
name = random_string(255)
return Tag(name)
+
+
+def make_bookmark():
+ """Generate a fake bookmark for testing use."""
+ bmark = Bmark(random_url(),
+ username="admin",
+ desc=random_string(),
+ ext=random_string(),
+ tags=u"bookmarks")
+ DBSession.add(bmark)
+ DBSession.flush()
+ return bmark
@@ -1,20 +1,18 @@
"""Test that we're meeting delicious API specifications"""
-from datetime import datetime, timedelta
+import feedparser
import logging
+import time
import transaction
import unittest
-from mock import Mock, patch
+from datetime import datetime
from nose.tools import ok_, eq_
from pyramid import testing
-from bookie.lib import access
from bookie.models import DBSession
from bookie.models import Bmark
-from bookie.models import Hashed
-from bookie.models import Tag
-from bookie.models import bmarks_tags
from bookie.tests import TestViewBase
from bookie.tests import empty_db
+from bookie.tests.factory import make_bookmark
LOG = logging.getLogger(__name__)
@@ -35,7 +33,6 @@ def _add_bmark(self):
bmark_us.stored = datetime.now()
bmark_us.updated = datetime.now()
- DBSession.add(bmark_us)
transaction.commit()
def setUp(self):
@@ -94,3 +91,57 @@ def test_renders(self):
res = self.app.get('/admin/new')
ok_('Add Bookmark' in res.body,
"Should see the add bookmark title")
+
+
+class TestRSSFeeds(TestViewBase):
+ """Verify the RSS feeds function correctly."""
+
+ def test_rss_added(self):
+ """Viewing /recent should have a rss url in the content."""
+ body_str = "application/rss+xml"
+ res = self.app.get('/recent')
+
+ eq_(res.status, "200 OK",
+ msg='recent status is 200, ' + res.status)
+ ok_(body_str in res.body,
+ msg="Request should contain rss str: " + res.body)
+
+ def test_rss_matches_request(self):
+ """The url should match the /recent request with tags."""
+ body_str = "rss/ubuntu"
+ res = self.app.get('/recent/ubuntu')
+
+ eq_(res.status, "200 OK",
+ msg='recent status is 200, ' + res.status)
+ ok_(body_str in res.body,
+ msg="Request should contain rss url: " + res.body)
+
+ def test_rss_is_parseable(self):
+ """The rss feed should be a parseable feed."""
+ bmarks = [make_bookmark() for i in range(10)]
+ transaction.commit()
+
+ res = self.app.get('/rss')
+
+ eq_(res.status, "200 OK",
+ msg='recent status is 200, ' + res.status)
+
+ # http://packages.python.org/feedparser/introduction.html#parsing-a-feed-from-a-string
+ parsed = feedparser.parse(res.body)
+ links = []
+ for entry in parsed.entries:
+ links.append({
+ 'title': entry.title,
+ 'category': entry.category,
+ 'date': time.strftime('%d %b %Y', entry.updated_parsed),
+ 'description': entry.description,
+ 'link': entry.link,
+ })
+
+ ok_(links, 'The feed should have a list of links.')
+ eq_(10, len(links), 'There are 10 links in the feed.')
+
+ sample_item = links[0]
+ ok_(sample_item['title'], 'Items have a title.')
+ ok_(sample_item['link'], 'Items have a link to reach things.')
+ ok_(sample_item['description'], 'Items have a description string.')
View
@@ -10,6 +10,7 @@
from bookie.models import Bmark
from bookie.models import BmarkMgr
from bookie.models import TagMgr
+from bookie.views import api
LOG = logging.getLogger(__name__)
RESULTS_MAX = 50
@@ -32,6 +33,9 @@ def recent(request):
rdict = request.matchdict
params = request.params
+ # Make sure we generate a url to feed our rss link.
+ current_route = request.current_route_url()
+
# check for auth related stuff
# are we looking for a specific user
username = rdict.get('username', None)
@@ -45,6 +49,7 @@ def recent(request):
ret = {
'username': username,
'tags': tags,
+ 'rss_url': current_route.replace('recent', 'rss')
}
# if we've got url parameters for the page/count then use those to help
@@ -55,6 +60,31 @@ def recent(request):
return ret
+@view_config(
+ route_name="bmark_recent_rss",
+ renderer="/bmark/rss.mako")
+@view_config(
+ route_name="bmark_recent_rss_tags",
+ renderer="/bmark/rss.mako")
+@view_config(
+ route_name="user_bmark_rss",
+ renderer="/bmark/rss.mako")
+@view_config(
+ route_name="user_bmark_rss_tags",
+ renderer="/bmark/rss.mako")
+def recent_rss(request):
+ rdict = request.matchdict
+ request.response.content_type = 'application/atom+xml; charset=UTF-8'
+
+ tags = rdict.get('tags', None)
+ username = rdict.get('username', None)
+
+ ret = api.bmark_recent(request)
+ ret['username'] = username
+ ret['tags'] = tags
+ return ret
+
+
@view_config(
route_name="user_bmark_edit",
renderer="/bmark/edit.mako")

0 comments on commit 56c911d

Please sign in to comment.