def test_no_resolve(self):
        """
        Asserts that None is returned when there is scope resolution
        """

        with self.assertRaises(TypeError):
            resolve_user_scope('bad_scope', {})
Beispiel #2
0
    def test_no_resolve(self):
        """
        Asserts that None is returned when there is scope resolution
        """

        with self.assertRaises(TypeError):
            resolve_user_scope('bad_scope', {})
    def test_no_resolver(self):
        """
        Asserts that an exception is raised when we try to resolve
        via a non-existing scope_name
        """

        with self.assertRaises(TypeError):
            resolve_user_scope('non-existing', {})
Beispiel #4
0
    def test_no_resolver(self):
        """
        Asserts that an exception is raised when we try to resolve
        via a non-existing scope_name
        """

        with self.assertRaises(TypeError):
            resolve_user_scope('non-existing', {})
Beispiel #5
0
    def test_bad_return_type(self):
        """
        Asserts that we can't allow for an illegal type to
        be returned
        """

        register_user_scope_resolver('badtype_scope', TestListScopeResolver())

        with self.assertRaises(TypeError):
            resolve_user_scope('badtype_scope', {})
    def test_bad_return_type(self):
        """
        Asserts that we can't allow for an illegal type to
        be returned
        """

        register_user_scope_resolver('badtype_scope', TestListScopeResolver())

        with self.assertRaises(TypeError):
            resolve_user_scope('badtype_scope', {})
Beispiel #7
0
    def test_no_instantiation(self):
        """
        Asserts that NotificationScopeResolver is an abstract base clas
        """

        with self.assertRaises(TypeError):
            NotificationUserScopeResolver()  # pylint: disable=abstract-class-instantiated

        register_user_scope_resolver('bad_scope', BadTestScopeResolver())

        with self.assertRaises(NotImplementedError):
            resolve_user_scope('bad_scope', {})
    def test_no_instantiation(self):
        """
        Asserts that NotificationScopeResolver is an abstract base clas
        """

        with self.assertRaises(TypeError):
            NotificationUserScopeResolver()

        register_user_scope_resolver('bad_scope', BadTestScopeResolver())

        with self.assertRaises(NotImplementedError):
            resolve_user_scope('bad_scope', {})
    def test_django_orm_based_resolver(self):
        """
        Assert proper operations when working with Django ORM cursors
        """

        users = resolve_user_scope('values_list_query_set', {})
        users_list = [user for user in users]
        self.assertEqual(len(users_list), 1)
        self.assertEqual(users_list[0], self.test_user.id)

        users = resolve_user_scope('values_query_set', {})
        users_list = [user for user in users]
        self.assertEqual(len(users_list), 1)
        self.assertEqual(users_list[0]['id'], self.test_user.id)
Beispiel #10
0
    def test_django_orm_based_resolver(self):
        """
        Assert proper operations when working with Django ORM cursors
        """

        users = resolve_user_scope('values_list_query_set', {})
        users_list = list(users)
        self.assertEqual(len(users_list), 1)
        self.assertEqual(users_list[0], self.test_user.id)

        users = resolve_user_scope('values_query_set', {})
        users_list = [user for user in users]
        self.assertEqual(len(users_list), 1)
        self.assertEqual(users_list[0]['id'], self.test_user.id)
Beispiel #11
0
def _send_to_scoped_users(msg,
                          scope_name,
                          scope_context,
                          preferred_channel=None,
                          channel_context=None):
    """
    Helper method to send to a scoped set of users.
    scope_context contains all of the information
    that can be passed into a NotificationScopeResolver
    """

    # user_ids can be a list, a generator function, or a ValuesQuerySet/ValuesListQuerySet (Django ORM)
    user_ids = resolve_user_scope(scope_name, scope_context)

    if not user_ids:
        err_msg = (
            'Could not resolve distribution scope "{name}" with context {context}! '
            'Message id "{_id}" was not sent!').format(name=scope_name,
                                                       context=scope_context,
                                                       _id=msg.id)

        raise Exception(err_msg)

    # optional parameter to exclude certain
    # ids
    exclude_list = scope_context.get('exclude_user_ids')

    num_dispatched = bulk_publish_notification_to_users(
        user_ids,
        msg,
        exclude_user_ids=exclude_list,
        preferred_channel=preferred_channel,
        channel_context=channel_context)

    return num_dispatched
