Adding paging to interactions and activities on dashboard.

This commit is contained in:
Samuel Clay 2012-04-12 11:18:56 -07:00
parent ed88b94712
commit 4612bedb49
14 changed files with 255 additions and 109 deletions

View file

@ -17,6 +17,7 @@ from apps.rss_feeds.models import Feed
from apps.rss_feeds.tasks import NewFeeds
from utils import log as logging
from utils.user_functions import generate_secret_token
from utils.feed_functions import relative_timesince
from vendor.timezones.fields import TimeZoneField
from vendor.paypal.standard.ipn.signals import subscription_signup
from zebra.signals import zebra_webhook_customer_subscription_created
@ -206,54 +207,6 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()}
}) + ('?' + next + '=1' if next else '')
class MInteraction(mongo.Document):
user_id = mongo.IntField()
date = mongo.DateTimeField(default=datetime.datetime.now)
category = mongo.StringField()
title = mongo.StringField()
content = mongo.StringField()
with_user_id = mongo.IntField()
feed_id = mongo.IntField()
content_id = mongo.StringField()
meta = {
'collection': 'interactions',
'indexes': [('user_id', 'date'), 'category'],
'allow_inheritance': False,
'index_drop_dups': True,
'ordering': ['-date'],
}
def __unicode__(self):
user = User.objects.get(pk=self.user_id)
with_user = self.with_user_id and User.objects.get(pk=self.with_user_id)
return "<%s> %s on %s: %s - %s" % (user.username, with_user and with_user.username, self.date,
self.category, self.content and self.content[:20])
@classmethod
def new_follow(cls, follower_user_id, followee_user_id):
cls.objects.create(user_id=followee_user_id,
with_user_id=follower_user_id,
category='follow')
@classmethod
def new_comment_reply(cls, user_id, reply_user_id, reply_content, social_feed_id, story_id):
cls.objects.create(user_id=user_id,
with_user_id=reply_user_id,
category='comment_reply',
content=reply_content,
feed_id=social_feed_id,
content_id=story_id)
@classmethod
def new_reply_reply(cls, user_id, reply_user_id, reply_content, social_feed_id, story_id):
cls.objects.create(user_id=user_id,
with_user_id=reply_user_id,
category='reply_reply',
content=reply_content,
feed_id=social_feed_id,
content_id=story_id)
class MActivity(mongo.Document):
user_id = mongo.IntField()
@ -276,6 +229,22 @@ class MActivity(mongo.Document):
user = User.objects.get(pk=self.user_id)
return "<%s> %s - %s" % (user.username, self.category, self.content and self.content[:20])
@classmethod
def user(cls, user, page=1):
page = max(1, page)
limit = 5
offset = (page-1) * limit
activities_db = cls.objects.filter(user_id=user.pk)[offset:offset+limit+1]
activities = []
for activity_db in activities_db:
activity = activity_db.to_mongo()
activity['date'] = relative_timesince(activity_db.date)
activities.append(activity)
return activities
@classmethod
def new_starred_story(cls, user_id, story_title, story_feed_id, story_id):
cls.objects.create(user_id=user_id,

View file

@ -13,4 +13,5 @@ urlpatterns = patterns('',
url(r'^is_premium/?', views.profile_is_premium, name='profile-is-premium'),
url(r'^paypal_ipn/?', include('paypal.standard.ipn.urls'), name='paypal-ipn'),
url(r'^stripe_form/?', views.stripe_form, name='stripe-form'),
url(r'^activities/?', views.load_activities, name='profile-activities'),
)

View file

@ -9,12 +9,14 @@ from django.template import RequestContext
from django.shortcuts import render_to_response
from django.core.mail import mail_admins
from django.conf import settings
from apps.profile.models import Profile, change_password
from apps.profile.models import Profile, change_password, MActivity
from apps.reader.models import UserSubscription
from apps.profile.forms import StripePlusPaymentForm, PLANS
from apps.social.models import MSocialServices
from utils import json_functions as json
from utils.user_functions import ajax_login_required
from utils.view_functions import render_to
from utils.user_functions import get_user
from vendor.paypal.standard.forms import PayPalPaymentsForm
SINGLE_FIELD_PREFS = ('timezone','feed_pane_size','hide_mobile','send_emails',
@ -247,3 +249,14 @@ def stripe_form(request):
},
context_instance=RequestContext(request)
)
@render_to('reader/activities_module.xhtml')
def load_activities(request):
user = get_user(request)
page = max(1, int(request.REQUEST.get('page', 1)))
activities = MActivity.user(user, page=page)
return {
'activities': activities,
'page': page,
}

