def test_get_highlights_without_module(self, mock_get_module): mock_get_module.return_value = None with self.store.bulk_operations(self.course_key): self._create_chapter(highlights='Test highlight') with self.assertRaisesRegex(CourseUpdateDoesNotExist, 'Course module .* not found'): get_week_highlights(self.user, self.course_key, 1) yesterday = datetime.datetime.utcnow() - datetime.timedelta(days=1) today = datetime.datetime.utcnow() with self.assertRaisesRegex(CourseUpdateDoesNotExist, 'Course module .* not found'): get_next_section_highlights(self.user, self.course_key, yesterday, today.date())
def test_get_next_section_highlights(self, mock_duration): mock_duration.return_value = datetime.timedelta(days=2) yesterday = datetime.datetime.utcnow() - datetime.timedelta(days=1) today = datetime.datetime.utcnow() tomorrow = datetime.datetime.utcnow() + datetime.timedelta(days=1) with self.store.bulk_operations(self.course_key): self._create_chapter( # Week 1 highlights=[u'a', u'b', u'á'], ) self._create_chapter( # Week 2 highlights=[u'skipped a week'], ) self.assertEqual( get_next_section_highlights(self.user, self.course_key, yesterday, today.date()), ([u'skipped a week'], 2), ) with self.assertRaises(CourseUpdateDoesNotExist): get_next_section_highlights(self.user, self.course_key, yesterday, tomorrow.date())
def get_schedules(self): course_key = CourseKey.from_string(self.course_id) target_date = self.target_datetime.date() schedules = get_schedules_with_due_date(course_key, target_date).filter( self.experience_filter, active=True, enrollment__user__is_active=True, ) template_context = get_base_template_context(self.site) for schedule in schedules: enrollment = schedule.enrollment course = schedule.enrollment.course user = enrollment.user start_date = schedule.start_date LOG.info(u'Received a schedule for user {} in course {} for date {}'.format( user.username, self.course_id, target_date, )) try: week_highlights, week_num = get_next_section_highlights(user, course.id, start_date, target_date) except CourseUpdateDoesNotExist: LOG.warning( u'Weekly highlights for user {} of course {} does not exist or is disabled'.format( user, course.id ) ) # continue to the next schedule, don't yield an email for this one continue unsubscribe_url = None if (COURSE_UPDATE_SHOW_UNSUBSCRIBE_WAFFLE_SWITCH.is_enabled() and 'bulk_email_optout' in settings.ACE_ENABLED_POLICIES): unsubscribe_url = reverse('bulk_email_opt_out', kwargs={ 'token': UsernameCipher.encrypt(user.username), 'course_id': str(enrollment.course_id), }) template_context.update({ 'course_name': course.display_name, 'course_url': _get_trackable_course_home_url(enrollment.course_id), 'week_num': week_num, 'week_highlights': week_highlights, # This is used by the bulk email optout policy 'course_ids': [str(enrollment.course_id)], 'unsubscribe_url': unsubscribe_url, }) template_context.update(_get_upsell_information_for_schedule(user, schedule)) yield (user, enrollment.course.closest_released_language, template_context, course.self_paced)
def test_get_next_section_highlights(self, mock_duration): # All of the dates chosen here are to make things easy and clean to calculate with date offsets # It only goes up to 6 days because we are using two_days_ago as our reference point # so 6 + 2 = 8 days for the duration of the course mock_duration.return_value = datetime.timedelta(days=8) today = datetime.datetime.utcnow() two_days_ago = today - datetime.timedelta(days=2) two_days = today + datetime.timedelta(days=2) three_days = today + datetime.timedelta(days=3) four_days = today + datetime.timedelta(days=4) six_days = today + datetime.timedelta(days=6) with self.store.bulk_operations(self.course_key): self._create_chapter( # Week 1 highlights=['a', 'b', 'á'], ) self._create_chapter( # Week 2 highlights=['skipped a week'], ) self._create_chapter( # Week 3 highlights=[]) self._create_chapter( # Week 4 highlights=['final week!']) self.assertEqual( get_next_section_highlights(self.user, self.course_key, two_days_ago, today.date()), (['skipped a week'], 2), ) exception_message = 'Next section [{}] has no highlights for {}'.format( 'chapter 3', self.course_key) with self.assertRaises(CourseUpdateDoesNotExist, msg=exception_message): get_next_section_highlights(self.user, self.course_key, two_days_ago, two_days.date()) # Returns None, None if the target date does not match any due dates. This is caused by # making the mock_duration 8 days and there being only 4 chapters so any odd day will # fail to match. self.assertEqual( get_next_section_highlights(self.user, self.course_key, two_days_ago, three_days.date()), (None, None), ) self.assertEqual( get_next_section_highlights(self.user, self.course_key, two_days_ago, four_days.date()), (['final week!'], 4), ) exception_message = 'Last section was reached. There are no more highlights for {}'.format( self.course_key) with self.assertRaises(CourseUpdateDoesNotExist, msg=exception_message): get_next_section_highlights(self.user, self.course_key, two_days_ago, six_days.date())
def get_schedules(self): """ Grabs possible schedules that could receive a Course Next Section Update and if a next section highlight is applicable for the user, yields information needed to send the next section highlight email. """ target_date = self.target_datetime.date() course_duration = get_expected_duration(self.course_id) schedules = Schedule.objects.select_related('enrollment').filter( self.experience_filter, enrollment__is_active=True, enrollment__course_id=self.course_id, enrollment__user__is_active=True, start_date__gte=target_date - course_duration, start_date__lt=target_date, ) template_context = get_base_template_context(self.site) for schedule in schedules: course = schedule.enrollment.course # We don't want to show any updates if the course has ended so we short circuit here. if course.end and course.end.date() <= target_date: return # Next Section Updates are only for Self-paced courses since it uses Personalized # Learner Schedule logic. See CourseUpdateResolver for Instructor-paced updates if not course.self_paced: continue user = schedule.enrollment.user start_date = max(filter(None, (schedule.start_date, course.start))) LOG.info('Received a schedule for user {} in course {} for date {}'.format( user.username, self.course_id, target_date, )) try: week_highlights, week_num = get_next_section_highlights(user, course.id, start_date, target_date) # (None, None) is returned when there is no section with a due date of the target_date if week_highlights is None: continue except CourseUpdateDoesNotExist as e: log_message = self.log_prefix + ': ' + str(e) LOG.warning(log_message) # continue to the next schedule, don't yield an email for this one continue unsubscribe_url = None if (COURSE_UPDATE_SHOW_UNSUBSCRIBE_WAFFLE_SWITCH.is_enabled() and 'bulk_email_optout' in settings.ACE_ENABLED_POLICIES): unsubscribe_url = reverse('bulk_email_opt_out', kwargs={ 'token': UsernameCipher.encrypt(user.username), 'course_id': str(course.id), }) template_context.update({ 'course_name': course.display_name, 'course_url': _get_trackable_course_home_url(course.id), 'week_num': week_num, 'week_highlights': week_highlights, # This is used by the bulk email optout policy 'course_ids': [str(course.id)], 'unsubscribe_url': unsubscribe_url, }) template_context.update(_get_upsell_information_for_schedule(user, schedule)) yield (user, course.closest_released_language, template_context)