Beispiel #1
0
 def get_questions_needing_reminder(self,
                                    user=None,
                                    activity_type=None,
                                    recurrence_delay=None):
     """returns list of questions that need a reminder,
     corresponding the given ``activity_type``
     ``user`` - is the user receiving the reminder
     ``recurrence_delay`` - interval between sending the
     reminders about the same question
     """
     #todo: goes to thread
     from askbot.models import Activity  #avoid circular import
     question_list = list()
     for question in self:
         try:
             activity = Activity.objects.get(user=user,
                                             question=question,
                                             activity_type=activity_type)
             now = datetime.datetime.now()
             if now < activity.active_at + recurrence_delay:
                 continue
         except Activity.DoesNotExist:
             activity = Activity(
                 user=user,
                 question=question,
                 activity_type=activity_type,
                 content_object=question,
             )
         activity.active_at = datetime.datetime.now()
         activity.save()
         question_list.append(question)
     return question_list
Beispiel #2
0
 def get_questions_needing_reminder(self,
                                 user = None,
                                 activity_type = None,
                                 recurrence_delay = None):
     """returns list of questions that need a reminder,
     corresponding the given ``activity_type``
     ``user`` - is the user receiving the reminder
     ``recurrence_delay`` - interval between sending the
     reminders about the same question
     """
     from askbot.models import Activity#avoid circular import
     question_list = list()
     for question in self:
         try:
             activity = Activity.objects.get(
                 user = user,
                 question = question,
                 activity_type = activity_type
             )
             now = datetime.datetime.now()
             if now < activity.active_at + recurrence_delay:
                 continue
         except Activity.DoesNotExist:
             activity = Activity(
                 user = user,
                 question = question,
                 activity_type = activity_type,
                 content_object = question,
             )
         activity.active_at = datetime.datetime.now()
         activity.save()
         question_list.append(question)
     return question_list
def remember_last_moderator(user):
    act = get_last_mod_alert_activity()
    if act:
        act.content_object = user
        act.save()
    else:
        act = Activity(user=user,
                       content_object=user,
                       activity_type=const.TYPE_ACTIVITY_MODERATION_ALERT_SENT)
        act.save()
Beispiel #4
0
def remember_last_moderator(user):
    """Save the the `user` as the one that was notified last"""
    act = get_last_mod_alert_activity()
    if act:
        act.content_object = user
        act.save()
    else:
        act = Activity(user=user,
                       content_object=user,
                       activity_type=const.TYPE_ACTIVITY_MODERATION_ALERT_SENT)
        act.save()
def remember_last_moderator(user):
    act = get_last_mod_alert_activity()
    if act:
        act.content_object = user
        act.save()
    else:
        act = Activity(
            user=user,
            content_object=user,
            activity_type=const.TYPE_ACTIVITY_MODERATION_ALERT_SENT
        )
        act.save()
def remember_last_moderator(user):
    """Save the the `user` as the one that was notified last"""
    act = get_last_mod_alert_activity()
    if act:
        act.content_object = user
        act.save()
    else:
        act = Activity(
            user=user,
            content_object=user,
            activity_type=const.TYPE_ACTIVITY_MODERATION_ALERT_SENT
        )
        act.save()
Beispiel #7
0
    def get_mock_context(self):
        from askbot.models import (Activity, Post, User)
        post_types = ('question', 'answer', 'comment')
        post = Post.objects.filter(post_type__in=post_types)[0]

        if post.is_question():
            activity_type = const.TYPE_ACTIVITY_ASK_QUESTION
        elif post.is_answer():
            activity_type = const.TYPE_ACTIVITY_ANSWER
        elif post.parent.is_question():
            activity_type = const.TYPE_ACTIVITY_COMMENT_QUESTION
        else:
            activity_type = const.TYPE_ACTIVITY_COMMENT_ANSWER

        activity = Activity(
            user=post.author,
            content_object=post,
            activity_type=activity_type,
            question=post.get_origin_post())
        return {
            'post': post,
            'from_user': post.author,
            'to_user': User.objects.exclude(id=post.author_id)[0],
            'update_activity': activity
        }
