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', {})
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_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)
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)
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', {}))
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)])
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)))
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
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