def _send_to_scoped_users(msg, scope_name, scope_context, preferred_channel=None, channel_context=None):
    """
    Helper method to send to a scoped set of users.
    scope_context contains all of the information
    that can be passed into a NotificationScopeResolver
    """

    # user_ids can be a list, a generator function, or a ValuesQuerySet/ValuesListQuerySet (Django ORM)
    user_ids = resolve_user_scope(scope_name, scope_context)

    if not user_ids:
        err_msg = (
            'Could not resolve distribution scope "{name}" with context {context}! '
            'Message id "{_id}" was not sent!'
        ).format(name=scope_name, context=scope_context, _id=msg.id)

        raise Exception(err_msg)

    # optional parameter to exclude certain
    # ids
    exclude_list = scope_context.get('exclude_user_ids')

    num_dispatched = bulk_publish_notification_to_users(
        user_ids,
        msg,
        exclude_user_ids=exclude_list,
        preferred_channel=preferred_channel,
        channel_context=channel_context
    )

    return num_dispatched
    def test_no_resolution(self):
        """
        Asserts that None is returned if the Resolvers can resolve
        """

        register_user_scope_resolver('none_resolver', TestListScopeResolver())

        self.assertIsNone(resolve_user_scope('none_resolver', {}))
Beispiel #14
0
    def test_no_resolution(self):
        """
        Asserts that None is returned if the Resolvers can resolve
        """

        register_user_scope_resolver('none_resolver', TestListScopeResolver())

        self.assertIsNone(resolve_user_scope('none_resolver', {}))
    def test_resolving_scope(self):
        """
        Happy path scope resolving
        """

        user_ids = resolve_user_scope('list_scope', {'range': 5})

        self.assertIsNotNone(user_ids)
        self.assertEqual(len(user_ids), 5)
        self.assertEqual(user_ids, [num for num in range(5)])

        user_ids = resolve_user_scope('generator_scope', {'range': 10})

        self.assertIsNotNone(user_ids)
        compare = []
        for user_id in user_ids:
            compare.append(user_id)

        # generators dont support len()
        self.assertEqual(compare, [num for num in range(10)])
Beispiel #16
0
    def test_resolving_scope(self):
        """
        Happy path scope resolving
        """

        user_ids = resolve_user_scope('list_scope', {'range': 5})

        self.assertIsNotNone(user_ids)
        self.assertEqual(len(user_ids), 5)
        self.assertEqual(user_ids, list(range(5)))

        user_ids = resolve_user_scope('generator_scope', {'range': 10})

        self.assertIsNotNone(user_ids)
        compare = []
        for user_id in user_ids:
            compare.append(user_id)

        # generators dont support len()
        self.assertEqual(compare, list(range(10)))
Beispiel #17
0
def bulk_publish_notification_to_scope(scope_name,
                                       scope_context,
                                       msg,
                                       exclude_user_ids=None,
                                       preferred_channel=None,
                                       channel_context=None):
    """
    This top level API method will publish a notification
    to a UserScope (potentially large). Basically this is a convenience method
    which simple resolves the scope and then called into
    bulk_publish_notifications_to_scope()

    IMPORTANT: In general one will want to call into this method behind a
    Celery task

    For built in Scope Resolvers ('course_group', 'course_enrollments')

        scope_context:
            if scope='course_group' then context = {'course_id': xxxx, 'group_id': xxxxx}
            if scope='course_enrollments' then context = {'course_id'}

    """
    log_msg = (
        'Publishing scoped Notification to scope name "{scope_name}" and scope '
        'context {scope_context} with message: {msg}').format(
            scope_name=scope_name, scope_context=scope_context, msg=msg)
    log.info(log_msg)

    user_ids = resolve_user_scope(scope_name, scope_context)

    if not user_ids:
        return 0

    return bulk_publish_notification_to_users(
        user_ids,
        msg,
        exclude_user_ids,
        preferred_channel=preferred_channel,
        channel_context=channel_context)
