Adding moderation queue to recommended feeds (finally!)

This commit is contained in:
Samuel Clay 2011-07-11 18:22:28 -07:00
parent 8d46754c99
commit ae7f79b3a0
14 changed files with 189 additions and 6822 deletions

View file

@ -63,6 +63,7 @@ def index(request):
active_count = UserSubscription.objects.filter(user=request.user, active=True).count() if authed else 0
train_count = UserSubscription.objects.filter(user=request.user, active=True, is_trained=False, feed__stories_last_month__gte=1).count() if authed else 0
recommended_feeds = RecommendedFeed.objects.filter(is_public=True, approved_date__lte=datetime.datetime.now()).select_related('feed')[:2]
unmoderated_feeds = RecommendedFeed.objects.filter(is_public=False, declined_date__isnull=True).select_related('feed')[:2]
statistics = MStatistics.all()
feedbacks = MFeedback.all()
@ -77,6 +78,7 @@ def index(request):
'train_count' : active_count - train_count,
'account_images' : range(1, 4),
'recommended_feeds' : recommended_feeds,
'unmoderated_feeds' : unmoderated_feeds,
'statistics' : statistics,
'feedbacks' : feedbacks,
'start_import_from_google_reader': request.session.get('import_from_google_reader', False),

View file

@ -7,7 +7,7 @@ from apps.rss_feeds.models import MFeedIcon
register = template.Library()
@register.inclusion_tag('recommendations/render_recommended_feed.xhtml', takes_context=True)
def render_recommended_feed(context, recommended_feeds):
def render_recommended_feed(context, recommended_feeds, unmoderated=False):
user = get_user(context['user'])
usersub = None
@ -18,11 +18,12 @@ def render_recommended_feed(context, recommended_feeds):
if recommended_feed:
return {
'recommended_feed': recommended_feed,
'description': recommended_feed.description or recommended_feed.feed.data.feed_tagline,
'usersub': usersub,
'feed_icon': feed_icon and feed_icon[0],
'user': context['user'],
'has_next_page': len(recommended_feeds) > 1
'recommended_feed' : recommended_feed,
'description' : recommended_feed.description or recommended_feed.feed.data.feed_tagline,
'usersub' : usersub,
'feed_icon' : feed_icon and feed_icon[0],
'user' : context['user'],
'has_next_page' : len(recommended_feeds) > 1,
'unmoderated' : unmoderated
}

View file

@ -4,5 +4,7 @@ from apps.recommendations import views
urlpatterns = patterns('',
url(r'^load_recommended_feed', views.load_recommended_feed, name='load-recommended-feed'),
url(r'^save_recommended_feed', views.save_recommended_feed, name='save-recommended-feed'),
url(r'^approve_feed', views.approve_feed, name='approve-recommended-feed'),
url(r'^decline_feed', views.decline_feed, name='decline-recommended-feed'),
url(r'^load_feed_info/(?P<feed_id>\d+)', views.load_feed_info, name='load-recommended-feed-info'),
)

View file

