Converting replies from original messages to reply ids. Also delaying emails on new replies and reshares so authors can change typos. Also dong a much better job with checking for dupe emails so no dupes ever get sent out.

This commit is contained in:
Samuel Clay 2012-07-27 18:58:35 -07:00
parent dcbb305f6e
commit 8eb106612a
10 changed files with 102 additions and 33 deletions

View file

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
from bson.objectid import ObjectId
from apps.social.models import MSharedStory
class Migration(DataMigration):
def forwards(self, orm):
stories = MSharedStory.objects.filter(has_replies=True)
story_count = stories.count()
print " ---> %s stories with replies" % story_count
for i, story in enumerate(stories):
print " ---> %s/%s: %s replies" % (i+1, story_count, len(story.replies))
replies = []
for reply in story.replies:
if not reply.reply_id:
reply.reply_id = ObjectId()
replies.append(reply)
story.replies = replies
story.save()
def backwards(self, orm):
"Write your backwards methods here."
models = {
}
complete_apps = ['social']
symmetrical = True

View file

@ -1025,13 +1025,16 @@ class MSocialSubscription(mongo.Document):
return social_subs return social_subs
class MCommentReply(mongo.EmbeddedDocument): class MCommentReply(mongo.EmbeddedDocument):
user_id = mongo.IntField() reply_id = mongo.ObjectIdField()
publish_date = mongo.DateTimeField() user_id = mongo.IntField()
comments = mongo.StringField() publish_date = mongo.DateTimeField()
liking_users = mongo.ListField(mongo.IntField()) comments = mongo.StringField()
email_sent = mongo.BooleanField(default=False)
liking_users = mongo.ListField(mongo.IntField())
def to_json(self): def to_json(self):
reply = { reply = {
'reply_id': self.reply_id,
'user_id': self.user_id, 'user_id': self.user_id,
'publish_date': relative_timesince(self.publish_date), 'publish_date': relative_timesince(self.publish_date),
'date': self.publish_date, 'date': self.publish_date,
@ -1041,6 +1044,8 @@ class MCommentReply(mongo.EmbeddedDocument):
meta = { meta = {
'ordering': ['publish_date'], 'ordering': ['publish_date'],
'id_field': 'reply_id',
'allow_inheritance': False,
} }
@ -1069,6 +1074,7 @@ class MSharedStory(mongo.Document):
mute_email_users = mongo.ListField(mongo.IntField()) mute_email_users = mongo.ListField(mongo.IntField())
liking_users = mongo.ListField(mongo.IntField()) liking_users = mongo.ListField(mongo.IntField())
emailed_reshare = mongo.BooleanField(default=False) emailed_reshare = mongo.BooleanField(default=False)
emailed_replies = mongo.ListField(mongo.ObjectIdField())
meta = { meta = {
'collection': 'shared_stories', 'collection': 'shared_stories',
@ -1079,7 +1085,11 @@ class MSharedStory(mongo.Document):
'ordering': ['shared_date'], 'ordering': ['shared_date'],
'allow_inheritance': False, 'allow_inheritance': False,
} }
def __unicode__(self):
user = User.objects.get(pk=self.user_id)
return "%s: %s (%s)%s%s" % (user.username, self.story_title[:20], self.story_feed_id, ': ' if self.has_comments else '', self.comments[:20])
@property @property
def guid_hash(self): def guid_hash(self):
return hashlib.sha1(self.story_guid).hexdigest() return hashlib.sha1(self.story_guid).hexdigest()
@ -1475,19 +1485,33 @@ class MSharedStory(mongo.Document):
user_ids.add(self.user_id) user_ids.add(self.user_id)
return list(user_ids) return list(user_ids)
def reply_for_id(self, reply_id):
for reply in self.replies:
if reply.reply_id == reply_id:
return reply
def send_emails_for_new_reply(self, reply_id):
if reply_id in self.emailed_replies:
logging.debug(" ***> Already sent reply email: %s on %s" % (reply_id, self))
return
def send_emails_for_new_reply(self, reply_user_id): reply = self.reply_for_id(reply_id)
if not reply:
logging.debug(" ***> Reply doesn't exist: %s on %s" % (reply_id, self))
return
notify_user_ids = self.notify_user_ids() notify_user_ids = self.notify_user_ids()
if reply_user_id in notify_user_ids: if reply.user_id in notify_user_ids:
notify_user_ids.remove(reply_user_id) notify_user_ids.remove(reply.user_id)
reply_user = User.objects.get(pk=reply_user_id) reply_user = User.objects.get(pk=reply.user_id)
reply_user_profile = MSocialProfile.get_user(reply_user_id) reply_user_profile = MSocialProfile.get_user(reply.user_id)
sent_emails = 0 sent_emails = 0
story_feed = Feed.objects.get(pk=self.story_feed_id) story_feed = Feed.objects.get(pk=self.story_feed_id)
comment = self.comments_with_author() comment = self.comments_with_author()
profile_user_ids = set([comment['user_id']]) profile_user_ids = set([comment['user_id']])
reply_user_ids = [reply['user_id'] for reply in comment['replies']] reply_user_ids = list(r['user_id'] for r in comment['replies'])
profile_user_ids = profile_user_ids.union(reply_user_ids) profile_user_ids = profile_user_ids.union(reply_user_ids)
if self.source_user_id: if self.source_user_id:
profile_user_ids.add(self.source_user_id) profile_user_ids.add(self.source_user_id)
@ -1534,8 +1558,15 @@ class MSharedStory(mongo.Document):
sent_emails, len(notify_user_ids), sent_emails, len(notify_user_ids),
'' if len(notify_user_ids) == 1 else 's', '' if len(notify_user_ids) == 1 else 's',
self.story_title[:30])) self.story_title[:30]))
self.emailed_replies.append(reply.reply_id)
self.save()
def send_email_for_reshare(self): def send_email_for_reshare(self):
if self.emailed_reshare:
logging.debug(" ***> Already sent reply email: %s" % self)
return
reshare_user = User.objects.get(pk=self.user_id) reshare_user = User.objects.get(pk=self.user_id)
reshare_user_profile = MSocialProfile.get_user(self.user_id) reshare_user_profile = MSocialProfile.get_user(self.user_id)
original_user = User.objects.get(pk=self.source_user_id) original_user = User.objects.get(pk=self.source_user_id)

