A whopper: marking stories as read in a social feed.

This commit is contained in:
Samuel Clay 2012-02-03 11:41:01 -08:00
parent 4fb275ccfc
commit 193201061e
8 changed files with 190 additions and 112 deletions

View file

@ -373,14 +373,15 @@ class MUserStory(mongo.Document):
user_id = mongo.IntField()
feed_id = mongo.IntField()
read_date = mongo.DateTimeField()
story_id = mongo.StringField()
story_id = mongo.StringField(unique_with=('user_id', 'feed_id'))
story_date = mongo.DateTimeField()
story = mongo.ReferenceField(MStory, unique_with=('user_id', 'feed_id'))
story = mongo.ReferenceField(MStory)
meta = {
'collection': 'userstories',
'indexes': [('user_id', 'feed_id'), ('feed_id', 'read_date'), ('feed_id', 'story_id')],
'allow_inheritance': False,
'index_drop_dups': True,
}
@classmethod

View file

@ -19,6 +19,7 @@ urlpatterns = patterns('',
url(r'^mark_all_as_read', views.mark_all_as_read, name='mark-all-as-read'),
url(r'^mark_story_as_read', views.mark_story_as_read, name='mark-story-as-read'),
url(r'^mark_feed_stories_as_read', views.mark_feed_stories_as_read, name='mark-feed-stories-as-read'),
url(r'^mark_social_stories_as_read', views.mark_social_stories_as_read, name='mark-social-stories-as-read'),
url(r'^mark_story_as_unread', views.mark_story_as_unread),
url(r'^mark_story_as_starred', views.mark_story_as_starred),
url(r'^mark_story_as_unstarred', views.mark_story_as_unstarred),

View file

@ -704,7 +704,6 @@ def mark_all_as_read(request):
def mark_story_as_read(request):
story_ids = request.REQUEST.getlist('story_id')
feed_id = int(get_argument_or_404(request, 'feed_id'))
usersub = None
try:
usersub = UserSubscription.objects.select_related('feed').get(user=request.user, feed=feed_id)
@ -716,17 +715,16 @@ def mark_story_as_read(request):
usersub = UserSubscription.objects.get(user=request.user,
feed=duplicate_feed[0].feed)
except (Feed.DoesNotExist):
return dict(code=-1)
return dict(code=-1, errors=["No feed exists for feed_id %d." % feed_id])
else:
return dict(code=-1)
return dict(code=-1, errors=["No feed exists for feed_id %d." % feed_id])
except UserSubscription.DoesNotExist:
pass
usersub = None
if usersub:
data = usersub.mark_story_ids_as_read(story_ids, request=request)
else:
socialsub = MSocialSubscription.objects.get(user_id=request.user.pk, subscription_user_id=feed_id)
data = socialsub.mark_story_ids_as_read(story_ids, request=request)
data = dict(code=-1, errors=["User is not subscribed to this feed."])
return data
@ -739,19 +737,54 @@ def mark_feed_stories_as_read(request):
feed_id = int(feed_id)
try:
usersub = UserSubscription.objects.select_related('feed').get(user=request.user, feed=feed_id)
except (UserSubscription.DoesNotExist, Feed.DoesNotExist):
data = usersub.mark_story_ids_as_read(story_ids)
except UserSubscription.DoesNotExist:
return dict(code=-1, error="You are not subscribed to this feed_id: %d" % feed_id)
except Feed.DoesNotExist:
duplicate_feed = DuplicateFeed.objects.filter(duplicate_feed_id=feed_id)
if duplicate_feed:
try:
usersub = UserSubscription.objects.get(user=request.user,
feed=duplicate_feed[0].feed)
except (UserSubscription.DoesNotExist, Feed.DoesNotExist):
continue
else:
continue
usersub.mark_story_ids_as_read(story_ids)
try:
if not duplicate_feed: raise Feed.DoesNotExist
usersub = UserSubscription.objects.get(user=request.user,
feed=duplicate_feed[0].feed)
data = usersub.mark_story_ids_as_read(story_ids)
except (UserSubscription.DoesNotExist, Feed.DoesNotExist):
return dict(code=-1, error="No feed exists for feed_id: %d" % feed_id)
return dict(code=1)
return data
@ajax_login_required
@json.json_view
def mark_social_stories_as_read(request):
code = 1
errors = []
data = None
users_feeds_stories = request.REQUEST.get('users_feeds_stories', "{}")
users_feeds_stories = json.decode(users_feeds_stories)
for social_user_id, feeds in users_feeds_stories.items():
for feed_id, story_ids in feeds.items():
feed_id = int(feed_id)
try:
print social_user_id, feed_id
socialsub = MSocialSubscription.objects.get(user_id=request.user.pk,
subscription_user_id=social_user_id)
data = socialsub.mark_story_ids_as_read(story_ids, feed_id, request=request)
except MSocialSubscription.DoesNotExist:
errors.append("You are not subscribed to this social user_id: %s" % social_user_id)
except Feed.DoesNotExist:
duplicate_feed = DuplicateFeed.objects.filter(duplicate_feed_id=feed_id)
if duplicate_feed:
try:
socialsub = MSocialSubscription.objects.get(user_id=request.user.pk,
subscription_user_id=social_user_id)
data = socialsub.mark_story_ids_as_read(story_ids, duplicate_feed[0].feed.pk, request=request)
except (UserSubscription.DoesNotExist, Feed.DoesNotExist):
code = -1
errors.append("No feed exists for feed_id %d." % feed_id)
else:
continue
return dict(code=code, errors=errors, data=data)
@ajax_login_required
@json.json_view

View file

@ -259,7 +259,7 @@ class MSocialSubscription(mongo.Document):
'is_trained': self.is_trained,
}
def mark_story_ids_as_read(self, story_ids, request=None):
def mark_story_ids_as_read(self, story_ids, feed_id, request=None):
data = dict(code=0, payload=story_ids)
if not request:
@ -278,8 +278,8 @@ class MSocialSubscription(mongo.Document):
story = MSharedStory.objects.get(user_id=self.subscription_user_id, story_guid=story_id)
now = datetime.datetime.utcnow()
date = now if now > story.story_date else story.story_date # For handling future stories
m = MUserStory(story=story, user_id=self.user_id,
feed_id=self.feed_id, read_date=date,
m = MUserStory(user_id=self.user_id,
feed_id=feed_id, read_date=date,
story_id=story_id, story_date=story.story_date)
m.save()

View file

@ -25,62 +25,6 @@ from vendor import facebook
from vendor import tweepy
from vendor.timezones.utilities import localtime_for_timezone
@json.json_view
def story_comments(request):
feed_id = int(request.POST['feed_id'])
story_id = request.POST['story_id']
full = request.POST.get('full', False)
compact = request.POST.get('compact', False)
shared_stories = MSharedStory.objects.filter(story_feed_id=feed_id, story_guid=story_id)
comments = [s.comments_with_author(compact=compact, full=full) for s in shared_stories]
return {'comments': comments}
@ajax_login_required
@json.json_view
def mark_story_as_shared(request):
code = 1
feed_id = int(request.POST['feed_id'])
story_id = request.POST['story_id']
comments = request.POST.get('comments', '')
story = MStory.objects(story_feed_id=feed_id, story_guid=story_id).limit(1).first()
if not story:
return {'code': -1, 'message': 'Story not found.'}
shared_story = MSharedStory.objects.filter(user_id=request.user.pk, story_feed_id=feed_id, story_guid=story_id)
if not shared_story:
story_db = dict([(k, v) for k, v in story._data.items()
if k is not None and v is not None])
story_values = dict(user_id=request.user.pk, comments=comments,
has_comments=bool(comments), **story_db)
MSharedStory.objects.create(**story_values)
logging.user(request, "~FCSharing: ~SB~FM%s (~FB%s~FM)" % (story.story_title[:50], comments[:100]))
else:
shared_story = shared_story[0]
shared_story.comments = comments
shared_story.has_comments = bool(comments)
shared_story.save()
logging.user(request, "~FCUpdating shared story: ~SB~FM%s (~FB%s~FM)" % (story.story_title[:50], comments[:100]))
story.count_comments()
story = Feed.format_story(story)
story = MSharedStory.stories_with_comments([story], request.user)[0]
return {'code': code, 'story': story}
def shared_stories_public(request, username):
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise Http404
shared_stories = MSharedStory.objects.filter(user_id=user.pk)
return HttpResponse("There are %s stories shared by %s." % (shared_stories.count(), username))
@json.json_view
def load_social_stories(request, user_id, username=None):
user = get_user(request)
@ -99,8 +43,8 @@ def load_social_stories(request, user_id, username=None):
return dict(stories=[])
stories = MSharedStory.stories_with_comments(stories, user, check_all=True)
story_feed_ids = list(set(s['story_feed_id'] for s in stories))
story_feed_ids = list(set(s['story_feed_id'] for s in stories))
socialsub = MSocialSubscription.objects.get(user_id=user.pk, subscription_user_id=social_user_id)
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)
@ -132,6 +76,7 @@ def load_social_stories(request, user_id, username=None):
userstories = set(us.story_id for us in userstories_db)
for story in stories:
story['social_user_id'] = social_user_id
story_feed_id = story['story_feed_id']
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now)
@ -204,6 +149,62 @@ def load_social_page(request, user_id, username=None):
return params
@json.json_view
def story_comments(request):
feed_id = int(request.POST['feed_id'])
story_id = request.POST['story_id']
full = request.POST.get('full', False)
compact = request.POST.get('compact', False)
shared_stories = MSharedStory.objects.filter(story_feed_id=feed_id, story_guid=story_id)
comments = [s.comments_with_author(compact=compact, full=full) for s in shared_stories]
return {'comments': comments}
@ajax_login_required
@json.json_view
def mark_story_as_shared(request):
code = 1
feed_id = int(request.POST['feed_id'])
story_id = request.POST['story_id']
comments = request.POST.get('comments', '')
story = MStory.objects(story_feed_id=feed_id, story_guid=story_id).limit(1).first()
if not story:
return {'code': -1, 'message': 'Story not found.'}
shared_story = MSharedStory.objects.filter(user_id=request.user.pk, story_feed_id=feed_id, story_guid=story_id)
if not shared_story:
story_db = dict([(k, v) for k, v in story._data.items()
if k is not None and v is not None])
story_values = dict(user_id=request.user.pk, comments=comments,
has_comments=bool(comments), **story_db)
MSharedStory.objects.create(**story_values)
logging.user(request, "~FCSharing: ~SB~FM%s (~FB%s~FM)" % (story.story_title[:50], comments[:100]))
else:
shared_story = shared_story[0]
shared_story.comments = comments
shared_story.has_comments = bool(comments)
shared_story.save()
logging.user(request, "~FCUpdating shared story: ~SB~FM%s (~FB%s~FM)" % (story.story_title[:50], comments[:100]))
story.count_comments()
story = Feed.format_story(story)
story = MSharedStory.stories_with_comments([story], request.user)[0]
return {'code': code, 'story': story}
def shared_stories_public(request, username):
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise Http404
shared_stories = MSharedStory.objects.filter(user_id=user.pk)
return HttpResponse("There are %s stories shared by %s." % (shared_stories.count(), username))
@json.json_view
def friends(request):
user = get_user(request)
@ -272,7 +273,8 @@ def shared_stories_rss_feed(request, user_id, username):
raise Http404
if user.username != username:
return HttpResponseRedirect(reverse('shared-story-feed', kwargs={'username': user.username, 'user_id': user.pk}))
params = {'username': user.username, 'user_id': user.pk}
return HttpResponseRedirect(reverse('shared-stories-rss-feed', kwargs=params))
social_profile = MSocialProfile.objects.get(user_id=user_id)