View file

@ -14,7 +14,6 @@ from apps.reader.models import UserSubscription, MUserStory
from apps.analyzer.models import MClassifierFeed, MClassifierAuthor, MClassifierTag, MClassifierTitle
from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds, apply_classifier_authors, apply_classifier_tags
from apps.rss_feeds.models import Feed, MStory
from apps.profile.models import MInteraction
from vendor import facebook
from vendor import tweepy
from utils import log as logging
@ -1042,3 +1041,72 @@ class MSocialServices(mongo.Document):
hashlib.md5(user.email).hexdigest()
profile.save()
return profile
class MInteraction(mongo.Document):
user_id = mongo.IntField()
date = mongo.DateTimeField(default=datetime.datetime.now)
category = mongo.StringField()
title = mongo.StringField()
content = mongo.StringField()
with_user_id = mongo.IntField()
feed_id = mongo.IntField()
content_id = mongo.StringField()
meta = {
'collection': 'interactions',
'indexes': [('user_id', 'date'), 'category'],
'allow_inheritance': False,
'index_drop_dups': True,
'ordering': ['-date'],
}
def __unicode__(self):
user = User.objects.get(pk=self.user_id)
with_user = self.with_user_id and User.objects.get(pk=self.with_user_id)
return "<%s> %s on %s: %s - %s" % (user.username, with_user and with_user.username, self.date,
self.category, self.content and self.content[:20])
@classmethod
def user(cls, user, page=1):
page = max(1, page)
limit = 5
offset = (page-1) * limit
interactions_db = cls.objects.filter(user_id=user.pk)[offset:offset+limit+1]
with_user_ids = [i.with_user_id for i in interactions_db if i.with_user_id]
social_profiles = dict((p.user_id, p) for p in MSocialProfile.objects.filter(user_id__in=with_user_ids))
interactions = []
for interaction_db in interactions_db:
interaction = interaction_db.to_mongo()
interaction['photo_url'] = getattr(social_profiles.get(interaction_db.with_user_id), 'photo_url', None)
interaction['with_user'] = social_profiles.get(interaction_db.with_user_id)
interaction['date'] = relative_timesince(interaction_db.date)
interactions.append(interaction)
print len(interactions), len(interactions_db)
return interactions
@classmethod
def new_follow(cls, follower_user_id, followee_user_id):
cls.objects.create(user_id=followee_user_id,
with_user_id=follower_user_id,
category='follow')
@classmethod
def new_comment_reply(cls, user_id, reply_user_id, reply_content, social_feed_id, story_id):
cls.objects.create(user_id=user_id,
with_user_id=reply_user_id,
category='comment_reply',
content=reply_content,
feed_id=social_feed_id,
content_id=story_id)
@classmethod
def new_reply_reply(cls, user_id, reply_user_id, reply_content, social_feed_id, story_id):
cls.objects.create(user_id=user_id,
with_user_id=reply_user_id,
category='reply_reply',
content=reply_content,
feed_id=social_feed_id,
content_id=story_id)

View file

