Adding river blurblogs. Still fishy, but it's pulling stories. Going to try to dev it.

This commit is contained in:
Samuel Clay 2012-08-07 14:37:07 -07:00
parent d58f342116
commit 18bade6733
11 changed files with 301 additions and 38 deletions

View file

@ -90,7 +90,7 @@ def feed_autocomplete(request):
'num_subscribers'
).order_by('-num_subscribers')[:5]
logging.user(request, "~FRAdd Search: ~SB%s ~FG(%s matches)" % (query, len(feeds),))
logging.user(request, "~FGAdd Search: ~SB%s ~SN(%s matches)" % (query, len(feeds),))
feeds = [{
'value': feed.feed_address,

View file

@ -705,7 +705,7 @@ class MSocialSubscription(mongo.Document):
unread_stories_key = stories_key
else:
r.sdiffstore(unread_stories_key, stories_key, read_stories_key)
sorted_stories_key = 'zB:%s' % (self.subscription_user_id)
unread_ranked_stories_key = 'zUB:%s:%s' % (self.user_id, self.subscription_user_id)
r.zinterstore(unread_ranked_stories_key, [sorted_stories_key, unread_stories_key])
@ -719,7 +719,9 @@ class MSocialSubscription(mongo.Document):
else:
byscorefunc = r.zrevrangebyscore
min_score = current_time
max_score = mark_read_time
now = datetime.datetime.now()
two_weeks_ago = now - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
max_score = int(time.mktime(two_weeks_ago.timetuple()))-1000
story_ids = byscorefunc(unread_ranked_stories_key, min_score,
max_score, start=offset, num=limit,
withscores=withscores)
@ -728,11 +730,11 @@ class MSocialSubscription(mongo.Document):
if not ignore_user_stories:
r.delete(unread_stories_key)
print "User_id: %s, sub user: %s, order: %s, filter: %s, stories: %s, min: %s, max: %s" % (self.user_id, self.subscription_user_id, order, read_filter, story_ids, min_score, max_score)
return [story_id for story_id in story_ids if story_id and story_id != 'None']
@classmethod
def feed_stories(cls, user_id, feed_ids, offset=0, limit=6, order='newest', read_filter='all'):
def feed_stories(cls, user_id, social_user_ids, offset=0, limit=6, order='newest', read_filter='all'):
r = redis.Redis(connection_pool=settings.REDIS_STORY_POOL)
if order == 'oldest':
@ -740,8 +742,8 @@ class MSocialSubscription(mongo.Document):
else:
range_func = r.zrevrange
if not isinstance(feed_ids, list):
feed_ids = [feed_ids]
if not isinstance(social_user_ids, list):
social_user_ids = [social_user_ids]
unread_ranked_stories_keys = 'zU:%s' % (user_id)
if offset and r.exists(unread_ranked_stories_keys):
@ -750,12 +752,11 @@ class MSocialSubscription(mongo.Document):
else:
r.delete(unread_ranked_stories_keys)
for feed_id in feed_ids:
us = cls.objects.get(user=user_id, feed=feed_id)
story_guids = us.get_stories(offset=0, limit=200,
order=order, read_filter=read_filter,
for social_user_id in social_user_ids:
us = cls.objects.get(user_id=user_id, subscription_user_id=social_user_id)
story_guids = us.get_stories(offset=0, limit=100,
# order=order, read_filter=read_filter,
withscores=True)
if story_guids:
r.zadd(unread_ranked_stories_keys, **dict(story_guids))

View file

@ -2,6 +2,7 @@ from django.conf.urls.defaults import url, patterns
from apps.social import views
urlpatterns = patterns('',
url(r'^river_stories/?$', views.load_river_blurblog, name='social-river-blurblog'),
url(r'^share_story/?$', views.mark_story_as_shared, name='mark-story-as-shared'),
url(r'^unshare_story/?$', views.mark_story_as_unshared, name='mark-story-as-unshared'),
url(r'^load_user_friends/?$', views.load_user_friends, name='load-user-friends'),

View file

@ -180,7 +180,122 @@ def load_social_stories(request, user_id, username=None):
"feeds": unsub_feeds,
"classifiers": classifiers,
}
@json.json_view
def load_river_blurblog(request):
limit = 12
start = time.time()
user = get_user(request)
social_user_ids = [int(uid) for uid in request.REQUEST.getlist('social_user_ids') if uid]
original_user_ids = list(social_user_ids)
page = int(request.REQUEST.get('page', 1))
order = request.REQUEST.get('order', 'newest')
read_filter = request.REQUEST.get('read_filter', 'unread')
relative_user_id = request.REQUEST.get('relative_user_id', None)
now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone)
UNREAD_CUTOFF = datetime.datetime.utcnow() - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
if not relative_user_id:
relative_user_id = get_user(request).pk
if not social_user_ids:
socialsubs = MSocialSubscription.objects.filter(user_id=user.pk)
social_user_ids = [s.subscription_user_id for s in socialsubs]
offset = (page-1) * limit
limit = page * limit - 1
story_ids = MSocialSubscription.feed_stories(user.pk, social_user_ids,
offset=offset, limit=limit,
order=order, read_filter=read_filter)
story_date_order = "%sstory_date" % ('' if order == 'oldest' else '-')
mstories = MStory.objects(id__in=story_ids).order_by(story_date_order)
stories = Feed.format_stories(mstories)
stories, user_profiles = MSharedStory.stories_with_comments_and_profiles(stories, relative_user_id,
check_all=True)
story_feed_ids = list(set(s['story_feed_id'] for s in stories))
usersubs = UserSubscription.objects.filter(user__pk=user.pk, feed__pk__in=story_feed_ids)
usersubs_map = dict((sub.feed_id, sub) for sub in usersubs)
unsub_feed_ids = list(set(story_feed_ids).difference(set(usersubs_map.keys())))
unsub_feeds = Feed.objects.filter(pk__in=unsub_feed_ids)
unsub_feeds = [feed.canonical(include_favicon=False) for feed in unsub_feeds]
date_delta = UNREAD_CUTOFF
# Find starred stories
if story_feed_ids:
starred_stories = MStarredStory.objects(
user_id=user.pk,
story_feed_id__in=story_feed_ids
).only('story_guid', 'starred_date')
starred_stories = dict([(story.story_guid, story.starred_date)
for story in starred_stories])
story_ids = [story['id'] for story in stories]
userstories_db = MUserStory.objects(user_id=user.pk,
feed_id__in=story_feed_ids,
story_id__in=story_ids).only('story_id')
userstories = set(us.story_id for us in userstories_db)
else:
starred_stories = {}
userstories = []
# Intelligence classifiers for all feeds involved
if story_feed_ids:
classifier_feeds = list(MClassifierFeed.objects(user_id=user.pk,
feed_id__in=story_feed_ids))
classifier_authors = list(MClassifierAuthor.objects(user_id=user.pk,
feed_id__in=story_feed_ids))
classifier_titles = list(MClassifierTitle.objects(user_id=user.pk,
feed_id__in=story_feed_ids))
classifier_tags = list(MClassifierTag.objects(user_id=user.pk,
feed_id__in=story_feed_ids))
else:
classifier_feeds = []
classifier_authors = []
classifier_titles = []
classifier_tags = []
classifiers = sort_classifiers_by_feed(user=user, feed_ids=story_feed_ids,
classifier_feeds=classifier_feeds,
classifier_authors=classifier_authors,
classifier_titles=classifier_titles,
classifier_tags=classifier_tags)
# Just need to format stories
for story in stories:
if story['id'] in userstories:
story['read_status'] = 1
else:
story['read_status'] = 0
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now)
story['long_parsed_date'] = format_story_link_date__long(story_date, now)
if story['id'] in starred_stories:
story['starred'] = True
starred_date = localtime_for_timezone(starred_stories[story['id']], user.profile.timezone)
story['starred_date'] = format_story_link_date__long(starred_date, now)
story['intelligence'] = {
'feed': apply_classifier_feeds(classifier_feeds, story['story_feed_id']),
'author': apply_classifier_authors(classifier_authors, story),
'tags': apply_classifier_tags(classifier_tags, story),
'title': apply_classifier_titles(classifier_titles, story),
}
diff = time.time() - start
timediff = round(float(diff), 2)
logging.user(request, "~FYLoading ~FCriver blurblogs stories~FY: ~SBp%s~SN (%s/%s "
"stories, ~SN%s/%s/%s feeds)" %
(page, len(stories), len(mstories), len(story_feed_ids),
len(social_user_ids), len(original_user_ids)))
return {
"stories": stories,
"user_profiles": user_profiles,
"feeds": unsub_feeds,
"classifiers": classifiers,
"elapsed_time": timediff,
}
def load_social_page(request, user_id, username=None, **kwargs):
start = time.time()
user = request.user

View file

@ -2811,20 +2811,24 @@ background: transparent;
.NB-feeds-header-river-container {
display: block;
border-bottom: 1px solid #F0F0F0;
}
.NB-feeds-header-river .NB-feeds-header-icon {
.NB-feeds-header-river-container .NB-feeds-header-icon {
background: transparent url('/media/embed/icons/silk/images.png') no-repeat 0 0;
}
.NB-feeds-header-river .NB-feeds-header-count {
.NB-feeds-header-river-container .NB-feeds-header-count {
background-color: #11448B;
display: block;
padding: 0 4px;
margin: 2px 3px 0 0;
}
.NB-feeds-header-river.NB-empty .NB-feeds-header-count {
.NB-feeds-header-river-container .NB-feeds-header {
border-bottom: 1px solid #303030;
}
.NB-feeds-header-river-container .NB-feeds-header.NB-empty .NB-feeds-header-count {
display: none;
}

View file

@ -25,7 +25,6 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
this.following_profiles = new NEWSBLUR.Collections.Users();
this.starred_stories = [];
this.starred_count = 0;
this.read_stories_river_count = 0;
this.flags = {
'favicons_fetching': false,
'has_chosen_feeds': false,
@ -131,7 +130,6 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
}
}
this.read_stories_river_count += 1;
$.isFunction(callback) && callback(read);
},
@ -165,7 +163,6 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
}
}
this.read_stories_river_count += 1;
$.isFunction(callback) && callback(read);
},
@ -510,8 +507,6 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
fetch_river_stories: function(feed_id, feeds, page, callback, error_callback, first_load) {
var self = this;
if (first_load || !page) this.read_stories_river_count = 0;
var pre_callback = function(data) {
self.load_feed_precallback(data, feed_id, callback, first_load);
@ -540,6 +535,36 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
});
},
fetch_river_blurblogs_stories: function(feed_id, page, callback, error_callback, first_load) {
var self = this;
var pre_callback = function(data) {
self.load_feed_precallback(data, feed_id, callback, first_load);
if (NEWSBLUR.reader.flags['non_premium_river_view']) {
var visible_stories = self.stories.visible().length;
var max_stories = NEWSBLUR.reader.constants.RIVER_STORIES_FOR_STANDARD_ACCOUNT;
NEWSBLUR.log(["checking no more stories", visible_stories, max_stories]);
if (visible_stories >= max_stories) {
self.flags['no_more_stories'] = true;
self.stories.trigger('no_more_stories');
}
}
};
this.feed_id = feed_id;
this.make_request('/social/river_stories', {
page: page,
order: this.view_setting(feed_id, 'order')
// read_filter: this.view_setting(feed_id, 'read_filter')
}, pre_callback, error_callback, {
'ajax_group': (page ? 'feed_page' : 'feed'),
'request_type': 'GET'
});
},
fetch_social_stories: function(feed_id, page, callback, error_callback, first_load) {
var self = this;

View file

@ -47,6 +47,8 @@ NEWSBLUR.Router = Backbone.Router.extend({
// NEWSBLUR.log(["folder", folder_name]);
if (folder_name == "everything") {
NEWSBLUR.reader.open_river_stories();
} else if (folder_name == "blurblogs") {
NEWSBLUR.reader.open_river_blurblogs_stories();
} else {
var folder = NEWSBLUR.assets.get_folder(folder_name);
if (folder) {

View file

@ -29,7 +29,8 @@
$feed_link_loader: $('#NB-feeds-list-loader'),
$feeds_progress: $('#NB-progress'),
$dashboard: $('.NB-feeds-header-dashboard'),
$river_header: $('.NB-feeds-header-river'),
$river_sites_header: $('.NB-feeds-header-river-sites'),
$river_blurblogs_header: $('.NB-feeds-header-river-blurblogs'),
$starred_header: $('.NB-feeds-header-starred'),
$tryfeed_header: $('.NB-feeds-header-tryfeed'),
$taskbar: $('.taskbar_nav'),
@ -921,7 +922,8 @@
this.model.flags['no_more_stories'] = false;
this.$s.$feed_stories.scrollTop(0);
this.$s.$starred_header.removeClass('NB-selected');
this.$s.$river_header.removeClass('NB-selected');
this.$s.$river_sites_header.removeClass('NB-selected');
this.$s.$river_blurblogs_header.removeClass('NB-selected');
this.$s.$tryfeed_header.removeClass('NB-selected');
this.model.feeds.deselect();
if (_.string.contains(this.active_feed, 'social:')) {
@ -1191,7 +1193,7 @@
this.active_folder = folder;
if (!folder) {
this.active_feed = 'river:';
this.$s.$river_header.addClass('NB-selected');
this.$s.$river_sites_header.addClass('NB-selected');
} else {
this.active_feed = 'river:' + folder_title;
folder_view.model.set('selected', true);
@ -1288,6 +1290,82 @@
return feeds;
},
// ===================
// = River Blurblogs =
// ===================
open_river_blurblogs_stories: function(options) {
options = options || {};
var $story_titles = this.$s.$story_titles;
var folder_title = "Blurblogs";
this.reset_feed();
this.hide_splash_page();
// this.active_folder = "blurblogs";
this.active_feed = 'river:blurblogs';
this.$s.$river_blurblogs_header.addClass('NB-selected');
this.iframe_scroll = null;
this.flags['opening_feed'] = true;
this.$s.$body.addClass('NB-view-river');
this.flags.river_view = true;
this.flags.social_view = true;
$('.task_view_page', this.$s.$taskbar).addClass('NB-disabled');
var explicit_view_setting = this.model.view_setting(this.active_feed, 'view');
if (!explicit_view_setting || explicit_view_setting == 'page') {
explicit_view_setting = 'feed';
}
this.set_correct_story_view_for_feed(this.active_feed, explicit_view_setting);
this.switch_taskbar_view(this.story_view);
this.setup_mousemove_on_views();
this.make_feed_title_in_stories();
NEWSBLUR.app.feed_list.scroll_to_show_selected_folder();
if (!options.silent) {
var slug = folder_title.replace(/ /g, '-').toLowerCase();
var url = "folder/" + slug;
if (!_.string.include(window.location.pathname, url)) {
NEWSBLUR.log(["Navigating to url", url]);
NEWSBLUR.router.navigate(url);
}
}
this.hide_stories_error();
this.show_stories_progress_bar(NEWSBLUR.assets.social_feeds.size());
this.model.fetch_river_blurblogs_stories(this.active_feed, 1,
_.bind(this.post_open_river_blurblogs_stories, this),
this.show_stories_error, true);
},
post_open_river_blurblogs_stories: function(data, first_load) {
// NEWSBLUR.log(['post_open_river_stories', data, this.active_feed]);
if (!data) {
return this.show_stories_error(data);
}
if (this.active_feed && this.active_feed.indexOf('river:') != -1) {
if (!NEWSBLUR.Globals.is_premium &&
NEWSBLUR.Globals.is_authenticated &&
this.flags['river_view'] &&
this.active_feed.indexOf('river:') != -1) {
this.flags['non_premium_river_view'] = true;
}
this.flags['opening_feed'] = false;
this.show_feed_hidden_story_title_indicator(true);
this.find_story_with_action_preference_on_open_feed();
this.show_story_titles_above_intelligence_level({'animate': false});
this.flags['story_titles_loaded'] = true;
if (this.counts['find_next_unread_on_page_of_feed_stories_load']) {
this.show_next_unread_story(true);
} else if (this.counts['find_last_unread_on_page_of_feed_stories_load']) {
this.show_last_unread_story(true);
} else if (this.counts['select_story_in_feed'] || this.flags['select_story_in_feed']) {
this.select_story_in_feed();
}
this.hide_stories_progress_bar();
}
},
// ==================
// = Social Stories =
@ -1813,6 +1891,11 @@
if (this.active_feed == 'starred') {
this.model.fetch_starred_stories(this.counts['page'], _.bind(this.post_open_starred_stories, this),
this.show_stories_error, false);
} else if (this.flags['social_view'] && this.active_feed == 'river:blurblogs') {
this.model.fetch_river_blurblogs_stories(this.active_feed,
this.counts['page'],
_.bind(this.post_open_river_blurblogs_stories, this),
this.show_stories_error, false);
} else if (this.flags['social_view']) {
this.model.fetch_social_stories(this.active_feed,
this.counts['page'], _.bind(this.post_open_social_stories, this),
@ -4317,10 +4400,14 @@
e.preventDefault();
self.open_starred_stories();
});
$.targetIs(e, { tagSelector: '.NB-feeds-header-river' }, function($t, $p){
$.targetIs(e, { tagSelector: '.NB-feeds-header-river-sites' }, function($t, $p){
e.preventDefault();
self.open_river_stories();
});
$.targetIs(e, { tagSelector: '.NB-feeds-header-river-blurblogs' }, function($t, $p){
e.preventDefault();
self.open_river_blurblogs_stories();
});
// = Stories ======================================================

View file

@ -301,7 +301,7 @@ var classifier_prototype = {
]),
(!NEWSBLUR.Globals.is_authenticated && $.make('div', { className: 'NB-trainer-not-authenticated' }, 'Please create an account and add sites you read. Then you can train them.')),
$.make('div', { className: 'NB-modal-submit' }, [
(!NEWSBLUR.Globals.is_authenticated && $.make('a', { href: '#', className: 'NB-modal-submit-close NB-modal-submit-button' }, 'Close')),
(!NEWSBLUR.Globals.is_authenticated && $.make('a', { href: '#', className: 'NB-modal-submit-grey NB-modal-submit-button' }, 'Close')),
(NEWSBLUR.Globals.is_authenticated && $.make('a', { href: '#', className: 'NB-modal-submit-begin NB-modal-submit-button NB-modal-submit-grey NB-disabled' }, 'Loading Training...'))
])
]);
@ -402,7 +402,7 @@ var classifier_prototype = {
$.make('input', { name: 'feed_id', value: this.feed_id, type: 'hidden' }),
$.make('a', { href: '#', className: 'NB-modal-submit-button NB-modal-submit-back' }, $.entity('«') + ' Back'),
$.make('a', { href: '#', className: 'NB-modal-submit-button NB-modal-submit-green NB-modal-submit-next NB-modal-submit-save' }, 'Save & Next '+$.entity('»')),
$.make('a', { href: '#', className: 'NB-modal-submit-button NB-modal-submit-close' }, 'Close')
$.make('a', { href: '#', className: 'NB-modal-submit-button NB-modal-submit-grey' }, 'Close')
])),
(!this.options['training'] && $.make('div', { className: 'NB-modal-submit' }, [
$.make('input', { name: 'story_id', value: this.story_id, type: 'hidden' }),
@ -662,7 +662,7 @@ var classifier_prototype = {
var $dislike = $('.NB-classifier-input-dislike', $classifier);
var $save = $('.NB-modal-submit-save', this.$modal);
var $close = $('.NB-modal-submit-close', this.$modal);
var $close = $('.NB-modal-submit-grey', this.$modal);
var $back = $('.NB-modal-submit-back', this.$modal);
if (classifier_opinion == 'like') {
@ -815,7 +815,7 @@ var classifier_prototype = {
self.retrain_all_sites();
});
$.targetIs(e, { tagSelector: '.NB-modal-submit-close' }, function($t, $p){
$.targetIs(e, { tagSelector: '.NB-modal-submit-grey' }, function($t, $p){
e.preventDefault();
self.save();
});

View file

@ -440,16 +440,6 @@
<div id="NB-feeds-list-loader">Everything is on its way...</div>
<div class="NB-feeds-header-wrapper">
<div class="NB-feeds-header-container NB-feeds-header-river-container">
<div class="NB-feeds-header NB-feeds-header-river NB-empty">
<div class="NB-feeds-header-count unread_count"></div>
<div class="NB-feeds-header-icon"></div>
<div class="NB-feeds-header-title">
Everything
</div>
</div>
</div>
<div class="NB-feeds-header-container NB-feeds-header-starred-container">
<div class="NB-feeds-header NB-feeds-header-starred NB-empty">
<div class="NB-feeds-header-count unread_count"></div>
@ -468,7 +458,29 @@
</div>
</div>
<div class="NB-feeds-header-container NB-feeds-header-river-container">
<div class="NB-feeds-header NB-feeds-header-river-blurblogs NB-empty">
<div class="NB-feeds-header-count unread_count"></div>
<div class="NB-feeds-header-icon"></div>
<div class="NB-feeds-header-title">
Blurblogs
</div>
</div>
</div>
<ul class="NB-socialfeeds NB-feedlist"></ul>
<div class="NB-feeds-header-container NB-feeds-header-river-container">
<div class="NB-feeds-header NB-feeds-header-river-sites NB-empty">
<div class="NB-feeds-header-count unread_count"></div>
<div class="NB-feeds-header-icon"></div>
<div class="NB-feeds-header-title">
All Stories
</div>
</div>
</div>
<ul class="folder NB-feedlist" id="feed_list"></ul>
</div>

View file

@ -255,6 +255,22 @@
required: true
example: "{<br>12: ['story_id_1', 'story_id_2'],<br>24: ['story_id_3']<br>}"
- url: /reader/mark_social_stories_as_read
method: POST
short_desc: "Mark stories from a blurblog as read."
long_desc:
- "Marks multiple stories as read."
- "Multiple story ids can be sent at once."
- "Multiple feeds can be sent."
tips:
- "Throttle requests to this endpoint. You don't need to send one request per story."
- "Queue up to 5 stories or once every 10 seconds before firing, whichever comes first."
params:
- key: users_feeds_stories
desc: "JSON serialized dictionary of user_ids to feed_ids to an array of story_ids."
required: true
example: "{<br>user_id: <br>{feed_id: ['story_id_1', 'story_id_2'],<br>24: ['story_id_3']<br>}<br>}"
- url: /reader/mark_story_as_unread
method: POST
short_desc: "Mark a story as unread."