View file

@ -46,11 +46,14 @@ NEWSBLUR.AssetModel.Reader.prototype = {
init: function() {
this.ajax = {};
this.ajax['queue'] = $.manageAjax.create('queue', {queue: true});
this.ajax['queue'] = $.manageAjax.create('queue', {queue: true});
this.ajax['queue_clear'] = $.manageAjax.create('queue_clear', {queue: 'clear'});
this.ajax['feed'] = $.manageAjax.create('feed', {queue: 'clear', abortOld: true, domCompleteTrigger: true});
this.ajax['feed_page'] = $.manageAjax.create('feed_page', {queue: 'clear', abortOld: true, abortIsNoSuccess: false, domCompleteTrigger: true});
this.ajax['statistics'] = $.manageAjax.create('statistics', {queue: 'clear', abortOld: true});
this.ajax['feed'] = $.manageAjax.create('feed', {queue: 'clear', abortOld: true,
domCompleteTrigger: true});
this.ajax['feed_page'] = $.manageAjax.create('feed_page', {queue: 'clear', abortOld: true,
abortIsNoSuccess: false,
domCompleteTrigger: true});
this.ajax['statistics'] = $.manageAjax.create('statistics', {queue: 'clear', abortOld: true});
$.ajaxSettings.traditional = true;
return;
},
@ -119,30 +122,64 @@ NEWSBLUR.AssetModel.Reader.prototype = {
mark_story_as_read: function(story_id, feed_id, callback) {
var self = this;
var read = false;
var story = this.get_story(story_id);
read = story.read_status;
story.read_status = 1;
if (!read && NEWSBLUR.Globals.is_authenticated) {
if (!(feed_id in this.queued_read_stories)) { this.queued_read_stories[feed_id] = []; }
this.queued_read_stories[feed_id].push(story_id);
// NEWSBLUR.log(['Marking Read', this.queued_read_stories, story_id]);
if (!story.read_status) {
story.read_status = 1;
this.make_request('/reader/mark_story_as_read', {
story_id: this.queued_read_stories[feed_id],
feed_id: feed_id
}, null, null, {
'ajax_group': 'queue_clear',
'beforeSend': function() {
self.queued_read_stories[feed_id] = [];
}
});
if (NEWSBLUR.Globals.is_authenticated) {
if (!(feed_id in this.queued_read_stories)) { this.queued_read_stories[feed_id] = []; }
this.queued_read_stories[feed_id].push(story_id);
// NEWSBLUR.log(['Marking Read', this.queued_read_stories, story_id]);
this.make_request('/reader/mark_story_as_read', {
story_id: this.queued_read_stories[feed_id],
feed_id: feed_id
}, null, null, {
'ajax_group': 'queue_clear',
'beforeSend': function() {
self.queued_read_stories = {};
}
});
}
}
this.read_stories_river_count += 1;
$.isFunction(callback) && callback(read);
$.isFunction(callback) && callback();
},
mark_social_story_as_read: function(story_id, social_feed_id, callback) {
var self = this;
var story = this.get_story(story_id);
var feed_id = story.story_feed_id;
var social_user_id = this.social_feeds[social_feed_id].user_id;
if (!story.read_status) {
story.read_status = 1;
if (NEWSBLUR.Globals.is_authenticated) {
if (!(social_user_id in this.queued_read_stories)) {
this.queued_read_stories[social_user_id] = {};
}
if (!(feed_id in this.queued_read_stories[social_user_id])) {
this.queued_read_stories[social_user_id][feed_id] = [];
}
this.queued_read_stories[social_user_id][feed_id].push(story_id);
// NEWSBLUR.log(['Marking Read', this.queued_read_stories, story_id]);
this.make_request('/reader/mark_social_stories_as_read', {
users_feeds_stories: $.toJSON(this.queued_read_stories)
}, null, null, {
'ajax_group': 'queue_clear',
'beforeSend': function() {
self.queued_read_stories = {};
}
});
}
}
this.read_stories_river_count += 1;
$.isFunction(callback) && callback();
},
mark_story_as_unread: function(story_id, feed_id, callback) {

View file

@ -662,7 +662,6 @@
if ($story && $story.length) {
// Check for NB-mark above and use that.
if ($story.closest('.NB-mark').length) {
console.log(["NB-mark", $story, $story.closest('.NB-mark')]);
$story = $story.closest('.NB-mark');
}
@ -1119,9 +1118,11 @@
},
load_router: function() {
NEWSBLUR.router = new NEWSBLUR.Router;
var route_found = Backbone.history.start({pushState: true});
this.load_url_next_param(route_found);
if (!NEWSBLUR.router) {
NEWSBLUR.router = new NEWSBLUR.Router;
var route_found = Backbone.history.start({pushState: true});
this.load_url_next_param(route_found);
}
},
make_feed_favicons: function() {
@ -2624,10 +2625,13 @@
mark_story_as_read: function(story_id) {
var self = this;
var $story_title = this.find_story_in_story_titles(story_id);
var feed_id = parseInt($story_title.data('feed_id'), 10) || this.active_feed;
var feed_id = this.active_feed;
this.model.mark_story_as_read(story_id, feed_id, function(read) {
var mark_read_fn = this.model.mark_story_as_read;
if (this.flags.social_view) {
mark_read_fn = this.model.mark_social_story_as_read;
}
mark_read_fn.call(this.model, story_id, feed_id, function(read) {
self.update_read_count(story_id, feed_id, false, read);
});
},
@ -7248,7 +7252,7 @@
this.check_feed_view_scrolled_to_bottom();
}
if (this.flags.river_view &&
if ((this.flags.river_view || this.flags.social_view) &&
!this.model.preference('feed_view_single_story')) {
var story;
if (this.flags.scrolling_by_selecting_story_title) {

View file

@ -3,7 +3,7 @@
<head>
<title>{{ social_profile.feed_title }}</title>
<link rel="alternate" type="application/rss+xml" href="{% url shared-story-feed user.pk user.username %}" title="{{ social_profile.feed_title }} RSS feed">
<link rel="alternate" type="application/rss+xml" href="{% url shared-stories-rss-feed user.pk user.username %}" title="{{ social_profile.feed_title }} RSS feed">
<link rel="shortcut icon" HREF="{{ social_profile.photo_url }}">
<link rel="stylesheet" href="{{ MEDIA_URL }}css/social/social_page.css" type="text/css" media="screen" title="no title" charset="utf-8">
{% if social_profile.custom_css %}
@ -18,7 +18,7 @@
<header class="NB-header">
<h1 class="NB-title">{{ social_profile.feed_title }}</h1>
<div class="NB-header-feed">
<a type="application/rss+xml" href="{% url shared-story-feed user.pk user.username %}">RSS feed for this page</a>
<a type="application/rss+xml" href="{% url shared-stories-rss-feed user.pk user.username %}">RSS feed for this page</a>
</div>
</header>