Merge branch 'categories'

* categories:
  Fixing folder merge so category additions can be idempotent. Also fixing social connect size for iPhone.
  Adding categories. Still needs subscription to categories being added to a user's subscription folders.
This commit is contained in:
Samuel Clay 2012-08-12 18:57:33 -07:00
commit 9711ec93d6
12 changed files with 217 additions and 10 deletions

View file

110
apps/categories/models.py Normal file
View file

@ -0,0 +1,110 @@
import mongoengine as mongo
from itertools import groupby
from apps.rss_feeds.models import Feed
from apps.reader.models import UserSubscription, UserSubscriptionFolders
from utils import json_functions as json
from utils.feed_functions import add_object_to_folder
class MCategory(mongo.Document):
title = mongo.StringField()
description = mongo.StringField()
feed_ids = mongo.ListField(mongo.IntField())
meta = {
'collection': 'category',
'indexes': ['title'],
'allow_inheritance': False,
'index_drop_dups': True,
}
def __unicode__(self):
return "%s: %s sites" % (self.title, len(self.feed_ids))
@classmethod
def serialize(cls, category=None):
categories = cls.objects.all()
if category:
categories = categories.filter(title=category)
data = dict(categories=[], feeds={})
feed_ids = set()
for category in categories:
category_output = {
'title': category.title,
'description': category.description,
'feed_ids': category.feed_ids,
}
data['categories'].append(category_output)
feed_ids.update(list(category.feed_ids))
feeds = Feed.objects.filter(pk__in=feed_ids)
for feed in feeds:
data['feeds'][feed.pk] = feed.canonical()
return data
@classmethod
def reload_sites(cls, category_title=None):
category_sites = MCategorySite.objects.all()
if category_title:
category_sites = category_sites.filter(category_title=category_title)
category_groups = groupby(sorted(category_sites, key=lambda c: c.category_title), key=lambda c: c.category_title)
for category_title, sites in category_groups:
category = cls.objects.get(title=category_title)
category.feed_ids = [site.feed_id for site in sites]
category.save()
print " ---> Reloaded category: %s" % category
@classmethod
def subscribe(cls, user_id, category_title):
category = cls.objects.get(title=category_title)
for feed_id in category.feed_ids:
us, _ = UserSubscription.objects.get_or_create(
feed_id=feed_id,
user_id=user_id,
defaults={
'needs_unread_recalc': True,
'active': True,
}
)
usf, created = UserSubscriptionFolders.objects.get_or_create(
user_id=user_id,
defaults={'folders': '[]'}
)
usf.add_folder('', category.title)
folders = json.decode(usf.folders)
for feed_id in category.feed_ids:
folders = add_object_to_folder(feed_id, category.title, folders)
usf.folders = json.encode(folders)
usf.save()
class MCategorySite(mongo.Document):
feed_id = mongo.IntField()
category_title = mongo.StringField()
meta = {
'collection': 'category_site',
'indexes': ['feed_id', 'category_title'],
'allow_inheritance': False,
'index_drop_dups': True,
}
def __unicode__(self):
feed = Feed.objects.get(pk=self.feed_id)
return "%s: %s" % (self.category_title, feed)
@classmethod
def add(cls, category_title, feed_id):
category_site, created = cls.objects.get_or_create(category_title=category_title,
feed_id=feed_id)
if not created:
print " ---> Site is already in category: %s" % category_site
else:
MCategory.reload_sites(category_title)

16
apps/categories/tests.py Normal file
View file

@ -0,0 +1,16 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

7
apps/categories/urls.py Normal file
View file

@ -0,0 +1,7 @@
from django.conf.urls.defaults import url, patterns
from apps.categories import views
urlpatterns = patterns('',
url(r'^/?$', views.all_categories, name='all-categories'),
url(r'^subscribe/?$', views.subscribe, name='categories-subscribe'),
)

37
apps/categories/views.py Normal file
View file

@ -0,0 +1,37 @@
from apps.categories.models import MCategory
from apps.reader.models import UserSubscriptionFolders
from utils import json_functions as json
from utils.user_functions import ajax_login_required
@json.json_view
def all_categories(request):
categories = MCategory.serialize()
return categories
@ajax_login_required
@json.json_view
def subscribe(request):
user = request.user
categories = MCategory.serialize()
category_titles = [c['title'] for c in categories['categories']]
subscribe_category_titles = request.REQUEST.getlist('category')
invalid_category_title = False
for category_title in subscribe_category_titles:
if category_title not in category_titles:
invalid_category_title = True
if not subscribe_category_titles or invalid_category_title:
message = "Choose one or more of these categories: %s" % ', '.join(category_titles)
return dict(code=-1, message=message)
for category_title in subscribe_category_titles:
MCategory.subscribe(user.pk, category_title)
usf = UserSubscriptionFolders.objects.get(user=user.pk)
return dict(code=1, message="Subscribed to %s %s" % (
len(subscribe_category_titles),
'category' if len(subscribe_category_titles) == 1 else 'categories',
), folders=json.decode(usf.folders))

View file