@ -8,6 +8,7 @@ urlpatterns = patterns('',
url(r'^profile/?$', views.profile, name='profile'),
url(r'^load_user_profile/?$', views.load_user_profile, name='load-user-profile'),
url(r'^save_user_profile/?$', views.save_user_profile, name='save-user-profile'),
url(r'^interactions/?$', views.load_interactions, name='social-interactions'),
url(r'^follow/?$', views.follow, name='social-follow'),
url(r'^unfollow/?$', views.unfollow, name='social-unfollow'),
url(r'^feed_trainer', views.social_feed_trainer, name='social-feed-trainer'),

View file

@ -7,12 +7,11 @@ from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.conf import settings
from apps.rss_feeds.models import MStory, Feed, MStarredStory
from apps.social.models import MSharedStory, MSocialServices, MSocialProfile, MSocialSubscription, MCommentReply
from apps.social.models import MRequestInvite
from apps.social.models import MRequestInvite, MInteraction
from apps.analyzer.models import MClassifierTitle, MClassifierAuthor, MClassifierFeed, MClassifierTag
from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds, apply_classifier_authors, apply_classifier_tags
from apps.analyzer.models import get_classifiers_for_user
from apps.reader.models import MUserStory, UserSubscription
from apps.profile.models import MInteraction
from utils import json_functions as json
from utils import log as logging
from utils import PyRSS2Gen as RSS
@ -121,12 +120,14 @@ def load_social_stories(request, user_id, username=None):
story['starred_date'] = format_story_link_date__long(starred_date, now)
if story['id'] in shared_stories:
story['shared'] = True
shared_date = localtime_for_timezone(shared_stories[story['id']]['shared_date'], user.profile.timezone)
shared_date = localtime_for_timezone(shared_stories[story['id']]['shared_date'],
user.profile.timezone)
story['shared_date'] = format_story_link_date__long(shared_date, now)
story['shared_comments'] = shared_stories[story['id']]['comments']
story['intelligence'] = {
'feed': apply_classifier_feeds(classifier_feeds, story['story_feed_id'], social_user_id=social_user_id),
'feed': apply_classifier_feeds(classifier_feeds, story['story_feed_id'],
social_user_id=social_user_id),
'author': apply_classifier_authors(classifier_authors, story),
'tags': apply_classifier_tags(classifier_tags, story),
'title': apply_classifier_titles(classifier_titles, story),
@ -180,7 +181,9 @@ def story_comments(request):
feed_id = int(request.REQUEST['feed_id'])
story_id = request.REQUEST['story_id']
shared_stories = MSharedStory.objects.filter(story_feed_id=feed_id, story_guid=story_id, has_comments=True)
shared_stories = MSharedStory.objects.filter(story_feed_id=feed_id,
story_guid=story_id,
has_comments=True)
comments = [s.comments_with_author() for s in shared_stories]
profile_user_ids = set([c['user_id'] for c in comments])
@ -202,7 +205,9 @@ def mark_story_as_shared(request):
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)
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])
@ -219,7 +224,8 @@ def mark_story_as_shared(request):
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]))
logging.user(request, "~FCUpdating shared story: ~SB~FM%s (~FB%s~FM)" % (
story.story_title[:50], comments[:100]))
story.count_comments()
shared_story.publish_update_to_subscribers()
@ -246,7 +252,9 @@ def save_comment_reply(request):
if not story:
return {'code': -1, 'message': 'Story not found.'}
shared_story = MSharedStory.objects.get(user_id=comment_user_id, story_feed_id=feed_id, story_guid=story_id)
shared_story = MSharedStory.objects.get(user_id=comment_user_id,
story_feed_id=feed_id,
story_guid=story_id)
reply = MCommentReply()
reply.user_id = request.user.pk
reply.publish_date = datetime.datetime.now()
@ -254,7 +262,8 @@ def save_comment_reply(request):
shared_story.replies.append(reply)
shared_story.save()
logging.user(request, "~FCReplying to comment in: ~SB~FM%s (~FB%s~FM)" % (story.story_title[:50], reply_comments[:100]))
logging.user(request, "~FCReplying to comment in: ~SB~FM%s (~FB%s~FM)" % (
story.story_title[:50], reply_comments[:100]))
comment = shared_story.comments_with_author()
profile_user_ids = set([comment['user_id']])
@ -483,7 +492,8 @@ def social_feed_trainer(request):
classifier['feed_tags'] = []
classifier['feed_authors'] = []
logging.user(user, "~FGLoading social trainer on ~SB%s: %s" % (social_user.username, social_profile.title))
logging.user(user, "~FGLoading social trainer on ~SB%s: %s" % (
social_user.username, social_profile.title))
return [classifier]
@ -506,7 +516,8 @@ def load_social_statistics(request, social_user_id, username=None):
# Classifier counts
stats['classifier_counts'] = social_profile.feed_classifier_counts
logging.user(request, "~FBStatistics social: ~SB%s ~FG(%s subs)" % (social_profile.user_id, social_profile.follower_count))
logging.user(request, "~FBStatistics social: ~SB%s ~FG(%s subs)" % (
social_profile.user_id, social_profile.follower_count))
return stats
@ -515,3 +526,14 @@ def load_social_settings(request, social_user_id, username=None):
social_profile = MSocialProfile.objects.get(user_id=social_user_id)
return social_profile.to_json()
@render_to('reader/interactions_module.xhtml')
def load_interactions(request):
user = get_user(request)
page = max(1, int(request.REQUEST.get('page', 1)))
interactions = MInteraction.user(user, page=page)
return {
'interactions': interactions,
'page': page,
}

