summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdolfo Fitoria <adolfo.fitoria@gmail.com>2012-08-10 17:00:13 -0600
committerAdolfo Fitoria <adolfo.fitoria@gmail.com>2012-08-10 17:00:13 -0600
commit004e600d870be74f5873587bc03fe32e1473ca0d (patch)
tree4aad1bc5da771dcfe92a3445abbf3ab20b0ce838
parentacc1c792a25a0c2b5ede8369cd7a681217a5f101 (diff)
parenta54c5f955b704f1c6b8c6a4ffc37c6307043dbfc (diff)
downloadaskbot-004e600d870be74f5873587bc03fe32e1473ca0d.tar.gz
askbot-004e600d870be74f5873587bc03fe32e1473ca0d.tar.bz2
askbot-004e600d870be74f5873587bc03fe32e1473ca0d.zip
Merge branch 'user-groups' into widget-creator
-rw-r--r--askbot/conf/minimum_reputation.py14
-rw-r--r--askbot/const/__init__.py20
-rw-r--r--askbot/models/__init__.py8
-rw-r--r--askbot/models/post.py167
-rw-r--r--askbot/models/question.py4
-rw-r--r--askbot/skins/default/templates/question/sidebar.html2
-rw-r--r--askbot/tasks.py133
-rw-r--r--askbot/views/commands.py41
8 files changed, 224 insertions, 165 deletions
diff --git a/askbot/conf/minimum_reputation.py b/askbot/conf/minimum_reputation.py
index 54ba0f65..2c3a3496 100644
--- a/askbot/conf/minimum_reputation.py
+++ b/askbot/conf/minimum_reputation.py
@@ -181,7 +181,6 @@ settings.register(
)
)
-
settings.register(
livesettings.IntegerValue(
MIN_REP,
@@ -190,3 +189,16 @@ settings.register(
description=_('Post answers and comments by email')
)
)
+
+settings.register(
+ livesettings.IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_TRIGGER_EMAIL',
+ default=15,
+ description=_('Trigger email notifications'),
+ help_text=_(
+ 'Reduces spam as notifications wont\'t be sent '
+ 'to regular users for posts of low karma users'
+ )
+ )
+)
diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py
index 2633055f..037ee379 100644
--- a/askbot/const/__init__.py
+++ b/askbot/const/__init__.py
@@ -176,6 +176,7 @@ TYPE_ACTIVITY_MODERATED_POST_EDIT = 25
TYPE_ACTIVITY_CREATE_REJECT_REASON = 26
TYPE_ACTIVITY_UPDATE_REJECT_REASON = 27
TYPE_ACTIVITY_VALIDATION_EMAIL_SENT = 28
+TYPE_ACTIVITY_POST_SHARED = 29
#TYPE_ACTIVITY_EDIT_QUESTION = 17
#TYPE_ACTIVITY_EDIT_ANSWER = 18
@@ -199,6 +200,7 @@ TYPE_ACTIVITY = (
(TYPE_ACTIVITY_FAVORITE, _('selected favorite')),
(TYPE_ACTIVITY_USER_FULL_UPDATED, _('completed user profile')),
(TYPE_ACTIVITY_EMAIL_UPDATE_SENT, _('email update sent to user')),
+ (TYPE_ACTIVITY_POST_SHARED, _('a post was shared')),
(
TYPE_ACTIVITY_UNANSWERED_REMINDER_SENT,
_('reminder about unanswered questions sent'),
@@ -244,6 +246,7 @@ RESPONSE_ACTIVITY_TYPES_FOR_INSTANT_NOTIFICATIONS = (
TYPE_ACTIVITY_UPDATE_QUESTION,
TYPE_ACTIVITY_ANSWER,
TYPE_ACTIVITY_ASK_QUESTION,
+ TYPE_ACTIVITY_POST_SHARED
)
@@ -256,6 +259,7 @@ RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY = (
TYPE_ACTIVITY_COMMENT_ANSWER,
TYPE_ACTIVITY_UPDATE_ANSWER,
TYPE_ACTIVITY_UPDATE_QUESTION,
+ TYPE_ACTIVITY_POST_SHARED,
# TYPE_ACTIVITY_PRIZE,
# TYPE_ACTIVITY_MARK_ANSWER,
# TYPE_ACTIVITY_VOTE_UP,
@@ -267,15 +271,15 @@ RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY = (
# TYPE_ACTIVITY_FAVORITE,
)
-
RESPONSE_ACTIVITY_TYPE_MAP_FOR_TEMPLATES = {
- TYPE_ACTIVITY_COMMENT_QUESTION: 'question_comment',
- TYPE_ACTIVITY_COMMENT_ANSWER: 'answer_comment',
- TYPE_ACTIVITY_UPDATE_ANSWER: 'answer_update',
- TYPE_ACTIVITY_UPDATE_QUESTION: 'question_update',
- TYPE_ACTIVITY_ANSWER: 'new_answer',
- TYPE_ACTIVITY_ASK_QUESTION: 'new_question',
- }
+ TYPE_ACTIVITY_COMMENT_QUESTION: 'question_comment',
+ TYPE_ACTIVITY_COMMENT_ANSWER: 'answer_comment',
+ TYPE_ACTIVITY_UPDATE_ANSWER: 'answer_update',
+ TYPE_ACTIVITY_UPDATE_QUESTION: 'question_update',
+ TYPE_ACTIVITY_ANSWER: 'new_answer',
+ TYPE_ACTIVITY_ASK_QUESTION: 'new_question',
+ TYPE_ACTIVITY_POST_SHARED: 'post_shared'
+}
assert(
set(RESPONSE_ACTIVITY_TYPES_FOR_INSTANT_NOTIFICATIONS) \
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index a0ce96f7..90d8df14 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -2807,6 +2807,8 @@ def format_instant_notification_email(
assert(isinstance(post, Post) and post.is_question())
elif update_type == 'new_question':
assert(isinstance(post, Post) and post.is_question())
+ elif update_type == 'post_shared':
+ pass
else:
raise ValueError('unexpected update_type %s' % update_type)
@@ -2831,9 +2833,11 @@ def format_instant_notification_email(
content_preview += '<p>======= Full thread summary =======</p>'
- content_preview += post.thread.format_for_email()
+ content_preview += post.thread.format_for_email(user=to_user)
- if post.is_comment():
+ if update_type == 'post_shared':
+ user_action = _('%(user)s shared a %(post_link)s.')
+ elif post.is_comment():
if update_type.endswith('update'):
user_action = _('%(user)s edited a %(post_link)s.')
else:
diff --git a/askbot/models/post.py b/askbot/models/post.py
index e72ff5e4..8d2d05a4 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -24,7 +24,9 @@ import askbot
from askbot.utils.slug import slugify
from askbot import const
+from askbot.models.user import Activity
from askbot.models.user import EmailFeedSetting
+from askbot.models.user import GroupMembership
from askbot.models.tag import Tag, MarkedTag
from askbot.models.tag import get_groups, tags_match_some_wildcard
from askbot.models.tag import get_global_group
@@ -576,6 +578,76 @@ class Post(models.Model):
tag__in=groups
).delete()
+
+ def issue_update_notifications(
+ self,
+ updated_by=None,
+ notify_sets=None,
+ activity_type=None,
+ timestamp=None,
+ diff=None
+ ):
+ """Called when a post is updated. Arguments:
+
+ * ``notify_sets`` - result of ``Post.get_notify_sets()`` method
+
+ The method does two things:
+
+ * records "red envelope" recipients of the post
+ * sends email alerts to all subscribers to the post
+ """
+ assert(activity_type is not None)
+ if self.is_comment():
+ #it's just a comment!
+ summary = self.text
+ else:
+ #summary = post.get_latest_revision().summary
+ if diff:
+ summary = diff
+ else:
+ summary = self.text
+
+ update_activity = Activity(
+ user = updated_by,
+ active_at = timestamp,
+ content_object = self,
+ activity_type = activity_type,
+ question = self.get_origin_post(),
+ summary = summary
+ )
+ update_activity.save()
+
+ update_activity.add_recipients(notify_sets['for_inbox'])
+
+ #create new mentions (barring the double-adds)
+ for u in notify_sets['for_mentions'] - notify_sets['for_inbox']:
+ Activity.objects.create_new_mention(
+ mentioned_whom = u,
+ mentioned_in = self,
+ mentioned_by = updated_by,
+ mentioned_at = timestamp
+ )
+
+ for user in (notify_sets['for_inbox'] | notify_sets['for_mentions']):
+ user.update_response_counts()
+
+ #shortcircuit if the email alerts are disabled
+ if askbot_settings.ENABLE_EMAIL_ALERTS == False:
+ return
+ #todo: fix this temporary spam protection plug
+ if askbot_settings.MIN_REP_TO_TRIGGER_EMAIL:
+ if not (updated_by.is_administrator() or updated_by.is_moderator()):
+ if updated_by.reputation < askbot_settings.MIN_REP_TO_TRIGGER_EMAIL:
+ notify_sets['for_email'] = \
+ [u for u in notify_sets['for_email'] if u.is_administrator()]
+
+ from askbot.models import send_instant_notifications_about_activity_in_post
+ send_instant_notifications_about_activity_in_post(
+ update_activity=update_activity,
+ post=self,
+ recipients=notify_sets['for_email'],
+ )
+
def make_private(self, user, group_id = None):
"""makes post private within user's groups
todo: this is a copy-paste in thread and post
@@ -650,15 +722,6 @@ class Post(models.Model):
raise NotImplementedError
- def get_authorized_group_ids(self):
- """returns values list query set for the group id's of the post"""
- if self.is_comment():
- return self.parent.get_authorized_group_ids()
- elif self.is_answer() or self.is_question():
- return self.groups.values_list('id', flat = True)
- else:
- raise NotImplementedError()
-
def delete(self, **kwargs):
"""deletes comment and concomitant response activity
records, as well as mention records, while preserving
@@ -726,21 +789,29 @@ class Post(models.Model):
if askbot_settings.GROUPS_ENABLED == False:
return candidates
else:
- #here we filter candidates against the post authorized groups
- authorized_group_ids = list(self.get_authorized_group_ids())
-
- if len(authorized_group_ids) == 0:#if there are no groups - all ok
+ if len(candidates) == 0:
return candidates
-
- #and filter the users by those groups
- filtered_candidates = list()
+ #get post groups
+ groups = list(self.groups.all())
+
+ if len(groups) == 0:
+ logging.critical('post %d is groupless' % self.id)
+ return list()
+
+ #load group memberships for the candidates
+ memberships = GroupMembership.objects.filter(
+ user__in=candidates,
+ group__in=groups
+ )
+ user_ids = set(memberships.values_list('user__id', flat=True))
+
+ #scan through the user ids and see which are group members
+ filtered_candidates = set()
for candidate in candidates:
- c_groups = candidate.get_groups()
- if c_groups.filter(id__in = authorized_group_ids).count():
- filtered_candidates.append(candidate)
+ if candidate.id in user_ids:
+ filtered_candidates.add(candidate)
return filtered_candidates
-
def format_for_email(
self, quote_level = 0, is_leaf_post = False, format = None
@@ -1064,10 +1135,7 @@ class Post(models.Model):
#print 'answer subscribers: ', answer_subscribers
#print 'exclude_list is ', exclude_list
- subscriber_set -= set(exclude_list)
-
- #print 'final subscriber set is ', subscriber_set
- return list(subscriber_set)
+ return subscriber_set - set(exclude_list)
def _comment__get_instant_notification_subscribers(
self,
@@ -1133,13 +1201,7 @@ class Post(models.Model):
subscriber_set.update(global_subscribers)
- #print 'exclude list is: ', exclude_list
- if exclude_list:
- subscriber_set -= set(exclude_list)
-
- #print 'final list of subscribers:', subscriber_set
-
- return list(subscriber_set)
+ return subscriber_set - set(exclude_list)
def get_instant_notification_subscribers(
self, potential_subscribers = None,
@@ -1158,7 +1220,7 @@ class Post(models.Model):
exclude_list=exclude_list
)
elif self.is_tag_wiki() or self.is_reject_reason():
- return list()
+ return set()
else:
raise NotImplementedError
@@ -1166,6 +1228,34 @@ class Post(models.Model):
# for subscriber in subscribers:
return self.filter_authorized_users(subscribers)
+ def get_notify_sets(self, mentioned_users=None, exclude_list=None):
+ """returns three lists in a dictionary with keys:
+ * 'for_inbox' - users for which to add inbox items
+ * 'for_mentions' - for whom mentions are added
+ * 'for_email' - to whom email notifications should be sent
+ """
+ result = dict()
+ result['for_mentions'] = set(mentioned_users) - set(exclude_list)
+ #what users are included depends on the post type
+ #for example for question - all Q&A contributors
+ #are included, for comments only authors of comments and parent
+ #post are included
+ result['for_inbox'] = self.get_response_receivers(exclude_list=exclude_list)
+
+ if askbot_settings.ENABLE_EMAIL_ALERTS == False:
+ result['for_email'] = set()
+ else:
+ #todo: weird thing is that only comments need the recipients
+ #todo: debug these calls and then uncomment in the repo
+ #argument to this call
+ result['for_email'] = self.get_instant_notification_subscribers(
+ potential_subscribers=result['for_inbox'],
+ mentioned_users=result['for_mentions'],
+ exclude_list=exclude_list
+ )
+ return result
+
+
def get_latest_revision(self):
return self.revisions.order_by('-revised_at')[0]
@@ -1708,9 +1798,7 @@ class Post(models.Model):
for answer in question.thread.posts.get_answers().all():
recipients.update(answer.get_author_list())
- recipients -= set(exclude_list)
-
- return list(recipients)
+ return recipients - set(exclude_list)
def _question__get_response_receivers(self, exclude_list = None):
"""returns list of users who might be interested
@@ -1731,8 +1819,7 @@ class Post(models.Model):
for a in self.thread.posts.get_answers().all():
recipients.update(a.get_author_list())
- recipients -= set(exclude_list)
- return recipients
+ return recipients - set(exclude_list)
def _comment__get_response_receivers(self, exclude_list = None):
"""Response receivers are commenters of the
@@ -1746,9 +1833,7 @@ class Post(models.Model):
include_comments = True,
)
)
- users -= set(exclude_list)
- return list(users)
-
+ return users - set(exclude_list)
def get_response_receivers(self, exclude_list = None):
"""returns a list of response receiving users
@@ -1761,7 +1846,7 @@ class Post(models.Model):
elif self.is_comment():
receivers = self._comment__get_response_receivers(exclude_list)
elif self.is_tag_wiki() or self.is_reject_reason():
- return list()#todo: who should get these?
+ return set()#todo: who should get these?
else:
raise NotImplementedError
diff --git a/askbot/models/question.py b/askbot/models/question.py
index 14515b8f..ab3e6959 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -560,9 +560,9 @@ class Thread(models.Model):
else:
return self.title
- def format_for_email(self):
+ def format_for_email(self, user=None):
"""experimental function: output entire thread for email"""
- question, answers, junk = self.get_cached_post_data()
+ question, answers, junk = self.get_cached_post_data(user=user)
output = question.format_for_email_as_subthread()
if answers:
answer_heading = ungettext(
diff --git a/askbot/skins/default/templates/question/sidebar.html b/askbot/skins/default/templates/question/sidebar.html
index 6c4643e9..ef99e988 100644
--- a/askbot/skins/default/templates/question/sidebar.html
+++ b/askbot/skins/default/templates/question/sidebar.html
@@ -84,7 +84,7 @@
<form action="{% url share_question_with_group %}" method="post">{% csrf_token %}
<input
type="hidden"
- name="group_name"
+ name="recipient_name"
value="{{ settings.GLOBAL_GROUP_NAME }}"
/>
<input type="hidden" name="thread_id" value="{{ thread.id }}"/>
diff --git a/askbot/tasks.py b/askbot/tasks.py
index da07477b..4aa11798 100644
--- a/askbot/tasks.py
+++ b/askbot/tasks.py
@@ -27,8 +27,7 @@ from celery.decorators import task
from askbot.conf import settings as askbot_settings
from askbot import const
from askbot import mail
-from askbot.models import Activity, Post, Thread, User, ReplyAddress
-from askbot.models import send_instant_notifications_about_activity_in_post
+from askbot.models import Post, Thread, User, ReplyAddress
from askbot.models.badges import award_badges_signal
# TODO: Make exceptions raised inside record_post_update_celery_task() ...
@@ -102,122 +101,36 @@ def record_post_update_celery_task(
diff = None,
):
#reconstitute objects from the database
- updated_by = User.objects.get(id = updated_by_id)
- post_content_type = ContentType.objects.get(id = post_content_type_id)
- post = post_content_type.get_object_for_this_type(id = post_id)
+ updated_by = User.objects.get(id=updated_by_id)
+ post_content_type = ContentType.objects.get(id=post_content_type_id)
+ post = post_content_type.get_object_for_this_type(id=post_id)
newly_mentioned_users = User.objects.filter(
- id__in = newly_mentioned_user_id_list
+ id__in=newly_mentioned_user_id_list
)
try:
- record_post_update(
- post = post,
- updated_by = updated_by,
- newly_mentioned_users = newly_mentioned_users,
- timestamp = timestamp,
- created = created,
- diff = diff
+ notify_sets = post.get_notify_sets(
+ mentioned_users=newly_mentioned_users,
+ exclude_list=[updated_by,]
+ )
+ #todo: take into account created == True case
+ #update_object is not used
+ (activity_type, update_object) = post.get_updated_activity_data(created)
+
+ post.issue_update_notifications(
+ updated_by=updated_by,
+ notify_sets=notify_sets,
+ activity_type=activity_type,
+ timestamp=timestamp,
+ diff=diff
)
+
except Exception:
- # HACK: exceptions from Celery job don;t propagate upwards to Django test runner
- # so at least le't sprint tracebacks
+ # HACK: exceptions from Celery job don't propagate upwards
+ # to the Django test runner
+ # so at least let's print tracebacks
print >>sys.stderr, traceback.format_exc()
raise
-def record_post_update(
- post = None,
- updated_by = None,
- newly_mentioned_users = None,
- timestamp = None,
- created = False,
- diff = None
- ):
- """Called when a post is updated. Arguments:
-
- * ``newly_mentioned_users`` - users who are mentioned in the
- post for the first time
- * ``created`` - a boolean. True when ``post`` has just been created
- * remaining arguments are self - explanatory
-
- The method does two things:
-
- * records "red envelope" recipients of the post
- * sends email alerts to all subscribers to the post
- """
- #todo: take into account created == True case
- (activity_type, update_object) = post.get_updated_activity_data(created)
-
- if post.is_comment():
- #it's just a comment!
- summary = post.text
- else:
- #summary = post.get_latest_revision().summary
- summary = diff
-
- update_activity = Activity(
- user = updated_by,
- active_at = timestamp,
- content_object = post,
- activity_type = activity_type,
- question = post.get_origin_post(),
- summary = summary
- )
- update_activity.save()
-
- #what users are included depends on the post type
- #for example for question - all Q&A contributors
- #are included, for comments only authors of comments and parent
- #post are included
- recipients = post.get_response_receivers(
- exclude_list = [updated_by, ]
- )
-
- update_activity.add_recipients(recipients)
-
- #create new mentions
- for u in newly_mentioned_users:
- #todo: a hack - some users will not have record of a mention
- #may need to fix this in the future. Added this so that
- #recipients of the response who are mentioned as well would
- #not get two notifications in the inbox for the same post
- if u in recipients:
- continue
- Activity.objects.create_new_mention(
- mentioned_whom = u,
- mentioned_in = post,
- mentioned_by = updated_by,
- mentioned_at = timestamp
- )
-
- assert(updated_by not in recipients)
-
- for user in (set(recipients) | set(newly_mentioned_users)):
- user.update_response_counts()
-
- #shortcircuit if the email alerts are disabled
- if askbot_settings.ENABLE_EMAIL_ALERTS == False:
- return
-
- #todo: weird thing is that only comments need the recipients
- #todo: debug these calls and then uncomment in the repo
- #argument to this call
- notification_subscribers = post.get_instant_notification_subscribers(
- potential_subscribers = recipients,
- mentioned_users = newly_mentioned_users,
- exclude_list = [updated_by, ]
- )
- #todo: fix this temporary spam protection plug
- if created:
- if not (updated_by.is_administrator() or updated_by.is_moderator()):
- if updated_by.reputation < 15:
- notification_subscribers = \
- [u for u in notification_subscribers if u.is_administrator()]
- send_instant_notifications_about_activity_in_post(
- update_activity = update_activity,
- post = post,
- recipients = notification_subscribers,
- )
-
-
@task(ignore_result = True)
def record_question_visit(
question_post = None,
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index 316895f7..1b6b21e8 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -1156,12 +1156,40 @@ def share_question_with_group(request):
group_name = form.cleaned_data['recipient_name']
thread = models.Thread.objects.get(id=thread_id)
+ question_post = thread._question_post()
+
+ #get notif set before
+ sets1 = question_post.get_notify_sets(
+ mentioned_users=list(),
+ exclude_list=[request.user,]
+ )
+
+ #share the post
if group_name == askbot_settings.GLOBAL_GROUP_NAME:
thread.make_public(recursive=True)
else:
group = models.Tag.group_tags.get(name=group_name)
thread.add_to_groups((group,), recursive=True)
+ #get notif sets after
+ sets2 = question_post.get_notify_sets(
+ mentioned_users=list(),
+ exclude_list=[request.user,]
+ )
+
+ notify_sets = {
+ 'for_mentions': sets2['for_mentions'] - sets1['for_mentions'],
+ 'for_email': sets2['for_email'] - sets1['for_email'],
+ 'for_inbox': sets2['for_inbox'] - sets1['for_inbox']
+ }
+
+ question_post.issue_update_notifications(
+ updated_by=request.user,
+ notify_sets=notify_sets,
+ activity_type=const.TYPE_ACTIVITY_POST_SHARED,
+ timestamp=datetime.datetime.now()
+ )
+
return HttpResponseRedirect(thread.get_absolute_url())
except Exception:
error_message = _('Sorry, looks like sharing request was invalid')
@@ -1181,6 +1209,19 @@ def share_question_with_user(request):
user = models.User.objects.get(username=username)
group = user.get_personal_group()
thread.add_to_groups([group], recursive=True)
+ #notify the person
+ #todo: see if user could already see the post - b/f the sharing
+ notify_sets = {
+ 'for_inbox': set([user]),
+ 'for_mentions': set([user]),
+ 'for_email': set([user])
+ }
+ thread._question_post().issue_update_notifications(
+ updated_by=request.user,
+ notify_sets=notify_sets,
+ activity_type=const.TYPE_ACTIVITY_POST_SHARED,
+ timestamp=datetime.datetime.now()
+ )
return HttpResponseRedirect(thread.get_absolute_url())
except Exception: