tasks.py 5.35 KB
Newer Older
1 2 3 4 5
"""
Defines asynchronous celery task for sending email notification (through edx-ace)
pertaining to new discussion forum comments.
"""
import logging
6
from urllib import urlencode
7 8 9 10 11 12 13 14 15
from urlparse import urljoin

from celery import task
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import Site

from celery_utils.logged_task import LoggedTask
from edx_ace import ace
16
from edx_ace.utils import date
17 18 19
from edx_ace.message import MessageType
from edx_ace.recipient import Recipient
from opaque_keys.edx.keys import CourseKey
20
from openedx.core.djangoapps.site_configuration.helpers import get_value
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
from lms.djangoapps.django_comment_client.utils import permalink
import lms.lib.comment_client as cc

from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.schedules.template_context import get_base_template_context


log = logging.getLogger(__name__)


DEFAULT_LANGUAGE = 'en'
ROUTING_KEY = getattr(settings, 'ACE_ROUTING_KEY', None)


class ResponseNotification(MessageType):
    def __init__(self, *args, **kwargs):
        super(ResponseNotification, self).__init__(*args, **kwargs)
        self.name = 'response_notification'


@task(base=LoggedTask, routing_key=ROUTING_KEY)
def send_ace_message(context):
    context['course_id'] = CourseKey.from_string(context['course_id'])
44
    context['site'] = Site.objects.get(id=context['site_id'])
45 46 47 48 49 50 51 52 53 54 55 56 57 58
    if _should_send_message(context):
        thread_author = User.objects.get(id=context['thread_author_id'])
        message_context = _build_message_context(context)
        message = ResponseNotification().personalize(
            Recipient(thread_author.username, thread_author.email),
            _get_course_language(context['course_id']),
            message_context
        )
        log.info('Sending forum comment email notification with context %s', message_context)
        ace.send(message)


def _should_send_message(context):
    cc_thread_author = cc.User(id=context['thread_author_id'], course_id=context['course_id'])
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
    return (
        _is_user_subscribed_to_thread(cc_thread_author, context['thread_id']) and
        _is_not_subcomment(context['comment_id']) and
        _is_first_comment(context['comment_id'], context['thread_id'])
    )


def _is_not_subcomment(comment_id):
    comment = cc.Comment.find(id=comment_id).retrieve()
    return not getattr(comment, 'parent_id', None)


def _is_first_comment(comment_id, thread_id):
    thread = cc.Thread.find(id=thread_id).retrieve(with_responses=True)
    first_comment = thread.children[0]
    return first_comment.get('id') == comment_id
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95


def _is_user_subscribed_to_thread(cc_user, thread_id):
    paginated_result = cc_user.subscribed_threads()
    thread_ids = {thread['id'] for thread in paginated_result.collection}

    while paginated_result.page < paginated_result.num_pages:
        next_page = paginated_result.page + 1
        paginated_result = cc_user.subscribed_threads(query_params={'page': next_page})
        thread_ids.update(thread['id'] for thread in paginated_result.collection)

    return thread_id in thread_ids


def _get_course_language(course_id):
    course_overview = CourseOverview.objects.get(id=course_id)
    language = course_overview.language or DEFAULT_LANGUAGE
    return language


def _build_message_context(context):
96 97
    message_context = get_base_template_context(Site.objects.get(id=context['site_id']))
    message_context.update(_deserialize_context_dates(context))
98
    message_context['post_link'] = _get_thread_url(context)
99
    message_context['ga_tracking_pixel_url'] = _generate_ga_pixel_url(context)
100 101 102
    return message_context


103 104 105 106 107 108
def _deserialize_context_dates(context):
    context['comment_created_at'] = date.deserialize(context['comment_created_at'])
    context['thread_created_at'] = date.deserialize(context['thread_created_at'])
    return context


109 110 111 112 113 114 115
def _get_thread_url(context):
    thread_content = {
        'type': 'thread',
        'course_id': context['course_id'],
        'commentable_id': context['thread_commentable_id'],
        'id': context['thread_id'],
    }
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
    return urljoin(context['site'].domain, permalink(thread_content))


def _generate_ga_pixel_url(context):
    # used for analytics
    query_params = {
        'v': '1',  # version, required for GA
        't': 'event',  #
        'ec': 'email',  # event category
        'ea': 'edx.bi.email.opened',  # event action: in this case, the user opened the email
        'tid': get_value("GOOGLE_ANALYTICS_TRACKING_ID", getattr(settings, "GOOGLE_ANALYTICS_TRACKING_ID", None)),  # tracking ID to associate this link with our GA instance
        'uid': context['thread_author_id'],
        'cs': 'sailthru',  # Campaign source - what sent the email
        'cm': 'email',  # Campaign medium - how the content is being delivered
        'cn': 'triggered_discussionnotification',  # Campaign name - human-readable name for this particular class of message
        'dp': '/email/ace/discussions/responsenotification/{0}/'.format(context['course_id']),  # document path, used for drilling down into specific events
        'dt': 'Reply to {0} at {1}'.format(context['thread_title'], context['comment_created_at']),  # document title, should match the title of the email
    }

    return u"{url}?{params}".format(
        url="https://www.google-analytics.com/collect",
        params=urlencode(query_params)
    )