@ -34,6 +34,7 @@ except:
pass
from apps.social.models import MSharedStory, MSocialProfile, MSocialServices
from apps.social.models import MSocialSubscription, MActivity
from apps.categories.models import MCategory
from apps.social.views import load_social_page
from utils import json_functions as json
from utils.user_functions import get_user, ajax_login_required
@ -234,6 +235,10 @@ def load_feeds(request):
user.profile.dashboard_date = datetime.datetime.now()
user.profile.save()
categories = None
if not user_subs:
categories = MCategory.serialize()
data = {
'feeds': feeds.values() if version == 2 else feeds,
'social_feeds': social_feeds,
@ -241,6 +246,7 @@ def load_feeds(request):
'social_services': social_services,
'folders': json.decode(folders.folders),
'starred_count': starred_count,
'categories': categories
}
return data
@ -317,6 +323,10 @@ def load_feeds_flat(request):
social_feeds = MSocialSubscription.feeds(**social_params)
social_profile = MSocialProfile.profile(user.pk)
categories = None
if not user_subs:
categories = MCategory.serialize()
logging.user(request, "~FBLoading ~SB%s~SN/~SB%s~SN feeds/socials ~FMflat~FB. %s" % (
len(feeds.keys()), len(social_feeds), '~SBUpdating counts.' if update_counts else ''))
@ -328,6 +338,7 @@ def load_feeds_flat(request):
"user": user.username,
"user_profile": user.profile,
"iphone_version": iphone_version,
"categories": categories,
}
return data

View file

@ -3162,9 +3162,9 @@
var to_folder = $('.NB-menu-manage-folder-move-confirm select').val();
var folder_view = NEWSBLUR.assets.folders.get_view($folder);
var in_folder = folder_view.collection.options.title;
var folder_name = folder_view.options.title;
var folder_name = folder_view.options.folder_title;
var child_folders = folder_view.collection.child_folder_names();
if (to_folder == in_folder ||
to_folder == folder_name ||
_.contains(child_folders, to_folder)) {

View file

@ -672,7 +672,7 @@ _.extend(NEWSBLUR.ReaderIntro.prototype, {
self.handle_opml_upload();
});
$.targetIs(e, { tagSelector: '.NB-friends-autofollow-checkbox' }, function($t, $p) {
self.model.preference('autofollow_friends', $t.is(':checked'));
NEWSBLUR.assets.preference('autofollow_friends', $t.is(':checked'));
});
$.targetIs(e, { tagSelector: '#NB-intro-uptodate-follow-newsblur' }, function($t, $p) {
self.follow_twitter_account('newsblur');

View file

@ -229,6 +229,7 @@ INSTALLED_APPS = (
'apps.push',
'apps.social',
'apps.oauth',
'apps.categories',
'south',
'utils',
'vendor',

View file

@ -4,8 +4,7 @@
<title>NewsBlur - Connecting...</title>
<head>
<meta name="viewport" content="initial-scale=.7, maximum-scale=.7">
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<style>
body {
@ -26,6 +25,14 @@
-moz-animation-iteration-count: infinite;
-moz-animation-timing-function: linear;
}
@media screen and (max-width: 320px) {
.NB-loader {
margin: 42px auto;
width: 200px;
height: 200px;
}
}
@-webkit-keyframes -webkit-slow-spin {
from {-webkit-transform: rotate(0deg)}
to {-webkit-transform: rotate(360deg)}
@ -85,7 +92,7 @@
} else {
console.log(["No opener, directing to /", opener]);
window.close();
window.location.href = "/";
window.location.href = "/{% if error %}?error={{ error|urlencode }}{% endif %};";
}
</script>
</head>

View file

@ -26,6 +26,7 @@ urlpatterns = patterns('',
(r'^mobile/', include('apps.mobile.urls')),
(r'^m/', include('apps.mobile.urls')),
(r'^push/', include('apps.push.urls')),
(r'^categories/', include('apps.categories.urls')),
url(r'^about/?', static_views.about, name='about'),
url(r'^faq/?', static_views.faq, name='faq'),
url(r'^api/?', static_views.api, name='api'),

View file

@ -170,18 +170,35 @@ def format_relative_date(date, future=False):
days = ((diff.seconds / 60) / 60 / 24)
return "%s day%s %s" % (days, '' if days == 1 else 's', '' if future else 'ago')
def add_object_to_folder(obj, folder, folders, parent='', added=False):
if not folder and not parent and obj not in folders:
def add_object_to_folder(obj, in_folder, folders, parent='', added=False):
obj_identifier = obj
if isinstance(obj, dict):
obj_identifier = obj.keys()[0]
print obj, obj_identifier, folders
if (not in_folder and not parent and
not isinstance(obj, dict) and
obj_identifier not in folders):
folders.append(obj)
return folders
child_folder_names = []
for item in folders:
if isinstance(item, dict):
child_folder_names.append(item.keys()[0])
if isinstance(obj, dict) and in_folder == parent:
if obj_identifier not in child_folder_names:
folders.append(obj)
return folders
for k, v in enumerate(folders):
if isinstance(v, dict):
for f_k, f_v in v.items():
if f_k == folder and obj not in f_v and not added:
if f_k == in_folder and obj_identifier not in f_v and not added:
f_v.append(obj)
added = True
folders[k][f_k] = add_object_to_folder(obj, folder, f_v, f_k, added)
folders[k][f_k] = add_object_to_folder(obj, in_folder, f_v, f_k, added)
return folders
def mail_feed_error_to_admin(feed, e, local_vars=None, subject=None):