mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-19 12:58:29 +00:00
Merge branch 'master' of github.com:samuelclay/newsblur
This commit is contained in:
commit
478827b51e
13 changed files with 378 additions and 150 deletions
|
@ -10,7 +10,7 @@ from django.utils.safestring import mark_safe
|
|||
from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.http import require_POST
|
||||
from apps.rss_feeds.models import Feed, Story, Tag
|
||||
from apps.reader.models import UserSubscription, UserSubscriptionFolders, UserStory
|
||||
from apps.reader.models import UserSubscription, UserStory
|
||||
from apps.analyzer.models import ClassifierTitle, ClassifierAuthor, ClassifierFeed, ClassifierTag
|
||||
from utils import json
|
||||
from utils.user_functions import get_user
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.shortcuts import render_to_response, get_list_or_404, get_object_or_404
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.template import RequestContext
|
||||
from django.core.cache import cache
|
||||
from apps.rss_feeds.models import Feed
|
||||
from apps.reader.models import UserSubscription, UserSubscriptionFolders
|
||||
from utils.json import json_encode
|
||||
from utils import json
|
||||
import utils.opml as opml
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import HttpResponse, HttpRequest
|
||||
|
@ -17,46 +19,128 @@ import codecs
|
|||
|
||||
def opml_upload(request):
|
||||
xml_opml = None
|
||||
message = "OK"
|
||||
code = 1
|
||||
|
||||
if request.method == 'POST':
|
||||
if 'file' in request.FILES:
|
||||
file = request.FILES['file']
|
||||
xml_opml = file.read()
|
||||
|
||||
data = opml_import(xml_opml, request.user).encode('utf-8')
|
||||
opml_importer = OPMLImporter(xml_opml, request.user)
|
||||
folders = opml_importer.process()
|
||||
|
||||
feeds = UserSubscription.objects.filter(user=user).values()
|
||||
data = json.encode(dict(message=message, code=code, payload=dict(folders=folders, feeds=feeds)))
|
||||
|
||||
return HttpResponse(data, mimetype='text/plain')
|
||||
|
||||
def opml_import(xml_opml, user):
|
||||
context = None
|
||||
outline = opml.from_string(xml_opml)
|
||||
feeds = []
|
||||
message = "OK"
|
||||
code = 1
|
||||
for folder in outline:
|
||||
print folder.text
|
||||
for feed in folder:
|
||||
print '\t%s - %s - %s' % (feed.title, type(feed.title), type(feed.title.decode('utf-8')))
|
||||
feed_data = dict(feed_address=feed.xmlUrl, feed_link=feed.htmlUrl, feed_title=feed.title)
|
||||
feeds.append(feed_data)
|
||||
new_feed = Feed(**feed_data)
|
||||
try:
|
||||
new_feed.save()
|
||||
except IntegrityError:
|
||||
new_feed = Feed.objects.get(feed_address=feed.xmlUrl)
|
||||
us = UserSubscription(feed=new_feed, user=user)
|
||||
try:
|
||||
us.save()
|
||||
except IntegrityError:
|
||||
us = UserSubscription.objects.get(feed=new_feed, user=user)
|
||||
user_sub_folder = UserSubscriptionFolders(user=user, feed=new_feed, user_sub=us, folder=folder.text)
|
||||
try:
|
||||
user_sub_folder.save()
|
||||
except IntegrityError:
|
||||
print 'Can\'t save user_sub_folder'
|
||||
# new_feed, _ = Feed.objects.get_or_create(feed_address=feed.xmlUrl, defaults=feed_data)
|
||||
# us, _ = UserSubscription.objects.get_or_create(feed=new_feed, user=user)
|
||||
# user_sub_folder, _ = UserSubscriptionFolders.objects.get_or_create(user=user, feed=new_feed, user_sub=us, defaults=dict(folder=folder.text))
|
||||
data = json_encode(dict(message=message, code=code, payload=dict(feeds=feeds, feed_count=len(feeds))))
|
||||
cache.delete('usersub:%s' % user)
|
||||
class OPMLImporter:
|
||||
|
||||
def __init__(self, opml_xml, user):
|
||||
self.user = user
|
||||
self.opml_xml = opml_xml
|
||||
|
||||
return data
|
||||
def process(self):
|
||||
outline = opml.from_string(self.opml_xml)
|
||||
folders = self.process_outline(outline)
|
||||
UserSubscriptionFolders.objects.create(user=self.user, folders=json.encode(folders))
|
||||
|
||||
return folders
|
||||
|
||||
def process_outline(self, outline):
|
||||
folders = []
|
||||
|
||||
for item in outline:
|
||||
if not hasattr(item, 'xmlUrl'):
|
||||
folder = item
|
||||
print 'New Folder: %s' % folder.text
|
||||
folders.append({folder.text: self.process_outline(folder)})
|
||||
elif hasattr(item, 'xmlUrl'):
|
||||
feed = item
|
||||
print '\t%s - %s - %s' % (feed.title, feed.htmlUrl, feed.xmlUrl,)
|
||||
feed_data = dict(feed_address=feed.xmlUrl, feed_link=feed.htmlUrl, feed_title=feed.title)
|
||||
# feeds.append(feed_data)
|
||||
feed_db, _ = Feed.objects.get_or_create(feed_address=feed.xmlUrl, defaults=dict(**feed_data))
|
||||
us, _ = UserSubscription.objects.get_or_create(feed=feed_db, user=self.user)
|
||||
folders.append(feed_db.pk)
|
||||
return folders
|
||||
|
||||
if __name__ == '__main__':
|
||||
opml_string = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- OPML generated by NetNewsWire -->
|
||||
<opml version="1.1">
|
||||
<head>
|
||||
<title>mySubscriptions</title>
|
||||
</head>
|
||||
<body>
|
||||
<outline text="New York" title="New York">
|
||||
<outline text="Brownstoner" description="" title="Brownstoner" type="rss" version="RSS" htmlUrl="http://www.brownstoner.com/" xmlUrl="http://www.brownstoner.com/index.xml"/>
|
||||
<outline text="Gothamist" description="" title="Gothamist" type="rss" version="RSS" htmlUrl="http://gothamist.com/" xmlUrl="http://gothamist.com/index.rdf"/>
|
||||
<outline text="Brokelyn" description="" title="Brokelyn" type="rss" version="RSS" htmlUrl="http://www.brokelyn.com" xmlUrl="http://www.brokelyn.com/feed/"/>
|
||||
<outline text="Streetsblog New York City" description="" title="Streetsblog New York City" type="rss" version="RSS" htmlUrl="http://www.streetsblog.org" xmlUrl="http://www.streetsblog.org/feed/"/>
|
||||
<outline text="The Corduroy Appreciation Club" description="" title="The Corduroy Appreciation Club" type="rss" version="RSS" htmlUrl="http://corduroyclub.com" xmlUrl="http://corduroyclub.com/feed"/>
|
||||
<outline text="FREEwilliamsburg" description="" title="FREEwilliamsburg" type="rss" version="RSS" htmlUrl="http://www.freewilliamsburg.com/" xmlUrl="http://www.freewilliamsburg.com/atom.xml"/>
|
||||
<outline text="Scouting NY" description="" title="Scouting NY" type="rss" version="RSS" htmlUrl="http://www.scoutingny.com" xmlUrl="http://www.scoutingny.com/?feed=rss2"/>
|
||||
<outline text="very small array" description="" title="very small array" type="rss" version="RSS" htmlUrl="http://www.verysmallarray.com" xmlUrl="http://www.verysmallarray.com/?feed=rss2"/>
|
||||
<outline text="Frugal Traveler" description="" title="Frugal Traveler" type="rss" version="RSS" htmlUrl="http://frugaltraveler.blogs.nytimes.com/" xmlUrl="http://frugaltraveler.blogs.nytimes.com/feed/"/>
|
||||
</outline>
|
||||
<outline text="Tech" title="Tech">
|
||||
<outline text="Joel on Software" description="" title="Joel on Software" type="rss" version="RSS" htmlUrl="http://www.joelonsoftware.com" xmlUrl="http://www.joelonsoftware.com/rss.xml"/>
|
||||
<outline text="Daring Fireball" description="" title="Daring Fireball" type="rss" version="RSS" htmlUrl="http://daringfireball.net/" xmlUrl="http://daringfireball.net/index.xml"/>
|
||||
<outline text="Techcrunch" description="" title="Techcrunch" type="rss" version="RSS" htmlUrl="http://techcrunch.com" xmlUrl="http://feeds.feedburner.com/Techcrunch"/>
|
||||
<outline text="Coding Horror" description="" title="Coding Horror" type="rss" version="RSS" htmlUrl="http://www.codinghorror.com/blog/" xmlUrl="http://feeds.feedburner.com/codinghorror/"/>
|
||||
<outline text="John Resig" description="" title="John Resig" type="rss" version="RSS" htmlUrl="http://ejohn.org" xmlUrl="http://feeds.feedburner.com/JohnResig"/>
|
||||
<outline text="Ted Dziuba" description="" title="Ted Dziuba" type="rss" version="RSS" htmlUrl="http://teddziuba.com/" xmlUrl="http://teddziuba.com/atom.xml"/>
|
||||
<outline text="Ajaxian » Front Page" description="" title="Ajaxian » Front Page" type="rss" version="RSS" htmlUrl="http://ajaxian.com" xmlUrl="http://ajaxian.com/index.xml"/>
|
||||
<outline text="James Padolsey" description="" title="James Padolsey" type="rss" version="RSS" htmlUrl="http://james.padolsey.com" xmlUrl="http://james.padolsey.com/feed/"/>
|
||||
<outline text="David Cramer's Blog" description="" title="David Cramer's Blog" type="rss" version="RSS" htmlUrl="http://www.davidcramer.net" xmlUrl="http://www.davidcramer.net/feed"/>
|
||||
<outline text="MacRumors : Mac News and Rumors" description="" title="MacRumors : Mac News and Rumors" type="rss" version="RSS" htmlUrl="http://www.macrumors.com" xmlUrl="http://www.macrumors.com/macrumors.xml"/>
|
||||
<outline text="wonko.com" description="" title="wonko.com" type="rss" version="RSS" htmlUrl="http://wonko.com/" xmlUrl="http://feeds.feedburner.com/wonko"/>
|
||||
<outline text="Devthought" description="" title="Devthought" type="rss" version="RSS" htmlUrl="http://devthought.com" xmlUrl="http://feeds2.feedburner.com/devthought?format=xml"/>
|
||||
<outline text="jQuery Blog" description="" title="jQuery Blog" type="rss" version="RSS" htmlUrl="http://blog.jquery.com" xmlUrl="http://blog.jquery.com/feed/"/>
|
||||
<outline text="Hacker News - Excerpts" description="" title="Hacker News - Excerpts" type="rss" version="RSS" htmlUrl="http://news.ycombinator.com/" xmlUrl="http://andrewtrusty.appspot.com/readability/feed?url=http%3A//news.ycombinator.com/rss"/>
|
||||
<outline text="Waxy.org" description="" title="Waxy.org" type="rss" version="RSS" htmlUrl="http://waxy.org/" xmlUrl="http://waxy.org/index.xml"/>
|
||||
<outline text="Lazy Pythonista" description="" title="Lazy Pythonista" type="rss" version="RSS" htmlUrl="http://lazypython.blogspot.com/" xmlUrl="http://lazypython.blogspot.com/feeds/posts/default?alt=rss"/>
|
||||
<outline text="cdixon.org - chris dixon's blog" description="" title="cdixon.org - chris dixon's blog" type="rss" version="RSS" htmlUrl="http://cdixon.org" xmlUrl="http://cdixon.org/feed/"/>
|
||||
<outline text="Bill the Lizard" description="" title="Bill the Lizard" type="rss" version="RSS" htmlUrl="http://www.billthelizard.com/" xmlUrl="http://www.billthelizard.com/feeds/posts/default"/>
|
||||
<outline text="inessential.com" description="" title="inessential.com" type="rss" version="RSS" htmlUrl="http://inessential.com/" xmlUrl="http://inessential.com/xml/rss.xml"/>
|
||||
</outline>
|
||||
<outline text="Blogs" title="Blogs">
|
||||
<outline text="You Look Nice Today" description="" title="You Look Nice Today" type="rss" version="RSS" htmlUrl="http://youlooknicetoday.com" xmlUrl="http://youlooknicetoday.com/rss.xml"/>
|
||||
<outline text="The Lens and the Eyebrow" description="" title="The Lens and the Eyebrow" type="rss" version="RSS" htmlUrl="http://blog.seemichaelsphotos.com" xmlUrl="http://blog.seemichaelsphotos.com/rss2.aspx"/>
|
||||
<outline text="43 Folders" description="" title="43 Folders" type="rss" version="RSS" htmlUrl="http://www.43folders.com" xmlUrl="http://www.43folders.com/rss.xml"/>
|
||||
<outline text="The Doree Chronicles" description="" title="The Doree Chronicles" type="rss" version="RSS" htmlUrl="http://doree.tumblr.com/" xmlUrl="http://doree.tumblr.com/rss"/>
|
||||
<outline text="the impossible cool." description="" title="the impossible cool." type="rss" version="RSS" htmlUrl="http://theimpossiblecool.tumblr.com/" xmlUrl="http://theimpossiblecool.tumblr.com/rss"/>
|
||||
<outline text="kottke.org" description="" title="kottke.org" type="rss" version="RSS" htmlUrl="http://kottke.org/" xmlUrl="http://feeds.kottke.org/main"/>
|
||||
<outline text="Philip Greenspun's Weblog" description="" title="Philip Greenspun's Weblog" type="rss" version="RSS" htmlUrl="http://blogs.law.harvard.edu/philg" xmlUrl="http://blogs.law.harvard.edu/philg/feed/"/>
|
||||
<outline text="Rands In Repose" description="" title="Rands In Repose" type="rss" version="RSS" htmlUrl="http://www.randsinrepose.com/" xmlUrl="http://www.randsinrepose.com/index.xml"/>
|
||||
<outline text="the selby - photos in your space" description="" title="the selby - photos in your space" type="rss" version="RSS" htmlUrl="http://www.theselby.com" xmlUrl="http://feeds2.feedburner.com/Theselby-PhotosInYourSpace?format=xml"/>
|
||||
<outline text="The Storybird blog" description="" title="The Storybird blog" type="rss" version="RSS" htmlUrl="http://blog.storybird.com" xmlUrl="http://feeds.feedburner.com/TheStorybirdBlog?format=xml"/>
|
||||
<outline text="You are what you share" description="" title="You are what you share" type="rss" version="RSS" htmlUrl="http://markury.com/" xmlUrl="http://markury.com/rss"/>
|
||||
<outline text="Bird feed" description="" title="Bird feed" type="rss" version="RSS" htmlUrl="http://storybird.tumblr.com/" xmlUrl="http://storybird.tumblr.com/rss"/>
|
||||
<outline text="one day at a time" description="" title="one day at a time" type="rss" version="RSS" htmlUrl="http://abangupjob.tumblr.com/" xmlUrl="http://abangupjob.tumblr.com/rss"/>
|
||||
<outline text="Pretty in the City • Karyn Bosnak" description="" title="Pretty in the City • Karyn Bosnak" type="rss" version="RSS" htmlUrl="http://www.prettyinthecity.com/blog/" xmlUrl="http://www.prettyinthecity.com/blog/rss.xml"/>
|
||||
<outline text="An Entirely Other Day: Full Feed" description="" title="An Entirely Other Day: Full Feed" type="rss" version="RSS" htmlUrl="http://www.eod.com/blog/" xmlUrl="http://feeds.feedburner.com/eod_full"/>
|
||||
<outline text="hipster puppies" description="" title="hipster puppies" type="rss" version="RSS" htmlUrl="http://hipsterpuppies.tumblr.com/" xmlUrl="http://hipsterpuppies.tumblr.com/rss"/>
|
||||
<outline text="The Bloglets" title="The Bloglets">
|
||||
<outline text="FAIL Blog: Pictures and Videos of Owned, Pwnd and Fail Moments" description="" title="FAIL Blog: Pictures and Videos of Owned, Pwnd and Fail Moments" type="rss" version="RSS" htmlUrl="http://failblog.org" xmlUrl="http://feeds.feedburner.com/failblog"/>
|
||||
<outline text="Iconic Photos" description="" title="Iconic Photos" type="rss" version="RSS" htmlUrl="http://iconicphotos.wordpress.com" xmlUrl="http://iconicphotos.wordpress.com/feed/"/>
|
||||
<outline text="nerdboyfriend" description="" title="nerdboyfriend" type="rss" version="RSS" htmlUrl="http://nerdboyfriend.com" xmlUrl="http://nerdboyfriend.com/?feed=rss2"/>
|
||||
<outline text="The Cellar - Image of the Day" description="" title="The Cellar - Image of the Day" type="rss" version="RSS" htmlUrl="http://cellar.org" xmlUrl="http://cellar.org/external.php?type=rss2&forumids=10"/>
|
||||
<outline text="The Sartorialist" description="" title="The Sartorialist" type="rss" version="RSS" htmlUrl="http://thesartorialist.blogspot.com/" xmlUrl="http://thesartorialist.blogspot.com/feeds/posts/default?alt=rss"/>
|
||||
</outline>
|
||||
</outline>
|
||||
<outline text="Cooking" title="Cooking">
|
||||
<outline text="Easy Recipes For Everyday Cooking by Savory Sweet Life" description="" title="Easy Recipes For Everyday Cooking by Savory Sweet Life" type="rss" version="RSS" htmlUrl="http://savorysweetlife.com" xmlUrl="http://savorysweetlife.com/?feed=rss2"/>
|
||||
<outline text="SALAD & CANDY" description="" title="SALAD & CANDY" type="rss" version="RSS" htmlUrl="http://saladandcandy.com/" xmlUrl="http://saladandcandy.com/rss"/>
|
||||
<outline text="smitten kitchen" description="" title="smitten kitchen" type="rss" version="RSS" htmlUrl="http://smittenkitchen.com" xmlUrl="http://feeds.feedburner.com/smittenkitchen"/>
|
||||
<outline text="Salt & Fat" description="" title="Salt & Fat" type="rss" version="RSS" htmlUrl="http://saltandfat.com/" xmlUrl="http://saltandfat.com/rss"/>
|
||||
</outline>
|
||||
<outline text="Samuel Clay's OfBrooklyn.com" description="" title="Samuel Clay's OfBrooklyn.com" type="rss" version="RSS" htmlUrl="http://conesus.com" xmlUrl="http://www.ofbrooklyn.com/feeds/all/"/>
|
||||
</body>
|
||||
</opml>"""
|
||||
user = User.objects.get(username='conesus')
|
||||
opml_importer = OPMLImporter(opml_string, user)
|
||||
data = opml_importer.process()
|
||||
print data
|
|
@ -165,15 +165,11 @@ class UserStory(models.Model):
|
|||
|
||||
class UserSubscriptionFolders(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
user_sub = models.ForeignKey(UserSubscription)
|
||||
feed = models.ForeignKey(Feed)
|
||||
folder = models.CharField(max_length=255)
|
||||
folders = models.TextField(default="{}")
|
||||
|
||||
def __unicode__(self):
|
||||
return ('[' + self.feed.feed_title + '] '
|
||||
+ self.folder)
|
||||
return "[%s]: %s" % (self.user, len(self.folders),)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "folders"
|
||||
verbose_name = "folder"
|
||||
unique_together = ("user", "user_sub")
|
||||
verbose_name = "folder"
|
|
@ -57,40 +57,23 @@ def logout(request):
|
|||
|
||||
def load_feeds(request):
|
||||
user = get_user(request)
|
||||
feeds = {}
|
||||
|
||||
folders = UserSubscriptionFolders.objects.get(user=user)
|
||||
user_subs = UserSubscription.objects.select_related('feed').filter(user=user)
|
||||
|
||||
us = UserSubscriptionFolders.objects.select_related('feed', 'user_sub').filter(
|
||||
user=user
|
||||
)
|
||||
# logging.info('UserSubs: %s' % us)
|
||||
feeds = []
|
||||
folders = []
|
||||
for sub in us:
|
||||
try:
|
||||
sub.feed.unread_count_positive = sub.user_sub.unread_count_positive
|
||||
sub.feed.unread_count_neutral = sub.user_sub.unread_count_neutral
|
||||
sub.feed.unread_count_negative = sub.user_sub.unread_count_negative
|
||||
except:
|
||||
logging.warn("Subscription %s does not exist outside of Folder." % (sub.feed))
|
||||
sub.delete()
|
||||
else:
|
||||
if sub.folder not in folders:
|
||||
folders.append(sub.folder)
|
||||
feeds.append({'folder': sub.folder, 'feeds': []})
|
||||
for folder in feeds:
|
||||
if folder['folder'] == sub.folder:
|
||||
folder['feeds'].append(sub.feed)
|
||||
for sub in user_subs:
|
||||
feeds[sub.feed.pk] = {
|
||||
'id': sub.feed.pk,
|
||||
'feed_title': sub.feed.feed_title,
|
||||
'feed_link': sub.feed.feed_link,
|
||||
'unread_count_positive': sub.unread_count_positive,
|
||||
'unread_count_neutral': sub.unread_count_neutral,
|
||||
'unread_count_negative': sub.unread_count_negative,
|
||||
}
|
||||
|
||||
# Alphabetize folders, then feeds inside folders
|
||||
feeds.sort(lambda x, y: cmp(x['folder'].lower(), y['folder'].lower()))
|
||||
for feed in feeds:
|
||||
feed['feeds'].sort(lambda x, y: cmp(x.feed_title.lower(), y.feed_title.lower()))
|
||||
for f in feed['feeds']:
|
||||
f.feed_address = mark_safe(f.feed_address)
|
||||
f.page_data = None
|
||||
|
||||
|
||||
data = json.encode(feeds)
|
||||
return HttpResponse(data, mimetype='application/json')
|
||||
data = dict(feeds=feeds, folders=json.decode(folders.folders))
|
||||
return HttpResponse(json.encode(data), mimetype='application/json')
|
||||
|
||||
def load_single_feed(request):
|
||||
user = get_user(request)
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand
|
|||
from django.core.handlers.wsgi import WSGIHandler
|
||||
from apps.rss_feeds.models import Feed, Story
|
||||
from django.core.cache import cache
|
||||
from apps.reader.models import UserSubscription, UserSubscriptionFolders, UserStory
|
||||
from apps.reader.models import UserSubscription, UserStory
|
||||
from optparse import OptionParser, make_option
|
||||
import os
|
||||
import logging
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.core.handlers.wsgi import WSGIHandler
|
|||
from apps.rss_feeds.models import Feed, Story
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Q
|
||||
from apps.reader.models import UserSubscription, UserSubscriptionFolders, UserStory
|
||||
from apps.reader.models import UserSubscription, UserStory
|
||||
from optparse import OptionParser, make_option
|
||||
from utils.management_functions import daemonize
|
||||
import os
|
||||
|
|
|
@ -164,12 +164,16 @@ class Feed(models.Model):
|
|||
user_stories_count = 0
|
||||
stories = Story.objects.filter(story_feed=self).order_by('-story_date')
|
||||
print 'Found %s stories in %s. Trimming...' % (stories.count(), self)
|
||||
for story in stories[1000:]:
|
||||
user_stories = UserStory.objects.filter(story=story)
|
||||
if stories.count() > 1000:
|
||||
old_story = stories[1000]
|
||||
user_stories = UserStory.objects.filter(feed=self,
|
||||
read_date__lte=old_story.story_date)
|
||||
user_stories_count = user_stories.count()
|
||||
user_stories.delete()
|
||||
story.delete()
|
||||
stories_deleted_count += 1
|
||||
old_stories = Story.objects.filter(story_feed=self,
|
||||
story_date__lte=old_story.story_date)
|
||||
stories_deleted_count = old_stories.count()
|
||||
old_stories.delete()
|
||||
|
||||
if stories_deleted_count:
|
||||
print "Trimming %s stories from %s. %s user stories." % (
|
||||
|
|
|
@ -135,6 +135,10 @@ a img {
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#feed_list .feeds {
|
||||
margin-left: 22px;
|
||||
}
|
||||
|
||||
#feed_list .feed {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
@ -149,11 +153,11 @@ a img {
|
|||
#feed_list img.feed_favicon {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 24px;
|
||||
left: 2px;
|
||||
}
|
||||
#feed_list .feed_title {
|
||||
display: block;
|
||||
padding: 4px 42px 2px 45px;
|
||||
padding: 4px 42px 2px 23px;
|
||||
text-decoration: none;
|
||||
color: #272727;
|
||||
line-height: 1.3em;
|
||||
|
|
127
media/js/jquery.tinysort.js
Normal file
127
media/js/jquery.tinysort.js
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* jQuery TinySort - A plugin to sort child nodes by (sub) contents or attributes.
|
||||
*
|
||||
* Version: 1.0.3
|
||||
*
|
||||
* Copyright (c) 2008 Ron Valstar
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* description
|
||||
* - A plugin to sort child nodes by (sub) contents or attributes.
|
||||
*
|
||||
* Usage:
|
||||
* $("ul#people>li").tsort();
|
||||
* $("ul#people>li").tsort("span.surname");
|
||||
* $("ul#people>li").tsort("span.surname",{order:"desc"});
|
||||
* $("ul#people>li").tsort({place:"end"});
|
||||
*
|
||||
* Change default like so:
|
||||
* $.tinysort.defaults.order = "desc";
|
||||
*
|
||||
* in this update:
|
||||
* - tested with jQuery 1.4.1
|
||||
* - correct isNum return
|
||||
*
|
||||
* in last update:
|
||||
* - matching numerics did not work for trailing zero's, replaced with regexp (which should now work for + and - signs as well)
|
||||
*
|
||||
* Todos
|
||||
* - fix mixed literal/numeral values
|
||||
* - determine if I have to use setArray or pushStack
|
||||
*
|
||||
*/
|
||||
;(function($) {
|
||||
// default settings
|
||||
$.tinysort = {
|
||||
id: "TinySort"
|
||||
,version: "1.0.3"
|
||||
,defaults: {
|
||||
order: "asc" // order: asc, desc or rand
|
||||
,attr: "" // order by attribute value
|
||||
,place: "start" // place ordered elements at position: start, end, org (original position), first
|
||||
,returns: false // return all elements or only the sorted ones (true/false)
|
||||
}
|
||||
};
|
||||
$.fn.extend({
|
||||
tinysort: function(_find,_settings) {
|
||||
if (_find&&typeof(_find)!="string") {
|
||||
_settings = _find;
|
||||
_find = null;
|
||||
}
|
||||
|
||||
var oSettings = $.extend({}, $.tinysort.defaults, _settings);
|
||||
|
||||
var oElements = {}; // contains sortable- and non-sortable list per parent
|
||||
this.each(function(i) {
|
||||
// element or sub selection
|
||||
var mElm = (!_find||_find=="")?$(this):$(this).find(_find);
|
||||
// text or attribute value
|
||||
var sSort = oSettings.order=="rand"?""+Math.random():(oSettings.attr==""?mElm.text():mElm.attr(oSettings.attr));
|
||||
// to sort or not to sort
|
||||
var mParent = $(this).parent();
|
||||
if (!oElements[mParent]) oElements[mParent] = {s:[],n:[]}; // s: sort, n: not sort
|
||||
if (mElm.length>0) oElements[mParent].s.push({s:sSort,e:$(this),n:i}); // s:string, e:element, n:number
|
||||
else oElements[mParent].n.push({e:$(this),n:i});
|
||||
});
|
||||
//
|
||||
// sort
|
||||
for (var sParent in oElements) {
|
||||
var oParent = oElements[sParent];
|
||||
oParent.s.sort(
|
||||
function zeSort(a,b) {
|
||||
var x = a.s.toLowerCase?a.s.toLowerCase():a.s;
|
||||
var y = b.s.toLowerCase?b.s.toLowerCase():b.s;
|
||||
if (isNum(a.s)&&isNum(b.s)) {
|
||||
x = parseFloat(a.s);
|
||||
y = parseFloat(b.s);
|
||||
}
|
||||
return (oSettings.order=="asc"?1:-1)*(x<y?-1:(x>y?1:0));
|
||||
}
|
||||
);
|
||||
}
|
||||
//
|
||||
// order elements and fill new order
|
||||
var aNewOrder = [];
|
||||
for (var sParent in oElements) {
|
||||
var oParent = oElements[sParent];
|
||||
var aOrg = []; // list for original position
|
||||
var iLow = $(this).length;
|
||||
switch (oSettings.place) {
|
||||
case "first": $.each(oParent.s,function(i,obj) { iLow = Math.min(iLow,obj.n) }); break;
|
||||
case "org": $.each(oParent.s,function(i,obj) { aOrg.push(obj.n) }); break;
|
||||
case "end": iLow = oParent.n.length; break;
|
||||
default: iLow = 0;
|
||||
}
|
||||
var aCnt = [0,0]; // count how much we've sorted for retreival from either the sort list or the non-sort list (oParent.s/oParent.n)
|
||||
for (var i=0;i<$(this).length;i++) {
|
||||
var bSList = i>=iLow&&i<iLow+oParent.s.length;
|
||||
if (contains(aOrg,i)) bSList = true;
|
||||
var mEl = (bSList?oParent.s:oParent.n)[aCnt[bSList?0:1]].e;
|
||||
mEl.parent().append(mEl);
|
||||
if (bSList||!oSettings.returns) aNewOrder.push(mEl.get(0));
|
||||
aCnt[bSList?0:1]++;
|
||||
}
|
||||
}
|
||||
//
|
||||
return this.setArray(aNewOrder); // setArray or pushStack?
|
||||
}
|
||||
});
|
||||
// is numeric
|
||||
function isNum(n) {
|
||||
var x = /^\s*?[\+-]?(\d*\.?\d*?)\s*?$/.exec(n);
|
||||
return x&&x.length>0?x[1]:false;
|
||||
};
|
||||
// array contains
|
||||
function contains(a,n) {
|
||||
var bInside = false;
|
||||
$.each(a,function(i,m) {
|
||||
if (!bInside) bInside = m==n;
|
||||
});
|
||||
return bInside;
|
||||
};
|
||||
// set functions
|
||||
$.fn.TinySort = $.fn.Tinysort = $.fn.tsort = $.fn.tinysort;
|
||||
})(jQuery);
|
|
@ -25,6 +25,7 @@ NEWSBLUR.AssetModel = function() {
|
|||
|
||||
NEWSBLUR.AssetModel.Reader = function() {
|
||||
this.feeds = {};
|
||||
this.folders = [];
|
||||
this.stories = {};
|
||||
};
|
||||
|
||||
|
@ -135,9 +136,10 @@ NEWSBLUR.AssetModel.Reader.prototype = {
|
|||
load_feeds: function(callback) {
|
||||
var self = this;
|
||||
|
||||
var pre_callback = function(folders) {
|
||||
self.folders = folders;
|
||||
callback(folders);
|
||||
var pre_callback = function(subscriptions) {
|
||||
self.feeds = subscriptions.feeds;
|
||||
self.folders = subscriptions.folders;
|
||||
callback();
|
||||
};
|
||||
|
||||
this.make_request('/reader/load_feeds', {}, pre_callback);
|
||||
|
@ -182,15 +184,8 @@ NEWSBLUR.AssetModel.Reader.prototype = {
|
|||
|
||||
get_feed: function(feed_id, callback) {
|
||||
var self = this;
|
||||
for (fld in this.folders) {
|
||||
var feeds = this.folders[fld].feeds;
|
||||
for (f in feeds) {
|
||||
if (feeds[f].id == feed_id) {
|
||||
return feeds[f];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
return this.feeds[feed_id];
|
||||
},
|
||||
|
||||
get_feed_tags: function() {
|
||||
|
|
|
@ -334,63 +334,85 @@
|
|||
load_feeds: function() {
|
||||
var self = this;
|
||||
|
||||
var callback = function() {
|
||||
var $feed_list = self.$feed_list.empty();
|
||||
var folders = self.model.folders;
|
||||
|
||||
$('#story_taskbar').css({'display': 'block'});
|
||||
// NEWSBLUR.log(['Subscriptions', {'folders':folders}]);
|
||||
for (fo in folders) {
|
||||
var feeds = folders[fo].feeds;
|
||||
var $folder = $.make('div', { className: 'folder' }, [
|
||||
$.make('span', { className: 'folder_title' }, folders[fo].folder),
|
||||
$.make('div', { className: 'feeds' })
|
||||
]);
|
||||
for (f in feeds) {
|
||||
var unread_class = '';
|
||||
if (feeds[f].unread_count_positive) {
|
||||
unread_class += ' unread_positive';
|
||||
}
|
||||
if (feeds[f].unread_count_neutral) {
|
||||
unread_class += ' unread_neutral';
|
||||
}
|
||||
if (feeds[f].unread_count_negative) {
|
||||
unread_class += ' unread_negative';
|
||||
}
|
||||
var $feed = $.make('div', { className: 'feed ' + unread_class }, [
|
||||
$.make('span', {
|
||||
className: 'unread_count unread_count_positive '
|
||||
+ (feeds[f].unread_count_positive
|
||||
? "unread_count_full"
|
||||
: "unread_count_empty")
|
||||
}, ''+feeds[f].unread_count_positive),
|
||||
$.make('span', {
|
||||
className: 'unread_count unread_count_neutral '
|
||||
+ (feeds[f].unread_count_neutral
|
||||
? "unread_count_full"
|
||||
: "unread_count_empty")
|
||||
}, ''+feeds[f].unread_count_neutral),
|
||||
$.make('span', {
|
||||
className: 'unread_count unread_count_negative '
|
||||
+ (feeds[f].unread_count_negative
|
||||
? "unread_count_full"
|
||||
: "unread_count_empty")
|
||||
}, ''+feeds[f].unread_count_negative),
|
||||
$.make('img', { className: 'feed_favicon', src: self.google_favicon_url + feeds[f].feed_link }),
|
||||
$.make('span', { className: 'feed_title' }, feeds[f].feed_title)
|
||||
]).data('feed_id', feeds[f].id);
|
||||
$('.feeds', $folder).append($feed);
|
||||
}
|
||||
$feed_list.append($folder);
|
||||
}
|
||||
$('.unread_count', $feed_list).corners('4px');
|
||||
};
|
||||
|
||||
if ($('#feed_list').length) {
|
||||
this.model.load_feeds(callback);
|
||||
this.model.load_feeds($.rescope(this.make_feeds, this));
|
||||
}
|
||||
},
|
||||
|
||||
make_feeds: function() {
|
||||
var $feed_list = this.$feed_list.empty();
|
||||
var folders = this.model.folders;
|
||||
var feeds = this.model.feeds;
|
||||
NEWSBLUR.log(['Making feeds', {'folders': folders, 'feeds': feeds}]);
|
||||
|
||||
$('#story_taskbar').css({'display': 'block'});
|
||||
// NEWSBLUR.log(['Subscriptions', {'folders':folders}]);
|
||||
var $folder = this.make_feeds_folder(folders);
|
||||
$feed_list.append($folder);
|
||||
$('.unread_count', $feed_list).corners('4px');
|
||||
},
|
||||
|
||||
make_feeds_folder: function(items) {
|
||||
var $feeds = $.make('div');
|
||||
|
||||
for (var i in items) {
|
||||
var item = items[i];
|
||||
|
||||
if (typeof item == "number") {
|
||||
var feed = this.model.feeds[item];
|
||||
|
||||
var unread_class = '';
|
||||
if (feed.unread_count_positive) {
|
||||
unread_class += ' unread_positive';
|
||||
}
|
||||
if (feed.unread_count_neutral) {
|
||||
unread_class += ' unread_neutral';
|
||||
}
|
||||
if (feed.unread_count_negative) {
|
||||
unread_class += ' unread_negative';
|
||||
}
|
||||
var $feed = $.make('div', { className: 'feed ' + unread_class }, [
|
||||
$.make('span', {
|
||||
className: 'unread_count unread_count_positive '
|
||||
+ (feed.unread_count_positive
|
||||
? "unread_count_full"
|
||||
: "unread_count_empty")
|
||||
}, ''+feed.unread_count_positive),
|
||||
$.make('span', {
|
||||
className: 'unread_count unread_count_neutral '
|
||||
+ (feed.unread_count_neutral
|
||||
? "unread_count_full"
|
||||
: "unread_count_empty")
|
||||
}, ''+feed.unread_count_neutral),
|
||||
$.make('span', {
|
||||
className: 'unread_count unread_count_negative '
|
||||
+ (feed.unread_count_negative
|
||||
? "unread_count_full"
|
||||
: "unread_count_empty")
|
||||
}, ''+feed.unread_count_negative),
|
||||
$.make('img', { className: 'feed_favicon', src: this.google_favicon_url + feed.feed_link }),
|
||||
$.make('span', { className: 'feed_title' }, feed.feed_title)
|
||||
]).data('feed_id', feed.id);
|
||||
|
||||
$feeds.append($feed);
|
||||
} else if (typeof item == "object") {
|
||||
for (var o in item) {
|
||||
var folder = item[o];
|
||||
var $folder = $.make('div', { className: 'folder' }, [
|
||||
$.make('span', { className: 'folder_title' }, o),
|
||||
$.make('div', { className: 'feeds' }, this.make_feeds_folder(folder))
|
||||
]);
|
||||
$feeds.append($folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$('.feed', $feeds).tsort('.feed_title');
|
||||
$('.folder', $feeds).tsort('.folder_title');
|
||||
|
||||
return $feeds;
|
||||
},
|
||||
|
||||
// =====================
|
||||
// = Story Titles Pane =
|
||||
// =====================
|
||||
|
|
15
settings.py
15
settings.py
|
@ -108,7 +108,8 @@ elif DEV_SERVER1:
|
|||
MEDIA_URL = '/media/'
|
||||
DEBUG = True
|
||||
# CACHE_BACKEND = 'locmem:///'
|
||||
CACHE_BACKEND = 'memcached://127.0.0.1:11211'
|
||||
# CACHE_BACKEND = 'memcached://127.0.0.1:11211'
|
||||
CACHE_BACKEND = 'dummy:///'
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format='%(asctime)s %(levelname)s %(message)s',
|
||||
filename=LOG_FILE,
|
||||
|
@ -181,6 +182,7 @@ COMPRESS_JS = {
|
|||
'js/jquery.ui.core.js',
|
||||
'js/jquery.ui.slider.js',
|
||||
'js/jquery.layout.js',
|
||||
'js/jquery.tinysort.js',
|
||||
'js/jquery.fieldselection.js',
|
||||
|
||||
'js/newsblur/assetmodel.js',
|
||||
|
@ -267,5 +269,16 @@ INSTALLED_APPS = (
|
|||
'apps.registration',
|
||||
'apps.opml_import',
|
||||
'apps.profile',
|
||||
'devserver',
|
||||
# 'debug_toolbar'
|
||||
)
|
||||
|
||||
DEVSERVER_MODULES = (
|
||||
'devserver.modules.sql.SQLRealTimeModule',
|
||||
'devserver.modules.sql.SQLSummaryModule',
|
||||
'devserver.modules.profile.ProfileSummaryModule',
|
||||
|
||||
# Modules not enabled by default
|
||||
'devserver.modules.profile.MemoryUseModule',
|
||||
'devserver.modules.cache.CacheSummaryModule',
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from apps.rss_feeds.models import Story
|
||||
from django.core.cache import cache
|
||||
from apps.reader.models import UserSubscription, UserSubscriptionFolders, UserStory
|
||||
from apps.reader.models import UserSubscription, UserStory
|
||||
from apps.rss_feeds.importer import PageImporter
|
||||
from utils import feedparser, threadpool
|
||||
from django.db import transaction
|
||||
|
|
Loading…
Add table
Reference in a new issue