View file

@ -19,9 +19,9 @@ class EmailNewFollower(Task):
class EmailCommentReplies(Task): class EmailCommentReplies(Task):
def run(self, shared_story_id, reply_user_id): def run(self, shared_story_id, reply_id):
shared_story = MSharedStory.objects.get(id=shared_story_id) shared_story = MSharedStory.objects.get(id=shared_story_id)
shared_story.send_emails_for_new_reply(reply_user_id) shared_story.send_emails_for_new_reply(reply_id)
class EmailStoryReshares(Task): class EmailStoryReshares(Task):

View file

@ -2,6 +2,7 @@ import time
import datetime import datetime
import zlib import zlib
import random import random
from bson.objectid import ObjectId
from django.shortcuts import get_object_or_404, render_to_response from django.shortcuts import get_object_or_404, render_to_response
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -364,8 +365,8 @@ def mark_story_as_shared(request):
if service not in shared_story.posted_to_services: if service not in shared_story.posted_to_services:
PostToService.delay(shared_story_id=shared_story.id, service=service) PostToService.delay(shared_story_id=shared_story.id, service=service)
if shared_story.source_user_id and not shared_story.emailed_reshare and shared_story.comments: if shared_story.source_user_id and shared_story.comments:
EmailStoryReshares.delay(shared_story_id=shared_story.id) EmailStoryReshares.apply_async(kwargs=dict(shared_story_id=shared_story.id), countdown=60)
if format == 'html': if format == 'html':
stories = MSharedStory.attach_users_to_stories(stories, profiles) stories = MSharedStory.attach_users_to_stories(stories, profiles)
@ -435,6 +436,7 @@ def save_comment_reply(request):
comment_user_id = request.POST['comment_user_id'] comment_user_id = request.POST['comment_user_id']
reply_comments = request.POST.get('reply_comments') reply_comments = request.POST.get('reply_comments')
original_message = request.POST.get('original_message') original_message = request.POST.get('original_message')
reply_id = request.POST.get('reply_id')
format = request.REQUEST.get('format', 'json') format = request.REQUEST.get('format', 'json')
if not reply_comments: if not reply_comments:
@ -448,12 +450,14 @@ def save_comment_reply(request):
reply.publish_date = datetime.datetime.now() reply.publish_date = datetime.datetime.now()
reply.comments = reply_comments reply.comments = reply_comments
if original_message: if reply_id:
replies = [] replies = []
for story_reply in shared_story.replies: for story_reply in shared_story.replies:
if (story_reply.user_id == reply.user_id and if (story_reply.user_id == reply.user_id and
strip_tags(story_reply.comments) == original_message): story_reply.reply_id == ObjectId(reply_id)):
reply.publish_date = story_reply.publish_date reply.publish_date = story_reply.publish_date
reply.reply_id = story_reply.reply_id
original_message = story_reply.comments
replies.append(reply) replies.append(reply)
else: else:
replies.append(story_reply) replies.append(story_reply)
@ -461,6 +465,7 @@ def save_comment_reply(request):
logging.user(request, "~FCUpdating comment reply in ~FM%s: ~SB~FB%s~FM" % ( logging.user(request, "~FCUpdating comment reply in ~FM%s: ~SB~FB%s~FM" % (
shared_story.story_title[:20], reply_comments[:30])) shared_story.story_title[:20], reply_comments[:30]))
else: else:
reply.reply_id = ObjectId()
logging.user(request, "~FCReplying to comment in: ~FM%s: ~SB~FB%s~FM" % ( logging.user(request, "~FCReplying to comment in: ~FM%s: ~SB~FB%s~FM" % (
shared_story.story_title[:20], reply_comments[:30])) shared_story.story_title[:20], reply_comments[:30]))
shared_story.replies.append(reply) shared_story.replies.append(reply)
@ -468,7 +473,7 @@ def save_comment_reply(request):
comment = shared_story.comments_with_author() comment = shared_story.comments_with_author()
profile_user_ids = set([comment['user_id']]) profile_user_ids = set([comment['user_id']])
reply_user_ids = [reply['user_id'] for reply in comment['replies']] reply_user_ids = list(r['user_id'] for r in comment['replies'])
profile_user_ids = profile_user_ids.union(reply_user_ids) profile_user_ids = profile_user_ids.union(reply_user_ids)
profiles = MSocialProfile.objects.filter(user_id__in=list(profile_user_ids)) profiles = MSocialProfile.objects.filter(user_id__in=list(profile_user_ids))
profiles = [profile.to_json(compact=True) for profile in profiles] profiles = [profile.to_json(compact=True) for profile in profiles]
@ -496,8 +501,9 @@ def save_comment_reply(request):
original_message=original_message, original_message=original_message,
social_feed_id=comment_user_id, social_feed_id=comment_user_id,
story_id=story_id) story_id=story_id)
if not original_message:
EmailCommentReplies.delay(shared_story_id=shared_story.id, reply_user_id=request.user.pk) EmailCommentReplies.apply_async(kwargs=dict(shared_story_id=shared_story.id,
reply_id=reply.reply_id), countdown=60)
if format == 'html': if format == 'html':
comment = MSharedStory.attach_users_to_comment(comment, profiles) comment = MSharedStory.attach_users_to_comment(comment, profiles)

