diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py index 72320ebca8..74e3b4b1b5 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -32,7 +32,7 @@ class CourseModeViewTest(ModuleStoreTestCase): self.client.login(username=self.user.username, password="edx") @ddt.data( - # is_active?, enrollment_mode, upgrade?, redirect? auto_register? + # is_active?, enrollment_mode, upgrade?, redirect?, auto_register? (True, 'verified', True, True, False), # User is already verified (True, 'verified', False, True, False), # User is already verified (True, 'honor', True, False, False), # User isn't trying to upgrade @@ -162,9 +162,9 @@ class CourseModeViewTest(ModuleStoreTestCase): # Mapping of course modes to the POST parameters sent # when the user chooses that mode. POST_PARAMS_FOR_COURSE_MODE = { - 'audit': {'audit_mode': True}, - 'honor': {'honor-code': True}, - 'verified': {'certificate_mode': True, 'contribution': '1.23'} + 'honor': {'honor_mode': True}, + 'verified': {'verified_mode': True, 'contribution': '1.23'}, + 'unsupported': {'unsupported_mode': True}, } # TODO (ECOM-16): Remove the auto-register flag once the AB-test completes @@ -172,10 +172,8 @@ class CourseModeViewTest(ModuleStoreTestCase): @ddt.data( (False, 'honor', 'dashboard'), (False, 'verified', 'show_requirements'), - (False, 'audit', 'dashboard'), (True, 'honor', 'dashboard'), (True, 'verified', 'show_requirements'), - (True, 'audit', 'dashboard'), ) @ddt.unpack def test_choose_mode_redirect(self, auto_register, course_mode, expected_redirect): @@ -191,7 +189,7 @@ class CourseModeViewTest(ModuleStoreTestCase): # Choose the mode (POST request) choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)]) - resp = self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[course_mode]) + response = self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[course_mode]) # Verify the redirect if expected_redirect == 'dashboard': @@ -204,11 +202,11 @@ class CourseModeViewTest(ModuleStoreTestCase): else: self.fail("Must provide a valid redirect URL name") - self.assertRedirects(resp, redirect_url) + self.assertRedirects(response, redirect_url) def test_remember_donation_for_course(self): # Create the course modes - for mode in ('audit', 'honor', 'verified'): + for mode in ('honor', 'verified'): CourseModeFactory(mode_slug=mode, course_id=self.course.id) # Choose the mode (POST request) @@ -223,29 +221,34 @@ class CourseModeViewTest(ModuleStoreTestCase): expected_amount = decimal.Decimal(self.POST_PARAMS_FOR_COURSE_MODE['verified']['contribution']) self.assertEqual(actual_amount, expected_amount) - def test_enrollment_skipped_if_autoreg(self): + # TODO (ECOM-16): Remove auto-register booleans once the AB-test completes + @ddt.data(False, True) + def test_successful_honor_enrollment(self, auto_register): # TODO (ECOM-16): Remove once we complete the auto-reg AB test. - session = self.client.session - session['auto_register'] = True - session.save() + if auto_register: + self.client.session['auto_register'] = True + self.client.session.save() # Create the course modes - for mode in ('audit', 'honor', 'verified'): + for mode in ('honor', 'verified'): CourseModeFactory(mode_slug=mode, course_id=self.course.id) - # Now enroll in the course - CourseEnrollmentFactory( - user=self.user, - is_active=True, - mode="honor", - course_id=unicode(self.course.id), - ) - # Choose the mode (POST request) choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)]) - self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE['audit']) + self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE['honor']) - # Verify that enrollment mode is still honor + # Verify the enrollment mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) - self.assertEqual(mode, "honor") + self.assertEqual(mode, 'honor') self.assertEqual(is_active, True) + + def test_unsupported_enrollment_mode_failure(self): + # Create the supported course modes + for mode in ('honor', 'verified'): + CourseModeFactory(mode_slug=mode, course_id=self.course.id) + + # Choose an unsupported mode (POST request) + choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)]) + response = self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE['unsupported']) + + self.assertEqual(400, response.status_code) diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 249aff9568..79e12a452e 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -21,18 +21,19 @@ from xmodule.modulestore.django import modulestore class ChooseModeView(View): - """ - View used when the user is asked to pick a mode + """View used when the user is asked to pick a mode. When a get request is used, shows the selection page. - When a post request is used, assumes that it is a form submission - from the selection page, parses the response, and then sends user - to the next step in the flow + + When a post request is used, assumes that it is a form submission + from the selection page, parses the response, and then sends user + to the next step in the flow. + """ @method_decorator(login_required) def get(self, request, course_id, error=None): - """ Displays the course mode choice page + """Displays the course mode choice page. Args: request (`Request`): The Django Request object. @@ -121,7 +122,19 @@ class ChooseModeView(View): @method_decorator(login_required) def post(self, request, course_id): - """ Takes the form submission from the page and parses it """ + """Takes the form submission from the page and parses it. + + Args: + request (`Request`): The Django Request object. + course_id (unicode): The slash-separated course key. + + Returns: + Status code 400 when the requested mode is unsupported. When the honor mode + is selected, redirects to the dashboard. When the verified mode is selected, + returns error messages if the indicated contribution amount is invalid or + below the minimum, otherwise redirects to the verification flow. + + """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) user = request.user @@ -134,26 +147,26 @@ class ChooseModeView(View): upgrade = request.GET.get('upgrade', False) - requested_mode = self.get_requested_mode(request.POST) + requested_mode = self._get_requested_mode(request.POST) allowed_modes = CourseMode.modes_for_course_dict(course_key) if requested_mode not in allowed_modes: return HttpResponseBadRequest(_("Enrollment mode not supported")) - if requested_mode in ("audit", "honor"): - # TODO (ECOM-16): Skip enrollment if we're in the experimental branch - if not request.session.get('auto_register', False): - CourseEnrollment.enroll(user, course_key, requested_mode) + # TODO (ECOM-16): Remove if the experimental variant wins. Functionally, + # it doesn't matter, but it will avoid hitting the database. + if requested_mode == 'honor': + CourseEnrollment.enroll(user, course_key, requested_mode) return redirect('dashboard') mode_info = allowed_modes[requested_mode] - if requested_mode == "verified": + if requested_mode == 'verified': amount = request.POST.get("contribution") or \ request.POST.get("contribution-other-amt") or 0 try: - # validate the amount passed in and force it into two digits + # Validate the amount passed in and force it into two digits amount_value = decimal.Decimal(amount).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: error_msg = _("Invalid amount selected.") @@ -172,14 +185,20 @@ class ChooseModeView(View): reverse('verify_student_show_requirements', kwargs={'course_id': course_key.to_deprecated_string()}) + "?upgrade={}".format(upgrade)) - def get_requested_mode(self, request_dict): + def _get_requested_mode(self, request_dict): + """Get the user's requested mode + + Args: + request_dict (`QueryDict`): A dictionary-like object containing all given HTTP POST parameters. + + Returns: + The course mode slug corresponding to the choice in the POST parameters, + None if the choice in the POST parameters is missing or is an unsupported mode. + """ - Given the request object of `user_choice`, return the - corresponding course mode slug - """ - if 'audit_mode' in request_dict: - return 'audit' - if 'certificate_mode' and request_dict.get("honor-code"): - return 'honor' - if 'certificate_mode' in request_dict: + if 'verified_mode' in request_dict: return 'verified' + if 'honor_mode' in request_dict: + return 'honor' + else: + return None diff --git a/common/templates/course_modes/choose.html b/common/templates/course_modes/choose.html index 0d4c4993f6..1d28a4d972 100644 --- a/common/templates/course_modes/choose.html +++ b/common/templates/course_modes/choose.html @@ -120,50 +120,14 @@ $(document).ready(function() { %endif <%include file="_contribution.html" args="suggested_prices=suggested_prices, currency=currency, chosen_price=chosen_price, min_price=min_price"/> - -
${_("Sign up to audit this course for free and track your own progress.")}
+${_("Audit this course for free and have complete access to all the course material, activities, tests, and forums. If your work is satisfactory and you abide by the Honor Code, you'll receive a personalized Honor Code Certificate to showcase your achievement.")}
${_("You can audit this course and still have complete access to the course material. If you complete and pass the course you will automatically earn an Honor Code Certificate free of charge.")}
+${_("Audit this course for free and have complete access to all the course material, activities, tests, and forums. If your work is satisfactory and you abide by the Honor Code, you'll receive a personalized Honor Code Certificate to showcase your achievement.")}