def bulk_publish_notification_to_scope(scope_name, scope_context, msg, exclude_user_ids=None,
                                       preferred_channel=None, channel_context=None):
    """
    This top level API method will publish a notification
    to a UserScope (potentially large). Basically this is a convenience method
    which simple resolves the scope and then called into
    bulk_publish_notifications_to_scope()

    IMPORTANT: In general one will want to call into this method behind a
    Celery task

    For built in Scope Resolvers ('course_group', 'course_enrollments')

        scope_context:
            if scope='course_group' then context = {'course_id': xxxx, 'group_id': xxxxx}
            if scope='course_enrollments' then context = {'course_id'}

    """
    log_msg = (
        'Publishing scoped Notification to scope name "{scope_name}" and scope '
        'context {scope_context} with message: {msg}'
    ).format(scope_name=scope_name, scope_context=scope_context, msg=msg)
    log.info(log_msg)

    user_ids = resolve_user_scope(scope_name, scope_context)

    if not user_ids:
        return 0

    return bulk_publish_notification_to_users(
        user_ids,
        msg,
        exclude_user_ids,
        preferred_channel=preferred_channel,
        channel_context=channel_context
    )
    def dispatch_notification_to_user(self, user_id, msg, channel_context=None):
        """
        Send a notification to a user, which - in a TriggerEmailChannel Notification
        """

        # call into one of the registered resolvers to get the email for this
        # user
        scope_results = resolve_user_scope(
            'user_email_resolver',
            {
                'user_id': user_id,
                'fields': {
                    'user_id': True,
                    'email': True,
                    'first_name': True,
                    'last_name': True,
                }
            }
        )
        msg = self._get_linked_resolved_msg(msg)
        msg.created = datetime.datetime.now(pytz.UTC)

        user_msg = UserNotification(
            user_id=user_id,
            msg=msg
        )
        config = const.NOTIFICATION_DIGEST_GROUP_CONFIG
        for result in scope_results:
            #
            # Do the rendering and the sending of the email
            #
            if isinstance(result, dict):
                email = result['email']
            else:
                email = result

            renderer = get_renderer_for_type(user_msg.msg.msg_type)
            notification_html = ''
            if renderer and renderer.can_render_format(const.RENDER_FORMAT_HTML):
                notification_html = renderer.render(  # pylint: disable=unused-variable
                    user_msg.msg,
                    const.RENDER_FORMAT_HTML,
                    None
                )
            # create the image dictionary to store the
            # img_path, unique id and title for the image.
            branded_logo = dict(title='Logo', path=const.NOTIFICATION_BRANDED_DEFAULT_LOGO, cid=str(uuid.uuid4()))

            group_name = get_group_name_for_msg_type(user_msg.msg.msg_type.name)
            resolve_links = user_msg.msg.resolve_links
            click_link = user_msg.msg.payload['_click_link']

            if resolve_links and not click_link.startswith('http'):
                click_link = const.NOTIFICATION_EMAIL_CLICK_LINK_URL_FORMAT.format(
                    url_path=click_link,
                    encoded_url_path=urllib.quote(click_link),
                    user_msg_id=user_msg.id,
                    msg_id=user_msg.msg.id,
                    hostname=const.NOTIFICATION_APP_HOSTNAME
                )

            context = {
                'branded_logo': branded_logo['cid'],
                'notification_html': notification_html,
                'user_first_name': result['first_name'] if isinstance(result, dict) else None,
                'user_last_name': result['last_name'] if isinstance(result, dict) else None,
                'group_name': group_name,
                'group_title': config['groups'][group_name]['display_name'],
                'click_link': click_link
            }

            # render the notifications html template
            email_body = with_inline_css(
                render_to_string("django/triggered_notification_email/triggered_email.html", context))

            html_part = MIMEMultipart(_subtype='related')
            html_part.attach(MIMEText(email_body, _subtype='html'))
            logo_image = attach_image(branded_logo, 'Header Logo')
            if logo_image:
                html_part.attach(logo_image)

            log.info('Sending Notification email to {email}'.format(email=email))

            msg = EmailMessage(const.NOTIFICATION_TRIGGERED_EMAIL_SUBJECT, None,
                               const.NOTIFICATION_EMAIL_FROM_ADDRESS, [email])
            msg.attach(html_part)
            msg.send()
        return user_msg
