def schedules_for_bin(self): week_num = abs(self.day_offset) // 7 schedules = self.get_schedules_with_target_date_by_bin_and_orgs( order_by='enrollment__course', ) template_context = get_base_template_context(self.site) for schedule in schedules: enrollment = schedule.enrollment course = schedule.enrollment.course user = enrollment.user # (Weekly) Course Updates are only for Instructor-paced courses. # See CourseNextSectionUpdate for Self-paced updates. if course.self_paced: continue try: week_highlights = get_week_highlights(user, enrollment.course_id, week_num) except CourseUpdateDoesNotExist: LOG.warning( 'Weekly highlights for user {} in week {} of course {} does not exist or is disabled' .format(user, week_num, enrollment.course_id)) # continue to the next schedule, don't yield an email for this one else: 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': schedule.enrollment.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, schedule.enrollment.course.closest_released_language, template_context)
def assertPrefValid(self, user): """Ensure that the correct preference for the user is persisted""" pref = UserPreference.objects.get(user=user, key=NOTIFICATION_PREF_KEY) self.assertTrue(pref) # check exists and only 1 (.get) # now coerce username to utf-8 encoded str, since we test with non-ascii unicdoe above and # the unittest framework has hard time coercing to unicode. # decrypt also can't take a unicode input, so coerce its input to str self.assertEqual(six.binary_type(user.username.encode('utf-8')), UsernameCipher().decrypt(str(pref.value)))
def setUp(self): super().setUp() self.user = UserFactory.create(username="******", email='*****@*****.**') self.course = CourseFactory.create(run='testcourse1', display_name='Test Course Title') self.token = UsernameCipher.encrypt('testuser1') self.request_factory = RequestFactory() self.url = reverse('bulk_email_opt_out', args=[self.token, str(self.course.id)]) # Ensure we start with no opt-out records assert Optout.objects.count() == 0
def get_unsubscribed_link(username, course_id): """ :param username: username :param course_id: :return: AES encrypted token based on the user email """ token = UsernameCipher.encrypt(username) optout_url = reverse('bulk_email_opt_out', kwargs={'token': token, 'course_id': course_id}) url = '{base_url}{optout_url}'.format(base_url=settings.LMS_ROOT_URL, optout_url=optout_url) return url
def get_unsubscribed_link(username, course_id): """ :param username: username :param course_id: :return: AES encrypted token based on the user email """ lms_root_url = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL) token = UsernameCipher.encrypt(username) optout_url = reverse('bulk_email_opt_out', kwargs={'token': token, 'course_id': course_id}) url = f'{lms_root_url}{optout_url}' return url
def setUp(self): super(OptOutEmailUpdatesViewTest, self).setUp() self.user = UserFactory.create(username="******") self.token = UsernameCipher.encrypt('testuser1') self.request_factory = RequestFactory() self.course = CourseFactory.create(run='testcourse1', display_name='Test Course Title') self.url = reverse('bulk_email_opt_out', args=[self.token, text_type(self.course.id)]) # Ensure we start with no opt-out records self.assertEqual(Optout.objects.count(), 0)
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 setUp(self): super(OptOutEmailUpdatesViewTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments self.user = UserFactory.create(username="******", email='*****@*****.**') self.course = CourseFactory.create(run='testcourse1', display_name='Test Course Title') self.token = UsernameCipher.encrypt('testuser1') self.request_factory = RequestFactory() self.url = reverse('bulk_email_opt_out', args=[self.token, text_type(self.course.id)]) # Ensure we start with no opt-out records assert Optout.objects.count() == 0
def opt_out_email_updates(request, token, course_id): """ A view that let users opt out of any email updates. This meant is meant to be the target of an opt-out link or button. The `token` parameter must decrypt to a valid username. The `course_id` is the string course key of any course. Raises a 404 if there are any errors parsing the input. """ try: username = UsernameCipher().decrypt(token.encode()) user = User.objects.get(username=username) course_key = CourseKey.from_string(course_id) course = get_course_by_id(course_key, depth=0) except UnicodeDecodeError: raise Http404("base64url") except UsernameDecryptionException as exn: raise Http404(text_type(exn)) except User.DoesNotExist: raise Http404("username") except InvalidKeyError: raise Http404("course") context = { 'course': course, 'cancelled': False, 'confirmed': False, } if request.method == 'POST': if request.POST.get('submit') == 'confirm': Optout.objects.get_or_create(user=user, course_id=course.id) log.info( u"User %s (%s) opted out of receiving emails from course %s", user.username, user.email, course_id, ) context['confirmed'] = True else: context['cancelled'] = True return render_to_response('bulk_email/unsubscribe.html', context)
def opt_out_email_updates(request, token, course_id): """ A view that let users opt out of any email updates. This meant is meant to be the target of an opt-out link or button. The `token` parameter must decrypt to a valid username. The `course_id` is the string course key of any course. Raises a 404 if there are any errors parsing the input. """ try: username = UsernameCipher().decrypt(token).decode("utf-8") user = User.objects.get(username=username) course_key = CourseKey.from_string(course_id) course = get_course_by_id(course_key, depth=0) except UnicodeDecodeError: raise Http404("base64url") # lint-amnesty, pylint: disable=raise-missing-from except UsernameDecryptionException as exn: raise Http404(text_type(exn)) # lint-amnesty, pylint: disable=raise-missing-from except User.DoesNotExist: raise Http404("username") # lint-amnesty, pylint: disable=raise-missing-from except InvalidKeyError: raise Http404("course") # lint-amnesty, pylint: disable=raise-missing-from unsub_check = request.POST.get('unsubscribe', False) context = { 'course': course, 'unsubscribe': unsub_check } if request.method == 'GET': return render_to_response('bulk_email/confirm_unsubscribe.html', context) if request.method == 'POST' and unsub_check: Optout.objects.get_or_create(user=user, course_id=course_key) log.info( u"User %s (%s) opted out of receiving emails from course %s", user.username, user.email, course_id, ) return render_to_response('bulk_email/unsubscribe_success.html', context)
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)