@ -7,17 +7,21 @@ from apps.recommendations.models import RecommendedFeed
from apps.reader.models import UserSubscription
from apps.rss_feeds.models import Feed, MFeedIcon
from utils import json_functions as json
from utils.user_functions import get_user, ajax_login_required
from utils.user_functions import get_user, ajax_login_required, admin_only
def load_recommended_feed(request):
user = get_user(request)
page = int(request.REQUEST.get('page', 0))
usersub = None
refresh = request.REQUEST.get('refresh')
now = datetime.datetime.now
user = get_user(request)
page = int(request.REQUEST.get('page', 0))
usersub = None
refresh = request.REQUEST.get('refresh')
now = datetime.datetime.now
unmoderated = request.REQUEST.get('unmoderated', False) == 'true'
recommended_feeds = RecommendedFeed.objects.filter(is_public=True, approved_date__lte=now)[page:page+2]
if unmoderated:
recommended_feeds = RecommendedFeed.objects.filter(is_public=False, declined_date__isnull=True)[page:page+2]
else:
recommended_feeds = RecommendedFeed.objects.filter(is_public=True, approved_date__lte=now)[page:page+2]
if recommended_feeds and request.user.is_authenticated():
usersub = UserSubscription.objects.filter(user=user, feed=recommended_feeds[0].feed)
if refresh != 'true' and page > 0:
@ -34,6 +38,7 @@ def load_recommended_feed(request):
'feed_icon' : feed_icon and feed_icon[0],
'has_next_page' : len(recommended_feeds) > 1,
'has_previous_page' : page != 0,
'unmoderated' : unmoderated,
}, context_instance=RequestContext(request))
else:
return HttpResponse("")
@ -71,4 +76,31 @@ def save_recommended_feed(request):
)
)
return dict(code=code if created else -1)
return dict(code=code if created else -1)
@admin_only
@ajax_login_required
def approve_feed(request):
feed_id = request.POST['feed_id']
feed = get_object_or_404(Feed, pk=int(feed_id))
recommended_feed = RecommendedFeed.objects.filter(feed=feed)[0]
recommended_feed.is_public = True
recommended_feed.approved_date = datetime.datetime.now()
recommended_feed.save()
return load_recommended_feed(request)
@admin_only
@ajax_login_required
def decline_feed(request):
feed_id = request.POST['feed_id']
feed = get_object_or_404(Feed, pk=int(feed_id))
recommended_feeds = RecommendedFeed.objects.filter(feed=feed)
for recommended_feed in recommended_feeds:
recommended_feed.is_public = False
recommended_feed.declined_date = datetime.datetime.now()
recommended_feed.save()
return load_recommended_feed(request)

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,9 @@
/* ========== */
/* = Global = */
/* ========== */
body {
overflow-y: scroll !important;
}
.NB-favicon {
width: 16px;
height: 16px;

File diff suppressed because it is too large Load diff

View file

@ -49,7 +49,7 @@
load_feeds: function() {
this.flags.active_view = 'feeds';
$.mobile.pageLoading();
$.mobile.showPageLoadingMsg();
this.pages.feeds.unbind('pagebeforeshow').bind('pagebeforeshow', _.bind(function(e) {
$('ul', this.$s.$feed_list).listview('refresh');
@ -84,7 +84,7 @@
this.flags.feeds_loaded = true;
$feed_list.html($feeds);
$('ul', $feed_list).listview();
$.mobile.pageLoading(true);
$.mobile.hidePageLoadingMsg();
},
make_feed_title: function(feed_id) {
@ -135,7 +135,7 @@
load_stories: function(feed_id) {
this.flags.active_view = 'stories';
$.mobile.pageLoading();
$.mobile.showPageLoadingMsg();
this.active_feed = feed_id;
this.model.load_feed(feed_id, this.page, this.page == 1, _.bind(this.build_stories, this));
},
@ -163,7 +163,7 @@
}
$('ul', $story_list).listview();
$.mobile.pageLoading(true);
$.mobile.hidePageLoadingMsg();
},
load_next_page_of_stories: function() {
@ -219,7 +219,7 @@
load_story_detail: function(story_id) {
this.flags.active_view = 'story_detail';
$.mobile.pageLoading();
$.mobile.showPageLoadingMsg();
var $story_detail_view = this.$s.$story_detail;
var story = this.model.get_story(story_id);
@ -229,7 +229,7 @@
this.colorize_story_title(story);
$('.ul-li-right', this.pages.story).jqmData('icon', 'NB-'+score_color);
$story_detail_view.html($story);
$.mobile.pageLoading(true);
$.mobile.hidePageLoadingMsg();
this.mark_story_as_read(story);
},
@ -301,12 +301,16 @@
},
mark_story_as_read: function(story) {
var story_id = story.id;
var feed_id = story.story_feed_id;
var story_id = story.id;
var feed_id = story.story_feed_id;
this.model.mark_story_as_read(story_id, feed_id, _.bind(function(read) {
this.update_read_count(story_id, feed_id, false, read);
}, this));
this.model.mark_story_as_read(story_id, feed_id, _.bind(function(read) {
this.update_read_count(story_id, feed_id);
}, this));
},
update_read_count: function(story_id, feed_id) {
},
// ==========
@ -319,7 +323,7 @@
this.$s.$feed_list.delegate('li a', 'tap', function(e) {
e.preventDefault();
var feed_id = $(e.currentTarget).jqmData('feed-id');
$.mobile.changePage('stories');
$.mobile.changePage(self.pages.stories);
self.reset_feed();
self.load_stories(feed_id);
});
@ -327,16 +331,16 @@
this.$s.$story_list.delegate('li a', 'tap', function(e) {
e.preventDefault();
var story_id = $(e.currentTarget).jqmData('story-id');
$.mobile.pageLoading();
$.mobile.changePage('story');
$.mobile.showPageLoadingMsg();
$.mobile.changePage(self.pages.story);
self.load_story_detail(story_id);
});
this.$s.$story_detail.delegate('li a', 'tap', function(e) {
e.preventDefault();
var story_id = $(e.currentTarget).jqmData('story-id');
$.mobile.pageLoading();
$.mobile.changePage('story');
$.mobile.showPageLoadingMsg();
$.mobile.changePage(self.pages.story);
self.load_story_detail(story_id);
});

View file

@ -725,13 +725,28 @@ NEWSBLUR.AssetModel.Reader.prototype = {
this.make_request('/reader/features', {'page': page}, callback, callback, {request_type: 'GET'});
},
load_recommended_feed: function(page, refresh, callback, error_callback) {
load_recommended_feed: function(page, refresh, unmoderated, callback, error_callback) {
this.make_request('/recommendations/load_recommended_feed', {
'page': page,
'refresh': refresh
'page' : page,
'refresh' : refresh,
'unmoderated' : unmoderated
}, callback, error_callback, {request_type: 'GET'});
},
approve_feed_in_moderation_queue: function(feed_id, callback) {
this.make_request('/recommendations/approve_feed', {
'feed_id' : feed_id,
'unmoderated' : true
}, callback, {request_type: 'GET'});
},
decline_feed_in_moderation_queue: function(feed_id, callback) {
this.make_request('/recommendations/decline_feed', {
'feed_id' : feed_id,
'unmoderated' : true
}, callback, {request_type: 'GET'});
},
load_dashboard_graphs: function(callback, error_callback) {
this.make_request('/statistics/dashboard_graphs', {}, callback, error_callback, {request_type: 'GET'});
},

View file

@ -5042,13 +5042,43 @@
this.open_add_feed_modal({url: feed_address});
},
load_recommended_feed: function(direction, refresh) {
approve_feed_in_moderation_queue: function(feed_id) {
var self = this;
var $module = $('.NB-module-recommended');
var $module = $('.NB-module-recommended.NB-recommended-unmoderated');
$module.addClass('NB-loading');
this.model.approve_feed_in_moderation_queue(feed_id, function(resp) {
if (!resp) return;
$module.removeClass('NB-loading');
$module.replaceWith(resp);
self.load_javascript_elements_on_page();
});
},
decline_feed_in_moderation_queue: function(feed_id) {
var self = this;
var $module = $('.NB-module-recommended.NB-recommended-unmoderated');
$module.addClass('NB-loading');
this.model.decline_feed_in_moderation_queue(feed_id, function(resp) {
if (!resp) return;
$module.removeClass('NB-loading');
$module.replaceWith(resp);
self.load_javascript_elements_on_page();
});
},
load_recommended_feed: function(direction, refresh, unmoderated) {
var self = this;
var $module = unmoderated ?
$('.NB-module-recommended.NB-recommended-unmoderated') :
$('.NB-module-recommended:not(.NB-recommended-unmoderated)');
$module.addClass('NB-loading');
direction = direction || 0;
this.model.load_recommended_feed(this.counts['recommended_feed_page']+direction, !!refresh, function(resp) {
this.model.load_recommended_feed(this.counts['recommended_feed_page']+direction,
!!refresh, unmoderated, function(resp) {
if (!resp) return;
self.counts['recommended_feed_page'] += direction;
@ -5561,17 +5591,33 @@
});
});
$.targetIs(e, { tagSelector: '.NB-recommended-decline' }, function($t, $p){
e.preventDefault();
var feed_id = $t.closest('.NB-recommended').attr('data-feed-id');
self.decline_feed_in_moderation_queue(feed_id);
});
$.targetIs(e, { tagSelector: '.NB-recommended-approve' }, function($t, $p){
e.preventDefault();
var feed_id = $t.closest('.NB-recommended').attr('data-feed-id');
self.approve_feed_in_moderation_queue(feed_id);
});
$.targetIs(e, { tagSelector: '.NB-module-recommended .NB-module-next-page' }, function($t, $p){
e.preventDefault();
if (!$t.hasClass('NB-disabled')) {
self.load_recommended_feed(1);
console.log(['parent', $t.closest('.NB-module-recommended'), $t.closest('.NB-module-recommended').hasClass('NB-recommended-unmoderated')]);
var unmoderated = $t.closest('.NB-module-recommended').hasClass('NB-recommended-unmoderated');
self.load_recommended_feed(1, false, unmoderated);
}
});
$.targetIs(e, { tagSelector: '.NB-module-recommended .NB-module-previous-page' }, function($t, $p){
e.preventDefault();
if (!$t.hasClass('NB-disabled')) {
self.load_recommended_feed(-1);
var unmoderated = $t.closest('.NB-module-recommended').hasClass('NB-recommended-unmoderated');
console.log(['parent', $t.closest('.NB-module-recommended')]);
self.load_recommended_feed(-1, false, unmoderated);
}
});

View file

@ -208,7 +208,7 @@ COMPRESS_JS = {
'mobile': {
'source_filenames': (
'js/jquery-1.6.1.js',
'js/mobile/jquery.mobile-1.0a4.js',
'js/mobile/jquery.mobile-1.0b1.js',
'js/jquery.ajaxmanager.3.js',
'js/underscore.js',
'js/inflector.js',
@ -253,7 +253,7 @@ COMPRESS_CSS = {
},
'mobile': {
'source_filenames': (
'css/mobile/jquery.mobile-1.0a4.css',
'css/mobile/jquery.mobile-1.0b1.css',
'css/mobile/mobile.css',
),
'output_filename': 'css/mobile/mobile-compressed-?.css',

View file

@ -83,6 +83,11 @@ $(document).ready(function() {
{% if recommended_feeds %}
{% render_recommended_feed recommended_feeds %}
{% endif %}
{% if user.is_staff and unmoderated_feeds %}
{% render_recommended_feed unmoderated_feeds 1 %}
{% endif %}
</div>

View file

@ -1,6 +1,10 @@
<div class="NB-module-recommended NB-module">
<div class="NB-module-recommended NB-module {% if unmoderated %}NB-recommended-unmoderated{% endif %}">
<h5 class="NB-module-header">
Recommended Site
{% if unmoderated %}
Moderation Queue
{% else %}
Recommended Site
{% endif %}
<div class="NB-module-header-right">
{% if has_next_page or has_previous_page %}
<a href="#" class="NB-module-direction NB-module-next-page NB-javascript {% if not has_next_page %}NB-disabled{% endif %}"></a>
@ -8,9 +12,15 @@
{% endif %}
<div class="NB-spinner NB-left"></div>
<div class="NB-module-recommended-date">
{{ recommended_feed.approved_date|date:"M j" }}
<span>{{ recommended_feed.approved_date|date:"S" }}</span>,
{{ recommended_feed.approved_date|date:"Y" }}
{% if unmoderated %}
{{ recommended_feed.created_date|date:"M j" }}
<span>{{ recommended_feed.created_date|date:"S" }}</span>,
{{ recommended_feed.created_date|date:"Y" }}
{% else %}
{{ recommended_feed.approved_date|date:"M j" }}
<span>{{ recommended_feed.approved_date|date:"S" }}</span>,
{{ recommended_feed.approved_date|date:"Y" }}
{% endif %}
</div>
</div>
</h5>
@ -34,6 +44,10 @@
<div class="NB-recommended-try NB-modal-submit-green NB-modal-submit-button NB-javascript">Try</div>
<div class="NB-recommended-add NB-modal-submit-close NB-modal-submit-button NB-javascript">Add</div>
{% endif %}
{% if unmoderated %}
<div class="NB-recommended-approve NB-modal-submit-green NB-modal-submit-button NB-javascript">Approve</div>
<div class="NB-recommended-decline NB-modal-submit-close NB-modal-submit-button NB-javascript">Decline</div>
{% endif %}
</div>
</div>

View file

@ -27,6 +27,25 @@ def ajax_login_required(function=None):
else:
return _dec(function)
def admin_only(function=None):
def _dec(view_func):
def _view(request, *args, **kwargs):
if not request.user.is_staff:
return HttpResponseForbidden()
else:
return view_func(request, *args, **kwargs)
_view.__name__ = view_func.__name__
_view.__dict__ = view_func.__dict__
_view.__doc__ = view_func.__doc__
return _view
if function is None:
return _dec
else:
return _dec(function)
def get_user(request):
if not hasattr(request, 'user'):
user = request