Beispiel #20
0
    def dispatch_notification_to_user(self,
                                      user_id,
                                      msg,
                                      channel_context=None):
        """
        Send a notification to a user, which - in a TriggerEmailChannel Notification
        """

        # call into one of the registered resolvers to get the email for this
        # user
        scope_results = resolve_user_scope(
            'user_email_resolver', {
                'user_id': user_id,
                'fields': {
                    'user_id': True,
                    'email': True,
                    'first_name': True,
                    'last_name': True,
                }
            })
        msg = self._get_linked_resolved_msg(msg)
        msg.created = datetime.datetime.now(pytz.UTC)

        user_msg = UserNotification(user_id=user_id, msg=msg)
        config = const.NOTIFICATION_DIGEST_GROUP_CONFIG
        for result in scope_results:
            #
            # Do the rendering and the sending of the email
            #
            if isinstance(result, dict):
                email = result['email']
            else:
                email = result

            renderer = get_renderer_for_type(user_msg.msg.msg_type)
            notification_html = ''
            if renderer and renderer.can_render_format(
                    const.RENDER_FORMAT_HTML):
                notification_html = renderer.render(  # pylint: disable=unused-variable
                    user_msg.msg, const.RENDER_FORMAT_HTML, None)
            # create the image dictionary to store the
            # img_path, unique id and title for the image.
            branded_logo = dict(title='Logo',
                                path=const.NOTIFICATION_BRANDED_DEFAULT_LOGO,
                                cid=str(uuid.uuid4()))

            group_name = get_group_name_for_msg_type(
                user_msg.msg.msg_type.name)
            resolve_links = user_msg.msg.resolve_links
            click_link = user_msg.msg.payload['_click_link']

            if resolve_links and not click_link.startswith('http'):
                click_link = const.NOTIFICATION_EMAIL_CLICK_LINK_URL_FORMAT.format(
                    url_path=click_link,
                    encoded_url_path=six.moves.urllib.parse.quote(click_link),
                    user_msg_id=user_msg.id,
                    msg_id=user_msg.msg.id,
                    hostname=const.NOTIFICATION_APP_HOSTNAME)

            context = {
                'branded_logo':
                branded_logo['cid'],
                'notification_html':
                notification_html,
                'user_first_name':
                result['first_name'] if isinstance(result, dict) else None,
                'user_last_name':
                result['last_name'] if isinstance(result, dict) else None,
                'group_name':
                group_name,
                'group_title':
                config['groups'][group_name]['display_name'],
                'click_link':
                click_link
            }

            # render the notifications html template
            email_body = with_inline_css(
                render_to_string(
                    "django/triggered_notification_email/triggered_email.html",
                    context))

            html_part = MIMEMultipart(_subtype='related')
            html_part.attach(MIMEText(email_body, _subtype='html'))
            logo_image = attach_image(branded_logo, 'Header Logo')
            if logo_image:
                html_part.attach(logo_image)

            log.info('Sending Notification email to %s', email)

            msg = EmailMessage(const.NOTIFICATION_TRIGGERED_EMAIL_SUBJECT, '',
                               const.NOTIFICATION_EMAIL_FROM_ADDRESS, [email])
            msg.attach(html_part)
            msg.send()
        return user_msg