Beispiel #8
0
    def get_mock_context_sample3(self):
        """question edit alert"""
        #get edited answer
        from django.db.models import Count
        from askbot.models import (Activity, Post, User)
        posts = Post.objects.annotate(edit_count=Count('revisions')).filter(
            post_type='question', edit_count__gt=1)

        try:
            post = posts[0]
        except IndexError:
            return None

        to_users = User.objects.exclude(id=post.author_id)
        if to_users.count() == 0:
            return None
        to_user = to_users[0]

        activity = Activity(user=post.author,
                            content_object=post,
                            activity_type=const.TYPE_ACTIVITY_UPDATE_QUESTION,
                            question=post)
        return {
            'post': post,
            'from_user': post.author,
            'to_user': to_user,
            'update_activity': activity
        }
Beispiel #9
0
    def get_mock_context_sample1(self):
        """New question alert"""
        from askbot.models import (Activity, Post, User)
        posts = Post.objects.filter(post_type='question')
        if posts.count() == 0:
            return None
        post = posts[0]

        to_users = User.objects.exclude(id=post.author_id)
        if to_users.count() == 0:
            return None
        to_user = to_users[0]

        activity = Activity(user=post.author,
                            content_object=post,
                            activity_type=const.TYPE_ACTIVITY_ASK_QUESTION,
                            question=post)
        return {
            'post': post,
            'from_user': post.author,
            'to_user': to_user,
            'update_activity': activity
        }