View file

@ -3961,7 +3961,8 @@ background: transparent;
opacity: .6;
display: none;
}
.NB-module.NB-loading .NB-spinner {
.NB-module.NB-loading .NB-spinner,
.NB-module-item.NB-loading .NB-spinner {
display: block;
}
.NB-module .NB-module-content-header {
@ -4098,7 +4099,7 @@ background: transparent;
margin-top: 0;
overflow: hidden;
}
.NB-module .NB-module-activity .NB-module-content-header {
.NB-module .NB-module-activities .NB-module-content-header {
margin-left: 12px;
padding-right: 12px;
}

View file

@ -965,6 +965,18 @@ NEWSBLUR.AssetModel.Reader.prototype = {
}, callback, error_callback, {request_type: 'GET'});
},
load_interactions_page: function(page, callback, error_callback) {
this.make_request('/social/interactions', {
'page': page
}, callback, error_callback, {request_type: 'GET'});
},
load_activities_page: function(page, callback, error_callback) {
this.make_request('/profile/activities', {
'page': page
}, callback, error_callback, {request_type: 'GET'});
},
approve_feed_in_moderation_queue: function(feed_id, date, callback) {
this.make_request('/recommendations/approve_feed', {
'feed_id' : feed_id,

View file

@ -46,7 +46,9 @@
'fetched_feeds': 0,
'page_fill_outs': 0,
'recommended_feed_page': 0,
'feed_view_positions_timer': 0
'feed_view_positions_timer': 0,
'interactions_page': 1,
'activities_page': 1
};
this.cache = {
'iframe_stories': {},
@ -6542,6 +6544,53 @@
}
},
// ===============================
// = Interactions and Activities =
// ===============================
load_interactions_page: function(direction) {
var self = this;
var $module = $('.NB-module-interactions');
$module.addClass('NB-loading');
direction = direction || 0;
console.log(["loading page", self.counts['interactions_page']+direction]);
this.model.load_interactions_page(this.counts['interactions_page']+direction,
function(resp) {
$module.removeClass('NB-loading');
if (!resp) return;
$module.replaceWith(resp);
$module = $('.NB-module-interactions');
var page = $module[0].className.match(/NB-page-(\d+)/)[1];
console.log(["now on page", page]);
self.counts['interactions_page'] = parseInt(page, 10);
self.load_javascript_elements_on_page();
}, function() {
$module.removeClass('NB-loading');
});
},
load_activities_page: function(direction) {
var self = this;
var $module = $('.NB-module-activities');
$module.addClass('NB-loading');
direction = direction || 0;
this.model.load_activities_page(this.counts['activities_page']+direction,
function(resp) {
$module.removeClass('NB-loading');
if (!resp) return;
$module.replaceWith(resp);
$module = $('.NB-module-activities');
var page = $module[0].className.match(/NB-page-(\d+)/)[1];
self.counts['activities_page'] = parseInt(page, 10);
self.load_javascript_elements_on_page();
}, function() {
$module.removeClass('NB-loading');
});
},
// ========
// = FTUX =
// ========
@ -6833,13 +6882,14 @@
this.model.load_recommended_feed(this.counts['recommended_feed_page']+direction,
!!refresh, unmoderated, function(resp) {
if (!resp) return;
self.counts['recommended_feed_page'] += direction;
$module.removeClass('NB-loading');
if (!resp) return;
$module.replaceWith(resp);
self.counts['recommended_feed_page'] += direction;
self.load_javascript_elements_on_page();
}, $.noop);
}, function() {
$module.removeClass('NB-loading');
});
},
// ====================
@ -6864,11 +6914,13 @@
$module.addClass('NB-loading');
this.model.load_dashboard_graphs(function(resp) {
if (!resp) return;
$module.removeClass('NB-loading');
if (!resp) return;
$module.replaceWith(resp);
self.load_javascript_elements_on_page();
}, $.noop);
}, function() {
$module.removeClass('NB-loading');
});
},
@ -7706,6 +7758,30 @@
var page = $t.prevAll('.NB-module-page-indicator').length;
self.load_howitworks_page(page);
});
$.targetIs(e, { tagSelector: '.NB-module-next-page', childOf: '.NB-module-interactions' }, function($t, $p){
e.preventDefault();
if (!$t.hasClass('NB-disabled')) {
self.load_interactions_page(1);
}
});
$.targetIs(e, { tagSelector: '.NB-module-previous-page', childOf: '.NB-module-interactions' }, function($t, $p){
e.preventDefault();
if (!$t.hasClass('NB-disabled')) {
self.load_interactions_page(-1);
}
});
$.targetIs(e, { tagSelector: '.NB-module-next-page', childOf: '.NB-module-activities' }, function($t, $p){
e.preventDefault();
if (!$t.hasClass('NB-disabled')) {
self.load_activities_page(1);
}
});
$.targetIs(e, { tagSelector: '.NB-module-previous-page', childOf: '.NB-module-activities' }, function($t, $p){
e.preventDefault();
if (!$t.hasClass('NB-disabled')) {
self.load_activities_page(-1);
}
});
$.targetIs(e, { tagSelector: '.NB-splash-meta-about' }, function($t, $p){
e.preventDefault();
NEWSBLUR.about = new NEWSBLUR.About();

View file

@ -1,7 +1,7 @@
express = require 'express'
mongo = require 'mongodb'
MONGODB_SERVER = if process.env.NODE_ENV == 'dev' then 'localhost' else 'db04'
MONGODB_SERVER = if process.env.NODE_ENV == 'development' then 'localhost' else 'db04'
server = new mongo.Server(MONGODB_SERVER, 27017,
auto_reconnect: true
poolSize: 12)

View file

@ -6,7 +6,7 @@
mongo = require('mongodb');
MONGODB_SERVER = process.env.NODE_ENV === 'dev' ? 'localhost' : 'db04';
MONGODB_SERVER = process.env.NODE_ENV === 'development' ? 'localhost' : 'db04';
server = new mongo.Server(MONGODB_SERVER, 27017, {
auto_reconnect: true,

View file

@ -2,13 +2,13 @@
{% if activities %}
<div class="NB-module-item NB-module-activity">
<div class="NB-module-item NB-module-activities NB-page-{{ page }}">
<h3 class="NB-module-content-header">
{# <div class="NB-module-header-right"> #}
{# <div class="NB-spinner NB-left"></div> #}
{# <a href="#" class="NB-module-direction NB-module-next-page NB-javascript {% if activities|length <= 5 %}NB-disabled{% endif %}"></a> #}
{# <a href="#" class="NB-module-direction NB-module-previous-page NB-disabled"></a> #}
{# </div> #}
<div class="NB-module-header-right">
<div class="NB-spinner NB-left"></div>
<a href="#" class="NB-module-direction NB-module-next-page NB-javascript {% if activities|length <= 5 %}NB-disabled{% endif %}"></a>
<a href="#" class="NB-module-direction NB-module-previous-page {% if page <= 1 %}NB-disabled{% endif %}"></a>
</div>
Your Activity
</h3>

View file

@ -1,14 +1,14 @@
{% load utils_tags typogrify_tags statistics_tags %}
{% if interactions %}
<div class="NB-module NB-module-features">
<div class="NB-module NB-module-interactions NB-page-{{ page }}">
<h5 class="NB-module-header">
{# <div class="NB-module-header-right"> #}
{# <div class="NB-spinner NB-left"></div> #}
{# <a href="#" class="NB-module-direction NB-module-next-page NB-javascript {% if interactions|length <= 5 %}NB-disabled{% endif %}"></a> #}
{# <a href="#" class="NB-module-direction NB-module-previous-page NB-disabled"></a> #}
{# </div> #}
<div class="NB-module-header-right">
<div class="NB-spinner NB-left"></div>
<a href="#" class="NB-module-direction NB-module-next-page NB-javascript {% if interactions|length <= 5 %}NB-disabled{% endif %}"></a>
<a href="#" class="NB-module-direction NB-module-previous-page {% if page <= 1 %}NB-disabled{% endif %}"></a>
</div>
Interactions
</h5>

View file

@ -3,10 +3,9 @@ from django.conf import settings
from django import template
from apps.reader.forms import FeatureForm
from apps.reader.models import Feature
from apps.profile.models import MInteraction, MActivity
from apps.social.models import MSocialProfile
from apps.profile.models import MActivity
from apps.social.models import MInteraction
from vendor.timezones.utilities import localtime_for_timezone
from utils.feed_functions import relative_timesince
from utils.user_functions import get_user
register = template.Library()
@ -35,41 +34,25 @@ def render_features_module(context):
}
@register.inclusion_tag('reader/interactions_module.xhtml', takes_context=True)
def render_interactions_module(context):
def render_interactions_module(context, page=1):
user = get_user(context['user'])
interactions_db = MInteraction.objects.filter(user_id=user.pk)[0:5]
with_user_ids = [i.with_user_id for i in interactions_db if i.with_user_id]
social_profiles = dict((p.user_id, p) for p in MSocialProfile.objects.filter(user_id__in=with_user_ids))
interactions = []
for interaction_db in interactions_db:
interaction = interaction_db.to_mongo()
interaction['photo_url'] = getattr(social_profiles.get(interaction_db.with_user_id), 'photo_url', None)
interaction['with_user'] = social_profiles.get(interaction_db.with_user_id)
interaction['date'] = relative_timesince(interaction_db.date)
interactions.append(interaction)
interactions = MInteraction.user(user, page)
return {
'user': user,
'interactions': interactions,
'page': page,
}
@register.inclusion_tag('reader/activities_module.xhtml', takes_context=True)
def render_activities_module(context):
def render_activities_module(context, page=1):
user = get_user(context['user'])
activities_db = MActivity.objects.filter(user_id=user.pk)[:6]
activities = MActivity.user(user, page)
activities = []
for activity_db in activities_db[:5]:
activity = activity_db.to_mongo()
activity['date'] = relative_timesince(activity_db.date)
activities.append(activity)
if len(activities_db) > 5:
activities.append(activities_db[5].to_mongo())
return {
'user': user,
'activities': activities,
'page': page,
}
@register.filter