def test_purge_command_check(self): """ Invoke the Management Command """ msg_type = self.provider.save_notification_type(self.notification_type) msg1 = self.provider.save_notification_message(NotificationMessage( namespace='namespace1', msg_type=msg_type, payload={ 'foo': 'bar' } )) msg2 = self.provider.save_notification_message(NotificationMessage( namespace='namespace1', msg_type=msg_type, payload={ 'test': 'test' } )) # now reset the time to 66 days ago # in order to save the user notification message in the past. reset_time = datetime.now(pytz.UTC) - timedelta(days=66) with freeze_time(reset_time): self.provider.save_user_notification(UserNotification( user_id=self.test_user_id, msg=msg1 )) self.provider.save_user_notification(UserNotification( user_id=self.test_user_id, msg=msg2 )) # user notifications count self.assertEqual( self.provider.get_num_notifications_for_user( self.test_user_id, filters={ 'namespace': 'namespace1' } ), 2 ) # run the management command for purging notifications. force_purge.Command().handle() # now get the user notification count. # count should be 0 at that moment. because # all the notifications have been deleted. self.assertEqual( self.provider.get_num_notifications_for_user( self.test_user_id, filters={ 'namespace': 'namespace1' } ), 0 )
def test_data_object_inequality(self): """ Make sure that we can verify inequality between two objects """ obj1 = DataObjectWithTypedFields(id=1, test_int_field=100, test_dict_field={'foo': 'bar'}, test_class_field=NotificationMessage( msg_type=NotificationType( name='testing', renderer='foo.renderer', ), namespace='namespace', payload={'field': 'value'})) obj2 = DataObjectWithTypedFields(id=1, test_int_field=100, test_dict_field={'foo': 'bar'}, test_class_field=NotificationMessage( msg_type=NotificationType( name='something-different', renderer='foo.renderer', ), namespace='namespace', payload={'field': 'value'})) self.assertNotEqual(obj1, obj2)
def test_typed_fields(self): """ Assert proper behavior with TypedFields inside of BaseDataObjects """ # make sure we can make proper assignments on initialization msg = NotificationMessage() obj = DataObjectWithTypedFields( id=1, test_int_field=100, test_dict_field={ 'foo': 'bar' }, test_class_field=msg, ) self.assertTrue(isinstance(obj.test_int_field, int)) self.assertTrue(isinstance(obj.test_dict_field, dict)) self.assertTrue(isinstance(obj.test_class_field, NotificationMessage)) self.assertEqual(obj.test_int_field, 100) self.assertEqual(obj.test_dict_field, {'foo': 'bar'}) self.assertEqual(obj.test_class_field, msg) # make sure we work with longs as well obj = DataObjectWithTypedFields( id=long(1), ) self.assertTrue(isinstance(obj.id, long)) # make sure we can set fields after initialization obj = DataObjectWithTypedFields() obj.test_int_field = 100 obj.test_dict_field = { 'foo': 'bar' } obj.test_class_field = NotificationMessage() self.assertTrue(isinstance(obj.test_int_field, int)) self.assertTrue(isinstance(obj.test_dict_field, dict)) self.assertTrue(isinstance(obj.test_class_field, NotificationMessage)) # make sure we can set typed fields as None obj = DataObjectWithTypedFields( test_int_field=None, test_dict_field=None, test_class_field=None, ) self.assertTrue(isinstance(obj.test_int_field, type(None))) self.assertTrue(isinstance(obj.test_dict_field, type(None))) self.assertTrue(isinstance(obj.test_class_field, type(None))) with self.assertRaises(TypeError): # RelatedObjectField can only point to # subclasses of BaseDataObject RelatedObjectField(object)
def setUp(self): """ Initialize tests, by creating users and populating some unread notifications """ create_default_notification_preferences() self.store = notification_store() self.test_user_id = 1001 self.from_timestamp = datetime.datetime.now( pytz.UTC) - datetime.timedelta(days=1) self.weekly_from_timestamp = datetime.datetime.now( pytz.UTC) - datetime.timedelta(days=7) self.to_timestamp = datetime.datetime.now(pytz.UTC) self.msg_type = self.store.save_notification_type( NotificationType( name='foo.bar', renderer= 'edx_notifications.renderers.basic.BasicSubjectBodyRenderer', )) self.msg_type_no_renderer = self.store.save_notification_type( NotificationType( name='foo.baz', renderer='foo', )) # create two notifications with freeze_time(self.to_timestamp): msg = self.store.save_notification_message( NotificationMessage( msg_type=self.msg_type, namespace='foo', payload={ 'subject': 'foo', 'body': 'bar' }, )) self.notification1 = publish_notification_to_user( self.test_user_id, msg) with freeze_time(self.to_timestamp): msg = self.store.save_notification_message( NotificationMessage( msg_type=self.msg_type_no_renderer, namespace='bar', payload={ 'subject': 'foo', 'body': 'bar' }, )) self.notification2 = publish_notification_to_user( self.test_user_id, msg)
def test_purge_expired_read_notifications(self): """ Test to check for the older read messages. If exists delete those messages from the database. """ msg_type = self._save_notification_type() msg1 = self.provider.save_notification_message( NotificationMessage(namespace='namespace1', msg_type=msg_type, payload={'foo': 'bar'})) msg2 = self.provider.save_notification_message( NotificationMessage(namespace='namespace1', msg_type=msg_type, payload={'test': 'test'})) # now reset the time to 10 days ago # in order to save the user notification messages in the past. reset_time = datetime.now(pytz.UTC) - timedelta(days=10) with freeze_time(reset_time): self.provider.save_user_notification( UserNotification(user_id=self.test_user_id, msg=msg1)) # mark the user notification as read. self.provider.mark_user_notifications_read(self.test_user_id) # now reset the time to 2 days ago # in order to save the user notification messages in the past. reset_time = datetime.now(pytz.UTC) - timedelta(days=2) with freeze_time(reset_time): self.provider.save_user_notification( UserNotification(user_id=self.test_user_id, msg=msg2)) # mark the user notification as read. self.provider.mark_user_notifications_read(self.test_user_id) # user notifications count self.assertEqual( self.provider.get_num_notifications_for_user( self.test_user_id, filters={'namespace': 'namespace1'}), 2) # purge older read messages. purge_older_read_messages = datetime.now(pytz.UTC) - timedelta(days=6) self.provider.purge_expired_notifications( purge_read_messages_older_than=purge_older_read_messages) # now get the user notification count. # count should be 1 at that moment. because # only 1 notification has been deleted. self.assertEqual( self.provider.get_num_notifications_for_user( self.test_user_id, filters={'namespace': 'namespace1'}), 1)
def _setup_user_notifications(self): """ Helper to build out some """ msg_type = self._save_notification_type() # set up some notifications msg1 = self.provider.save_notification_message(NotificationMessage( namespace='namespace1', msg_type=msg_type, payload={ 'foo': 'bar', 'one': 1, 'none': None, 'datetime': datetime.utcnow(), 'iso8601-fakeout': '--T::', # something to throw off the iso8601 parser heuristic } )) map1 = self.provider.save_user_notification(UserNotification( user_id=self.test_user_id, msg=msg1 )) msg_type2 = self.provider.save_notification_type( NotificationType( name='foo.bar.another', renderer='foo.renderer', ) ) msg2 = self.provider.save_notification_message(NotificationMessage( namespace='namespace2', msg_type=msg_type2, payload={ 'foo': 'baz', 'one': 1, 'none': None, 'datetime': datetime.utcnow(), 'iso8601-fakeout': '--T::', # something to throw off the iso8601 parser heuristic } )) map2 = self.provider.save_user_notification(UserNotification( user_id=self.test_user_id, msg=msg2 )) return map1, msg1, map2, msg2
def test_multiple_notifications(self): """ Test Case for retrieving multiple notifications """ msg1 = NotificationMessage( namespace='test-runner', msg_type=self.msg_type, payload={ 'foo': 'bar' } ) msg2 = NotificationMessage( namespace='test-runner', msg_type=self.msg_type, payload={ 'second': 'one' } ) # publish user_msg1 = publish_notification_to_user(self.user.id, msg1) user_msg2 = publish_notification_to_user(self.user.id, msg2) response = self.client.get(reverse('edx_notifications.consumer.notifications')) self.assertEqual(response.status_code, 200) results = json.loads(response.content.decode('utf-8')) self.assertEqual(len(results), 2) # the last one written should be the first one read self._compare_user_msg_to_result(user_msg2, results[0]) # the first one written should be second one received self._compare_user_msg_to_result(user_msg1, results[1]) # now do query with a namespace filter response = self.client.get( reverse('edx_notifications.consumer.notifications'), { 'namespace': 'test-runner' } ) self.assertEqual(response.status_code, 200) results = json.loads(response.content.decode('utf-8')) self.assertEqual(len(results), 2) # did we get two back? self._compare_user_msg_to_result(user_msg2, results[0]) self._compare_user_msg_to_result(user_msg1, results[1])
def test_over_limit_counting(self): """ Verifies that our counting operations will work as expected even when our count is greater that the NOTIFICATION_MAX_LIST_SIZE which is the maximum page size """ self.assertEqual(const.NOTIFICATION_MAX_LIST_SIZE, 1) msg_type = self._save_notification_type() for __ in range(10): msg = self.provider.save_notification_message(NotificationMessage( namespace='namespace1', msg_type=msg_type, payload={ 'foo': 'bar' } )) self.provider.save_user_notification(UserNotification( user_id=self.test_user_id, msg=msg )) self.assertEqual( self.provider.get_num_notifications_for_user( self.test_user_id, filters={ 'namespace': 'namespace1', } ), 10 )
def _save_new_notification(self, payload='This is a test payload'): """ Helper method to create a new notification """ msg_type = self._save_notification_type() msg = NotificationMessage( msg_type=msg_type, payload={ 'foo': 'bar', 'one': 1, 'none': None, 'datetime': datetime.utcnow(), 'iso8601-fakeout': '--T::', # something to throw off the iso8601 parser heuristic }, resolve_links={ 'param1': 'value1' }, object_id='foo-item' ) with self.assertNumQueries(1): result = self.provider.save_notification_message(msg) self.assertIsNotNone(result) self.assertIsNotNone(result.id) return result
def test_mark_user_notification_read(self): """ """ msg_type = self._save_notification_type() for __ in range(10): msg = self.provider.save_notification_message( NotificationMessage(namespace='namespace1', msg_type=msg_type, payload={'foo': 'bar'})) self.provider.save_user_notification( UserNotification(user_id=self.test_user_id, msg=msg)) self.assertEqual( self.provider.get_num_notifications_for_user(self.test_user_id, filters={ 'namespace': 'namespace1', }), 10) self.provider.mark_user_notifications_read(self.test_user_id) self.assertEqual( self.provider.get_num_notifications_for_user(self.test_user_id, filters={ 'namespace': 'namespace1', 'read': False }), 0)
def test_bulk_publish_list(self): """ Make sure we can bulk publish to a number of users passing in a list """ msg = NotificationMessage( namespace='test-runner', msg_type=self.msg_type, payload={ 'foo': 'bar' } ) # now send to more than our internal chunking size bulk_publish_notification_to_users( list(range(1, const.NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE * 2 + 1)), msg ) # now read them all back for user_id in range(1, const.NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE * 2 + 1): notifications = get_notifications_for_user(user_id) self.assertTrue(isinstance(notifications, list)) self.assertEqual(len(notifications), 1) self.assertTrue(isinstance(notifications[0], UserNotification))
def _create_notification_message(app_id, payload): notification_type = get_notification_type( 'open-edx.mobileapps.notifications') notification_message = NotificationMessage(namespace=str(app_id), msg_type=notification_type, payload=payload) return notification_message
def setUp(self): """ start up stuff """ register_user_scope_resolver('list_scope', TestListScopeResolver()) self.store = notification_store() self.msg_type = self.store.save_notification_type( NotificationType( name='foo.bar', renderer='foo', ) ) msg = NotificationMessage( msg_type=self.msg_type, payload={'foo': 'bar'}, ) msg.add_payload( { 'extra': 'stuff' }, channel_name='other_channel' ) self.msg = self.store.save_notification_message(msg)
def fire_file_upload_notification(self, notifications_service): log.info('{}.fire_file_upload_notification on location = {}'.format( self.__class__.__name__, self.location)) # this NotificationType is registered in the list of default Open edX Notifications msg_type = notifications_service.get_notification_type( NotificationMessageTypes.FILE_UPLOADED) workgroup_user_ids = [] uploader_username = '' for user in self.workgroup.users: # don't send to ourselves if user.id != self.user_id: workgroup_user_ids.append(user.id) else: uploader_username = user.username msg = NotificationMessage(msg_type=msg_type, namespace=unicode(self.course_id), payload={ '_schema_version': 1, 'action_username': uploader_username, 'activity_name': self.activity.display_name, }) location = unicode(self.location) if self.location else '' add_click_link_params(msg, unicode(self.course_id), location) # NOTE: We're not using Celery here since we are expecting that we # will have only a very small handful of workgroup users notifications_service.bulk_publish_notification_to_users( workgroup_user_ids, msg)
def test_json_renderer(self): """ Make sure JSON renderer returns correct renderings """ msg_type = NotificationType( name='open-edx.edx_notifications.lib.tests.test_publisher', renderer='edx_notifications.renderers.basic.JsonRenderer', ) register_notification_type(msg_type) msg = NotificationMessage(namespace='test-runner', msg_type=msg_type, payload={ 'subject': 'test subject', 'body': 'test body', }) renderer = JsonRenderer() self.assertTrue(renderer.can_render_format(RENDER_FORMAT_JSON)) self.assertIsNone(renderer.get_template_path(RENDER_FORMAT_JSON)) self.assertEqual( json.loads(renderer.render(msg, RENDER_FORMAT_JSON, None)), msg.payload)
def test_click_links_params(self): """ Make sure the helper methods work """ msg = NotificationMessage(payload={'foo': 'bar'}) msg.add_click_link_params({ 'param1': 'val1', 'param2': 'val2', }) click_links = msg.get_click_link_params() self.assertIsNotNone(click_links) self.assertEqual(click_links['param1'], 'val1') self.assertEqual(click_links['param2'], 'val2') msg.add_click_link_params({ 'param3': 'val3', }) click_links = msg.get_click_link_params() self.assertEqual(click_links['param1'], 'val1') self.assertEqual(click_links['param2'], 'val2') self.assertEqual(click_links['param3'], 'val3')
def test_publish_to_scope(self): """ Make sure we can bulk publish to a number of users passing in a resultset from a Django ORM query """ register_user_scope_resolver("list_scope", TestListScopeResolver()) msg = NotificationMessage(namespace='test-runner', msg_type=self.msg_type, payload={'foo': 'bar'}) bulk_publish_notification_to_scope( scope_name="list_scope", # the TestListScopeResolver expects a "range" property in the context scope_context={"range": 5}, msg=msg) for user_id in range(4): # have to fudge this a bit as the contract on user_id # says > 0 only allowed user_id = user_id + 1 notifications = get_notifications_for_user(user_id) self.assertTrue(isinstance(notifications, list)) self.assertEqual(len(notifications), 1) self.assertTrue(isinstance(notifications[0], UserNotification))
def test_wildcard_group_mapping(self): """ Test that adds the default notification type mapping """ msg_type = self.store.save_notification_type( NotificationType( name='open-edx.lms.discussions.new-discussion-added', renderer='open-edx.lms.discussions.new-discussion-added', ) ) # create cohort notification msg = self.store.save_notification_message( NotificationMessage( msg_type=msg_type, namespace='cohort-thread-added', payload={'subject': 'foo', 'body': 'bar'}, ) ) publish_notification_to_user(self.test_user_id, msg) register_namespace_resolver(TestNamespaceResolver()) set_user_notification_preference(self.test_user_id, const.NOTIFICATION_DAILY_DIGEST_PREFERENCE_NAME, 'true') self.assertEqual( send_notifications_digest( self.from_timestamp, self.to_timestamp, const.NOTIFICATION_DAILY_DIGEST_PREFERENCE_NAME, 'subject', '*****@*****.**' ), 3 )
def test_bulk_publish_generator(self): """ Make sure we can bulk publish to a number of users passing in a generator function """ msg = NotificationMessage(namespace='test-runner', msg_type=self.msg_type, payload={'foo': 'bar'}) def _user_id_generator(): """ Just spit our an generator that goes from 1 to 100 """ for user_id in range(1, 100): yield user_id # now send to more than our internal chunking size bulk_publish_notification_to_users(_user_id_generator(), msg) # now read them all back for user_id in range(1, 100): notifications = get_notifications_for_user(user_id) self.assertTrue(isinstance(notifications, list)) self.assertEqual(len(notifications), 1) self.assertTrue(isinstance(notifications[0], UserNotification))
def test_bulk_publish_orm_query(self): """ Make sure we can bulk publish to a number of users passing in a resultset from a Django ORM query """ # set up some test users in Django User's model User(username='******').save() User(username='******').save() User(username='******').save() msg = NotificationMessage(namespace='test-runner', msg_type=self.msg_type, payload={'foo': 'bar'}) resultset = User.objects.values_list('id', flat=True).all() # pylint: disable=no-member num_sent = bulk_publish_notification_to_users(resultset, msg) # make sure we sent 3 self.assertEqual(num_sent, 3) # now read them back for user in User.objects.all(): # pylint: disable=no-member notifications = get_notifications_for_user(user.id) self.assertTrue(isinstance(notifications, list)) self.assertEqual(len(notifications), 1) self.assertTrue(isinstance(notifications[0], UserNotification))
def test_multipayload(self): """ Test that a channel will use the right payload """ msg = NotificationMessage(namespace='test-runner', msg_type=self.msg_type, payload={'foo': 'bar'}) msg.add_payload({'one': 'two'}, channel_name='durable') # now do happy path sent_user_msg = publish_notification_to_user(self.test_user_id, msg) # now query back the notification to make sure it got stored # and we can retrieve it self.assertEquals(get_notifications_count_for_user(self.test_user_id), 1) notifications = get_notifications_for_user(self.test_user_id) self.assertTrue(isinstance(notifications, list)) self.assertEqual(len(notifications), 1) self.assertTrue(isinstance(notifications[0], UserNotification)) read_user_msg = notifications[0] self.assertEqual(read_user_msg.user_id, self.test_user_id) self.assertIsNone(read_user_msg.read_at) # should be unread self.assertEqual(read_user_msg, sent_user_msg) # make sure the message that got persisted contains only # the default payload self.assertEqual(read_user_msg.msg.payload, msg.get_payload(channel_name='durable'))
def test_someone_elses_notification(self): """ Simple test to make sure that we can get counts for someone elses notification """ msg = NotificationMessage( namespace='test-runner', msg_type=self.msg_type, payload={ 'foo': 'bar' } ) # publish to some other user_id user_msg = publish_notification_to_user(99999, msg) self.assertIsNotNone(user_msg) # now query API response = self.client.get(reverse('edx_notifications.consumer.notifications.count')) self.assertEqual(response.status_code, 200) results = json.loads(response.content.decode('utf-8')) self.assertIn('count', results) self.assertEqual(results['count'], 0)
def test_bulk_publish_list_exclude(self): """ Make sure we can bulk publish to a number of users passing in a list, and also pass in an exclusion list to make sure the people in the exclude list does not get the notification """ msg = NotificationMessage(namespace='test-runner', msg_type=self.msg_type, payload={'foo': 'bar'}) user_ids = list( range(1, const.NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE * 2 + 1)) exclude_user_ids = list( range(1, const.NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE * 2 + 1, 2)) # now send to more than our internal chunking size bulk_publish_notification_to_users(user_ids, msg, exclude_user_ids=exclude_user_ids) # now read them all back for user_id in range( 1, const.NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE * 2 + 1): notifications = get_notifications_for_user(user_id) self.assertTrue(isinstance(notifications, list)) self.assertEqual(len(notifications), 1 if user_id not in exclude_user_ids else 0) if user_id not in exclude_user_ids: self.assertTrue(isinstance(notifications[0], UserNotification))
def _set_activity_timed_notification(self, course_id, msg_type, event_date, send_at_date, services, timer_name_suffix): notifications_service = services.get('notifications') activity_date_tz = event_date.replace(tzinfo=pytz.UTC) send_at_date_tz = send_at_date.replace(tzinfo=pytz.UTC) msg = NotificationMessage( msg_type=notifications_service.get_notification_type(msg_type), namespace=unicode(course_id), payload={ '_schema_version': 1, 'activity_name': self.activity.display_name, 'stage': self.display_name, 'due_date': activity_date_tz.strftime('%-m/%-d/%-y'), }) add_click_link_params(msg, course_id, self.location) notifications_service.publish_timed_notification( msg=msg, send_at=send_at_date_tz, # send to all students participating in this project scope_name=NotificationScopes.PARTICIPANTS, scope_context={ 'course_id': unicode(course_id), 'content_id': unicode(self.activity.project.location), }, timer_name=self._get_stage_timer_name(timer_name_suffix), ignore_if_past_due=True # don't send if we're already late! )
def _set_activity_timed_notification(self, course_id, activity, msg_type, component, milestone_date, send_at_date, services, timer_name_suffix): component_name = component.name notifications_service = services.get('notifications') courseware_parent_info = services.get('courseware_parent_info') courseware_info = self.get_courseware_info(courseware_parent_info) activity_name = courseware_info['activity_name'] activity_location = courseware_info['activity_location'] project_location = courseware_info['project_location'] milestone_date_tz = milestone_date.replace(tzinfo=pytz.UTC) send_at_date_tz = send_at_date.replace(tzinfo=pytz.UTC) msg = NotificationMessage( msg_type=notifications_service.get_notification_type(msg_type), namespace=unicode(course_id), payload={ '_schema_version': 1, 'activity_name': activity_name, 'stage': component_name, 'due_date': milestone_date_tz.strftime('%-m/%-d/%-y'), }) # # add in all the context parameters we'll need to # generate a URL back to the website that will # present the new course announcement # # IMPORTANT: This can be changed to msg.add_click_link() if we # have a particular URL that we wish to use. In the initial use case, # we need to make the link point to a different front end website # so we need to resolve these links at dispatch time # msg.add_click_link_params({ 'course_id': unicode(course_id), 'activity_location': unicode(activity_location), }) notifications_service.publish_timed_notification( msg=msg, send_at=send_at_date_tz, # send to all students participating in this project scope_name='group_project_participants', scope_context={ 'course_id': unicode(course_id), 'content_id': unicode(project_location), }, timer_name=self._get_component_timer_name(component, timer_name_suffix), ignore_if_past_due=True # don't send if we're already late! )
def test_marking_read_state(self): """ Verify proper behavior when marking notfications as read/unread """ msg = NotificationMessage(namespace='test-runner', msg_type=self.msg_type, payload={'foo': 'bar'}) # now do happy path sent_user_msg = publish_notification_to_user(self.test_user_id, msg) # now mark msg as read by this user mark_notification_read(self.test_user_id, sent_user_msg.msg.id) # shouldn't be counted in unread counts self.assertEquals( get_notifications_count_for_user( self.test_user_id, filters={ 'read': False, 'unread': True, }, ), 0) # Should be counted in read counts self.assertEquals( get_notifications_count_for_user( self.test_user_id, filters={ 'read': True, 'unread': False, }, ), 1) # now mark msg as unread by this user mark_notification_read(self.test_user_id, sent_user_msg.msg.id, read=False) # Should be counted in unread counts self.assertEquals( get_notifications_count_for_user( self.test_user_id, filters={ 'read': False, 'unread': True, }, ), 1) # Shouldn't be counted in read counts self.assertEquals( get_notifications_count_for_user( self.test_user_id, filters={ 'read': True, 'unread': False, }, ), 0)
def fire_file_upload_notification(self, notifications_service): try: # this NotificationType is registered in the list of default Open edX Notifications msg_type = notifications_service.get_notification_type( 'open-edx.xblock.group-project.file-uploaded') workgroup_user_ids = [] uploader_username = '' for user in self.workgroup['users']: # don't send to ourselves if user['id'] != self.user_id: workgroup_user_ids.append(user['id']) else: uploader_username = user['username'] # get the activity name which is simply our hosting # Sequence's Display Name, so call out to a new xBlock # runtime Service courseware_info = self.get_courseware_info( self.runtime.service(self, 'courseware_parent_info')) activity_name = courseware_info['activity_name'] activity_location = courseware_info['activity_location'] msg = NotificationMessage(msg_type=msg_type, namespace=unicode(self.course_id), payload={ '_schema_version': 1, 'action_username': uploader_username, 'activity_name': activity_name, }) # # add in all the context parameters we'll need to # generate a URL back to the website that will # present the new course announcement # # IMPORTANT: This can be changed to msg.add_click_link() if we # have a particular URL that we wish to use. In the initial use case, # we need to make the link point to a different front end website # so we need to resolve these links at dispatch time # msg.add_click_link_params({ 'course_id': unicode(self.course_id), 'activity_location': unicode(activity_location) if activity_location else '', }) # NOTE: We're not using Celery here since we are expectating that we # will have only a very small handful of workgroup users notifications_service.bulk_publish_notification_to_users( workgroup_user_ids, msg) except Exception, ex: # While we *should* send notification, if there is some # error here, we don't want to blow the whole thing up. # So log it and continue.... log.exception(ex)
def test_notification_count(self): """ Simple test to make sure that we get the right count back after publishing a notification to this test user """ msg = NotificationMessage(namespace='test-runner', msg_type=self.msg_type, payload={'foo': 'bar'}) # publish user_msg = publish_notification_to_user(self.user.id, msg) self.assertIsNotNone(user_msg) url = reverse('edx_notifications.consumer.notifications.count') # now query API response = self.client.get(url) self.assertEqual(response.status_code, 200) results = json.loads(response.content.decode('utf-8')) self.assertIn('count', results) self.assertEqual(results['count'], 1) # query just the unread response = self.client.get(url, {'read': False, 'unread': True}) self.assertEqual(response.status_code, 200) results = json.loads(response.content.decode('utf-8')) self.assertIn('count', results) self.assertEqual(results['count'], 1) # query just the read, which should be 0 response = self.client.get(url, {'read': True, 'unread': False}) self.assertEqual(response.status_code, 200) results = json.loads(response.content.decode('utf-8')) self.assertIn('count', results) self.assertEqual(results['count'], 0) # now mark the message as read mark_notification_read(self.user.id, user_msg.msg.id) # query just the unread, should be 0 response = self.client.get(url, {'read': False, 'unread': True}) self.assertEqual(response.status_code, 200) results = json.loads(response.content.decode('utf-8')) self.assertIn('count', results) self.assertEqual(results['count'], 0) # query just the read, which should be 1 response = self.client.get(url, {'read': True, 'unread': False}) self.assertEqual(response.status_code, 200) results = json.loads(response.content.decode('utf-8')) self.assertIn('count', results) self.assertEqual(results['count'], 1)
def test_message_validation(self): """ Make sure validation of NotificationMessage is correct """ msg = NotificationMessage() # intentionally blank with self.assertRaises(ValidationError): msg.validate()
def handle_progress_post_save_signal(sender, instance, **kwargs): """ Handle the pre-save ORM event on CourseModuleCompletions """ if settings.FEATURES['ENABLE_NOTIFICATIONS']: # If notifications feature is enabled, then we need to get the user's # rank before the save is made, so that we can compare it to # after the save and see if the position changes leaderboard_rank = StudentSocialEngagementScore.get_user_leaderboard_position( instance.course_id, instance.user.id, get_aggregate_exclusion_user_ids(instance.course_id))['position'] if leaderboard_rank == 0: # quick escape when user is not in the leaderboard # which means rank = 0. Trouble is 0 < 3, so unfortunately # the semantics around 0 don't match the logic below return # logic for Notification trigger is when a user enters into the Leaderboard leaderboard_size = getattr(settings, 'LEADERBOARD_SIZE', 3) presave_leaderboard_rank = instance.presave_leaderboard_rank if instance.presave_leaderboard_rank else sys.maxint if leaderboard_rank <= leaderboard_size and presave_leaderboard_rank > leaderboard_size: try: notification_msg = NotificationMessage( msg_type=get_notification_type( u'open-edx.lms.leaderboard.engagement.rank-changed'), namespace=unicode(instance.course_id), payload={ '_schema_version': '1', 'rank': leaderboard_rank, 'leaderboard_name': 'Engagement', }) # # add in all the context parameters we'll need to # generate a URL back to the website that will # present the new course announcement # # IMPORTANT: This can be changed to msg.add_click_link() if we # have a particular URL that we wish to use. In the initial use case, # we need to make the link point to a different front end website # so we need to resolve these links at dispatch time # notification_msg.add_click_link_params({ 'course_id': unicode(instance.course_id), }) publish_notification_to_user(int(instance.user.id), notification_msg) except Exception, ex: # Notifications are never critical, so we don't want to disrupt any # other logic processing. So log and continue. log.exception(ex)