def test_link_resolution(self): """ Go through and set up a notification and publish it but with links to resolve """ msg = NotificationMessage(namespace="test-runner", msg_type=self.msg_type, payload={"foo": "bar"}) # this resolve_links resolutions are defined in settings.py # for testing purposes msg.add_click_link_params({"param1": "foo_param", "param2": "bar_param"}) # make sure it asserts that user_id is an integer with self.assertRaises(ContractNotRespected): publish_notification_to_user("bad-id", msg) # now do happy path sent_user_msg = publish_notification_to_user(self.test_user_id, msg) # now make sure the links got resolved and put into # the payload self.assertIsNotNone(sent_user_msg.msg.get_click_link()) # make sure the resolution is what we expect # NOTE: the mappings are defined in settings.py for testing purposes self.assertEqual(sent_user_msg.msg.get_click_link(), "/path/to/foo_param/url/bar_param") # now do it all over again since there is caching of link resolvers sent_user_msg = publish_notification_to_user(self.test_user_id, msg) self.assertTrue(sent_user_msg.msg.get_click_link()) self.assertEqual(sent_user_msg.msg.get_click_link(), "/path/to/foo_param/url/bar_param")
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_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 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 _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 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 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, user_id=instance.user.id, exclude_users=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)
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)
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 handle_studentgradebook_post_save_signal(sender, instance, **kwargs): """ Handle the pre-save ORM event on CourseModuleCompletions """ if settings.FEATURES['ENABLE_NOTIFICATIONS']: # attach the rank of the user before the save is completed data = StudentGradebook.get_user_position( instance.course_id, instance.user.id, exclude_users=get_aggregate_exclusion_user_ids(instance.course_id)) leaderboard_rank = data['user_position'] grade = data['user_grade'] # logic for Notification trigger is when a user enters into the Leaderboard if grade > 0.0: 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.gradebook.rank-changed' ), namespace=unicode(instance.course_id), payload={ '_schema_version': '1', 'rank': leaderboard_rank, 'leaderboard_name': 'Proficiency', }) # # 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)
def handle_studentgradebook_post_save_signal(sender, instance, **kwargs): """ Handle the pre-save ORM event on CourseModuleCompletions """ invalid_user_data_cache('grade', instance.course_id, instance.user.id) if settings.FEATURES['ENABLE_NOTIFICATIONS']: # attach the rank of the user before the save is completed data = StudentGradebook.get_user_position( instance.course_id, user_id=instance.user.id, exclude_users=get_aggregate_exclusion_user_ids(instance.course_id) ) leaderboard_rank = data['user_position'] grade = data['user_grade'] # logic for Notification trigger is when a user enters into the Leaderboard if grade > 0.0: 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.gradebook.rank-changed'), namespace=unicode(instance.course_id), payload={ '_schema_version': '1', 'rank': leaderboard_rank, 'leaderboard_name': 'Proficiency', } ) # # 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)
def fire_grades_posted_notification(self, group_id, 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.grades-posted') # 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, '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 '', }) # Bulk publish to the 'group_project_workgroup' user scope notifications_service.bulk_publish_notification_to_scope( 'group_project_workgroup', { # I think self.workgroup['id'] is a string version of an integer 'workgroup_id': group_id, }, 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 fire_grades_posted_notification(self, group_id, 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.grades-posted') # 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, '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 '', }) # Bulk publish to the 'group_project_workgroup' user scope notifications_service.bulk_publish_notification_to_scope( 'group_project_workgroup', { # I think self.workgroup['id'] is a string version of an integer 'workgroup_id': group_id, }, 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_link_resolution(self): """ Go through and set up a notification and publish it but with links to resolve """ msg = NotificationMessage( namespace='test-runner', msg_type=self.msg_type, payload={ 'foo': 'bar' } ) # this resolve_links resolutions are defined in settings.py # for testing purposes msg.add_click_link_params({ 'param1': 'foo_param', 'param2': 'bar_param', }) # make sure it asserts that user_id is an integer with self.assertRaises(ContractNotRespected): publish_notification_to_user('bad-id', msg) # now do happy path sent_user_msg = publish_notification_to_user(self.test_user_id, msg) # now make sure the links got resolved and put into # the payload self.assertIsNotNone(sent_user_msg.msg.get_click_link()) # make sure the resolution is what we expect # NOTE: the mappings are defined in settings.py for testing purposes self.assertEqual(sent_user_msg.msg.get_click_link(), '/path/to/foo_param/url/bar_param') # now do it all over again since there is caching of link resolvers sent_user_msg = publish_notification_to_user(self.test_user_id, msg) self.assertTrue(sent_user_msg.msg.get_click_link()) self.assertEqual(sent_user_msg.msg.get_click_link(), '/path/to/foo_param/url/bar_param')
def index(request): """ Returns a basic HTML snippet rendering of a notification count """ global NAMESPACE if request.method == 'POST': register_user_scope_resolver('user_email_resolver', TestUserResolver(request.user)) if request.POST.get('change_namespace'): namespace_str = request.POST['namespace'] NAMESPACE = namespace_str if namespace_str != "None" else None elif request.POST.get('send_digest'): send_digest(request, request.POST.get('digest_email')) else: type_name = request.POST['notification_type'] channel_name = request.POST['notification_channel'] if not channel_name: channel_name = None msg_type = get_notification_type(type_name) msg = NotificationMessage( msg_type=msg_type, namespace=NAMESPACE, payload=CANNED_TEST_PAYLOAD[type_name], ) if type_name == 'testserver.msg-with-resolved-click-link': msg.add_click_link_params({ 'param1': 'param_val1', 'param2': 'param_val2', }) publish_notification_to_user(request.user.id, msg, preferred_channel=channel_name) template = loader.get_template('index.html') # call to the helper method to build up all the context we need # to render the "notification_widget" that is embedded in our # test page context_dict = get_notifications_widget_context({ 'user': request.user, 'notification_types': get_all_notification_types(), 'global_variables': { 'app_name': 'Notification Test Server', 'hide_link_is_visible': settings.HIDE_LINK_IS_VISIBLE, 'always_show_dates_on_unread': True, 'notification_preference_tab_is_visible': settings.NOTIFICATION_PREFERENCES_IS_VISIBLE, }, # for test purposes, set up a short-poll which contacts the server # every 10 seconds to see if there is a new notification # # NOTE: short-poll technique should not be used in a production setting with # any reasonable number of concurrent users. This is just for # testing purposes. # 'refresh_watcher': { 'name': 'short-poll', 'args': { 'poll_period_secs': 10, }, }, 'include_framework_js': True, 'namespace': NAMESPACE, }) return HttpResponse(template.render(RequestContext(request, context_dict)))
def _send_discussion_notification( type_name, course_id, thread, request_user, excerpt=None, recipient_user_id=None, recipient_group_id=None, recipient_exclude_user_ids=None, extra_payload=None, is_anonymous_user=False ): """ Helper method to consolidate Notification trigger workflow """ try: # is Notifications feature enabled? if not settings.FEATURES.get("ENABLE_NOTIFICATIONS", False): return if is_anonymous_user: action_username = _('An anonymous user') else: action_username = request_user.username # get the notification type. msg = NotificationMessage( msg_type=get_notification_type(type_name), namespace=course_id, # base payload, other values will be passed in as extra_payload payload={ '_schema_version': '1', 'action_username': action_username, 'thread_title': thread.title, } ) # add in additional payload info # that might be type specific if extra_payload: msg.payload.update(extra_payload) if excerpt: msg.payload.update({ 'excerpt': excerpt, }) # Add information so that we can resolve # click through links in the Notification # rendering, typically this will be used # to bring the user back to this part of # the discussion forum # # IMPORTANT: This can be changed to msg.add_click_link() if we # have a URL that we wish to use. In the initial use case, # we need to make the link point to a different front end # msg.add_click_link_params({ 'course_id': course_id, 'commentable_id': thread.commentable_id, 'thread_id': thread.id, }) if recipient_user_id: # send notification to single user publish_notification_to_user(recipient_user_id, msg) if recipient_group_id: # Send the notification_msg to the CourseGroup via Celery # But we can also exclude some users from that list if settings.FEATURES.get('ENABLE_NOTIFICATIONS_CELERY', False): publish_course_group_notification_task.delay( recipient_group_id, msg, exclude_user_ids=recipient_exclude_user_ids ) else: publish_course_group_notification_task( recipient_group_id, msg, exclude_user_ids=recipient_exclude_user_ids ) 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)
def _send_discussion_notification(type_name, course_id, thread, request_user, excerpt=None, recipient_user_id=None, recipient_group_id=None, recipient_exclude_user_ids=None, extra_payload=None, is_anonymous_user=False): """ Helper method to consolidate Notification trigger workflow """ try: # is Notifications feature enabled? if not settings.FEATURES.get("ENABLE_NOTIFICATIONS", False): return if is_anonymous_user: action_username = _('An anonymous user') else: action_username = request_user.username # get the notification type. msg = NotificationMessage( msg_type=get_notification_type(type_name), namespace=course_id, # base payload, other values will be passed in as extra_payload payload={ '_schema_version': '1', 'action_username': action_username, 'thread_title': thread.title, }) # add in additional payload info # that might be type specific if extra_payload: msg.payload.update(extra_payload) if excerpt: msg.payload.update({ 'excerpt': excerpt, }) # Add information so that we can resolve # click through links in the Notification # rendering, typically this will be used # to bring the user back to this part of # the discussion forum # # IMPORTANT: This can be changed to msg.add_click_link() if we # have a URL that we wish to use. In the initial use case, # we need to make the link point to a different front end # msg.add_click_link_params({ 'course_id': course_id, 'commentable_id': thread.commentable_id, 'thread_id': thread.id, }) if recipient_user_id: # send notification to single user publish_notification_to_user(recipient_user_id, msg) if recipient_group_id: # Send the notification_msg to the CourseGroup via Celery # But we can also exclude some users from that list if settings.FEATURES.get('ENABLE_NOTIFICATIONS_CELERY', False): publish_course_group_notification_task.delay( recipient_group_id, msg, exclude_user_ids=recipient_exclude_user_ids) else: publish_course_group_notification_task( recipient_group_id, msg, exclude_user_ids=recipient_exclude_user_ids) 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)
def handle(self, *args, **options): if not settings.FEATURES['ENABLE_NOTIFICATIONS']: return # Increase time range so that users don't miss notification in case cron job is skipped or delayed. time_range = timezone.now() - timezone.timedelta( minutes=options['time_range'] * 2) leaderboard_size = getattr(settings, 'LEADERBOARD_SIZE', 3) courses = Aggregator.objects.filter( aggregation_name='course', last_modified__gte=time_range).distinct().values_list('course_key', flat=True) for course_key in courses: all_progress = Aggregator.objects.filter( aggregation_name='course', course_key=course_key, percent__gt=0).exclude(user__in=User.objects.filter( courseaccessrole__course_id=course_key, courseaccessrole__role__in=[ 'staff', 'observer', 'assistant', 'instructor' ])).order_by('-percent', 'last_modified')[:leaderboard_size] all_leaders = LeaderBoard.objects.filter( course_key=course_key).all() leaders = {l.position: l for l in all_leaders} positions = {l.user_id: l.position for l in all_leaders} for idx, progress in enumerate(all_progress): position = idx + 1 leader = leaders.get(position) if not leader: leader = LeaderBoard(course_key=course_key, position=position) old_leader = leader.user_id old_position = positions.get(progress.user_id, sys.maxsize) leader.user_id = progress.user_id leader.save() is_new = progress.modified >= time_range if old_leader != progress.user_id and position < old_position and is_new: try: notification_msg = NotificationMessage( msg_type=get_notification_type( 'open-edx.lms.leaderboard.progress.rank-changed' ), namespace=str(course_key), payload={ '_schema_version': '1', 'rank': position, 'leaderboard_name': 'Progress', }) # # 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': str(course_key), }) publish_notification_to_user(int(leader.user_id), notification_msg) except Exception as ex: # pylint: disable=broad-except # Notifications are never critical, so we don't want to disrupt any # other logic processing. So log and continue. log.exception(ex)