From 4507f7286a00dafd3db2516a98c4d91f1eded86e Mon Sep 17 00:00:00 2001 From: Samuel Clay Date: Tue, 30 Nov 2010 10:30:18 -0500 Subject: [PATCH] Stars now save to the new StarredStory mongo model. THey are also persistent on the client-side. Now to just make a special starred stories view and counter. --- apps/reader/urls.py | 2 + apps/reader/views.py | 42 ++++++++++++++++++++- apps/rss_feeds/models.py | 65 +++++++++++++++++++++++++++------ media/css/reader.css | 4 +- media/js/newsblur/assetmodel.js | 18 ++++++--- media/js/newsblur/reader.js | 19 ++++++++-- 6 files changed, 126 insertions(+), 24 deletions(-) diff --git a/apps/reader/urls.py b/apps/reader/urls.py index ac515a07e..0228b2838 100644 --- a/apps/reader/urls.py +++ b/apps/reader/urls.py @@ -14,6 +14,8 @@ urlpatterns = patterns('', url(r'^refresh_feeds', views.refresh_feeds, name='refresh-feeds'), 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), + url(r'^mark_story_as_starred', views.mark_story_as_starred), + url(r'^mark_story_as_unstarred', views.mark_story_as_unstarred), url(r'^mark_feed_as_read', views.mark_feed_as_read), url(r'^delete_feed', views.delete_feed, name='delete-feed'), url(r'^delete_folder', views.delete_folder, name='delete-folder'), diff --git a/apps/reader/views.py b/apps/reader/views.py index 00da3dcac..16e598435 100644 --- a/apps/reader/views.py +++ b/apps/reader/views.py @@ -20,7 +20,7 @@ from apps.analyzer.models import get_classifiers_for_user from apps.reader.models import UserSubscription, UserSubscriptionFolders, MUserStory, Feature from apps.reader.forms import SignupForm, LoginForm, FeatureForm try: - from apps.rss_feeds.models import Feed, MFeedPage, DuplicateFeed, MStory, FeedLoadtime + from apps.rss_feeds.models import Feed, MFeedPage, DuplicateFeed, MStory, MStarredStory, FeedLoadtime except: pass from utils import json_functions as json, urlnorm @@ -291,6 +291,9 @@ def load_single_feed(request): userstories_db = MUserStory.objects(user_id=user.pk, feed_id=feed.pk, read_date__gte=usersub.mark_read_date) + starred_stories = MStarredStory.objects(user_id=request.user.pk, story_feed_id=feed_id).only('story_guid') + starred_stories = [story.story_guid for story in starred_stories] + for us in userstories_db: if hasattr(us.story, 'story_guid') and isinstance(us.story.story_guid, unicode): userstories.append(us.story.story_guid) @@ -311,6 +314,8 @@ def load_single_feed(request): story['read_status'] = 1 elif not story.get('read_status') and story['story_date'] > usersub.last_read_date: story['read_status'] = 0 + if story['id'] in starred_stories: + story['starred'] = True story['intelligence'] = { 'feed': apply_classifier_feeds(classifier_feeds, feed), 'author': apply_classifier_authors(classifier_authors, story), @@ -715,4 +720,37 @@ def login_as(request): def iframe_buster(request): logging.info(" ---> [%s] iFrame bust!" % (request.user,)) - return HttpResponse(status=204) \ No newline at end of file + return HttpResponse(status=204) + +@ajax_login_required +@json.json_view +def mark_story_as_starred(request): + code = 1 + feed_id = int(request.POST['feed_id']) + story_id = request.POST['story_id'] + + story = MStory.objects(story_feed_id=feed_id, story_guid=story_id).limit(1) + if story: + story_db = dict([(k, v) for k, v in story[0]._data.items() + if k is not None and v is not None]) + story_values = dict(user_id=request.user.pk, **story_db) + MStarredStory.objects.create(**story_values) + else: + code = -1 + + return {'code': code} + +@ajax_login_required +@json.json_view +def mark_story_as_unstarred(request): + code = 1 + feed_id = int(request.POST['feed_id']) + story_id = request.POST['story_id'] + + starred_story = MStarredStory.objects(user_id=request.user.pk, story_guid=story_id, story_feed_id=feed_id) + if starred_story: + starred_story.delete() + else: + code = -1 + + return {'code': code} \ No newline at end of file diff --git a/apps/rss_feeds/models.py b/apps/rss_feeds/models.py index 1d656e4db..d8e529160 100644 --- a/apps/rss_feeds/models.py +++ b/apps/rss_feeds/models.py @@ -761,20 +761,22 @@ class Story(models.Model): self.story_title = self.story_title[:255] super(Story, self).save(*args, **kwargs) + class MStory(mongo.Document): '''A feed item''' - story_feed_id = mongo.IntField() - story_date = mongo.DateTimeField() - story_title = mongo.StringField(max_length=1024) - story_content = mongo.StringField() - story_content_z = mongo.BinaryField() - story_original_content = mongo.StringField() + story_feed_id = mongo.IntField() + story_date = mongo.DateTimeField() + story_title = mongo.StringField(max_length=1024) + story_content = mongo.StringField() + story_content_z = mongo.BinaryField() + story_original_content = mongo.StringField() story_original_content_z = mongo.BinaryField() - story_content_type = mongo.StringField(max_length=255) - story_author_name = mongo.StringField() - story_permalink = mongo.StringField() - story_guid = mongo.StringField() - story_tags = mongo.ListField(mongo.StringField(max_length=250)) + story_content_type = mongo.StringField(max_length=255) + story_author_name = mongo.StringField() + story_permalink = mongo.StringField() + story_guid = mongo.StringField() + story_tags = mongo.ListField(mongo.StringField(max_length=250)) + meta = { 'collection': 'stories', @@ -791,7 +793,42 @@ class MStory(mongo.Document): self.story_original_content_z = zlib.compress(self.story_original_content) self.story_original_content = None super(MStory, self).save(*args, **kwargs) - + + +class MStarredStory(mongo.Document): + """Like MStory, but not inherited due to large overhead of _cls and _type in + mongoengine's inheritance model on every single row.""" + user_id = mongo.IntField() + story_feed_id = mongo.IntField() + story_date = mongo.DateTimeField() + story_title = mongo.StringField(max_length=1024) + story_content = mongo.StringField() + story_content_z = mongo.BinaryField() + story_original_content = mongo.StringField() + story_original_content_z = mongo.BinaryField() + story_content_type = mongo.StringField(max_length=255) + story_author_name = mongo.StringField() + story_permalink = mongo.StringField() + story_guid = mongo.StringField(unique_with=('user_id',)) + story_tags = mongo.ListField(mongo.StringField(max_length=250)) + + meta = { + 'collection': 'starred_stories', + 'indexes': [('user_id', '-story_date'), 'story_feed_id'], + 'ordering': ['-story_date'], + 'allow_inheritance': False, + } + + def save(self, *args, **kwargs): + if self.story_content: + self.story_content_z = zlib.compress(self.story_content) + self.story_content = None + if self.story_original_content: + self.story_original_content_z = zlib.compress(self.story_original_content) + self.story_original_content = None + super(MStarredStory, self).save(*args, **kwargs) + + class FeedUpdateHistory(models.Model): fetch_date = models.DateTimeField(auto_now=True) number_of_feeds = models.IntegerField() @@ -809,6 +846,7 @@ class FeedUpdateHistory(models.Model): self.average_per_feed = str(self.seconds_taken / float(max(1.0,self.number_of_feeds))) super(FeedUpdateHistory, self).save(*args, **kwargs) + class FeedFetchHistory(models.Model): feed = models.ForeignKey(Feed, related_name='feed_fetch_history') status_code = models.CharField(max_length=10, null=True, blank=True) @@ -826,6 +864,7 @@ class FeedFetchHistory(models.Model): self.exception and self.exception[:50] ) + class MFeedFetchHistory(mongo.Document): feed_id = mongo.IntField() status_code = mongo.IntField() @@ -844,6 +883,7 @@ class MFeedFetchHistory(mongo.Document): self.exception = unicode(self.exception) super(MFeedFetchHistory, self).save(*args, **kwargs) + class PageFetchHistory(models.Model): feed = models.ForeignKey(Feed, related_name='page_fetch_history') status_code = models.CharField(max_length=10, null=True, blank=True) @@ -861,6 +901,7 @@ class PageFetchHistory(models.Model): self.exception and self.exception[:50] ) + class MPageFetchHistory(mongo.Document): feed_id = mongo.IntField() status_code = mongo.IntField() diff --git a/media/css/reader.css b/media/css/reader.css index 8d815fec5..9c2e8ff61 100644 --- a/media/css/reader.css +++ b/media/css/reader.css @@ -1056,8 +1056,10 @@ background: transparent; background: transparent url('../img/reader/star_blue.png') no-repeat 4px 0px; opacity: .25; } -#story_titles .NB-story-star:hover { +#story_titles .NB-story-star:hover, +#story_titles .NB-story-starred .NB-story-star { opacity: 1; + display: block; } #story_titles .story.read { diff --git a/media/js/newsblur/assetmodel.js b/media/js/newsblur/assetmodel.js index b67ff77c5..4b380f3bb 100644 --- a/media/js/newsblur/assetmodel.js +++ b/media/js/newsblur/assetmodel.js @@ -21,6 +21,7 @@ NEWSBLUR.AssetModel.Reader = function() { this.stories = {}; this.read_stories = {}; this.classifiers = {}; + this.starred_stories = []; this.DEFAULT_VIEW = NEWSBLUR.Preferences.default_view || 'page'; }; @@ -122,17 +123,23 @@ NEWSBLUR.AssetModel.Reader.prototype = { $.isFunction(callback) && callback(read); }, + mark_story_as_starred: function(story_id, feed_id, callback) { + var self = this; + this.make_request('/reader/mark_story_as_starred', { + story_id: story_id, + feed_id: feed_id + }, callback); + }, + mark_feed_as_read: function(feed_id, callback) { var self = this; var feed_ids = _.isArray(feed_id) ? _.select(feed_id, function(f) { return f; }) : [feed_id]; - this.make_request('/reader/mark_feed_as_read', - { - feed_id: feed_ids - }, callback - ); + this.make_request('/reader/mark_feed_as_read', { + feed_id: feed_ids + }, callback); }, load_feeds: function(callback, error_callback) { @@ -191,6 +198,7 @@ NEWSBLUR.AssetModel.Reader.prototype = { this.feed_authors = data.feed_authors; this.feed_id = feed_id; this.classifiers = data.classifiers; + this.starred_stories = data.starred_stories; this.story_keys = []; for (var s in data.stories) { this.story_keys.push(data.stories[s].id); diff --git a/media/js/newsblur/reader.js b/media/js/newsblur/reader.js index 997ce3521..9f2c49ee5 100644 --- a/media/js/newsblur/reader.js +++ b/media/js/newsblur/reader.js @@ -1697,6 +1697,16 @@ NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifierStory(story_id, feed_id, {'score': -1}); }, + mark_story_as_starred: function(story_id, $button) { + // $button.attr({'title': 'Saving...'}); + $button.tipsy({'title': 'Saving...'}); + $button.closest('.story').addClass('NB-story-starred'); + this.model.mark_story_as_starred(story_id, this.active_feed, function() { + // $button.attr({'title': 'Saved!'}); + $button.tipsy({'title': 'Saved!'}); + }); + }, + // ===================== // = Story Titles Pane = // ===================== @@ -1745,10 +1755,11 @@ make_story_title: function(story) { var unread_view = this.model.preference('unread_view'); var read = story.read_status - ? 'read' + ? ' read ' : ''; var score = this.compute_story_score(story); var score_color = 'neutral'; + var starred = story.starred ? ' NB-story-starred ' : ''; if (score > 0) score_color = 'positive'; if (score < 0) score_color = 'negative'; var $story_tags = $.make('span', { className: 'NB-storytitles-tags'}); @@ -1759,7 +1770,7 @@ $story_tags.append($tag); break; } - var $story_title = $.make('div', { className: 'story ' + read + ' NB-story-' + score_color }, [ + var $story_title = $.make('div', { className: 'story' + read + starred + 'NB-story-' + score_color }, [ $.make('a', { href: story.story_permalink, className: 'story_title' }, [ $.make('span', { className: 'NB-storytitles-title' }, story.story_title), $.make('span', { className: 'NB-storytitles-author' }, story.story_authors), @@ -3498,10 +3509,10 @@ self.mark_story_as_like(story_id, $t); story_prevent_bubbling = true; }); - $.targetIs(e, { tagSelector: '.NB-story-dislike' }, function($t, $p){ + $.targetIs(e, { tagSelector: '.NB-story-star' }, function($t, $p){ e.preventDefault(); var story_id = $t.parents('.story').data('story_id'); - self.mark_story_as_dislike(story_id, $t); + self.mark_story_as_starred(story_id, $t); story_prevent_bubbling = true; }); $.targetIs(e, { tagSelector: 'a.button.like' }, function($t, $p){