diff --git a/apps/reader/models.py b/apps/reader/models.py index dc68469a6..a548a309d 100644 --- a/apps/reader/models.py +++ b/apps/reader/models.py @@ -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 diff --git a/apps/reader/urls.py b/apps/reader/urls.py index a750440d1..221aee675 100644 --- a/apps/reader/urls.py +++ b/apps/reader/urls.py @@ -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), diff --git a/apps/reader/views.py b/apps/reader/views.py index 34b50ebbc..890fe4fc6 100644 --- a/apps/reader/views.py +++ b/apps/reader/views.py @@ -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 diff --git a/apps/social/models.py b/apps/social/models.py index 05a71049b..c02822ccd 100644 --- a/apps/social/models.py +++ b/apps/social/models.py @@ -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() diff --git a/apps/social/views.py b/apps/social/views.py index 507c53a8a..b44b7802d 100644 --- a/apps/social/views.py +++ b/apps/social/views.py @@ -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) diff --git a/media/js/newsblur/common/assetmodel.js b/media/js/newsblur/common/assetmodel.js index 98b9be2a0..cdc821773 100644 --- a/media/js/newsblur/common/assetmodel.js +++ b/media/js/newsblur/common/assetmodel.js @@ -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) { diff --git a/media/js/newsblur/reader/reader.js b/media/js/newsblur/reader/reader.js index aadf8dc13..339bdeeb6 100644 --- a/media/js/newsblur/reader/reader.js +++ b/media/js/newsblur/reader/reader.js @@ -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) { diff --git a/templates/social/social_page.xhtml b/templates/social/social_page.xhtml index e34637a61..8d0f69f0a 100644 --- a/templates/social/social_page.xhtml +++ b/templates/social/social_page.xhtml @@ -3,7 +3,7 @@