Beispiel #10
0
def record_post_update(
        post = None,
        updated_by = None,
        newly_mentioned_users = None,
        timestamp = None,
        created = False
    ):
    """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)

    update_activity = Activity(
                    user = updated_by,
                    active_at = timestamp, 
                    content_object = post, 
                    activity_type = activity_type,
                    question = post.get_origin_post()
                )
    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)

    assert(updated_by not in recipients)

    for user in set(recipients) | set(newly_mentioned_users):
        user.increment_response_count()
        user.save()

    #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,
                        )
    def get_updated_questions_for_user(self, user):
        """
        retreive relevant question updates for the user
        according to their subscriptions and recorded question
        views
        """

        user_feeds = EmailFeedSetting.objects.filter(
            subscriber=user
        ).exclude(frequency__in=('n', 'i'))

        should_proceed = False
        for feed in user_feeds:
            if feed.should_send_now() == True:
                should_proceed = True
                break

        #shortcircuit - if there is no ripe feed to work on for this user
        if should_proceed == False:
            return {}

        #these are placeholders for separate query sets per question group
        #there are four groups - one for each EmailFeedSetting.feed_type
        #and each group has subtypes A and B
        #that's because of the strange thing commented below
        #see note on Q and F objects marked with todo tag
        q_sel_A = None
        q_sel_B = None

        q_ask_A = None
        q_ask_B = None

        q_ans_A = None
        q_ans_B = None

        q_all_A = None
        q_all_B = None

        #base question query set for this user
        #basic things - not deleted, not closed, not too old
        #not last edited by the same user
        base_qs = Post.objects.get_questions().exclude(
            thread__last_activity_by=user
        ).exclude(
            thread__last_activity_at__lt=user.date_joined#exclude old stuff
        ).exclude(
            deleted=True
        ).exclude(
            thread__closed=True
        ).order_by('-thread__last_activity_at')

        if askbot_settings.CONTENT_MODERATION_MODE == 'premoderation':
            base_qs = base_qs.filter(approved = True)
        #todo: for some reason filter on did not work as expected ~Q(viewed__who=user) |
        #      Q(viewed__who=user,viewed__when__lt=F('thread__last_activity_at'))
        #returns way more questions than you might think it should
        #so because of that I've created separate query sets Q_set2 and Q_set3
        #plus two separate queries run faster!

        #build two two queries based

        #questions that are not seen by the user at all
        not_seen_qs = base_qs.filter(~Q(viewed__who=user))
        #questions that were seen, but before last modification
        seen_before_last_mod_qs = base_qs.filter(
            Q(viewed__who=user, viewed__when__lt=F('thread__last_activity_at'))
        )

        #shorten variables for convenience
        Q_set_A = not_seen_qs
        Q_set_B = seen_before_last_mod_qs

        if askbot.is_multilingual():
            languages = user.languages.split()
        else:
            languages = None

        for feed in user_feeds:
            if feed.feed_type == 'm_and_c':
                #alerts on mentions and comments are processed separately
                #because comments to questions do not trigger change of last_updated
                #this may be changed in the future though, see
                #http://askbot.org/en/question/96/
                continue

            #each group of updates represented by the corresponding
            #query set has it's own cutoff time
            #that cutoff time is computed for each user individually
            #and stored as a parameter "cutoff_time"

            #we won't send email for a given question if an email has been
            #sent after that cutoff_time
            if feed.should_send_now():
                if DEBUG_THIS_COMMAND == False:
                    feed.mark_reported_now()
                cutoff_time = feed.get_previous_report_cutoff_time()

                if feed.feed_type == 'q_sel':
                    q_sel_A = Q_set_A.filter(thread__followed_by=user)
                    q_sel_A.cutoff_time = cutoff_time #store cutoff time per query set
                    q_sel_B = Q_set_B.filter(thread__followed_by=user)
                    q_sel_B.cutoff_time = cutoff_time #store cutoff time per query set

                elif feed.feed_type == 'q_ask':
                    q_ask_A = Q_set_A.filter(author=user)
                    q_ask_A.cutoff_time = cutoff_time
                    q_ask_B = Q_set_B.filter(author=user)
                    q_ask_B.cutoff_time = cutoff_time

                elif feed.feed_type == 'q_ans':
                    q_ans_A = Q_set_A.filter(thread__posts__author=user, thread__posts__post_type='answer')
                    q_ans_A = q_ans_A[:askbot_settings.MAX_ALERTS_PER_EMAIL]
                    q_ans_A.cutoff_time = cutoff_time

                    q_ans_B = Q_set_B.filter(thread__posts__author=user, thread__posts__post_type='answer')
                    q_ans_B = q_ans_B[:askbot_settings.MAX_ALERTS_PER_EMAIL]
                    q_ans_B.cutoff_time = cutoff_time

                elif feed.feed_type == 'q_all':
                    q_all_A = user.get_tag_filtered_questions(Q_set_A)
                    q_all_B = user.get_tag_filtered_questions(Q_set_B)

                    q_all_A = q_all_A[:askbot_settings.MAX_ALERTS_PER_EMAIL]
                    q_all_B = q_all_B[:askbot_settings.MAX_ALERTS_PER_EMAIL]
                    q_all_A.cutoff_time = cutoff_time
                    q_all_B.cutoff_time = cutoff_time

        #build ordered list questions for the email report
        q_list = SortedDict()

        #todo: refactor q_list into a separate class?
        extend_question_list(q_sel_A, q_list, languages=languages)
        extend_question_list(q_sel_B, q_list, languages=languages)

        #build list of comment and mention responses here
        #it is separate because posts are not marked as changed
        #when people add comments
        #mention responses could be collected in the loop above, but
        #it is inconvenient, because feed_type m_and_c bundles the two
        #also we collect metadata for these here
        try:
            feed = user_feeds.get(feed_type='m_and_c')
            if feed.should_send_now():
                cutoff_time = feed.get_previous_report_cutoff_time()
                comments = Post.objects.get_comments().filter(
                    added_at__lt=cutoff_time
                ).exclude(author=user).select_related('parent')
                q_commented = list()

                for c in comments:
                    post = c.parent

                    if post.author_id != user.pk:
                        continue

                    #skip is post was seen by the user after
                    #the comment posting time
                    q_commented.append(post.get_origin_post())

                extend_question_list(
                    q_commented,
                    q_list,
                    cutoff_time=cutoff_time,
                    add_comment=True,
                    languages=languages
                )

                mentions = Activity.objects.get_mentions(
                    mentioned_at__lt=cutoff_time,
                    mentioned_whom=user
                )

                #print 'have %d mentions' % len(mentions)
                #MM = Activity.objects.filter(activity_type = const.TYPE_ACTIVITY_MENTION)
                #print 'have %d total mentions' % len(MM)
                #for m in MM:
                #    print m

                mention_posts = get_all_origin_posts(mentions)
                q_mentions_id = [q.id for q in mention_posts]

                q_mentions_A = Q_set_A.filter(id__in = q_mentions_id)
                q_mentions_A.cutoff_time = cutoff_time
                extend_question_list(
                    q_mentions_A,
                    q_list,
                    add_mention=True,
                    languages=languages
                )

                q_mentions_B = Q_set_B.filter(id__in = q_mentions_id)
                q_mentions_B.cutoff_time = cutoff_time
                extend_question_list(
                    q_mentions_B,
                    q_list,
                    add_mention=True,
                    languages=languages
                )
        except EmailFeedSetting.DoesNotExist:
            pass

        if user.email_tag_filter_strategy == const.INCLUDE_INTERESTING:
            extend_question_list(q_all_A, q_list, languages=languages)
            extend_question_list(q_all_B, q_list, languages=languages)

        extend_question_list(q_ask_A, q_list, limit=True, languages=languages)
        extend_question_list(q_ask_B, q_list, limit=True, languages=languages)

        extend_question_list(q_ans_A, q_list, limit=True, languages=languages)
        extend_question_list(q_ans_B, q_list, limit=True, languages=languages)

        if user.email_tag_filter_strategy == const.EXCLUDE_IGNORED:
            extend_question_list(q_all_A, q_list, limit=True, languages=languages)
            extend_question_list(q_all_B, q_list, limit=True, languages=languages)

        ctype = ContentType.objects.get_for_model(Post)
        EMAIL_UPDATE_ACTIVITY = const.TYPE_ACTIVITY_EMAIL_UPDATE_SENT

        #up to this point we still don't know if emails about
        #collected questions were sent recently
        #the next loop examines activity record and decides
        #for each question, whether it needs to be included or not
        #into the report

        for q, meta_data in q_list.items():
            #this loop edits meta_data for each question
            #so that user will receive counts on new edits new answers, etc
            #and marks questions that need to be skipped
            #because an email about them was sent recently enough

            #also it keeps a record of latest email activity per question per user
            try:
                #todo: is it possible to use content_object here, instead of
                #content type and object_id pair?
                update_info = Activity.objects.get(
                    user=user,
                    content_type=ctype,
                    object_id=q.id,
                    activity_type=EMAIL_UPDATE_ACTIVITY
                )
                emailed_at = update_info.active_at
            except Activity.DoesNotExist:
                update_info = Activity(
                                    user=user, 
                                    content_object=q, 
                                    activity_type=EMAIL_UPDATE_ACTIVITY
                                )
                emailed_at = datetime.datetime(1970, 1, 1)  #long time ago
                if django_settings.USE_TZ:
                    emailed_at = timezone.make_aware(emailed_at, timezone.utc)
            except Activity.MultipleObjectsReturned:
                raise Exception(
                                'server error - multiple question email activities '
                                'found per user-question pair'
                                )

            cutoff_time = meta_data['cutoff_time']#cutoff time for the question

            #skip question if we need to wait longer because
            #the delay before the next email has not yet elapsed
            #or if last email was sent after the most recent modification
            if emailed_at > cutoff_time or emailed_at > q.thread.last_activity_at:
                meta_data['skip'] = True
                continue

            #collect info on all sorts of news that happened after
            #the most recent emailing to the user about this question
            q_rev = q.revisions.filter(revised_at__gt=emailed_at)
            q_rev = q_rev.exclude(author=user)

            #now update all sorts of metadata per question
            meta_data['q_rev'] = len(q_rev)
            if len(q_rev) > 0 and q.added_at == q_rev[0].revised_at:
                meta_data['q_rev'] = 0
                meta_data['new_q'] = True
            else:
                meta_data['new_q'] = False

            new_ans = Post.objects.get_answers(user).filter(
                thread=q.thread,
                added_at__gt=emailed_at,
                deleted=False,
            )
            new_ans = new_ans.exclude(author=user)
            meta_data['new_ans'] = len(new_ans)

            ans_ids = Post.objects.get_answers(user).filter(
                thread=q.thread,
                added_at__gt=emailed_at,
                deleted=False,
            ).values_list('id', flat=True)

            ans_rev = PostRevision.objects.filter(post__id__in = ans_ids)
            ans_rev = ans_rev.exclude(author=user).distinct()

            meta_data['ans_rev'] = len(ans_rev)

            comments = meta_data.get('comments', 0)
            mentions = meta_data.get('mentions', 0)

            #print meta_data
            #finally skip question if there are no news indeed
            if len(q_rev) + len(new_ans) + len(ans_rev) + comments + mentions == 0:
                meta_data['skip'] = True
                #print 'skipping'
            else:
                meta_data['skip'] = False
                #print 'not skipping'
                update_info.active_at = timezone.now() 
                if DEBUG_THIS_COMMAND == False:
                    update_info.save() #save question email update activity
        #q_list is actually an ordered dictionary
        #print 'user %s gets %d' % (user.username, len(q_list.keys()))
        #todo: sort question list by update time
        return q_list
Beispiel #12
0
def record_post_update(
        post = None,
        updated_by = None,
        newly_mentioned_users = None,
        timestamp = None,
        created = False
    ):
    """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
    """
    start_time = time.time()

    #todo: take into account created == True case
    (activity_type, update_object) = post.get_updated_activity_data(created)

    update_activity = Activity(
                    user = updated_by,
                    active_at = timestamp, 
                    content_object = post, 
                    activity_type = activity_type,
                    question = post.get_origin_post()
                )
    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)

    assert(updated_by not in recipients)

    for user in (set(recipients) | set(newly_mentioned_users)):
        user.update_response_counts()

    #todo: weird thing is that only comments need the recipients
    #todo: debug these calls and then uncomment in the repo
    #argument to this call
    pre_notif_time = time.time()
    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 False:
        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()]

    #Updater always gets an email
    notification_subscribers.append(updated_by)

    pre_email_time = time.time()
    send_instant_notifications_about_activity_in_post(
                            update_activity = update_activity,
                            post = post,
                            recipients = notification_subscribers,
                        )
    debug_str = "\nTitle: %s" % post.get_origin_post().title
    debug_str += "\nEmailed%s\n" % get_subs_email(notification_subscribers)
    #debug_str += "  Pre-notif Time: %8.3f\n" % float(pre_notif_time - start_time)
    #debug_str += "  Sub Search Time: %8.3f\n" % float(pre_email_time - pre_notif_time)
    #debug_str += "  Email Time: %8.3f\n" % float(time.time() - pre_email_time)
    debug_str += "Total Elapsed Time: %8.3f" % float(time.time() - start_time)
    logging.critical(debug_str)
Beispiel #13
0
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.post_type != 'comment':
        #summary = post.get_latest_revision().summary
        summary = diff
    else:
        #it's just a comment!
        summary = post.comment

    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()

    #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,
    )
    def get_updated_questions_for_user(self, user):
        """
        retreive relevant question updates for the user
        according to their subscriptions and recorded question
        views
        """

        user_feeds = EmailFeedSetting.objects.filter(subscriber=user).exclude(
            frequency__in=('n', 'i'))

        should_proceed = False
        for feed in user_feeds:
            if feed.should_send_now() == True:
                should_proceed = True
                break

        #shortcircuit - if there is no ripe feed to work on for this user
        if should_proceed == False:
            return {}

        #these are placeholders for separate query sets per question group
        #there are four groups - one for each EmailFeedSetting.feed_type
        #and each group has subtypes A and B
        #that's because of the strange thing commented below
        #see note on Q and F objects marked with todo tag
        q_sel_A = None
        q_sel_B = None

        q_ask_A = None
        q_ask_B = None

        q_ans_A = None
        q_ans_B = None

        q_all_A = None
        q_all_B = None

        #base question query set for this user
        #basic things - not deleted, not closed, not too old
        #not last edited by the same user
        base_qs = Post.objects.get_questions(
        ).exclude(thread__last_activity_by=user).exclude(
            thread__last_activity_at__lt=user.date_joined  #exclude old stuff
        ).exclude(deleted=True).exclude(
            thread__closed=True).order_by('-thread__last_activity_at')

        if askbot_settings.ENABLE_CONTENT_MODERATION:
            base_qs = base_qs.filter(approved=True)
        #todo: for some reason filter on did not work as expected ~Q(viewed__who=user) |
        #      Q(viewed__who=user,viewed__when__lt=F('thread__last_activity_at'))
        #returns way more questions than you might think it should
        #so because of that I've created separate query sets Q_set2 and Q_set3
        #plus two separate queries run faster!

        #build two two queries based

        #questions that are not seen by the user at all
        not_seen_qs = base_qs.filter(~Q(viewed__who=user))
        #questions that were seen, but before last modification
        seen_before_last_mod_qs = base_qs.filter(
            Q(viewed__who=user,
              viewed__when__lt=F('thread__last_activity_at')))

        #shorten variables for convenience
        Q_set_A = not_seen_qs
        Q_set_B = seen_before_last_mod_qs

        if getattr(django_settings, 'ASKBOT_MULTILINGUAL', False):
            languages = user.languages.split()
        else:
            languages = None

        for feed in user_feeds:
            if feed.feed_type == 'm_and_c':
                #alerts on mentions and comments are processed separately
                #because comments to questions do not trigger change of last_updated
                #this may be changed in the future though, see
                #http://askbot.org/en/question/96/
                continue

            #each group of updates represented by the corresponding
            #query set has it's own cutoff time
            #that cutoff time is computed for each user individually
            #and stored as a parameter "cutoff_time"

            #we won't send email for a given question if an email has been
            #sent after that cutoff_time
            if feed.should_send_now():
                if DEBUG_THIS_COMMAND == False:
                    feed.mark_reported_now()
                cutoff_time = feed.get_previous_report_cutoff_time()

                if feed.feed_type == 'q_sel':
                    q_sel_A = Q_set_A.filter(thread__followed_by=user)
                    q_sel_A.cutoff_time = cutoff_time  #store cutoff time per query set
                    q_sel_B = Q_set_B.filter(thread__followed_by=user)
                    q_sel_B.cutoff_time = cutoff_time  #store cutoff time per query set

                elif feed.feed_type == 'q_ask':
                    q_ask_A = Q_set_A.filter(author=user)
                    q_ask_A.cutoff_time = cutoff_time
                    q_ask_B = Q_set_B.filter(author=user)
                    q_ask_B.cutoff_time = cutoff_time

                elif feed.feed_type == 'q_ans':
                    q_ans_A = Q_set_A.filter(thread__posts__author=user,
                                             thread__posts__post_type='answer')
                    q_ans_A = q_ans_A[:askbot_settings.MAX_ALERTS_PER_EMAIL]
                    q_ans_A.cutoff_time = cutoff_time

                    q_ans_B = Q_set_B.filter(thread__posts__author=user,
                                             thread__posts__post_type='answer')
                    q_ans_B = q_ans_B[:askbot_settings.MAX_ALERTS_PER_EMAIL]
                    q_ans_B.cutoff_time = cutoff_time

                elif feed.feed_type == 'q_all':
                    q_all_A = user.get_tag_filtered_questions(Q_set_A)
                    q_all_B = user.get_tag_filtered_questions(Q_set_B)

                    q_all_A = q_all_A[:askbot_settings.MAX_ALERTS_PER_EMAIL]
                    q_all_B = q_all_B[:askbot_settings.MAX_ALERTS_PER_EMAIL]
                    q_all_A.cutoff_time = cutoff_time
                    q_all_B.cutoff_time = cutoff_time

        #build ordered list questions for the email report
        q_list = SortedDict()

        #todo: refactor q_list into a separate class?
        extend_question_list(q_sel_A, q_list, languages=languages)
        extend_question_list(q_sel_B, q_list, languages=languages)

        #build list of comment and mention responses here
        #it is separate because posts are not marked as changed
        #when people add comments
        #mention responses could be collected in the loop above, but
        #it is inconvenient, because feed_type m_and_c bundles the two
        #also we collect metadata for these here
        try:
            feed = user_feeds.get(feed_type='m_and_c')
            if feed.should_send_now():
                cutoff_time = feed.get_previous_report_cutoff_time()
                comments = Post.objects.get_comments().filter(
                    added_at__lt=cutoff_time, ).exclude(author=user)
                q_commented = list()
                for c in comments:
                    post = c.parent
                    if post.author != user:
                        continue

                    #skip is post was seen by the user after
                    #the comment posting time
                    q_commented.append(post.get_origin_post())

                extend_question_list(q_commented,
                                     q_list,
                                     cutoff_time=cutoff_time,
                                     add_comment=True,
                                     languages=languages)

                mentions = Activity.objects.get_mentions(
                    mentioned_at__lt=cutoff_time, mentioned_whom=user)

                #print 'have %d mentions' % len(mentions)
                #MM = Activity.objects.filter(activity_type = const.TYPE_ACTIVITY_MENTION)
                #print 'have %d total mentions' % len(MM)
                #for m in MM:
                #    print m

                mention_posts = get_all_origin_posts(mentions)
                q_mentions_id = [q.id for q in mention_posts]

                q_mentions_A = Q_set_A.filter(id__in=q_mentions_id)
                q_mentions_A.cutoff_time = cutoff_time
                extend_question_list(q_mentions_A,
                                     q_list,
                                     add_mention=True,
                                     languages=languages)

                q_mentions_B = Q_set_B.filter(id__in=q_mentions_id)
                q_mentions_B.cutoff_time = cutoff_time
                extend_question_list(q_mentions_B,
                                     q_list,
                                     add_mention=True,
                                     languages=languages)
        except EmailFeedSetting.DoesNotExist:
            pass

        if user.email_tag_filter_strategy == const.INCLUDE_INTERESTING:
            extend_question_list(q_all_A, q_list, languages=languages)
            extend_question_list(q_all_B, q_list, languages=languages)

        extend_question_list(q_ask_A, q_list, limit=True, languages=languages)
        extend_question_list(q_ask_B, q_list, limit=True, languages=languages)

        extend_question_list(q_ans_A, q_list, limit=True, languages=languages)
        extend_question_list(q_ans_B, q_list, limit=True, languages=languages)

        if user.email_tag_filter_strategy == const.EXCLUDE_IGNORED:
            extend_question_list(q_all_A,
                                 q_list,
                                 limit=True,
                                 languages=languages)
            extend_question_list(q_all_B,
                                 q_list,
                                 limit=True,
                                 languages=languages)

        ctype = ContentType.objects.get_for_model(Post)
        EMAIL_UPDATE_ACTIVITY = const.TYPE_ACTIVITY_EMAIL_UPDATE_SENT

        #up to this point we still don't know if emails about
        #collected questions were sent recently
        #the next loop examines activity record and decides
        #for each question, whether it needs to be included or not
        #into the report

        for q, meta_data in q_list.items():
            #this loop edits meta_data for each question
            #so that user will receive counts on new edits new answers, etc
            #and marks questions that need to be skipped
            #because an email about them was sent recently enough

            #also it keeps a record of latest email activity per question per user
            try:
                #todo: is it possible to use content_object here, instead of
                #content type and object_id pair?
                update_info = Activity.objects.get(
                    user=user,
                    content_type=ctype,
                    object_id=q.id,
                    activity_type=EMAIL_UPDATE_ACTIVITY)
                emailed_at = update_info.active_at
            except Activity.DoesNotExist:
                update_info = Activity(user=user,
                                       content_object=q,
                                       activity_type=EMAIL_UPDATE_ACTIVITY)
                emailed_at = datetime.datetime(1970, 1, 1)  #long time ago
            except Activity.MultipleObjectsReturned:
                raise Exception(
                    'server error - multiple question email activities '
                    'found per user-question pair')

            cutoff_time = meta_data[
                'cutoff_time']  #cutoff time for the question

            #skip question if we need to wait longer because
            #the delay before the next email has not yet elapsed
            #or if last email was sent after the most recent modification
            if emailed_at > cutoff_time or emailed_at > q.thread.last_activity_at:
                meta_data['skip'] = True
                continue

            #collect info on all sorts of news that happened after
            #the most recent emailing to the user about this question
            q_rev = q.revisions.filter(revised_at__gt=emailed_at)
            q_rev = q_rev.exclude(author=user)

            #now update all sorts of metadata per question
            meta_data['q_rev'] = len(q_rev)
            if len(q_rev) > 0 and q.added_at == q_rev[0].revised_at:
                meta_data['q_rev'] = 0
                meta_data['new_q'] = True
            else:
                meta_data['new_q'] = False

            new_ans = Post.objects.get_answers(user).filter(
                thread=q.thread,
                added_at__gt=emailed_at,
                deleted=False,
            )
            new_ans = new_ans.exclude(author=user)
            meta_data['new_ans'] = len(new_ans)

            ans_ids = Post.objects.get_answers(user).filter(
                thread=q.thread,
                added_at__gt=emailed_at,
                deleted=False,
            ).values_list('id', flat=True)
            ans_rev = PostRevision.objects.filter(post__id__in=ans_ids)
            ans_rev = ans_rev.exclude(author=user).distinct()
            meta_data['ans_rev'] = len(ans_rev)

            comments = meta_data.get('comments', 0)
            mentions = meta_data.get('mentions', 0)

            #print meta_data
            #finally skip question if there are no news indeed
            if len(q_rev) + len(new_ans) + len(
                    ans_rev) + comments + mentions == 0:
                meta_data['skip'] = True
                #print 'skipping'
            else:
                meta_data['skip'] = False
                #print 'not skipping'
                update_info.active_at = datetime.datetime.now()
                if DEBUG_THIS_COMMAND == False:
                    update_info.save()  #save question email update activity
        #q_list is actually an ordered dictionary
        #print 'user %s gets %d' % (user.username, len(q_list.keys()))
        #todo: sort question list by update time
        return q_list
Beispiel #15
0
        return Action.objects.filter(thread__pk__in=thread_pks)

User.add_to_class('ext_noattr', UserExtension())

#remove old attribute to prevent duplicates in the Activity fields.
#1- search 'activity_type' into the Activity local_fields and save the index
local_fields = Activity._meta.local_fields

for field in local_fields:
    if field.name == 'activity_type':
        index = local_fields.index(field)
        break

#2- remove the found element from the local_fields list.
Activity._meta.local_fields.pop(index)

#3- add to class
Activity.add_to_class(
    'activity_type',
    models.SmallIntegerField(choices=ae_const.TYPE_ACTIVITY_CHOICES))

#4- move the added field to the last index to the previous one
field = local_fields.pop()
local_fields.insert(index, field)

#5- clean cache(s)
#del Activity._meta._field_cache
#del Activity._meta._field_name_cache

#print("\n\nActivity._meta.local_fields: %s\n\n" % Activity._meta.local_fields)
Beispiel #16
0
        thread_pks = Vote.objects.filter(pk__in=self.votes.all()).declareds().values_list('voted_post__thread__pk', flat=True).distinct()
        return Action.objects.filter(thread__pk__in=thread_pks)


User.add_to_class('ext_noattr', UserExtension())

#remove old attribute to prevent duplicates in the Activity fields.
#1- search 'activity_type' into the Activity local_fields and save the index
local_fields = Activity._meta.local_fields

for field in local_fields:
    if field.name == 'activity_type':
        index = local_fields.index(field)
        break

#2- remove the found element from the local_fields list.
Activity._meta.local_fields.pop(index)

#3- add to class
Activity.add_to_class('activity_type', models.SmallIntegerField(choices = ae_const.TYPE_ACTIVITY_CHOICES))

#4- move the added field to the last index to the previous one
field = local_fields.pop()
local_fields.insert(index, field)

#5- clean cache(s)
#del Activity._meta._field_cache
#del Activity._meta._field_name_cache

#print("\n\nActivity._meta.local_fields: %s\n\n" % Activity._meta.local_fields)
Beispiel #17
0
def record_post_update(
        post = None,
        updated_by = None,
        newly_mentioned_users = None,
        timestamp = None,
        created = False
    ):
    """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)

    update_activity = Activity(
                    user = updated_by,
                    active_at = timestamp, 
                    content_object = post, 
                    activity_type = activity_type,
                    question = post.get_origin_post()
                )
    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)

    assert(updated_by not in recipients)

    for user in (set(recipients) | set(newly_mentioned_users)):
        user.update_response_counts()

    #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,
                        )
Beispiel #18
0
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()

    #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,
                        )
Beispiel #19
0
def record_post_update_task(
        post_id,
        post_content_type_id,
        newly_mentioned_user_id_list = None, 
        updated_by_id = None,
        timestamp = None,
        created = False,
    ):

    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)

    #todo: take into account created == True case
    (activity_type, update_object) = post.get_updated_activity_data(created)

    update_activity = Activity(
                    user = updated_by,
                    active_at = timestamp, 
                    content_object = post, 
                    activity_type = activity_type,
                    question = post.get_origin_post()
                )
    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)

    assert(updated_by not in recipients)

    newly_mentioned_users = User.objects.filter(
                                id__in = newly_mentioned_user_id_list
                            )

    for user in set(recipients) | set(newly_mentioned_users):
        user.increment_response_count()
        user.save()

    #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,
                        )