View file

@ -260,7 +260,7 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
} }
}, },
save_comment_reply: function(story_id, story_feed_id, comment_user_id, reply_comments, original_message, callback, error_callback) { save_comment_reply: function(story_id, story_feed_id, comment_user_id, reply_comments, reply_id, callback, error_callback) {
var pre_callback = _.bind(function(data) { var pre_callback = _.bind(function(data) {
if (data.user_profiles) { if (data.user_profiles) {
this.add_user_profiles(data.user_profiles); this.add_user_profiles(data.user_profiles);
@ -273,7 +273,7 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
story_feed_id: story_feed_id, story_feed_id: story_feed_id,
comment_user_id: comment_user_id, comment_user_id: comment_user_id,
reply_comments: reply_comments, reply_comments: reply_comments,
original_message: original_message reply_id: reply_id
}, pre_callback, error_callback); }, pre_callback, error_callback);
}, },

View file

@ -95,13 +95,13 @@ NEWSBLUR.SocialPageAssets = Backbone.Router.extend({
} }
}, },
save_comment_reply: function(story_id, story_feed_id, comment_user_id, reply_comments, original_message, callback, error_callback) { save_comment_reply: function(story_id, story_feed_id, comment_user_id, reply_comments, reply_id, callback, error_callback) {
this.make_request('/social/save_comment_reply', { this.make_request('/social/save_comment_reply', {
story_id: story_id, story_id: story_id,
story_feed_id: story_feed_id, story_feed_id: story_feed_id,
comment_user_id: comment_user_id, comment_user_id: comment_user_id,
reply_comments: reply_comments, reply_comments: reply_comments,
original_message: original_message, reply_id: reply_id,
format: 'html' format: 'html'
}, callback, error_callback, { }, callback, error_callback, {
request_type: 'POST' request_type: 'POST'

View file

@ -42,7 +42,7 @@ NEWSBLUR.Views.StoryCommentReply = Backbone.View.extend({
}, },
edit_reply: function() { edit_reply: function() {
this.options.comment.open_reply({is_editing: true, $reply: this.$el}); this.options.comment.open_reply({is_editing: true, reply: this.model, $reply: this.$el});
} }

View file

@ -132,15 +132,13 @@ NEWSBLUR.Views.StoryComment = Backbone.View.extend({
var $form = $.make('div', { className: 'NB-story-comment-reply NB-story-comment-reply-form' }, [ var $form = $.make('div', { className: 'NB-story-comment-reply NB-story-comment-reply-form' }, [
$.make('img', { className: 'NB-story-comment-reply-photo', src: current_user.get('photo_url') }), $.make('img', { className: 'NB-story-comment-reply-photo', src: current_user.get('photo_url') }),
$.make('div', { className: 'NB-story-comment-username NB-story-comment-reply-username' }, current_user.get('username')), $.make('div', { className: 'NB-story-comment-username NB-story-comment-reply-username' }, current_user.get('username')),
$.make('input', { type: 'text', className: 'NB-input NB-story-comment-reply-comments' }), $.make('input', { type: 'text', className: 'NB-input NB-story-comment-reply-comments', value: options.reply && options.reply.get("comments") }),
$.make('div', { className: 'NB-modal-submit-button NB-modal-submit-green' }, options.is_editing ? 'Save' : 'Post') $.make('div', { className: 'NB-modal-submit-button NB-modal-submit-green' }, options.is_editing ? 'Save' : 'Post')
]); ]);
this.remove_social_comment_reply_form(); this.remove_social_comment_reply_form();
if (options.is_editing && options.$reply) { if (options.is_editing && options.$reply) {
var original_message = $('.NB-story-comment-reply-content', options.$reply).text(); $form.data('reply_id', options.reply.get("reply_id"));
$('input', $form).val(original_message);
$form.data('original_message', original_message);
options.$reply.hide().addClass('NB-story-comment-reply-hidden'); options.$reply.hide().addClass('NB-story-comment-reply-hidden');
options.$reply.after($form); options.$reply.after($form);
} else { } else {
@ -172,7 +170,7 @@ NEWSBLUR.Views.StoryComment = Backbone.View.extend({
var $submit = $(".NB-modal-submit-button", $form); var $submit = $(".NB-modal-submit-button", $form);
var comment_user_id = this.model.get('user_id'); var comment_user_id = this.model.get('user_id');
var comment_reply = $('.NB-story-comment-reply-comments', $form).val(); var comment_reply = $('.NB-story-comment-reply-comments', $form).val();
var original_message = $form.data('original_message'); var reply_id = $form.data('reply_id');
if (!comment_reply || comment_reply.length <= 1) { if (!comment_reply || comment_reply.length <= 1) {
this.remove_social_comment_reply_form(); this.remove_social_comment_reply_form();
@ -189,7 +187,7 @@ NEWSBLUR.Views.StoryComment = Backbone.View.extend({
$submit.addClass('NB-disabled').text('Posting...'); $submit.addClass('NB-disabled').text('Posting...');
NEWSBLUR.assets.save_comment_reply(this.options.story.id, this.options.story.get('story_feed_id'), NEWSBLUR.assets.save_comment_reply(this.options.story.id, this.options.story.get('story_feed_id'),
comment_user_id, comment_reply, comment_user_id, comment_reply,
original_message, reply_id,
_.bind(function(data) { _.bind(function(data) {
if (this.options.on_social_page) { if (this.options.on_social_page) {
this.options.story_comments_view.replace_comment(this.model.get('user_id'), data); this.options.story_comments_view.replace_comment(this.model.get('user_id'), data);

View file

@ -9,6 +9,7 @@ from django.http import HttpResponse, HttpResponseForbidden, Http404
from django.core.mail import mail_admins from django.core.mail import mail_admins
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from mongoengine.queryset import QuerySet as MongoQuerySet from mongoengine.queryset import QuerySet as MongoQuerySet
from bson.objectid import ObjectId
import sys import sys
import datetime import datetime
@ -45,7 +46,7 @@ def json_encode(data, *args, **kwargs):
# Same as for lists above. # Same as for lists above.
elif isinstance(data, dict): elif isinstance(data, dict):
ret = _dict(data) ret = _dict(data)
elif isinstance(data, Decimal): elif isinstance(data, (Decimal, ObjectId)):
# json.dumps() cant handle Decimal # json.dumps() cant handle Decimal
ret = str(data) ret = str(data)
elif isinstance(data, models.query.QuerySet): elif isinstance(data, models.query.QuerySet):

View file

@ -78,7 +78,7 @@ def _mongodb_decode_wire_protocol(message):
try: try:
if message[zidx:]: if message[zidx:]:
msg = bson.decode_all(message[zidx:]) msg = bson.decode_all(message[zidx:])
except Exception, e: except InvalidBSON:
msg = 'invalid bson' msg = 'invalid bson'
return { 'op': op, 'collection': collection_name, return { 'op': op, 'collection': collection_name,
'msg_id': msg_id, 'skip': skip, 'limit': limit, 'msg_id': msg_id, 'skip': skip, 'limit': limit,