NewsBlur/apps/social/views.py

569 lines
25 KiB
Python
Raw Normal View History

import time
import datetime
import zlib
from django.shortcuts import get_object_or_404
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.template.loader import render_to_string
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, MInteraction, MActivity
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 utils import json_functions as json
from utils import log as logging
from utils import PyRSS2Gen as RSS
from utils.user_functions import get_user, ajax_login_required
from utils.view_functions import render_to
from utils.story_functions import format_story_link_date__short
from utils.story_functions import format_story_link_date__long
from vendor.timezones.utilities import localtime_for_timezone
2012-04-05 13:51:08 -07:00
@json.json_view
def request_invite(request):
if not request.POST.get('username'):
return {}
2012-04-05 13:51:08 -07:00
MRequestInvite.objects.create(username=request.POST['username'])
logging.user(request, " ---> ~BG~FB~SBInvite requested: %s" % request.POST['username'])
return {}
@json.json_view
def load_social_stories(request, user_id, username=None):
start = time.time()
user = get_user(request)
social_user_id = int(user_id)
social_user = get_object_or_404(User, pk=social_user_id)
offset = int(request.REQUEST.get('offset', 0))
limit = int(request.REQUEST.get('limit', 6))
page = request.REQUEST.get('page')
if page: offset = limit * (int(page) - 1)
now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone)
UNREAD_CUTOFF = datetime.datetime.utcnow() - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
mstories = MSharedStory.objects(user_id=social_user.pk).order_by('-shared_date')[offset:offset+limit]
stories = Feed.format_stories(mstories)
if not stories:
return dict(stories=[])
stories, user_profiles = MSharedStory.stories_with_comments_and_profiles(stories, user, check_all=True)
story_feed_ids = list(set(s['story_feed_id'] for s in stories))
try:
socialsub = MSocialSubscription.objects.get(user_id=user.pk, subscription_user_id=social_user_id)
except MSocialSubscription.DoesNotExist:
socialsub = None
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)
unsub_feed_ids = list(set(story_feed_ids).difference(set(usersubs_map.keys())))
unsub_feeds = Feed.objects.filter(pk__in=unsub_feed_ids)
unsub_feeds = dict((feed.pk, feed.canonical(include_favicon=False)) for feed in unsub_feeds)
date_delta = UNREAD_CUTOFF
if socialsub and date_delta < socialsub.mark_read_date:
date_delta = socialsub.mark_read_date
# Get intelligence classifier for user
classifier_feeds = list(MClassifierFeed.objects(user_id=user.pk, social_user_id=social_user_id))
classifier_authors = list(MClassifierAuthor.objects(user_id=user.pk, social_user_id=social_user_id))
classifier_titles = list(MClassifierTitle.objects(user_id=user.pk, social_user_id=social_user_id))
classifier_tags = list(MClassifierTag.objects(user_id=user.pk, social_user_id=social_user_id))
# Merge with feed specific classifiers
classifier_feeds = classifier_feeds + list(MClassifierFeed.objects(user_id=user.pk, feed_id__in=story_feed_ids))
classifier_authors = classifier_authors + list(MClassifierAuthor.objects(user_id=user.pk, feed_id__in=story_feed_ids))
classifier_titles = classifier_titles + list(MClassifierTitle.objects(user_id=user.pk, feed_id__in=story_feed_ids))
classifier_tags = classifier_tags + list(MClassifierTag.objects(user_id=user.pk, feed_id__in=story_feed_ids))
story_ids = [story['id'] for story in stories]
userstories_db = MUserStory.objects(user_id=user.pk,
feed_id__in=story_feed_ids,
story_id__in=story_ids).only('story_id')
userstories = set(us.story_id for us in userstories_db)
starred_stories = MStarredStory.objects(user_id=user.pk,
story_feed_id__in=story_feed_ids,
story_guid__in=story_ids).only('story_guid', 'starred_date')
shared_stories = MSharedStory.objects(user_id=user.pk,
story_feed_id__in=story_feed_ids,
story_guid__in=story_ids)\
.only('story_guid', 'shared_date', 'comments')
starred_stories = dict([(story.story_guid, story.starred_date) for story in starred_stories])
shared_stories = dict([(story.story_guid, dict(shared_date=story.shared_date, comments=story.comments))
for story in shared_stories])
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)
shared_date = localtime_for_timezone(story['shared_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(shared_date, now)
story['long_parsed_date'] = format_story_link_date__long(shared_date, now)
if story['id'] in userstories:
story['read_status'] = 1
elif story['shared_date'] < date_delta:
story['read_status'] = 1
elif not usersubs_map.get(story_feed_id):
story['read_status'] = 0
elif not story.get('read_status') and story['story_date'] < usersubs_map[story_feed_id].mark_read_date:
story['read_status'] = 1
elif not story.get('read_status') and story['shared_date'] < date_delta:
story['read_status'] = 1
# elif not story.get('read_status') and socialsub and story['shared_date'] > socialsub.last_read_date:
# story['read_status'] = 0
else:
story['read_status'] = 0
# print story['read_status'], story['shared_date'], date_delta
if story['id'] in starred_stories:
story['starred'] = True
starred_date = localtime_for_timezone(starred_stories[story['id']], user.profile.timezone)
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)
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),
'author': apply_classifier_authors(classifier_authors, story),
'tags': apply_classifier_tags(classifier_tags, story),
'title': apply_classifier_titles(classifier_titles, story),
}
if socialsub:
socialsub.feed_opens += 1
socialsub.save()
end = time.time()
logging.user(request, "~FCLoading shared stories: ~SB%s stories ~SN(%.2f sec)" % (len(stories), end-start))
return dict(stories=stories, user_profiles=user_profiles, feeds=unsub_feeds)
@render_to('social/social_page.xhtml')
def load_social_page(request, user_id, username=None):
user = get_user(request)
social_user_id = int(user_id)
social_user = get_object_or_404(User, pk=social_user_id)
offset = int(request.REQUEST.get('offset', 0))
limit = int(request.REQUEST.get('limit', 12))
page = request.REQUEST.get('page')
if page: offset = limit * (int(page) - 1)
mstories = MSharedStory.objects(user_id=social_user.pk).order_by('-shared_date')[offset:offset+limit]
stories = Feed.format_stories(mstories)
if not stories:
return dict(stories=[])
story_feed_ids = list(set(s['story_feed_id'] for s in stories))
feeds = Feed.objects.filter(pk__in=story_feed_ids)
feeds = dict((feed.pk, feed.canonical(include_favicon=False)) for feed in feeds)
for story in stories:
if story['story_feed_id'] in feeds:
# Feed could have been deleted.
story['feed'] = feeds[story['story_feed_id']]
shared_date = localtime_for_timezone(story['shared_date'], social_user.profile.timezone)
story['shared_date'] = shared_date
2012-02-22 09:48:45 -08:00
stories, profiles = MSharedStory.stories_with_comments_and_profiles(stories, user, check_all=True)
social_profile = MSocialProfile.objects.get(user_id=social_user_id)
params = {
'user': user,
'social_user': social_user,
'stories': stories,
'social_profile': social_profile.page(),
'feeds': feeds,
}
return params
@json.json_view
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)
comments = [s.comments_with_author() for s in shared_stories]
profile_user_ids = set([c['user_id'] for c in comments])
profile_user_ids = profile_user_ids.union([r['user_id'] for c in comments for r in c['replies']])
profiles = MSocialProfile.objects.filter(user_id__in=list(profile_user_ids))
profiles = [profile.to_json(compact=True) for profile in profiles]
return {'comments': comments, 'user_profiles': profiles}
@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', '')
source_user_id = request.POST.get('source_user_id')
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. Reload this site.'}
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)
2012-04-03 19:24:02 -07:00
shared_story = MSharedStory.objects.create(**story_values)
shared_story.set_source_user_id(source_user_id)
socialsubs = MSocialSubscription.objects.filter(subscription_user_id=request.user.pk)
for socialsub in socialsubs:
socialsub.needs_unread_recalc = True
socialsub.save()
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()
2012-04-03 19:24:02 -07:00
shared_story.publish_update_to_subscribers()
story = Feed.format_story(story)
stories, profiles = MSharedStory.stories_with_comments_and_profiles([story], request.user)
story = stories[0]
return {'code': code, 'story': story, 'user_profiles': profiles}
@ajax_login_required
@json.json_view
def save_comment_reply(request):
code = 1
feed_id = int(request.POST['story_feed_id'])
story_id = request.POST['story_id']
comment_user_id = request.POST['comment_user_id']
reply_comments = request.POST.get('reply_comments')
if not reply_comments:
return {'code': -1, 'message': 'Reply comments cannot be empty.'}
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. Reload this site.'}
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()
reply.comments = reply_comments
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]))
comment = shared_story.comments_with_author()
profile_user_ids = set([comment['user_id']])
reply_user_ids = [reply['user_id'] for reply in comment['replies']]
profile_user_ids = profile_user_ids.union(reply_user_ids)
profiles = MSocialProfile.objects.filter(user_id__in=list(profile_user_ids))
profiles = [profile.to_json(compact=True) for profile in profiles]
# Interaction for every other replier and original commenter
MActivity.new_comment_reply(user_id=request.user.pk,
comment_user_id=comment['user_id'],
reply_content=reply_comments,
story_feed_id=feed_id,
story_id=story_id)
if comment['user_id'] != request.user.pk:
MInteraction.new_comment_reply(user_id=comment['user_id'],
reply_user_id=request.user.pk,
reply_content=reply_comments,
social_feed_id=comment_user_id,
story_id=story_id)
for user_id in set(reply_user_ids).difference([comment['user_id']]):
if request.user.pk != user_id:
MInteraction.new_reply_reply(user_id=user_id,
reply_user_id=request.user.pk,
reply_content=reply_comments,
social_feed_id=comment_user_id,
story_id=story_id)
return {'code': code, 'comment': comment, 'user_profiles': profiles}
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))
@ajax_login_required
@json.json_view
def profile(request):
user_id = request.GET.get('user_id', request.user.pk)
user_profile = MSocialProfile.objects.get(user_id=user_id)
user_profile = user_profile.to_json(full=True, common_follows_with_user=request.user.pk)
profile_ids = set(user_profile['followers_youknow'] + user_profile['followers_everybody'] +
user_profile['following_youknow'] + user_profile['following_everybody'])
profiles = MSocialProfile.profiles(profile_ids)
activities = MActivity.user(user_id, page=1, public=True)
activities_html = render_to_string('reader/activities_module.xhtml', {
'activities': activities,
'username': user_profile['username'],
'public': True,
})
logging.user(request, "~BB~FRLoading social profile: %s" % user_profile['username'])
payload = {
'user_profile': user_profile,
# XXX TODO: Remove following 4 vestigial params.
'followers_youknow': user_profile['followers_youknow'],
'followers_everybody': user_profile['followers_everybody'],
'following_youknow': user_profile['following_youknow'],
'following_everybody': user_profile['following_everybody'],
'profiles': dict([(p.user_id, p.to_json(compact=True)) for p in profiles]),
'activities': activities,
'activities_html': activities_html,
}
return payload
@ajax_login_required
@json.json_view
def load_user_profile(request):
social_profile, _ = MSocialProfile.objects.get_or_create(user_id=request.user.pk)
social_services, _ = MSocialServices.objects.get_or_create(user_id=request.user.pk)
return {
'services': social_services,
'user_profile': social_profile.to_json(full=True),
}
@ajax_login_required
@json.json_view
def save_user_profile(request):
data = request.POST
profile, _ = MSocialProfile.objects.get_or_create(user_id=request.user.pk)
profile.location = data['location']
profile.bio = data['bio']
profile.website = data['website']
profile.save()
social_services = MSocialServices.objects.get(user_id=request.user.pk)
profile = social_services.set_photo(data['photo_service'])
logging.user(request, "~BB~FRSaving social profile")
return dict(code=1, user_profile=profile.to_json(full=True))
@ajax_login_required
@json.json_view
def load_user_friends(request):
social_profile, _ = MSocialProfile.objects.get_or_create(user_id=request.user.pk)
social_services, _ = MSocialServices.objects.get_or_create(user_id=request.user.pk)
following_profiles = MSocialProfile.profiles(social_profile.following_user_ids)
follower_profiles = MSocialProfile.profiles(social_profile.follower_user_ids)
return {
'services': social_services,
'autofollow': social_services.autofollow,
'user_profile': social_profile.to_json(full=True),
'following_profiles': following_profiles,
'follower_profiles': follower_profiles,
}
@ajax_login_required
@json.json_view
def follow(request):
profile, _ = MSocialProfile.objects.get_or_create(user_id=request.user.pk)
user_id = request.POST['user_id']
try:
follow_user_id = int(user_id)
except ValueError:
try:
follow_user_id = int(user_id.replace('social:', ''))
follow_profile = MSocialProfile.objects.get(user_id=follow_user_id)
except (ValueError, MSocialProfile.DoesNotExist):
follow_username = user_id.replace('social:', '')
try:
follow_profile = MSocialProfile.objects.get(username=follow_username)
except MSocialProfile.DoesNotExist:
raise Http404
follow_user_id = follow_profile.user_id
profile.follow_user(follow_user_id)
follow_profile = MSocialProfile.objects.get(user_id=follow_user_id)
social_params = {
'user_id': request.user.pk,
'subscription_user_id': follow_user_id,
'include_favicon': True,
'update_counts': True,
}
follow_subscription = MSocialSubscription.feeds(**social_params)
logging.user(request, "~BB~FRFollowing: %s" % follow_profile.username)
return {
"user_profile": profile.to_json(full=True),
"follow_profile": follow_profile.to_json(common_follows_with_user=request.user.pk),
"follow_subscription": follow_subscription,
}
@ajax_login_required
@json.json_view
def unfollow(request):
profile = MSocialProfile.objects.get(user_id=request.user.pk)
user_id = request.POST['user_id']
try:
unfollow_user_id = int(user_id)
except ValueError:
try:
unfollow_user_id = int(user_id.replace('social:', ''))
unfollow_profile = MSocialProfile.objects.get(user_id=unfollow_user_id)
except (ValueError, MSocialProfile.DoesNotExist):
unfollow_username = user_id.replace('social:', '')
try:
unfollow_profile = MSocialProfile.objects.get(username=unfollow_username)
except MSocialProfile.DoesNotExist:
raise Http404
unfollow_user_id = unfollow_profile.user_id
profile.unfollow_user(unfollow_user_id)
unfollow_profile = MSocialProfile.objects.get(user_id=unfollow_user_id)
logging.user(request, "~BB~FRUnfollowing: %s" % unfollow_profile.username)
return {
'user_profile': profile.to_json(full=True),
'unfollow_profile': unfollow_profile.to_json(common_follows_with_user=request.user.pk),
}
@json.json_view
def find_friends(request):
query = request.GET.get('query')
profiles = MSocialProfile.objects.filter(username__icontains=query)[:3]
if not profiles:
profiles = MSocialProfile.objects.filter(email__icontains=query)[:3]
if not profiles:
profiles = MSocialProfile.objects.filter(blog_title__icontains=query)[:3]
return dict(profiles=profiles)
2012-02-02 17:43:17 -08:00
def shared_stories_rss_feed(request, user_id, username):
try:
user = User.objects.get(pk=user_id)
except User.DoesNotExist:
raise Http404
if user.username != username:
params = {'username': user.username, 'user_id': user.pk}
return HttpResponseRedirect(reverse('shared-stories-rss-feed', kwargs=params))
2012-02-02 17:43:17 -08:00
social_profile = MSocialProfile.objects.get(user_id=user_id)
data = {}
data['title'] = social_profile.blog_title
link = reverse('shared-stories-public', kwargs={'username': user.username})
data['link'] = "http://www.newsblur.com/%s" % link
data['description'] = "Stories shared by %s on NewsBlur." % user.username
data['lastBuildDate'] = datetime.datetime.utcnow()
data['items'] = []
data['generator'] = 'NewsBlur'
data['docs'] = None
shared_stories = MSharedStory.objects.filter(user_id=user.pk)[:30]
for shared_story in shared_stories:
story_data = {
'title': shared_story.story_title,
'link': shared_story.story_permalink,
'description': zlib.decompress(shared_story.story_content_z),
'guid': shared_story.story_guid,
'pubDate': shared_story.story_date,
}
data['items'].append(RSS.RSSItem(**story_data))
rss = RSS.RSS2(**data)
return HttpResponse(rss.to_xml())
@json.json_view
def social_feed_trainer(request):
social_user_id = request.REQUEST.get('user_id')
social_profile = MSocialProfile.objects.get(user_id=social_user_id)
social_user = get_object_or_404(User, pk=social_user_id)
user = get_user(request)
social_profile.count_stories()
classifier = social_profile.to_json()
classifier['classifiers'] = get_classifiers_for_user(user, social_user_id=classifier['id'])
classifier['num_subscribers'] = social_profile.follower_count
classifier['feed_tags'] = []
classifier['feed_authors'] = []
logging.user(user, "~FGLoading social trainer on ~SB%s: %s" % (
social_user.username, social_profile.title))
return [classifier]
2012-02-02 17:43:17 -08:00
@json.json_view
def load_social_statistics(request, social_user_id, username=None):
stats = dict()
social_profile = MSocialProfile.objects.get(user_id=social_user_id)
social_profile.save_feed_story_history_statistics()
social_profile.save_classifier_counts()
# Stories per month - average and month-by-month breakout
stats['average_stories_per_month'] = social_profile.average_stories_per_month
stats['story_count_history'] = social_profile.story_count_history
# Subscribers
stats['subscriber_count'] = social_profile.follower_count
stats['num_subscribers'] = social_profile.follower_count
# 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))
return stats
@json.json_view
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.pk, page=page)
return {
'interactions': interactions,
'page': page,
}