diff --git a/cms/djangoapps/contentstore/views/certificates.py b/cms/djangoapps/contentstore/views/certificates.py index dd656fa28d..b6bbe3ebe8 100644 --- a/cms/djangoapps/contentstore/views/certificates.py +++ b/cms/djangoapps/contentstore/views/certificates.py @@ -362,7 +362,7 @@ def certificates_list_handler(request, course_key_string): certificate_web_view_url = get_lms_link_for_certificate_web_view( user_id=request.user.id, course_key=course_key, - mode=course_modes[0] # CourseMode.modes_for_course returns default mode 'honor' if doesn't find anyone. + mode=course_modes[0] # CourseMode.modes_for_course returns default mode if doesn't find anyone. ) certificates = None is_active = False diff --git a/common/djangoapps/course_modes/helpers.py b/common/djangoapps/course_modes/helpers.py index e69de29bb2..4a3ceae231 100644 --- a/common/djangoapps/course_modes/helpers.py +++ b/common/djangoapps/course_modes/helpers.py @@ -0,0 +1,86 @@ +""" Helper methods for CourseModes. """ +from django.utils.translation import ugettext_lazy as _ + +from course_modes.models import CourseMode +from student.helpers import ( + VERIFY_STATUS_NEED_TO_VERIFY, + VERIFY_STATUS_SUBMITTED, + VERIFY_STATUS_APPROVED +) + +DISPLAY_VERIFIED = "verified" +DISPLAY_HONOR = "honor" +DISPLAY_AUDIT = "audit" +DISPLAY_PROFESSIONAL = "professional" + + +def enrollment_mode_display(mode, verification_status, course_id): + """ Select appropriate display strings and CSS classes. + + Uses mode and verification status to select appropriate display strings and CSS classes + for certificate display. + + Args: + mode (str): enrollment mode. + verification_status (str) : verification status of student + + Returns: + dictionary: + """ + show_image = False + image_alt = '' + enrollment_title = '' + enrollment_value = '' + display_mode = _enrollment_mode_display(mode, verification_status, course_id) + + if display_mode == DISPLAY_VERIFIED: + if verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED]: + enrollment_title = _("Your verification is pending") + enrollment_value = _("Verified: Pending Verification") + show_image = True + image_alt = _("ID verification pending") + elif verification_status == VERIFY_STATUS_APPROVED: + enrollment_title = _("You're enrolled as a verified student") + enrollment_value = _("Verified") + show_image = True + image_alt = _("ID Verified Ribbon/Badge") + elif display_mode == DISPLAY_HONOR: + enrollment_title = _("You're enrolled as an honor code student") + enrollment_value = _("Honor Code") + elif display_mode == DISPLAY_PROFESSIONAL: + enrollment_title = _("You're enrolled as a professional education student") + enrollment_value = _("Professional Ed") + + return { + 'enrollment_title': unicode(enrollment_title), + 'enrollment_value': unicode(enrollment_value), + 'show_image': show_image, + 'image_alt': unicode(image_alt), + 'display_mode': _enrollment_mode_display(mode, verification_status, course_id) + } + + +def _enrollment_mode_display(enrollment_mode, verification_status, course_id): + """Checking enrollment mode and status and returns the display mode + Args: + enrollment_mode (str): enrollment mode. + verification_status (str) : verification status of student + + Returns: + display_mode (str) : display mode for certs + """ + course_mode_slugs = [mode.slug for mode in CourseMode.modes_for_course(course_id)] + + if enrollment_mode == CourseMode.VERIFIED: + if verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_APPROVED]: + display_mode = DISPLAY_VERIFIED + elif DISPLAY_HONOR in course_mode_slugs: + display_mode = DISPLAY_HONOR + else: + display_mode = DISPLAY_AUDIT + elif enrollment_mode in [CourseMode.PROFESSIONAL, CourseMode.NO_ID_PROFESSIONAL_MODE]: + display_mode = DISPLAY_PROFESSIONAL + else: + display_mode = enrollment_mode + + return display_mode diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py index 2c43d80cee..4402a3e00a 100644 --- a/common/djangoapps/course_modes/models.py +++ b/common/djangoapps/course_modes/models.py @@ -99,8 +99,8 @@ class CourseMode(models.Model): NO_ID_PROFESSIONAL_MODE = "no-id-professional" CREDIT_MODE = "credit" - DEFAULT_MODE = Mode(HONOR, _('Honor Code Certificate'), 0, '', 'usd', None, None, None) - DEFAULT_MODE_SLUG = HONOR + DEFAULT_MODE = Mode(AUDIT, _('Audit'), 0, '', 'usd', None, None, None) + DEFAULT_MODE_SLUG = AUDIT # Modes that allow a student to pursue a verified certificate VERIFIED_MODES = [VERIFIED, PROFESSIONAL] @@ -112,7 +112,7 @@ class CourseMode(models.Model): CREDIT_MODES = [CREDIT_MODE] # Modes that are allowed to upsell - UPSELL_TO_VERIFIED_MODES = [HONOR] + UPSELL_TO_VERIFIED_MODES = [HONOR, AUDIT] class Meta(object): unique_together = ('course_id', 'mode_slug', 'currency') @@ -511,8 +511,27 @@ class CourseMode(models.Model): if cls.is_white_label(course_id, modes_dict=modes_dict): return False - # Check that the default mode is available. - return cls.HONOR in modes_dict + # Check that a free mode is available. + return cls.AUDIT in modes_dict or cls.HONOR in modes_dict + + @classmethod + def auto_enroll_mode(cls, course_id, modes_dict=None): + """ + return the auto-enrollable mode from given dict + + Args: + modes_dict (dict): course modes. + + Returns: + String: Mode name + """ + if modes_dict is None: + modes_dict = cls.modes_for_course_dict(course_id) + + if cls.HONOR in modes_dict: + return cls.HONOR + elif cls.AUDIT in modes_dict: + return cls.AUDIT @classmethod def is_white_label(cls, course_id, modes_dict=None): @@ -552,96 +571,6 @@ class CourseMode(models.Model): modes = cls.modes_for_course(course_id) return min(mode.min_price for mode in modes if mode.currency.lower() == currency.lower()) - @classmethod - def enrollment_mode_display(cls, mode, verification_status): - """ Select appropriate display strings and CSS classes. - - Uses mode and verification status to select appropriate display strings and CSS classes - for certificate display. - - Args: - mode (str): enrollment mode. - verification_status (str) : verification status of student - - Returns: - dictionary: - """ - - # import inside the function to avoid the circular import - from student.helpers import ( - VERIFY_STATUS_NEED_TO_VERIFY, - VERIFY_STATUS_SUBMITTED, - VERIFY_STATUS_APPROVED - ) - - show_image = False - image_alt = '' - - if mode == cls.VERIFIED: - if verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED]: - enrollment_title = _("Your verification is pending") - enrollment_value = _("Verified: Pending Verification") - show_image = True - image_alt = _("ID verification pending") - elif verification_status == VERIFY_STATUS_APPROVED: - enrollment_title = _("You're enrolled as a verified student") - enrollment_value = _("Verified") - show_image = True - image_alt = _("ID Verified Ribbon/Badge") - else: - enrollment_title = _("You're enrolled as an honor code student") - enrollment_value = _("Honor Code") - elif mode == cls.HONOR: - enrollment_title = _("You're enrolled as an honor code student") - enrollment_value = _("Honor Code") - elif mode == cls.AUDIT: - enrollment_title = _("You're auditing this course") - enrollment_value = _("Auditing") - elif mode in [cls.PROFESSIONAL, cls.NO_ID_PROFESSIONAL_MODE]: - enrollment_title = _("You're enrolled as a professional education student") - enrollment_value = _("Professional Ed") - else: - enrollment_title = '' - enrollment_value = '' - - return { - 'enrollment_title': unicode(enrollment_title), - 'enrollment_value': unicode(enrollment_value), - 'show_image': show_image, - 'image_alt': unicode(image_alt), - 'display_mode': cls._enrollment_mode_display(mode, verification_status) - } - - @staticmethod - def _enrollment_mode_display(enrollment_mode, verification_status): - """Checking enrollment mode and status and returns the display mode - Args: - enrollment_mode (str): enrollment mode. - verification_status (str) : verification status of student - - Returns: - display_mode (str) : display mode for certs - """ - - # import inside the function to avoid the circular import - from student.helpers import ( - VERIFY_STATUS_NEED_TO_VERIFY, - VERIFY_STATUS_SUBMITTED, - VERIFY_STATUS_APPROVED - ) - - if enrollment_mode == CourseMode.VERIFIED: - if verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_APPROVED]: - display_mode = "verified" - else: - display_mode = "honor" - elif enrollment_mode in [CourseMode.PROFESSIONAL, CourseMode.NO_ID_PROFESSIONAL_MODE]: - display_mode = "professional" - else: - display_mode = enrollment_mode - - return display_mode - def to_tuple(self): """ Takes a mode model and turns it into a model named tuple. diff --git a/common/djangoapps/course_modes/tests/test_models.py b/common/djangoapps/course_modes/tests/test_models.py index 69de9de4a8..24a0dc3fde 100644 --- a/common/djangoapps/course_modes/tests/test_models.py +++ b/common/djangoapps/course_modes/tests/test_models.py @@ -15,6 +15,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locator import CourseLocator import pytz +from course_modes.helpers import enrollment_mode_display from course_modes.models import CourseMode, Mode @@ -193,6 +194,21 @@ class CourseModeModelTest(TestCase): # Verify that we can or cannot auto enroll self.assertEqual(CourseMode.can_auto_enroll(self.course_key), can_auto_enroll) + @ddt.data( + ([], None), + (["honor", "audit", "verified"], "honor"), + (["honor", "audit"], "honor"), + (["audit", "verified"], "audit"), + (["professional"], None), + (["no-id-professional"], None), + (["credit", "audit", "verified"], "audit"), + (["credit"], None), + ) + @ddt.unpack + def test_auto_enroll_mode(self, modes, result): + # Verify that the proper auto enroll mode is returned + self.assertEqual(CourseMode.auto_enroll_mode(self.course_key, modes), result) + def test_all_modes_for_courses(self): now = datetime.now(pytz.UTC) future = now + timedelta(days=1) @@ -316,30 +332,30 @@ class CourseModeModelTest(TestCase): def test_enrollment_mode_display(self, mode, verification_status): if mode == "verified": self.assertEqual( - CourseMode.enrollment_mode_display(mode, verification_status), + enrollment_mode_display(mode, verification_status, self.course_key), self._enrollment_display_modes_dicts(verification_status) ) self.assertEqual( - CourseMode.enrollment_mode_display(mode, verification_status), + enrollment_mode_display(mode, verification_status, self.course_key), self._enrollment_display_modes_dicts(verification_status) ) self.assertEqual( - CourseMode.enrollment_mode_display(mode, verification_status), + enrollment_mode_display(mode, verification_status, self.course_key), self._enrollment_display_modes_dicts(verification_status) ) elif mode == "honor": self.assertEqual( - CourseMode.enrollment_mode_display(mode, verification_status), + enrollment_mode_display(mode, verification_status, self.course_key), self._enrollment_display_modes_dicts(mode) ) elif mode == "audit": self.assertEqual( - CourseMode.enrollment_mode_display(mode, verification_status), + enrollment_mode_display(mode, verification_status, self.course_key), self._enrollment_display_modes_dicts(mode) ) elif mode == "professional": self.assertEqual( - CourseMode.enrollment_mode_display(mode, verification_status), + enrollment_mode_display(mode, verification_status, self.course_key), self._enrollment_display_modes_dicts(mode) ) @@ -375,9 +391,9 @@ class CourseModeModelTest(TestCase): 'ID verification pending', 'verified'], "verify_approved": ["You're enrolled as a verified student", "Verified", True, 'ID Verified Ribbon/Badge', 'verified'], - "verify_none": ["You're enrolled as an honor code student", "Honor Code", False, '', 'honor'], + "verify_none": ["", "", False, '', 'audit'], "honor": ["You're enrolled as an honor code student", "Honor Code", False, '', 'honor'], - "audit": ["You're auditing this course", "Auditing", False, '', 'audit'], + "audit": ["", "", False, '', 'audit'], "professional": ["You're enrolled as a professional education student", "Professional Ed", False, '', 'professional'] } diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py index 66197bf194..369b16d8bc 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -177,6 +177,7 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase): # Mapping of course modes to the POST parameters sent # when the user chooses that mode. POST_PARAMS_FOR_COURSE_MODE = { + 'audit': {}, 'honor': {'honor_mode': True}, 'verified': {'verified_mode': True, 'contribution': '1.23'}, 'unsupported': {'unsupported_mode': True}, @@ -227,9 +228,9 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase): expected_amount = decimal.Decimal(self.POST_PARAMS_FOR_COURSE_MODE['verified']['contribution']) self.assertEqual(actual_amount, expected_amount) - def test_successful_honor_enrollment(self): + def test_successful_default_enrollment(self): # Create the course modes - for mode in ('honor', 'verified'): + for mode in (CourseMode.DEFAULT_MODE_SLUG, 'verified'): CourseModeFactory(mode_slug=mode, course_id=self.course.id) # Enroll the user in the default mode (honor) to emulate @@ -242,11 +243,11 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase): # Explicitly select the honor 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['honor']) + self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[CourseMode.DEFAULT_MODE_SLUG]) # Verify that the user's enrollment remains unchanged mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) - self.assertEqual(mode, 'honor') + self.assertEqual(mode, CourseMode.DEFAULT_MODE_SLUG) self.assertEqual(is_active, True) def test_unsupported_enrollment_mode_failure(self): diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 331ecbd38a..58dbf8839c 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -174,12 +174,16 @@ class ChooseModeView(View): if requested_mode not in allowed_modes: return HttpResponseBadRequest(_("Enrollment mode not supported")) - if requested_mode == 'honor': - # The user will have already been enrolled in the honor mode at this + if requested_mode == 'audit': + # The user will have already been enrolled in the audit mode at this # point, so we just redirect them to the dashboard, thereby avoiding # hitting the database a second time attempting to enroll them. return redirect(reverse('dashboard')) + if requested_mode == 'honor': + CourseEnrollment.enroll(user, course_key, mode=requested_mode) + return redirect(reverse('dashboard')) + mode_info = allowed_modes[requested_mode] if requested_mode == 'verified': @@ -224,6 +228,8 @@ class ChooseModeView(View): return 'verified' if 'honor_mode' in request_dict: return 'honor' + if 'audit_mode' in request_dict: + return 'audit' else: return None diff --git a/common/djangoapps/enrollment/api.py b/common/djangoapps/enrollment/api.py index f7774b53a1..1a0043eb0e 100644 --- a/common/djangoapps/enrollment/api.py +++ b/common/djangoapps/enrollment/api.py @@ -7,6 +7,7 @@ import importlib import logging from django.conf import settings from django.core.cache import cache +from course_modes.models import CourseMode from enrollment import errors log = logging.getLogger(__name__) @@ -132,10 +133,10 @@ def get_enrollment(user_id, course_id): return _data_api().get_course_enrollment(user_id, course_id) -def add_enrollment(user_id, course_id, mode='honor', is_active=True): +def add_enrollment(user_id, course_id, mode=CourseMode.DEFAULT_MODE_SLUG, is_active=True): """Enrolls a user in a course. - Enrolls a user in a course. If the mode is not specified, this will default to 'honor'. + Enrolls a user in a course. If the mode is not specified, this will default to `CourseMode.DEFAULT_MODE_SLUG`. Arguments: user_id (str): The user to enroll. @@ -143,7 +144,7 @@ def add_enrollment(user_id, course_id, mode='honor', is_active=True): Keyword Arguments: mode (str): Optional argument for the type of enrollment to create. Ex. 'audit', 'honor', 'verified', - 'professional'. If not specified, this defaults to 'honor'. + 'professional'. If not specified, this defaults to the default course mode. is_active (boolean): Optional argument for making the new enrollment inactive. If not specified, is_active defaults to True. @@ -154,7 +155,7 @@ def add_enrollment(user_id, course_id, mode='honor', is_active=True): >>> add_enrollment("Bob", "edX/DemoX/2014T2", mode="audit") { "created": "2014-10-20T20:18:00Z", - "mode": "honor", + "mode": "audit", "is_active": True, "user": "Bob", "course": { @@ -165,8 +166,8 @@ def add_enrollment(user_id, course_id, mode='honor', is_active=True): "course_end": "2015-05-06T00:00:00Z", "course_modes": [ { - "slug": "honor", - "name": "Honor Code Certificate", + "slug": "audit", + "name": "Audit", "min_price": 0, "suggested_prices": "", "currency": "usd", diff --git a/common/djangoapps/enrollment/tests/test_views.py b/common/djangoapps/enrollment/tests/test_views.py index 20052bff34..914075ee1f 100644 --- a/common/djangoapps/enrollment/tests/test_views.py +++ b/common/djangoapps/enrollment/tests/test_views.py @@ -47,7 +47,7 @@ class EnrollmentTestMixin(object): expected_status=status.HTTP_200_OK, email_opt_in=None, as_server=False, - mode=CourseMode.HONOR, + mode=CourseMode.DEFAULT_MODE_SLUG, is_active=None, enrollment_attributes=None, min_mongo_calls=0, @@ -169,13 +169,13 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): @ddt.data( # Default (no course modes in the database) - # Expect that users are automatically enrolled as "honor". - ([], CourseMode.HONOR), + # Expect that users are automatically enrolled as the default + ([], CourseMode.DEFAULT_MODE_SLUG), - # Audit / Verified / Honor + # Audit / Verified # We should always go to the "choose your course" page. - # We should also be enrolled as "honor" by default. - ([CourseMode.HONOR, CourseMode.VERIFIED, CourseMode.AUDIT], CourseMode.HONOR), + # We should also be enrolled as the default. + ([CourseMode.VERIFIED, CourseMode.AUDIT], CourseMode.DEFAULT_MODE_SLUG), ) @ddt.unpack def test_enroll(self, course_modes, enrollment_mode): @@ -198,8 +198,8 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): def test_check_enrollment(self): CourseModeFactory.create( course_id=self.course.id, - mode_slug=CourseMode.HONOR, - mode_display_name=CourseMode.HONOR, + mode_slug=CourseMode.DEFAULT_MODE_SLUG, + mode_display_name=CourseMode.DEFAULT_MODE_SLUG, ) # Create an enrollment self.assert_enrollment_status() @@ -209,7 +209,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): self.assertEqual(resp.status_code, status.HTTP_200_OK) data = json.loads(resp.content) self.assertEqual(unicode(self.course.id), data['course_details']['course_id']) - self.assertEqual(CourseMode.HONOR, data['mode']) + self.assertEqual(CourseMode.DEFAULT_MODE_SLUG, data['mode']) self.assertTrue(data['is_active']) @ddt.data( @@ -258,8 +258,8 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): def test_user_not_specified(self): CourseModeFactory.create( course_id=self.course.id, - mode_slug=CourseMode.HONOR, - mode_display_name=CourseMode.HONOR, + mode_slug=CourseMode.DEFAULT_MODE_SLUG, + mode_display_name=CourseMode.DEFAULT_MODE_SLUG, ) # Create an enrollment self.assert_enrollment_status() @@ -269,7 +269,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): self.assertEqual(resp.status_code, status.HTTP_200_OK) data = json.loads(resp.content) self.assertEqual(unicode(self.course.id), data['course_details']['course_id']) - self.assertEqual(CourseMode.HONOR, data['mode']) + self.assertEqual(CourseMode.DEFAULT_MODE_SLUG, data['mode']) self.assertTrue(data['is_active']) def test_user_not_authenticated(self): @@ -306,8 +306,8 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): # Try to enroll a user that is not the authenticated user. CourseModeFactory.create( course_id=self.course.id, - mode_slug=CourseMode.HONOR, - mode_display_name=CourseMode.HONOR, + mode_slug=CourseMode.DEFAULT_MODE_SLUG, + mode_display_name=CourseMode.DEFAULT_MODE_SLUG, ) self.assert_enrollment_status(username=self.other_user.username, expected_status=status.HTTP_404_NOT_FOUND) # Verify that the server still has access to this endpoint. @@ -340,8 +340,8 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): for course in self.course, other_course: CourseModeFactory.create( course_id=unicode(course.id), - mode_slug=CourseMode.HONOR, - mode_display_name=CourseMode.HONOR, + mode_slug=CourseMode.DEFAULT_MODE_SLUG, + mode_display_name=CourseMode.DEFAULT_MODE_SLUG, ) self.assert_enrollment_status( course_id=unicode(course.id), @@ -520,8 +520,8 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): self.rate_limit_config.save() CourseModeFactory.create( course_id=self.course.id, - mode_slug=CourseMode.HONOR, - mode_display_name=CourseMode.HONOR, + mode_slug=CourseMode.DEFAULT_MODE_SLUG, + mode_display_name=CourseMode.DEFAULT_MODE_SLUG, ) for attempt in xrange(self.rate_limit + 10): @@ -534,8 +534,8 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): self.rate_limit_config.save() CourseModeFactory.create( course_id=self.course.id, - mode_slug=CourseMode.HONOR, - mode_display_name=CourseMode.HONOR, + mode_slug=CourseMode.DEFAULT_MODE_SLUG, + mode_display_name=CourseMode.DEFAULT_MODE_SLUG, ) for attempt in xrange(self.rate_limit + 10): @@ -595,7 +595,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): def test_update_enrollment_with_mode(self): """With the right API key, update an existing enrollment with a new mode. """ # Create an honor and verified mode for a course. This allows an update. - for mode in [CourseMode.HONOR, CourseMode.VERIFIED]: + for mode in [CourseMode.DEFAULT_MODE_SLUG, CourseMode.VERIFIED]: CourseModeFactory.create( course_id=self.course.id, mode_slug=mode, @@ -605,11 +605,11 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): # Create an enrollment self.assert_enrollment_status(as_server=True) - # Check that the enrollment is honor. + # Check that the enrollment is default. self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id)) course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) self.assertTrue(is_active) - self.assertEqual(course_mode, CourseMode.HONOR) + self.assertEqual(course_mode, CourseMode.DEFAULT_MODE_SLUG) # Check that the enrollment upgraded to verified. self.assert_enrollment_status(as_server=True, mode=CourseMode.VERIFIED, expected_status=status.HTTP_200_OK) @@ -621,7 +621,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): """With the right API key, update an existing enrollment with credit mode and set enrollment attributes. """ - for mode in [CourseMode.HONOR, CourseMode.CREDIT_MODE]: + for mode in [CourseMode.DEFAULT_MODE_SLUG, CourseMode.CREDIT_MODE]: CourseModeFactory.create( course_id=self.course.id, mode_slug=mode, @@ -631,11 +631,11 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): # Create an enrollment self.assert_enrollment_status(as_server=True) - # Check that the enrollment is honor. + # Check that the enrollment is the default. self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id)) course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) self.assertTrue(is_active) - self.assertEqual(course_mode, CourseMode.HONOR) + self.assertEqual(course_mode, CourseMode.DEFAULT_MODE_SLUG) # Check that the enrollment upgraded to credit. enrollment_attributes = [{ @@ -657,7 +657,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): """Check response status is bad request when invalid enrollment attributes are passed """ - for mode in [CourseMode.HONOR, CourseMode.CREDIT_MODE]: + for mode in [CourseMode.DEFAULT_MODE_SLUG, CourseMode.CREDIT_MODE]: CourseModeFactory.create( course_id=self.course.id, mode_slug=mode, @@ -667,11 +667,11 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): # Create an enrollment self.assert_enrollment_status(as_server=True) - # Check that the enrollment is honor. + # Check that the enrollment is the default. self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id)) course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) self.assertTrue(is_active) - self.assertEqual(course_mode, CourseMode.HONOR) + self.assertEqual(course_mode, CourseMode.DEFAULT_MODE_SLUG) # Check that the enrollment upgraded to credit. enrollment_attributes = [{ @@ -687,12 +687,12 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): ) course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) self.assertTrue(is_active) - self.assertEqual(course_mode, CourseMode.HONOR) + self.assertEqual(course_mode, CourseMode.DEFAULT_MODE_SLUG) def test_downgrade_enrollment_with_mode(self): """With the right API key, downgrade an existing enrollment with a new mode. """ # Create an honor and verified mode for a course. This allows an update. - for mode in [CourseMode.HONOR, CourseMode.VERIFIED]: + for mode in [CourseMode.DEFAULT_MODE_SLUG, CourseMode.VERIFIED]: CourseModeFactory.create( course_id=self.course.id, mode_slug=mode, @@ -708,16 +708,20 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): self.assertTrue(is_active) self.assertEqual(course_mode, CourseMode.VERIFIED) - # Check that the enrollment downgraded to honor. - self.assert_enrollment_status(as_server=True, mode=CourseMode.HONOR, expected_status=status.HTTP_200_OK) + # Check that the enrollment was downgraded to the default mode. + self.assert_enrollment_status( + as_server=True, + mode=CourseMode.DEFAULT_MODE_SLUG, + expected_status=status.HTTP_200_OK + ) course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) self.assertTrue(is_active) - self.assertEqual(course_mode, CourseMode.HONOR) + self.assertEqual(course_mode, CourseMode.DEFAULT_MODE_SLUG) @ddt.data( - ((CourseMode.HONOR, ), CourseMode.HONOR), - ((CourseMode.HONOR, CourseMode.VERIFIED), CourseMode.HONOR), - ((CourseMode.HONOR, CourseMode.VERIFIED), CourseMode.VERIFIED), + ((CourseMode.DEFAULT_MODE_SLUG, ), CourseMode.DEFAULT_MODE_SLUG), + ((CourseMode.DEFAULT_MODE_SLUG, CourseMode.VERIFIED), CourseMode.DEFAULT_MODE_SLUG), + ((CourseMode.DEFAULT_MODE_SLUG, CourseMode.VERIFIED), CourseMode.VERIFIED), ((CourseMode.PROFESSIONAL, ), CourseMode.PROFESSIONAL), ((CourseMode.NO_ID_PROFESSIONAL_MODE, ), CourseMode.NO_ID_PROFESSIONAL_MODE), ((CourseMode.VERIFIED, CourseMode.CREDIT_MODE), CourseMode.VERIFIED), @@ -758,8 +762,12 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): self.assert_enrollment_activation(False, selected_mode) # Verify that omitting the mode returns 400 for course configurations - # in which the default (honor) mode doesn't exist. - expected_status = status.HTTP_200_OK if CourseMode.HONOR in configured_modes else status.HTTP_400_BAD_REQUEST + # in which the default mode doesn't exist. + expected_status = ( + status.HTTP_200_OK + if CourseMode.DEFAULT_MODE_SLUG in configured_modes + else status.HTTP_400_BAD_REQUEST + ) self.assert_enrollment_status( as_server=True, is_active=False, @@ -788,8 +796,8 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): def test_change_mode_from_user(self): """Users should not be able to alter the enrollment mode on an enrollment. """ - # Create an honor and verified mode for a course. This allows an update. - for mode in [CourseMode.HONOR, CourseMode.VERIFIED]: + # Create a default and a verified mode for a course. This allows an update. + for mode in [CourseMode.DEFAULT_MODE_SLUG, CourseMode.VERIFIED]: CourseModeFactory.create( course_id=self.course.id, mode_slug=mode, @@ -803,13 +811,13 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id)) course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) self.assertTrue(is_active) - self.assertEqual(course_mode, CourseMode.HONOR) + self.assertEqual(course_mode, CourseMode.DEFAULT_MODE_SLUG) # Get a 403 response when trying to upgrade yourself. self.assert_enrollment_status(mode=CourseMode.VERIFIED, expected_status=status.HTTP_403_FORBIDDEN) course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) self.assertTrue(is_active) - self.assertEqual(course_mode, CourseMode.HONOR) + self.assertEqual(course_mode, CourseMode.DEFAULT_MODE_SLUG) @ddt.data(*itertools.product( (CourseMode.HONOR, CourseMode.VERIFIED), diff --git a/common/djangoapps/enrollment/views.py b/common/djangoapps/enrollment/views.py index d3aa42eab2..110df7e906 100644 --- a/common/djangoapps/enrollment/views.py +++ b/common/djangoapps/enrollment/views.py @@ -287,9 +287,10 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): * Enroll the currently signed in user in a course. - Currently a user can use this command only to enroll the user in - honor mode. If honor mode is not supported for the course, the - request fails and returns the available modes. + Currently a user can use this command only to enroll the + user in the default course mode. If this is not + supported for the course, the request fails and returns + the available modes. This command can use a server-to-server call to enroll a user in other modes, such as "verified", "professional", or "credit". If @@ -325,7 +326,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): You cannot use the command to enroll a different user. * mode: Optional. The course mode for the enrollment. Individual - users cannot upgrade their enrollment mode from 'honor'. Only + users cannot upgrade their enrollment mode from the default. Only server-to-server requests can enroll with other modes. * is_active: Optional. A Boolean value indicating whether the @@ -353,7 +354,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): deactivate an enrollment. * mode: Optional. The course mode for the enrollment. Individual - users cannot upgrade their enrollment mode from "honor". Only + users cannot upgrade their enrollment mode from the default. Only server-to-server requests can enroll with other modes. * user: Optional. The user ID of the currently logged in user. You @@ -519,7 +520,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): } ) - mode = request.data.get('mode', CourseMode.HONOR) + mode = request.data.get('mode', CourseMode.DEFAULT_MODE_SLUG) has_api_key_permissions = self.has_api_key_permissions(request) @@ -531,7 +532,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) - if mode != CourseMode.HONOR and not has_api_key_permissions: + if mode != CourseMode.DEFAULT_MODE_SLUG and not has_api_key_permissions: return Response( status=status.HTTP_403_FORBIDDEN, data={ diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index daec221983..de736b7f9f 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -895,7 +895,7 @@ class CourseEnrollment(models.Model): # Represents the modes that are possible. We'll update this later with a # list of possible values. - mode = models.CharField(default="honor", max_length=100) + mode = models.CharField(default=CourseMode.DEFAULT_MODE_SLUG, max_length=100) objects = CourseEnrollmentManager() @@ -957,7 +957,7 @@ class CourseEnrollment(models.Model): # If we *did* just create a new enrollment, set some defaults if created: - enrollment.mode = "honor" + enrollment.mode = CourseMode.DEFAULT_MODE_SLUG enrollment.is_active = False enrollment.save() @@ -1043,8 +1043,8 @@ class CourseEnrollment(models.Model): u"mode:{}".format(self.mode)] ) if mode_changed: - # the user's default mode is "honor" and disabled for a course - # mode change events will only be emitted when the user's mode changes from this + # Only emit mode change events when the user's enrollment + # mode has changed from its previous setting self.emit_event(EVENT_NAME_ENROLLMENT_MODE_CHANGED) def emit_event(self, event_name): @@ -1090,7 +1090,7 @@ class CourseEnrollment(models.Model): ) @classmethod - def enroll(cls, user, course_key, mode="honor", check_access=False): + def enroll(cls, user, course_key, mode=CourseMode.DEFAULT_MODE_SLUG, check_access=False): """ Enroll a user in a course. This saves immediately. @@ -1103,8 +1103,8 @@ class CourseEnrollment(models.Model): `course_key` is our usual course_id string (e.g. "edX/Test101/2013_Fall) `mode` is a string specifying what kind of enrollment this is. The - default is 'honor', meaning honor certificate. Other options - include 'professional', 'verified', 'audit', + default is the default course mode, 'audit'. Other options + include 'professional', 'verified', 'honor', 'no-id-professional' and 'credit'. See CourseMode in common/djangoapps/course_modes/models.py. @@ -1165,7 +1165,7 @@ class CourseEnrollment(models.Model): return enrollment @classmethod - def enroll_by_email(cls, email, course_id, mode="honor", ignore_errors=True): + def enroll_by_email(cls, email, course_id, mode=CourseMode.DEFAULT_MODE_SLUG, ignore_errors=True): """ Enroll a user in a course given their email. This saves immediately. @@ -1181,9 +1181,10 @@ class CourseEnrollment(models.Model): `course_id` is our usual course_id string (e.g. "edX/Test101/2013_Fall) `mode` is a string specifying what kind of enrollment this is. The - default is "honor", meaning honor certificate. Future options - may include "audit", "verified_id", etc. Please don't use it - until we have these mapped out. + default is the default course mode, 'audit'. Other options + include 'professional', 'verified', 'honor', + 'no-id-professional' and 'credit'. + See CourseMode in common/djangoapps/course_modes/models.py. `ignore_errors` is a boolean indicating whether we should suppress `User.DoesNotExist` errors (returning None) or let it diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index f59750eb40..e9ed3395d5 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -52,8 +52,8 @@ class CourseModeFactory(DjangoModelFactory): model = CourseMode course_id = None - mode_display_name = u'Honor Code', - mode_slug = 'honor' + mode_display_name = CourseMode.DEFAULT_MODE.name + mode_slug = CourseMode.DEFAULT_MODE_SLUG min_price = 0 suggested_prices = '' currency = 'usd' diff --git a/common/djangoapps/student/tests/test_enrollment.py b/common/djangoapps/student/tests/test_enrollment.py index 2334c99b95..679a86e12b 100644 --- a/common/djangoapps/student/tests/test_enrollment.py +++ b/common/djangoapps/student/tests/test_enrollment.py @@ -7,6 +7,7 @@ from mock import patch from django.conf import settings from django.core.urlresolvers import reverse +from course_modes.models import CourseMode from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory from util.testing import UrlResetMixin @@ -41,13 +42,20 @@ class EnrollmentTest(UrlResetMixin, ModuleStoreTestCase): @ddt.data( # Default (no course modes in the database) # Expect that we're redirected to the dashboard - # and automatically enrolled as "honor" - ([], '', 'honor'), + # and automatically enrolled + ([], '', CourseMode.DEFAULT_MODE_SLUG), + + # Audit / Verified + # We should always go to the "choose your course" page. + # We should also be enrolled as the default mode. + (['verified', 'audit'], 'course_modes_choose', CourseMode.DEFAULT_MODE_SLUG), # Audit / Verified / Honor # We should always go to the "choose your course" page. - # We should also be enrolled as "honor" by default. - (['honor', 'verified', 'audit'], 'course_modes_choose', 'honor'), + # We should also be enrolled as the honor mode. + # Since honor and audit are currently offered together this precedence must + # be maintained. + (['honor', 'verified', 'audit'], 'course_modes_choose', CourseMode.HONOR), # Professional ed # Expect that we're sent to the "choose your track" page diff --git a/common/djangoapps/student/tests/test_recent_enrollments.py b/common/djangoapps/student/tests/test_recent_enrollments.py index 6372030367..ec94f4868b 100644 --- a/common/djangoapps/student/tests/test_recent_enrollments.py +++ b/common/djangoapps/student/tests/test_recent_enrollments.py @@ -127,22 +127,21 @@ class TestRecentEnrollments(ModuleStoreTestCase): self.assertContains(response, "Thank you for enrolling in") @ddt.data( - #Register as an honor in any course modes with no payment option + # Register as honor in any course modes with no payment option ([('audit', 0), ('honor', 0)], 'honor', True), ([('honor', 0)], 'honor', True), - ([], 'honor', True), - #Register as an honor in any course modes which has payment option + # Register as honor in any course modes which has payment option ([('honor', 10)], 'honor', False), # This is a paid course ([('audit', 0), ('honor', 0), ('professional', 20)], 'honor', True), ([('audit', 0), ('honor', 0), ('verified', 20)], 'honor', True), ([('audit', 0), ('honor', 0), ('verified', 20), ('professional', 20)], 'honor', True), - ([], 'honor', True), - #Register as an audit in any course modes with no payment option + # Register as audit in any course modes with no payment option ([('audit', 0), ('honor', 0)], 'audit', True), ([('audit', 0)], 'audit', True), - #Register as an audit in any course modes which has no payment option + ([], 'audit', True), + # Register as audit in any course modes which has no payment option ([('audit', 0), ('honor', 0), ('verified', 10)], 'audit', True), - #Register as a verified in any course modes which has payment option + # Register as verified in any course modes which has payment option ([('professional', 20)], 'professional', False), ([('verified', 20)], 'verified', False), ([('professional', 20), ('verified', 20)], 'verified', False), diff --git a/common/djangoapps/student/tests/test_verification_status.py b/common/djangoapps/student/tests/test_verification_status.py index c3002bb63d..498a147fa2 100644 --- a/common/djangoapps/student/tests/test_verification_status.py +++ b/common/djangoapps/student/tests/test_verification_status.py @@ -44,7 +44,7 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase): self.dashboard_url = reverse('dashboard') def test_enrolled_as_non_verified(self): - self._setup_mode_and_enrollment(None, "honor") + self._setup_mode_and_enrollment(None, "audit") # Expect that the course appears on the dashboard # without any verification messaging @@ -290,12 +290,9 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase): VerificationDeadline.set_deadline(self.course.id, deadline) BANNER_ALT_MESSAGES = { - None: "Honor", VERIFY_STATUS_NEED_TO_VERIFY: "ID verification pending", VERIFY_STATUS_SUBMITTED: "ID verification pending", VERIFY_STATUS_APPROVED: "ID Verified Ribbon/Badge", - VERIFY_STATUS_MISSED_DEADLINE: "Honor", - VERIFY_STATUS_NEED_TO_REVERIFY: "Honor" } NOTIFICATION_MESSAGES = { @@ -309,12 +306,12 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase): } MODE_CLASSES = { - None: "honor", + None: "audit", VERIFY_STATUS_NEED_TO_VERIFY: "verified", VERIFY_STATUS_SUBMITTED: "verified", VERIFY_STATUS_APPROVED: "verified", - VERIFY_STATUS_MISSED_DEADLINE: "honor", - VERIFY_STATUS_NEED_TO_REVERIFY: "honor" + VERIFY_STATUS_MISSED_DEADLINE: "audit", + VERIFY_STATUS_NEED_TO_REVERIFY: "audit" } def _assert_course_verification_status(self, status): @@ -334,7 +331,9 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase): self.assertContains(response, unicode(self.course.id)) # Verify that the correct banner is rendered on the dashboard - self.assertContains(response, self.BANNER_ALT_MESSAGES[status]) + alt_text = self.BANNER_ALT_MESSAGES.get(status) + if alt_text: + self.assertContains(response, alt_text) # Verify that the correct banner color is rendered self.assertContains( diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index 61c9d176ca..b44f6aad63 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -11,6 +11,7 @@ from urlparse import urljoin import pytz from mock import Mock, patch from opaque_keys.edx.locations import SlashSeparatedCourseKey +from pyquery import PyQuery as pq from django.conf import settings from django.contrib.auth.models import User, AnonymousUser @@ -18,6 +19,7 @@ from django.core.urlresolvers import reverse from django.test import TestCase from django.test.client import Client +from course_modes.models import CourseMode from student.models import ( anonymous_id_for_user, user_by_anonymous_id, CourseEnrollment, unique_id_for_user, LinkedInAddToProfileConfiguration @@ -248,7 +250,7 @@ class DashboardTest(ModuleStoreTestCase): self.client.login(username="jack", password="test") self._check_verification_status_on('verified', 'You\'re enrolled as a verified student') self._check_verification_status_on('honor', 'You\'re enrolled as an honor code student') - self._check_verification_status_on('audit', 'You\'re auditing this course') + self._check_verification_status_off('audit', '') self._check_verification_status_on('professional', 'You\'re enrolled as a professional education student') self._check_verification_status_on('no-id-professional', 'You\'re enrolled as a professional education student') @@ -268,8 +270,13 @@ class DashboardTest(ModuleStoreTestCase): attempt.approve() response = self.client.get(reverse('dashboard')) - self.assertNotContains(response, "class=\"course {0}\"".format(mode)) - self.assertNotContains(response, value) + + if mode == 'audit': + # Audit mode does not have a banner. Assert no banner element. + self.assertEqual(pq(response.content)(".sts-enrollment").length, 0) + else: + self.assertNotContains(response, "class=\"course {0}\"".format(mode)) + self.assertNotContains(response, value) @patch.dict("django.conf.settings.FEATURES", {'ENABLE_VERIFIED_CERTIFICATES': False}) def test_verification_status_invisible(self): @@ -280,7 +287,7 @@ class DashboardTest(ModuleStoreTestCase): self.client.login(username="jack", password="test") self._check_verification_status_off('verified', 'You\'re enrolled as a verified student') self._check_verification_status_off('honor', 'You\'re enrolled as an honor code student') - self._check_verification_status_off('audit', 'You\'re auditing this course') + self._check_verification_status_off('audit', '') def test_course_mode_info(self): verified_mode = CourseModeFactory.create( @@ -321,7 +328,12 @@ class DashboardTest(ModuleStoreTestCase): course_id=self.course.id ) course_reg_code = shoppingcart.models.CourseRegistrationCode( - code="abcde", course_id=self.course.id, created_by=self.user, invoice=sale_invoice_1, invoice_item=invoice_item, mode_slug='honor' + code="abcde", + course_id=self.course.id, + created_by=self.user, + invoice=sale_invoice_1, + invoice_item=invoice_item, + mode_slug=CourseMode.DEFAULT_MODE_SLUG ) course_reg_code.save() @@ -339,7 +351,6 @@ class DashboardTest(ModuleStoreTestCase): #now activate the user by enrolling him/her to the course response = self.client.post(redeem_url) self.assertEquals(response.status_code, 200) - response = self.client.get(reverse('dashboard')) self.assertIn('You can no longer access this course because payment has not yet been received', response.content) optout_object = Optout.objects.filter(user=self.user, course_id=self.course.id) @@ -566,7 +577,7 @@ class EnrollmentEventTestMixin(EventTestMixin): { 'course_id': course_key.to_deprecated_string(), 'user_id': user.pk, - 'mode': 'honor' + 'mode': CourseMode.DEFAULT_MODE_SLUG } ) self.mock_tracker.reset_mock() @@ -578,7 +589,7 @@ class EnrollmentEventTestMixin(EventTestMixin): { 'course_id': course_key.to_deprecated_string(), 'user_id': user.pk, - 'mode': 'honor' + 'mode': CourseMode.DEFAULT_MODE_SLUG } ) self.mock_tracker.reset_mock() @@ -754,19 +765,19 @@ class EnrollInCourseTest(EnrollmentEventTestMixin, TestCase): user = User.objects.create(username="justin", email="jh@fake.edx.org") course_id = SlashSeparatedCourseKey("edX", "Test101", "2013") - CourseEnrollment.enroll(user, course_id) + CourseEnrollment.enroll(user, course_id, "audit") self.assert_enrollment_event_was_emitted(user, course_id) - CourseEnrollment.enroll(user, course_id, "audit") - self.assert_enrollment_mode_change_event_was_emitted(user, course_id, "audit") - - # same enrollment mode does not emit an event - CourseEnrollment.enroll(user, course_id, "audit") - self.assert_no_events_were_emitted() - CourseEnrollment.enroll(user, course_id, "honor") self.assert_enrollment_mode_change_event_was_emitted(user, course_id, "honor") + # same enrollment mode does not emit an event + CourseEnrollment.enroll(user, course_id, "honor") + self.assert_no_events_were_emitted() + + CourseEnrollment.enroll(user, course_id, "audit") + self.assert_enrollment_mode_change_event_was_emitted(user, course_id, "audit") + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') class ChangeEnrollmentViewTest(ModuleStoreTestCase): @@ -789,7 +800,7 @@ class ChangeEnrollmentViewTest(ModuleStoreTestCase): ) return response - def test_enroll_as_honor(self): + def test_enroll_as_default(self): """Tests that a student can successfully enroll through this view""" response = self._enroll_through_view(self.course) self.assertEqual(response.status_code, 200) @@ -797,7 +808,7 @@ class ChangeEnrollmentViewTest(ModuleStoreTestCase): self.user, self.course.id ) self.assertTrue(is_active) - self.assertEqual(enrollment_mode, u'honor') + self.assertEqual(enrollment_mode, CourseMode.DEFAULT_MODE_SLUG) def test_cannot_enroll_if_already_enrolled(self): """ @@ -810,14 +821,14 @@ class ChangeEnrollmentViewTest(ModuleStoreTestCase): response = self._enroll_through_view(self.course) self.assertEqual(response.status_code, 400) - def test_change_to_honor_if_verified(self): + def test_change_to_default_if_verified(self): """ Tests that a student that is a currently enrolled verified student cannot - accidentally change their enrollment to verified + accidentally change their enrollment mode """ CourseEnrollment.enroll(self.user, self.course.id, mode=u'verified') self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id)) - # now try to enroll the student in the honor mode: + # now try to enroll the student in the default mode: response = self._enroll_through_view(self.course) self.assertEqual(response.status_code, 400) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user( @@ -826,7 +837,7 @@ class ChangeEnrollmentViewTest(ModuleStoreTestCase): self.assertTrue(is_active) self.assertEqual(enrollment_mode, u'verified') - def test_change_to_honor_if_verified_not_active(self): + def test_change_to_default_if_verified_not_active(self): """ Tests that one can renroll for a course if one has already unenrolled """ @@ -847,7 +858,7 @@ class ChangeEnrollmentViewTest(ModuleStoreTestCase): self.user, self.course.id ) self.assertTrue(is_active) - self.assertEqual(enrollment_mode, u'honor') + self.assertEqual(enrollment_mode, CourseMode.DEFAULT_MODE_SLUG) class AnonymousLookupTable(ModuleStoreTestCase): diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 033901154b..506dc069a6 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -1008,14 +1008,16 @@ def change_enrollment(request, check_access=True): # Check that auto enrollment is allowed for this course # (= the course is NOT behind a paywall) if CourseMode.can_auto_enroll(course_id): - # Enroll the user using the default mode (honor) + # Enroll the user using the default mode (audit) # We're assuming that users of the course enrollment table # will NOT try to look up the course enrollment model # by its slug. If they do, it's possible (based on the state of the database) # for no such model to exist, even though we've set the enrollment type - # to "honor". + # to "audit". try: - CourseEnrollment.enroll(user, course_id, check_access=check_access) + enroll_mode = CourseMode.auto_enroll_mode(course_id, available_modes) + if enroll_mode: + CourseEnrollment.enroll(user, course_id, check_access=check_access, mode=enroll_mode) except Exception: return HttpResponseBadRequest(_("Could not enroll")) diff --git a/common/templates/course_modes/choose.html b/common/templates/course_modes/choose.html index 0def3743fd..3b05aa7f41 100644 --- a/common/templates/course_modes/choose.html +++ b/common/templates/course_modes/choose.html @@ -143,15 +143,35 @@ from django.core.urlresolvers import reverse
-

${_("Audit This Course")}

+

${_("Earn an Honor Certificate")}

-

${_("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.")}

+

${_("Take this course for free and have complete access to all the course material, activities, tests, and forums. Please note that learners who earn a passing grade will earn a certificate in this course.")}

+
+ % elif "audit" in modes: + + ${_("or")} + + +
+
+ +

${_("Audit This Course")}

+
+

${_("Audit this course for free and have complete access to all the course material, activities, tests, and forums. Please note that this track does not offer a certificate for learners who earn a passing grade.")}

+
+
+ +
diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py index ade38ceb67..ea4aef579b 100644 --- a/lms/djangoapps/certificates/tests/test_api.py +++ b/lms/djangoapps/certificates/tests/test_api.py @@ -13,6 +13,7 @@ from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from student.models import CourseEnrollment from student.tests.factories import UserFactory +from course_modes.models import CourseMode from course_modes.tests.factories import CourseModeFactory from config_models.models import cache from util.testing import EventTestMixin @@ -333,6 +334,7 @@ class GenerateExampleCertificatesTest(TestCase): def test_generate_example_certs(self): # Generate certificates for the course + CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR) with self._mock_xqueue() as mock_queue: certs_api.generate_example_certificates(self.COURSE_KEY) diff --git a/lms/djangoapps/commerce/api/v0/tests/test_views.py b/lms/djangoapps/commerce/api/v0/tests/test_views.py index 93c4e7a114..3be720322c 100644 --- a/lms/djangoapps/commerce/api/v0/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v0/tests/test_views.py @@ -197,7 +197,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) with mock_create_basket(response=return_value): self._test_successful_ecommerce_api_call(False) - def _test_course_without_sku(self): + def _test_course_without_sku(self, enrollment_mode=CourseMode.DEFAULT_MODE_SLUG): """ Validates the view bypasses the E-Commerce API when the course has no CourseModes with SKUs. """ @@ -207,13 +207,16 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) # Validate the response content self.assertEqual(response.status_code, 200) - msg = Messages.NO_SKU_ENROLLED.format(enrollment_mode='honor', course_id=self.course.id, - username=self.user.username) + msg = Messages.NO_SKU_ENROLLED.format( + enrollment_mode=enrollment_mode, + course_id=self.course.id, + username=self.user.username + ) self.assertResponseMessage(response, msg) - def test_course_without_sku(self): + def test_course_without_sku_default(self): """ - If the course does NOT have a SKU, the user should be enrolled in the course (under the honor mode) and + If the course does NOT have a SKU, the user should be enrolled in the course (under the default mode) and redirected to the user dashboard. """ # Remove SKU from all course modes @@ -223,6 +226,24 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) self._test_course_without_sku() + def test_course_without_sku_honor(self): + """ + If the course does not have an SKU and has an honor mode, the user + should be enrolled as honor. This ensures backwards + compatibility with courses existing before the removal of + honor certificates. + """ + # Remove all existing course modes + CourseMode.objects.filter(course_id=self.course.id).delete() + # Ensure that honor mode exists + CourseMode( + mode_slug=CourseMode.HONOR, + mode_display_name="Honor Cert", + course_id=self.course.id + ).save() + # We should be enrolled in honor mode + self._test_course_without_sku(enrollment_mode=CourseMode.HONOR) + @override_settings(ECOMMERCE_API_URL=None, ECOMMERCE_API_SIGNING_KEY=None) def test_ecommerce_service_not_configured(self): """ @@ -240,7 +261,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id)) def assertProfessionalModeBypassed(self): - """ Verifies that the view returns HTTP 406 when a course with no honor mode is encountered. """ + """ Verifies that the view returns HTTP 406 when a course with no honor or audit mode is encountered. """ CourseMode.objects.filter(course_id=self.course.id).delete() mode = CourseMode.NO_ID_PROFESSIONAL_MODE @@ -252,7 +273,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) # The view should return an error status code self.assertEqual(response.status_code, 406) - msg = Messages.NO_HONOR_MODE.format(course_id=self.course.id) + msg = Messages.NO_DEFAULT_ENROLLMENT_MODE.format(course_id=self.course.id) self.assertResponseMessage(response, msg) def test_course_with_professional_mode_only(self): diff --git a/lms/djangoapps/commerce/api/v0/views.py b/lms/djangoapps/commerce/api/v0/views.py index ba548ddf0d..7a050b1ad0 100644 --- a/lms/djangoapps/commerce/api/v0/views.py +++ b/lms/djangoapps/commerce/api/v0/views.py @@ -59,9 +59,9 @@ class BasketsView(APIView): return True, course_key, None - def _enroll(self, course_key, user): + def _enroll(self, course_key, user, mode=CourseMode.DEFAULT_MODE_SLUG): """ Enroll the user in the course. """ - add_enrollment(user.username, unicode(course_key)) + add_enrollment(user.username, unicode(course_key), mode) def _handle_marketing_opt_in(self, request, course_key, user): """ @@ -100,19 +100,28 @@ class BasketsView(APIView): msg = Messages.ENROLLMENT_EXISTS.format(course_id=course_id, username=user.username) return DetailResponse(msg, status=HTTP_409_CONFLICT) - # If there is no honor course mode, this most likely a Prof-Ed course. Return an error so that the JS - # redirects to track selection. + # If there is no audit or honor course mode, this most likely + # a Prof-Ed course. Return an error so that the JS redirects + # to track selection. honor_mode = CourseMode.mode_for_course(course_key, CourseMode.HONOR) + audit_mode = CourseMode.mode_for_course(course_key, CourseMode.AUDIT) - if not honor_mode: - msg = Messages.NO_HONOR_MODE.format(course_id=course_id) + # Accept either honor or audit as an enrollment mode to + # maintain backwards compatibility with existing courses + default_enrollment_mode = audit_mode or honor_mode + + if not default_enrollment_mode: + msg = Messages.NO_DEFAULT_ENROLLMENT_MODE.format(course_id=course_id) return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE) - elif not honor_mode.sku: + elif default_enrollment_mode and not default_enrollment_mode.sku: # If there are no course modes with SKUs, enroll the user without contacting the external API. - msg = Messages.NO_SKU_ENROLLED.format(enrollment_mode=CourseMode.HONOR, course_id=course_id, - username=user.username) + msg = Messages.NO_SKU_ENROLLED.format( + enrollment_mode=default_enrollment_mode.slug, + course_id=course_id, + username=user.username + ) log.info(msg) - self._enroll(course_key, user) + self._enroll(course_key, user, default_enrollment_mode.slug) self._handle_marketing_opt_in(request, course_key, user) return DetailResponse(msg) @@ -131,7 +140,7 @@ class BasketsView(APIView): # Make the API call try: response_data = api.baskets.post({ - 'products': [{'sku': honor_mode.sku}], + 'products': [{'sku': default_enrollment_mode.sku}], 'checkout': True, }) @@ -158,7 +167,7 @@ class BasketsView(APIView): audit_log( 'checkout_requested', course_id=course_id, - mode=honor_mode.slug, + mode=default_enrollment_mode.slug, processor_name=None, user_id=user.id ) diff --git a/lms/djangoapps/commerce/api/v1/tests/test_views.py b/lms/djangoapps/commerce/api/v1/tests/test_views.py index f3db3da0cf..62245bef2a 100644 --- a/lms/djangoapps/commerce/api/v1/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v1/tests/test_views.py @@ -285,7 +285,7 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase) expected_modes = [ CourseMode( - mode_slug=u'honor', + mode_slug=CourseMode.DEFAULT_MODE_SLUG, min_price=150, currency=u'USD', sku=u'ABC123' ) diff --git a/lms/djangoapps/commerce/constants.py b/lms/djangoapps/commerce/constants.py index 729830a2e8..447847fb9b 100644 --- a/lms/djangoapps/commerce/constants.py +++ b/lms/djangoapps/commerce/constants.py @@ -15,4 +15,5 @@ class Messages(object): ORDER_COMPLETED = u'Order {order_number} was completed.' ORDER_INCOMPLETE_ENROLLED = u'Order {order_number} was created, but is not yet complete. User was enrolled.' NO_HONOR_MODE = u'Course {course_id} does not have an honor mode.' + NO_DEFAULT_ENROLLMENT_MODE = u'Course {course_id} does not have an honor or audit mode.' ENROLLMENT_EXISTS = u'User {username} is already enrolled in {course_id}.' diff --git a/lms/djangoapps/course_blocks/transformers/tests/test_helpers.py b/lms/djangoapps/course_blocks/transformers/tests/test_helpers.py index 6cc5072b60..1d049c8d25 100644 --- a/lms/djangoapps/course_blocks/transformers/tests/test_helpers.py +++ b/lms/djangoapps/course_blocks/transformers/tests/test_helpers.py @@ -1,6 +1,7 @@ """ Test helpers for testing course block transformers. """ +from course_modes.models import CourseMode from student.tests.factories import CourseEnrollmentFactory, UserFactory from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -221,7 +222,12 @@ class BlockParentsMapTestCase(ModuleStoreTestCase): self.password = 'test' self.student = UserFactory.create(is_staff=False, username='test_student', password=self.password) self.staff = UserFactory.create(is_staff=True, username='test_staff', password=self.password) - CourseEnrollmentFactory.create(is_active=True, mode='honor', user=self.student, course_id=self.course.id) + CourseEnrollmentFactory.create( + is_active=True, + mode=CourseMode.DEFAULT_MODE_SLUG, + user=self.student, + course_id=self.course.id + ) def assert_transform_results( self, diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index 0aeda4fdba..fb457e079d 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -58,10 +58,12 @@ class AboutTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, EventTrackingT ) self.purchase_course = CourseFactory.create(org='MITx', number='buyme', display_name='Course To Buy') - self.course_mode = CourseMode(course_id=self.purchase_course.id, - mode_slug="honor", - mode_display_name="honor cert", - min_price=10) + self.course_mode = CourseMode( + course_id=self.purchase_course.id, + mode_slug=CourseMode.DEFAULT_MODE_SLUG, + mode_display_name=CourseMode.DEFAULT_MODE_SLUG, + min_price=10 + ) self.course_mode.save() def test_anonymous_user(self): @@ -248,8 +250,7 @@ class AboutWithCappedEnrollmentsTestCase(LoginEnrollmentTestCase, ModuleStoreTes self.email = 'foo_second@test.com' self.password = 'bar' self.username = 'test_second' - self.create_account(self.username, - self.email, self.password) + self.create_account(self.username, self.email, self.password) self.activate_user(self.email) self.login(self.email, self.password) @@ -417,8 +418,8 @@ class AboutPurchaseCourseTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): """ course_mode = CourseMode( course_id=course.id, - mode_slug="honor", - mode_display_name="honor cert", + mode_slug=CourseMode.DEFAULT_MODE_SLUG, + mode_display_name=CourseMode.DEFAULT_MODE_SLUG, min_price=10, ) course_mode.save() diff --git a/lms/djangoapps/courseware/tests/test_microsites.py b/lms/djangoapps/courseware/tests/test_microsites.py index 9772375e7e..943b68ea87 100644 --- a/lms/djangoapps/courseware/tests/test_microsites.py +++ b/lms/djangoapps/courseware/tests/test_microsites.py @@ -201,8 +201,8 @@ class TestMicrosites(ModuleStoreTestCase, LoginEnrollmentTestCase): """ course_mode = CourseMode( course_id=self.course_with_visibility.id, - mode_slug="honor", - mode_display_name="honor cert", + mode_slug=CourseMode.DEFAULT_MODE_SLUG, + mode_display_name=CourseMode.DEFAULT_MODE_SLUG, min_price=10, ) course_mode.save() diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index ab271768de..cd2075f92f 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -27,6 +27,7 @@ from xblock.core import XBlock from xblock.fragment import Fragment from capa.tests.response_xml_factory import OptionResponseXMLFactory +from course_modes.models import CourseMode from courseware import module_render as render from courseware.courses import get_course_with_access, course_image_url, get_course_info_section from courseware.field_overrides import OverrideFieldData @@ -724,9 +725,9 @@ class TestProctoringRendering(ModuleStoreTestCase): ) @ddt.data( - ('honor', False, None, None), + (CourseMode.DEFAULT_MODE_SLUG, False, None, None), ( - 'honor', + CourseMode.DEFAULT_MODE_SLUG, True, 'eligible', { @@ -737,7 +738,7 @@ class TestProctoringRendering(ModuleStoreTestCase): } ), ( - 'honor', + CourseMode.DEFAULT_MODE_SLUG, True, 'submitted', { @@ -748,7 +749,7 @@ class TestProctoringRendering(ModuleStoreTestCase): } ), ( - 'honor', + CourseMode.DEFAULT_MODE_SLUG, True, 'error', { @@ -759,7 +760,7 @@ class TestProctoringRendering(ModuleStoreTestCase): } ), ( - 'verified', + CourseMode.VERIFIED, False, None, { @@ -770,7 +771,7 @@ class TestProctoringRendering(ModuleStoreTestCase): } ), ( - 'verified', + CourseMode.VERIFIED, False, 'declined', { @@ -781,7 +782,7 @@ class TestProctoringRendering(ModuleStoreTestCase): } ), ( - 'verified', + CourseMode.VERIFIED, False, 'submitted', { @@ -792,7 +793,7 @@ class TestProctoringRendering(ModuleStoreTestCase): } ), ( - 'verified', + CourseMode.VERIFIED, False, 'verified', { @@ -803,7 +804,7 @@ class TestProctoringRendering(ModuleStoreTestCase): } ), ( - 'verified', + CourseMode.VERIFIED, False, 'rejected', { @@ -814,7 +815,7 @@ class TestProctoringRendering(ModuleStoreTestCase): } ), ( - 'verified', + CourseMode.VERIFIED, False, 'error', { @@ -851,56 +852,56 @@ class TestProctoringRendering(ModuleStoreTestCase): @ddt.data( ( - 'honor', + CourseMode.DEFAULT_MODE_SLUG, True, None, 'Try a proctored exam', True ), ( - 'honor', + CourseMode.DEFAULT_MODE_SLUG, True, 'submitted', 'You have submitted this practice proctored exam', False ), ( - 'honor', + CourseMode.DEFAULT_MODE_SLUG, True, 'error', 'There was a problem with your practice proctoring session', True ), ( - 'verified', + CourseMode.VERIFIED, False, None, 'This exam is proctored', False ), ( - 'verified', + CourseMode.VERIFIED, False, 'submitted', 'You have submitted this proctored exam for review', True ), ( - 'verified', + CourseMode.VERIFIED, False, 'verified', 'Your proctoring session was reviewed and passed all requirements', False ), ( - 'verified', + CourseMode.VERIFIED, False, 'rejected', 'Your proctoring session was reviewed and did not pass requirements', True ), ( - 'verified', + CourseMode.VERIFIED, False, 'error', 'There was a problem with your proctoring session', diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index dbfc8a1faf..d83bcab859 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -708,7 +708,7 @@ class ProgressPageTests(ModuleStoreTestCase): ) # Enroll student into course - CourseEnrollment.enroll(self.user, self.course.id, mode='honor') + CourseEnrollment.enroll(self.user, self.course.id) resp = views.progress(self.request, course_id=self.course.id.to_deprecated_string(), student_id=self.user.id) # Assert that valid 'student_id' returns 200 status self.assertEqual(resp.status_code, 200) diff --git a/lms/djangoapps/instructor/enrollment.py b/lms/djangoapps/instructor/enrollment.py index 305ee8b636..d963721508 100644 --- a/lms/djangoapps/instructor/enrollment.py +++ b/lms/djangoapps/instructor/enrollment.py @@ -12,6 +12,7 @@ from django.core.urlresolvers import reverse from django.core.mail import send_mail from django.utils.translation import override as override_language +from course_modes.models import CourseMode from student.models import CourseEnrollment, CourseEnrollmentAllowed from courseware.models import StudentModule from edxmako.shortcuts import render_to_string @@ -110,7 +111,7 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal if previous_state.user: # if the student is currently unenrolled, don't enroll them in their # previous mode - course_mode = u"honor" + course_mode = CourseMode.DEFAULT_MODE_SLUG if previous_state.enrollment: course_mode = previous_state.mode diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index b7cdd755de..ef4bfa9d7c 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -1275,7 +1275,7 @@ class TestInstructorAPIEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTest create paid course mode. """ paid_course = CourseFactory.create() - CourseModeFactory.create(course_id=paid_course.id, min_price=50) + CourseModeFactory.create(course_id=paid_course.id, min_price=50, mode_slug=CourseMode.HONOR) CourseInstructorRole(paid_course.id).add_users(self.instructor) return paid_course @@ -1405,7 +1405,7 @@ class TestInstructorAPIEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTest def test_unenroll_and_enroll_verified(self): """ Test that unenrolling and enrolling a student from a verified track - results in that student being in an honor track + results in that student being in the default track """ course_enrollment = CourseEnrollment.objects.get( user=self.enrolled_student, course_id=self.course.id @@ -1422,7 +1422,7 @@ class TestInstructorAPIEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTest course_enrollment = CourseEnrollment.objects.get( user=self.enrolled_student, course_id=self.course.id ) - self.assertEqual(course_enrollment.mode, u'honor') + self.assertEqual(course_enrollment.mode, CourseMode.DEFAULT_MODE_SLUG) def _change_student_enrollment(self, user, course, action): """ @@ -2138,7 +2138,11 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment company_contact_email='test@123', recipient_name='R1', recipient_email='', customer_reference_number='PO#23') - paid_course_reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course.id) + paid_course_reg_item = PaidCourseRegistration.add_to_order( + self.cart, + self.course.id, + mode_slug=CourseMode.HONOR + ) # update the quantity of the cart item paid_course_reg_item resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': paid_course_reg_item.id, 'qty': '4'}) self.assertEqual(resp.status_code, 200) diff --git a/lms/djangoapps/instructor/tests/test_registration_codes.py b/lms/djangoapps/instructor/tests/test_registration_codes.py index 30cc2fbb7a..e6f250d633 100644 --- a/lms/djangoapps/instructor/tests/test_registration_codes.py +++ b/lms/djangoapps/instructor/tests/test_registration_codes.py @@ -1,6 +1,7 @@ """ Test for the registration code status information. """ +from course_modes.models import CourseMode from courseware.tests.factories import InstructorFactory from xmodule.modulestore.tests.factories import CourseFactory from django.utils.translation import ugettext as _ @@ -116,7 +117,7 @@ class TestCourseRegistrationCodeStatus(SharedModuleStoreTestCase): created_by=self.instructor, invoice=self.sale_invoice, invoice_item=self.invoice_item, - mode_slug='honor' + mode_slug=CourseMode.DEFAULT_MODE_SLUG ) reg_code = CourseRegistrationCode.objects.all()[0] @@ -247,7 +248,7 @@ class TestCourseRegistrationCodeStatus(SharedModuleStoreTestCase): created_by=self.instructor, invoice=self.sale_invoice, invoice_item=self.invoice_item, - mode_slug='honor', + mode_slug=CourseMode.DEFAULT_MODE_SLUG, is_valid=False ) @@ -278,7 +279,7 @@ class TestCourseRegistrationCodeStatus(SharedModuleStoreTestCase): created_by=self.instructor, invoice=self.sale_invoice, invoice_item=self.invoice_item, - mode_slug='honor' + mode_slug=CourseMode.DEFAULT_MODE_SLUG, ) reg_code = CourseRegistrationCode.objects.all()[0] diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py index 37ea9c4e7a..8a40121fb7 100644 --- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py +++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py @@ -56,10 +56,12 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT display_name='' ) - self.course_mode = CourseMode(course_id=self.course.id, - mode_slug="honor", - mode_display_name="honor cert", - min_price=40) + self.course_mode = CourseMode( + course_id=self.course.id, + mode_slug=CourseMode.DEFAULT_MODE_SLUG, + mode_display_name=CourseMode.DEFAULT_MODE.name, + min_price=40 + ) self.course_mode.save() # Create instructor account self.instructor = AdminFactory.create() diff --git a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py index 5e851a5019..9a51b00835 100644 --- a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py +++ b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py @@ -476,7 +476,7 @@ class TestInstructorDetailedEnrollmentReport(TestReportMixin, InstructorTaskCour created_by=self.instructor, invoice=self.sale_invoice_1, invoice_item=self.invoice_item, - mode_slug='honor' + mode_slug=CourseMode.DEFAULT_MODE_SLUG ) course_registration_code.save() @@ -517,7 +517,7 @@ class TestInstructorDetailedEnrollmentReport(TestReportMixin, InstructorTaskCour created_by=self.instructor, invoice=self.sale_invoice_1, invoice_item=self.invoice_item, - mode_slug='honor' + mode_slug=CourseMode.DEFAULT_MODE_SLUG ) course_registration_code.save() diff --git a/lms/djangoapps/mobile_api/social_facebook/test_utils.py b/lms/djangoapps/mobile_api/social_facebook/test_utils.py index 5bb3130ff4..c9c7304e10 100644 --- a/lms/djangoapps/mobile_api/social_facebook/test_utils.py +++ b/lms/djangoapps/mobile_api/social_facebook/test_utils.py @@ -1,5 +1,5 @@ """ - Test utils for Facebook functionality +Test utils for Facebook functionality """ import httpretty @@ -10,6 +10,7 @@ from django.conf import settings from django.core.urlresolvers import reverse from social.apps.django_app.default.models import UserSocialAuth +from course_modes.models import CourseMode from student.models import CourseEnrollment from student.views import login_oauth_token from openedx.core.djangoapps.user_api.preferences.api import get_user_preference, set_user_preference @@ -182,4 +183,4 @@ class SocialFacebookTestCase(ModuleStoreTestCase, APITestCase): self.assertTrue(CourseEnrollment.is_enrolled(user, course.id)) course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(user, course.id) self.assertTrue(is_active) - self.assertEqual(course_mode, 'honor') + self.assertEqual(course_mode, CourseMode.DEFAULT_MODE_SLUG) diff --git a/lms/djangoapps/shoppingcart/management/tests/test_retire_order.py b/lms/djangoapps/shoppingcart/management/tests/test_retire_order.py index 044c7115c1..140cbd309a 100644 --- a/lms/djangoapps/shoppingcart/management/tests/test_retire_order.py +++ b/lms/djangoapps/shoppingcart/management/tests/test_retire_order.py @@ -3,6 +3,7 @@ from tempfile import NamedTemporaryFile from django.core.management import call_command +from course_modes.models import CourseMode from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory from shoppingcart.models import Order, CertificateItem @@ -16,6 +17,11 @@ class TestRetireOrder(ModuleStoreTestCase): course = CourseFactory.create() self.course_key = course.id + CourseMode.objects.create( + course_id=self.course_key, + mode_slug=CourseMode.HONOR, + mode_display_name=CourseMode.HONOR + ) # set up test carts self.cart, __ = self._create_cart() diff --git a/lms/djangoapps/shoppingcart/tests/test_models.py b/lms/djangoapps/shoppingcart/tests/test_models.py index a22b91a791..98db8769b7 100644 --- a/lms/djangoapps/shoppingcart/tests/test_models.py +++ b/lms/djangoapps/shoppingcart/tests/test_models.py @@ -56,7 +56,13 @@ class OrderTest(ModuleStoreTestCase): self.course_key = course.id self.other_course_keys = [] for __ in xrange(1, 5): - self.other_course_keys.append(CourseFactory.create().id) + course_key = CourseFactory.create().id + CourseMode.objects.create( + course_id=course_key, + mode_slug=CourseMode.HONOR, + mode_display_name="Honor" + ) + self.other_course_keys.append(course_key) self.cost = 40 # Add mock tracker for event testing. @@ -64,6 +70,12 @@ class OrderTest(ModuleStoreTestCase): self.mock_tracker = patcher.start() self.addCleanup(patcher.stop) + CourseMode.objects.create( + course_id=self.course_key, + mode_slug=CourseMode.HONOR, + mode_display_name="Honor" + ) + def test_get_cart_for_user(self): # create a cart cart = Order.get_cart_for_user(user=self.user) @@ -479,10 +491,12 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase): self.cost = 40 self.course = CourseFactory.create() self.course_key = self.course.id - self.course_mode = CourseMode(course_id=self.course_key, - mode_slug="honor", - mode_display_name="honor cert", - min_price=self.cost) + self.course_mode = CourseMode( + course_id=self.course_key, + mode_slug=CourseMode.HONOR, + mode_display_name="honor cert", + min_price=self.cost + ) self.course_mode.save() self.percentage_discount = 20.0 self.cart = Order.get_cart_for_user(self.user) @@ -492,7 +506,7 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase): Test to check the total amount of the purchased items. """ - PaidCourseRegistration.add_to_order(self.cart, self.course_key) + PaidCourseRegistration.add_to_order(self.cart, self.course_key, mode_slug=CourseMode.HONOR) self.cart.purchase() total_amount = PaidCourseRegistration.get_total_amount_of_purchased_item(course_key=self.course_key) @@ -507,7 +521,7 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase): self.assertEqual(total_amount, 0.00) def test_add_to_order(self): - reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key) + reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key, mode_slug=CourseMode.HONOR) self.assertEqual(reg1.unit_cost, self.cost) self.assertEqual(reg1.line_cost, self.cost) @@ -545,7 +559,7 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase): self.cart.order_type = 'business' self.cart.save() - item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2) + item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2, mode_slug=CourseMode.HONOR) self.cart.purchase() registration_codes = CourseRegistrationCode.order_generated_registration_codes(self.course_key) self.assertEqual(registration_codes.count(), item.qty) @@ -710,14 +724,15 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase): def test_add_with_default_mode(self): """ - Tests add_to_cart where the mode specified in the argument is NOT in the database - and NOT the default "honor". In this case it just adds the user in the CourseMode.DEFAULT_MODE, 0 price + Tests add_to_cart where the mode specified in the argument is NOT + in the database and NOT the default "audit". In this case it + just adds the user in the CourseMode.DEFAULT_MODE for free. """ reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key, mode_slug="DNE") self.assertEqual(reg1.unit_cost, 0) self.assertEqual(reg1.line_cost, 0) - self.assertEqual(reg1.mode, "honor") + self.assertEqual(reg1.mode, CourseMode.DEFAULT_MODE_SLUG) self.assertEqual(reg1.user, self.user) self.assertEqual(reg1.status, "cart") self.assertEqual(self.cart.total_cost, 0) @@ -727,7 +742,7 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase): self.assertEqual(course_reg_code_item.unit_cost, 0) self.assertEqual(course_reg_code_item.line_cost, 0) - self.assertEqual(course_reg_code_item.mode, "honor") + self.assertEqual(course_reg_code_item.mode, CourseMode.DEFAULT_MODE_SLUG) self.assertEqual(course_reg_code_item.user, self.user) self.assertEqual(course_reg_code_item.status, "cart") self.assertEqual(self.cart.total_cost, 0) diff --git a/lms/djangoapps/shoppingcart/tests/test_reports.py b/lms/djangoapps/shoppingcart/tests/test_reports.py index a2304cceff..dccb1aa9e2 100644 --- a/lms/djangoapps/shoppingcart/tests/test_reports.py +++ b/lms/djangoapps/shoppingcart/tests/test_reports.py @@ -184,7 +184,7 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase): self.course_reg_code_annotation = CourseRegCodeItemAnnotation(course_id=self.course_key, annotation=self.TEST_ANNOTATION) self.course_reg_code_annotation.save() self.cart = Order.get_cart_for_user(self.user) - self.reg = PaidCourseRegistration.add_to_order(self.cart, self.course_key) + self.reg = PaidCourseRegistration.add_to_order(self.cart, self.course_key, mode_slug=course_mode.mode_slug) self.cert_item = CertificateItem.add_to_order(self.cart, self.course_key, self.cost, 'verified') self.cart.purchase() self.now = datetime.datetime.now(pytz.UTC) diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py index 459c763ecc..381277ed52 100644 --- a/lms/djangoapps/shoppingcart/tests/test_views.py +++ b/lms/djangoapps/shoppingcart/tests/test_views.py @@ -94,20 +94,41 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): self.coupon_code = 'abcde' self.reg_code = 'qwerty' self.percentage_discount = 10 - self.course_mode = CourseMode(course_id=self.course_key, - mode_slug="honor", - mode_display_name="honor cert", - min_price=self.cost) + self.course_mode = CourseMode( + course_id=self.course_key, + mode_slug=CourseMode.HONOR, + mode_display_name="honor cert", + min_price=self.cost + ) self.course_mode.save() # Saving another testing course mode self.testing_cost = 20 - self.testing_course_mode = CourseMode(course_id=self.testing_course.id, - mode_slug="honor", - mode_display_name="testing honor cert", - min_price=self.testing_cost) + self.testing_course_mode = CourseMode( + course_id=self.testing_course.id, + mode_slug=CourseMode.HONOR, + mode_display_name="testing honor cert", + min_price=self.testing_cost + ) self.testing_course_mode.save() + # And for the XSS course + CourseMode( + course_id=self.xss_course_key, + mode_slug=CourseMode.HONOR, + mode_display_name="honor cert", + min_price=self.cost + ).save() + + # And the verified course + self.verified_course_mode = CourseMode( + course_id=self.verified_course_key, + mode_slug=CourseMode.HONOR, + mode_display_name="honor cert", + min_price=self.cost + ) + self.verified_course_mode.save() + self.cart = Order.get_cart_for_user(self.user) self.addCleanup(patcher.stop) @@ -131,10 +152,12 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): percentage_discount=self.percentage_discount, created_by=self.user, is_active=is_active) coupon.save() - def add_reg_code(self, course_key, mode_slug='honor', is_valid=True): + def add_reg_code(self, course_key, mode_slug=None, is_valid=True): """ add dummy registration code into models """ + if mode_slug is None: + mode_slug = self.course_mode.mode_slug course_reg_code = CourseRegistrationCode( code=self.reg_code, course_id=course_key, created_by=self.user, mode_slug=mode_slug, @@ -159,7 +182,7 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): adding course to user cart """ self.login_user() - reg_item = PaidCourseRegistration.add_to_order(self.cart, course_key) + reg_item = PaidCourseRegistration.add_to_order(self.cart, course_key, mode_slug=self.course_mode.mode_slug) return reg_item def login_user(self): @@ -224,9 +247,18 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): test to check that that the same coupon code applied on multiple items in the cart. """ + for course_key, cost in ((self.course_key, 40), (self.testing_course.id, 20)): + CourseMode( + course_id=course_key, + mode_slug=CourseMode.DEFAULT_MODE_SLUG, + mode_display_name=CourseMode.DEFAULT_MODE_SLUG, + min_price=cost + ).save() self.login_user() # add first course to user cart - resp = self.client.post(reverse('shoppingcart.views.add_course_to_cart', args=[self.course_key.to_deprecated_string()])) + resp = self.client.post( + reverse('shoppingcart.views.add_course_to_cart', args=[self.course_key.to_deprecated_string()]) + ) self.assertEqual(resp.status_code, 200) # add and apply the coupon code to course in the cart self.add_coupon(self.course_key, True, self.coupon_code) @@ -237,7 +269,9 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): self.add_coupon(self.testing_course.id, True, self.coupon_code) #now add the second course to cart, the coupon code should be # applied when adding the second course to the cart - resp = self.client.post(reverse('shoppingcart.views.add_course_to_cart', args=[self.testing_course.id.to_deprecated_string()])) + resp = self.client.post( + reverse('shoppingcart.views.add_course_to_cart', args=[self.testing_course.id.to_deprecated_string()]) + ) self.assertEqual(resp.status_code, 200) #now check the user cart and see that the discount has been applied on both the courses resp = self.client.get(reverse('shoppingcart.views.show_cart', args=[])) @@ -586,7 +620,7 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): self.add_reg_code(course_key, mode_slug='verified') # Enroll as honor in the course with the current user. - CourseEnrollment.enroll(self.user, self.course_key) + CourseEnrollment.enroll(self.user, self.course_key, mode=CourseMode.HONOR) self.login_user() current_enrollment, __ = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_key) self.assertEquals('honor', current_enrollment) @@ -753,8 +787,17 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): @patch('shoppingcart.views.render_to_response', render_mock) def test_show_cart(self): self.login_user() - reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_key) - cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') + reg_item = PaidCourseRegistration.add_to_order( + self.cart, + self.course_key, + mode_slug=self.course_mode.mode_slug + ) + cert_item = CertificateItem.add_to_order( + self.cart, + self.verified_course_key, + self.cost, + self.course_mode.mode_slug + ) resp = self.client.get(reverse('shoppingcart.views.show_cart', args=[])) self.assertEqual(resp.status_code, 200) @@ -860,7 +903,6 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): for __ in range(num_items): CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') self.cart.purchase() - self.login_user() url = reverse('shoppingcart.views.show_receipt', args=[self.cart.id]) resp = self.client.get(url, HTTP_ACCEPT="application/json") @@ -891,7 +933,7 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): 'unit_cost': 40, 'quantity': 1, 'line_cost': 40, - 'line_desc': 'Honor Code Certificate for course Test Course', + 'line_desc': '{} for course Test Course'.format(self.verified_course_mode.mode_display_name), 'course_key': unicode(self.verified_course_key) }) @@ -922,8 +964,17 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): def test_show_receipt_json_multiple_items(self): # Two different item types - PaidCourseRegistration.add_to_order(self.cart, self.course_key) - CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') + PaidCourseRegistration.add_to_order( + self.cart, + self.course_key, + mode_slug=self.course_mode.mode_slug + ) + CertificateItem.add_to_order( + self.cart, + self.verified_course_key, + self.cost, + self.verified_course_mode.mode_slug + ) self.cart.purchase() self.login_user() @@ -950,7 +1001,7 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): 'unit_cost': 40, 'quantity': 1, 'line_cost': 40, - 'line_desc': 'Honor Code Certificate for course Test Course', + 'line_desc': '{} for course Test Course'.format(self.verified_course_mode.mode_display_name), 'course_key': unicode(self.verified_course_key) }) @@ -1011,7 +1062,7 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): self.client.login(username=self.instructor.username, password="test") cart = Order.get_cart_for_user(self.instructor) - PaidCourseRegistration.add_to_order(cart, self.course_key) + PaidCourseRegistration.add_to_order(cart, self.course_key, mode_slug=self.course_mode.mode_slug) cart.purchase(first='FirstNameTesting123', street1='StreetTesting123') total_amount = PaidCourseRegistration.get_total_amount_of_purchased_item(self.course_key) @@ -1058,8 +1109,12 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): # Two courses in user shopping cart self.login_user() - PaidCourseRegistration.add_to_order(self.cart, self.course_key) - item2 = PaidCourseRegistration.add_to_order(self.cart, self.testing_course.id) + PaidCourseRegistration.add_to_order(self.cart, self.course_key, mode_slug=self.course_mode.mode_slug) + item2 = PaidCourseRegistration.add_to_order( + self.cart, + self.testing_course.id, + mode_slug=self.course_mode.mode_slug + ) self.assertEquals(self.cart.orderitem_set.count(), 2) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code}) @@ -1113,7 +1168,11 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): @patch('shoppingcart.views.render_to_response', render_mock) def test_show_receipt_success(self): - reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_key) + reg_item = PaidCourseRegistration.add_to_order( + self.cart, + self.course_key, + mode_slug=self.course_mode.mode_slug + ) cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123') @@ -1157,7 +1216,7 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): def test_courseregcode_item_total_price(self): self.cart.order_type = 'business' self.cart.save() - CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2) + CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2, mode_slug=self.course_mode.mode_slug) self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123') self.assertEquals(CourseRegCodeItem.get_total_amount_of_purchased_item(self.course_key), 80) @@ -1165,7 +1224,12 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): def test_show_receipt_success_with_order_type_business(self): self.cart.order_type = 'business' self.cart.save() - reg_item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2) + reg_item = CourseRegCodeItem.add_to_order( + self.cart, + self.course_key, + 2, + mode_slug=self.course_mode.mode_slug + ) self.cart.add_billing_details(company_name='T1Omega', company_contact_name='C1', company_contact_email='test@t1.com', recipient_email='test@t2.com') self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123') @@ -1230,7 +1294,11 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): @patch('shoppingcart.views.render_to_response', render_mock) def test_show_receipt_success_with_upgrade(self): - reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_key) + reg_item = PaidCourseRegistration.add_to_order( + self.cart, + self.course_key, + mode_slug=self.course_mode.mode_slug + ) cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123') @@ -1255,7 +1323,11 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin): @patch('shoppingcart.views.render_to_response', render_mock) def test_show_receipt_success_refund(self): - reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_key) + reg_item = PaidCourseRegistration.add_to_order( + self.cart, + self.course_key, + mode_slug=self.course_mode.mode_slug + ) cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123') cert_item.status = "refunded" @@ -1493,10 +1565,12 @@ class ShoppingcartViewsClosedEnrollment(ModuleStoreTestCase): self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course') self.course_key = self.course.id - self.course_mode = CourseMode(course_id=self.course_key, - mode_slug="honor", - mode_display_name="honor cert", - min_price=self.cost) + self.course_mode = CourseMode( + course_id=self.course_key, + mode_slug=CourseMode.HONOR, + mode_display_name="honor cert", + min_price=self.cost + ) self.course_mode.save() self.testing_course = CourseFactory.create( org='Edx', @@ -1504,6 +1578,13 @@ class ShoppingcartViewsClosedEnrollment(ModuleStoreTestCase): display_name='Testing Super Course', metadata={"invitation_only": False} ) + self.testing_course_mode = CourseMode( + course_id=self.testing_course.id, + mode_slug=CourseMode.HONOR, + mode_display_name="honor cert", + min_price=self.cost + ) + self.course_mode.save() self.percentage_discount = 20.0 self.coupon_code = 'asdsad' self.course_mode = CourseMode(course_id=self.testing_course.id, @@ -1570,8 +1651,16 @@ class ShoppingcartViewsClosedEnrollment(ModuleStoreTestCase): def test_to_check_that_cart_item_enrollment_is_closed_when_clicking_the_payment_button(self): self.login_user() - PaidCourseRegistration.add_to_order(self.cart, self.course_key) - PaidCourseRegistration.add_to_order(self.cart, self.testing_course.id) + PaidCourseRegistration.add_to_order( + self.cart, + self.course_key, + mode_slug=self.course_mode.mode_slug + ) + PaidCourseRegistration.add_to_order( + self.cart, + self.testing_course.id, + mode_slug=self.testing_course_mode.mode_slug + ) # update the testing_course enrollment dates self.testing_course.enrollment_start = self.tomorrow @@ -1593,8 +1682,8 @@ class ShoppingcartViewsClosedEnrollment(ModuleStoreTestCase): self.login_user() self.cart.order_type = 'business' self.cart.save() - PaidCourseRegistration.add_to_order(self.cart, self.course_key) - CourseRegCodeItem.add_to_order(self.cart, self.testing_course.id, 2) + PaidCourseRegistration.add_to_order(self.cart, self.course_key, mode_slug=self.course_mode.mode_slug) + CourseRegCodeItem.add_to_order(self.cart, self.testing_course.id, 2, mode_slug=self.course_mode.mode_slug) # update the testing_course enrollment dates self.testing_course.enrollment_start = self.tomorrow @@ -2039,7 +2128,7 @@ class CSVReportViewsTest(SharedModuleStoreTestCase): report_type = 'itemized_purchase_report' start_date = '1970-01-01' end_date = '2100-01-01' - PaidCourseRegistration.add_to_order(self.cart, self.course_key) + PaidCourseRegistration.add_to_order(self.cart, self.course_key, mode_slug=self.course_mode.mode_slug) self.cart.purchase() self.login_user() self.add_to_download_group(self.user) diff --git a/lms/djangoapps/shoppingcart/views.py b/lms/djangoapps/shoppingcart/views.py index 06ded6ebe6..7789f9247a 100644 --- a/lms/djangoapps/shoppingcart/views.py +++ b/lms/djangoapps/shoppingcart/views.py @@ -339,8 +339,11 @@ def register_code_redemption(request, registration_code): } return render_to_response(template_to_render, context) elif request.method == "POST": - reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code, - request, limiter) + reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( + registration_code, + request, + limiter + ) course = get_course_by_id(course_registration.course_id, depth=0) # Restrict the user from enrolling based on country access rules diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py index f67b71b3ab..8718bdcaaa 100644 --- a/lms/djangoapps/student_account/test/test_views.py +++ b/lms/djangoapps/student_account/test/test_views.py @@ -17,6 +17,7 @@ from django.test import TestCase from django.test.utils import override_settings from django.http import HttpRequest +from course_modes.models import CourseMode from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH from openedx.core.lib.js_utils import escape_json_dumps @@ -263,7 +264,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi params = [ ('course_id', 'edX/DemoX/Demo_Course'), ('enrollment_action', 'enroll'), - ('course_mode', 'honor'), + ('course_mode', CourseMode.DEFAULT_MODE_SLUG), ('email_opt_in', 'true'), ('next', '/custom/final/destination') ] @@ -294,7 +295,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi params = [ ('course_id', 'course-v1:Org+Course+Run'), ('enrollment_action', 'enroll'), - ('course_mode', 'honor'), + ('course_mode', CourseMode.DEFAULT_MODE_SLUG), ('email_opt_in', 'true'), ('next', '/custom/final/destination'), ] diff --git a/lms/djangoapps/verify_student/tests/test_models.py b/lms/djangoapps/verify_student/tests/test_models.py index 7517e553d5..07fa37548f 100644 --- a/lms/djangoapps/verify_student/tests/test_models.py +++ b/lms/djangoapps/verify_student/tests/test_models.py @@ -472,6 +472,7 @@ class TestPhotoVerification(ModuleStoreTestCase): @ddt.unpack @ddt.data( {'enrollment_mode': 'honor', 'status': None, 'output': 'N/A'}, + {'enrollment_mode': 'audit', 'status': None, 'output': 'N/A'}, {'enrollment_mode': 'verified', 'status': False, 'output': 'Not ID Verified'}, {'enrollment_mode': 'verified', 'status': True, 'output': 'ID Verified'}, ) diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py index 21e08adf0b..f98153e180 100644 --- a/lms/djangoapps/verify_student/tests/test_views.py +++ b/lms/djangoapps/verify_student/tests/test_views.py @@ -105,7 +105,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): @ddt.data("verified", "professional") def test_start_flow_not_verified(self, course_mode): course = self._create_course(course_mode) - self._enroll(course.id, "honor") + self._enroll(course.id) response = self._get_page('verify_student_start_flow', course.id) self._assert_displayed_mode(response, course_mode) self._assert_steps_displayed( @@ -123,8 +123,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): @ddt.data("no-id-professional") def test_start_flow_with_no_id_professional(self, course_mode): course = self._create_course(course_mode) - # by default enrollment is honor - self._enroll(course.id, "honor") + self._enroll(course.id) response = self._get_page('verify_student_start_flow', course.id) self._assert_displayed_mode(response, course_mode) self._assert_steps_displayed( @@ -164,7 +163,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): @ddt.unpack def test_start_flow_already_verified(self, course_mode, verification_status): course = self._create_course(course_mode) - self._enroll(course.id, "honor") + self._enroll(course.id) self._set_verification_status(verification_status) response = self._get_page('verify_student_start_flow', course.id) self._assert_displayed_mode(response, course_mode) @@ -323,7 +322,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ) def test_verify_now_not_paid(self, page_name): course = self._create_course("verified") - self._enroll(course.id, "honor") + self._enroll(course.id) response = self._get_page(page_name, course.id, expected_status_code=302) self._assert_redirects_to_upgrade(response, course.id) @@ -440,7 +439,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): @ddt.data("verified", "professional") def test_upgrade(self, course_mode): course = self._create_course(course_mode) - self._enroll(course.id, "honor") + self._enroll(course.id) response = self._get_page('verify_student_upgrade_and_verify', course.id) self._assert_displayed_mode(response, course_mode) @@ -459,7 +458,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): def test_upgrade_already_verified(self): course = self._create_course("verified") - self._enroll(course.id, "honor") + self._enroll(course.id) self._set_verification_status("submitted") response = self._get_page('verify_student_upgrade_and_verify', course.id) @@ -746,7 +745,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): return course - def _enroll(self, course_key, mode): + def _enroll(self, course_key, mode=CourseMode.DEFAULT_MODE_SLUG): """Enroll the user in a course. """ CourseEnrollmentFactory.create( user=self.user, @@ -923,8 +922,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): """Check the course information on the page. """ mode_display_name = u"Introduction à l'astrophysique" course = CourseFactory.create(display_name=mode_display_name) - for course_mode in ["honor", "verified"]: - min_price = (self.MIN_PRICE if course_mode != "honor" else 0) + for course_mode in [CourseMode.DEFAULT_MODE_SLUG, "verified"]: + min_price = (self.MIN_PRICE if course_mode != CourseMode.DEFAULT_MODE_SLUG else 0) CourseModeFactory( course_id=course.id, mode_slug=course_mode, @@ -932,7 +931,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): min_price=min_price ) - self._enroll(course.id, "honor") + self._enroll(course.id) response_dict = self._get_page_data(self._get_page('verify_student_start_flow', course.id)) self.assertEqual(response_dict['course_name'], mode_display_name) @@ -948,7 +947,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): # setting a nonempty sku on the course will a trigger calls to # the ecommerce api to get payment processors. course = self._create_course("verified", sku='nonempty-sku') - self._enroll(course.id, "honor") + self._enroll(course.id) # mock out the payment processors endpoint httpretty.register_uri( diff --git a/lms/static/js/spec/student_account/finish_auth_spec.js b/lms/static/js/spec/student_account/finish_auth_spec.js index f29291747b..2086526631 100644 --- a/lms/static/js/spec/student_account/finish_auth_spec.js +++ b/lms/static/js/spec/student_account/finish_auth_spec.js @@ -95,7 +95,7 @@ ); }); - it('sends the user to the payment flow when the course mode is not honor', function() { + it('sends the user to the payment flow for a paid course mode', function() { // Simulate providing enrollment query string params // AND specifying a course mode. setFakeQueryParams({ @@ -114,13 +114,13 @@ ); }); - it('sends the user to the student dashboard when the course mode is honor', function() { + it('sends the user to the student dashboard for an unpaid course mode', function() { // Simulate providing enrollment query string params // AND specifying a course mode. setFakeQueryParams({ '?enrollment_action': 'enroll', '?course_id': COURSE_KEY, - '?course_mode': 'honor' + '?course_mode': 'audit' }); ajaxSpyAndInitialize(this); diff --git a/lms/static/js/verify_student/views/make_payment_step_view.js b/lms/static/js/verify_student/views/make_payment_step_view.js index 42c0176e58..f1313d5d87 100644 --- a/lms/static/js/verify_student/views/make_payment_step_view.js +++ b/lms/static/js/verify_student/views/make_payment_step_view.js @@ -26,7 +26,7 @@ var edx = edx || {}; hasVisibleReqs: false, platformName: '', alreadyVerified: false, - courseModeSlug: 'honor', + courseModeSlug: 'audit', verificationGoodUntil: '' }; }, diff --git a/lms/templates/dashboard/_dashboard_certificate_information.html b/lms/templates/dashboard/_dashboard_certificate_information.html index 4d75650a81..bf6f716a5b 100644 --- a/lms/templates/dashboard/_dashboard_certificate_information.html +++ b/lms/templates/dashboard/_dashboard_certificate_information.html @@ -33,9 +33,13 @@ else: % elif cert_status['status'] in ('generating', 'ready', 'notpassing', 'restricted'):

${_("Your final grade:")} ${"{0:.0f}%".format(float(cert_status['grade'])*100)}. - % if cert_status['status'] == 'notpassing' and enrollment.mode != 'audit': - ${_("Grade required for a {cert_name_short}:").format(cert_name_short=cert_name_short)} - ${"{0:.0f}%".format(float(course_overview.lowest_passing_grade)*100)}. + % if cert_status['status'] == 'notpassing': + % if enrollment.mode != 'audit': + ${_("Grade required for a {cert_name_short}:").format(cert_name_short=cert_name_short)} + % else: + ${_("Grade required to pass this course:")} + % endif + ${"{0:.0f}%".format(float(course_overview.lowest_passing_grade)*100)}. % elif cert_status['status'] == 'restricted' and enrollment.mode == 'verified':

${_("Your verified {cert_name_long} is being held pending confirmation that the issuance of your {cert_name_short} is in compliance with strict U.S. embargoes on Iran, Cuba, Syria and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {email}. If you would like a refund on your {cert_name_long}, please contact our billing address {billing_email}").format(email='{email}.'.format(email=settings.CONTACT_EMAIL), billing_email='{email}'.format(email=settings.PAYMENT_SUPPORT_EMAIL), cert_name_short=cert_name_short, cert_name_long=cert_name_long)} diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index b17e089ca1..50e6b1ac1a 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -9,6 +9,7 @@ from django.core.urlresolvers import reverse from markupsafe import escape from courseware.courses import get_course_university_about_section from course_modes.models import CourseMode +from course_modes.helpers import enrollment_mode_display from student.helpers import ( VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, @@ -34,7 +35,13 @@ from student.helpers import (

  • % if settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES'): - <% course_verified_certs = CourseMode.enrollment_mode_display(enrollment.mode, verification_status.get('status')) %> + <% + course_verified_certs = enrollment_mode_display( + enrollment.mode, + verification_status.get('status'), + course_overview.id + ) + %> <% mode_class = course_verified_certs.get('display_mode', '') if mode_class != '': @@ -69,7 +76,7 @@ from student.helpers import ( ${_('{course_number} {course_name} Cover Image').format(course_number=course_overview.number, course_name=course_overview.display_name_with_default) | h} % endif - % if settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES'): + % if settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES') and course_verified_certs.get('display_mode') != 'audit': ${_("Enrolled as: ")} % if course_verified_certs.get('show_image'): diff --git a/lms/templates/verify_student/missed_deadline.html b/lms/templates/verify_student/missed_deadline.html index 05be906911..f342ec0372 100644 --- a/lms/templates/verify_student/missed_deadline.html +++ b/lms/templates/verify_student/missed_deadline.html @@ -22,7 +22,7 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView ${_(u"The verification deadline for {course_name} was {date}. Verification is no longer available.").format( course_name=course.display_name, date=deadline)} % elif deadline_name == PayAndVerifyView.UPGRADE_DEADLINE: - ${_(u"The deadline to upgrade to a verified certificate for this course has passed. You can still earn an honor code certificate.")} + ${_(u"The deadline to upgrade to a verified certificate for this course has passed.")} % endif

    diff --git a/openedx/core/djangoapps/credit/tests/test_services.py b/openedx/core/djangoapps/credit/tests/test_services.py index c250ce0074..00080406c2 100644 --- a/openedx/core/djangoapps/credit/tests/test_services.py +++ b/openedx/core/djangoapps/credit/tests/test_services.py @@ -25,6 +25,15 @@ class CreditServiceTests(ModuleStoreTestCase): self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True) self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar') + def enroll(self, course_id=None): + """ + Enroll the test user in the given course's honor mode, or the test + course if not provided. + """ + if course_id is None: + course_id = self.course.id + return CourseEnrollment.enroll(self.user, course_id, mode='honor') + def test_user_not_found(self): """ Makes sure that get_credit_state returns None if user_id cannot be found @@ -46,7 +55,7 @@ class CreditServiceTests(ModuleStoreTestCase): inactive """ - enrollment = CourseEnrollment.enroll(self.user, self.course.id) + enrollment = self.enroll() enrollment.is_active = False enrollment.save() @@ -58,7 +67,7 @@ class CreditServiceTests(ModuleStoreTestCase): Credit eligible """ - CourseEnrollment.enroll(self.user, self.course.id) + self.enroll() self.credit_course.enabled = False self.credit_course.save() @@ -86,7 +95,7 @@ class CreditServiceTests(ModuleStoreTestCase): self.assertTrue(self.service.is_credit_course(self.course.id)) - CourseEnrollment.enroll(self.user, self.course.id) + self.enroll() # set course requirements set_credit_requirements( @@ -127,7 +136,7 @@ class CreditServiceTests(ModuleStoreTestCase): """ self.assertTrue(self.service.is_credit_course(self.course.id)) - CourseEnrollment.enroll(self.user, self.course.id) + self.enroll() # set course requirements set_credit_requirements( @@ -216,7 +225,7 @@ class CreditServiceTests(ModuleStoreTestCase): self.assertFalse(self.service.is_credit_course(no_credit_course.id)) - CourseEnrollment.enroll(self.user, no_credit_course.id) + self.enroll(no_credit_course.id) # this should be a no-op self.service.remove_credit_requirement_status( @@ -237,7 +246,7 @@ class CreditServiceTests(ModuleStoreTestCase): Make sure we can get back the optional course name """ - CourseEnrollment.enroll(self.user, self.course.id) + self.enroll() # make sure it is not returned by default credit_state = self.service.get_credit_state(self.user.id, self.course.id) @@ -258,7 +267,7 @@ class CreditServiceTests(ModuleStoreTestCase): self.assertFalse(self.service.is_credit_course(no_credit_course.id)) - CourseEnrollment.enroll(self.user, no_credit_course.id) + self.enroll(no_credit_course.id) # this should be a no-op self.service.set_credit_requirement_status( @@ -308,7 +317,7 @@ class CreditServiceTests(ModuleStoreTestCase): Make sure we can pass a course_id (string) and get back correct results as well """ - CourseEnrollment.enroll(self.user, self.course.id) + self.enroll() # set course requirements set_credit_requirements(