From 7837607ca902a05cd0bd5f6a4f072c5d7263b15a Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Thu, 11 Dec 2014 12:48:35 -0500 Subject: [PATCH 01/12] only recusively delete modules if all their parents are marked for deletion (PLAT-333) update '_delete_item' method for draft only modules and update delete_orphans management command fix test_courseware which fails due to invalid future date --- .../management/commands/delete_orphans.py | 42 +++++++++ .../commands/tests/test_delete_orphans.py | 40 +++++++++ .../contentstore/tests/test_orphan.py | 85 +++++++++++-------- cms/djangoapps/contentstore/views/item.py | 23 +++-- .../xmodule/modulestore/mongo/draft.py | 25 ++++-- .../tests/test_mixed_modulestore.py | 2 +- 6 files changed, 170 insertions(+), 47 deletions(-) create mode 100644 cms/djangoapps/contentstore/management/commands/delete_orphans.py create mode 100644 cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py diff --git a/cms/djangoapps/contentstore/management/commands/delete_orphans.py b/cms/djangoapps/contentstore/management/commands/delete_orphans.py new file mode 100644 index 0000000000..202f8c2057 --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/delete_orphans.py @@ -0,0 +1,42 @@ +"""Script for deleting orphans""" +from django.core.management.base import BaseCommand, CommandError +from contentstore.views.item import _delete_orphans +from opaque_keys.edx.keys import CourseKey +from opaque_keys import InvalidKeyError +from xmodule.modulestore import ModuleStoreEnum + + +class Command(BaseCommand): + """Command for deleting orphans""" + help = ''' + Delete orphans from a MongoDB backed course. Takes two arguments: + : the course id of the course whose orphans you want to delete + |commit|: optional argument. If not provided, will not run task. + ''' + + def handle(self, *args, **options): + if len(args) not in {1, 2}: + raise CommandError("delete_orphans requires one or more arguments: |commit|") + + try: + course_key = CourseKey.from_string(args[0]) + except InvalidKeyError: + raise CommandError("Invalid course key.") + + commit = False + if len(args) == 2: + commit = args[1] == 'commit' + + if commit: + print 'Deleting orphans from the course:' + deleted_items = _delete_orphans( + course_key, ModuleStoreEnum.UserID.mgmt_command, commit + ) + print "Success! Deleted the following orphans from the course:" + print "\n".join(deleted_items) + else: + print 'Dry run. The following orphans would have been deleted from the course:' + deleted_items = _delete_orphans( + course_key, ModuleStoreEnum.UserID.mgmt_command, commit + ) + print "\n".join(deleted_items) diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py b/cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py new file mode 100644 index 0000000000..649c4e7fb0 --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py @@ -0,0 +1,40 @@ +"""Tests running the delete_orphan command""" + +from django.core.management import call_command +from contentstore.tests.test_orphan import TestOrphanBase + + +class TestDeleteOrphan(TestOrphanBase): + """ + Tests for running the delete_orphan management command. + Inherits from TestOrphan in order to use its setUp method. + """ + def setUp(self): + super(TestDeleteOrphan, self).setUp() + self.course_id = self.course.id.to_deprecated_string() + + def test_delete_orphans_no_commit(self): + """ + Tests that running the command without a 'commit' argument + results in no orphans being deleted + """ + call_command('delete_orphans', self.course_id) + self.assertTrue(self.store.has_item(self.course.id.make_usage_key('html', 'multi_parent_html'))) + self.assertTrue(self.store.has_item(self.course.id.make_usage_key('vertical', 'OrphanVert'))) + self.assertTrue(self.store.has_item(self.course.id.make_usage_key('chapter', 'OrphanChapter'))) + self.assertTrue(self.store.has_item(self.course.id.make_usage_key('html', 'OrphanHtml'))) + + def test_delete_orphans_commit(self): + """ + Tests that running the command WITH the 'commit' argument + results in the orphans being deleted + """ + call_command('delete_orphans', self.course_id, 'commit') + + # make sure this module wasn't deleted + self.assertTrue(self.store.has_item(self.course.id.make_usage_key('html', 'multi_parent_html'))) + + # and make sure that these were + self.assertFalse(self.store.has_item(self.course.id.make_usage_key('vertical', 'OrphanVert'))) + self.assertFalse(self.store.has_item(self.course.id.make_usage_key('chapter', 'OrphanChapter'))) + self.assertFalse(self.store.has_item(self.course.id.make_usage_key('html', 'OrphanHtml'))) diff --git a/cms/djangoapps/contentstore/tests/test_orphan.py b/cms/djangoapps/contentstore/tests/test_orphan.py index 1b0c2f3281..4dede3f71c 100644 --- a/cms/djangoapps/contentstore/tests/test_orphan.py +++ b/cms/djangoapps/contentstore/tests/test_orphan.py @@ -8,47 +8,60 @@ from xmodule.modulestore.django import modulestore from contentstore.utils import reverse_course_url -class TestOrphan(CourseTestCase): +class TestOrphanBase(CourseTestCase): + """ + Base class for Studio tests that require orphaned modules + """ + def setUp(self): + super(TestOrphanBase, self).setUp() + + # create chapters and add them to course tree + chapter1 = self.store.create_child(self.user.id, self.course.location, 'chapter', "Chapter1") + self.store.publish(chapter1.location, self.user.id) + + chapter2 = self.store.create_child(self.user.id, self.course.location, 'chapter', "Chapter2") + self.store.publish(chapter2.location, self.user.id) + + # orphan chapter + orphan_chapter = self.store.create_item(self.user.id, self.course.id, 'chapter', "OrphanChapter") + self.store.publish(orphan_chapter.location, self.user.id) + + # create vertical and add it as child to chapter1 + vertical1 = self.store.create_child(self.user.id, chapter1.location, 'vertical', "Vertical1") + self.store.publish(vertical1.location, self.user.id) + + # create orphan vertical + orphan_vertical = self.store.create_item(self.user.id, self.course.id, 'vertical', "OrphanVert") + self.store.publish(orphan_vertical.location, self.user.id) + + # create component and add it to vertical1 + html1 = self.store.create_child(self.user.id, vertical1.location, 'html', "Html1") + self.store.publish(html1.location, self.user.id) + + # create component and add it as a child to vertical1 and orphan_vertical + multi_parent_html = self.store.create_child(self.user.id, vertical1.location, 'html', "multi_parent_html") + self.store.publish(multi_parent_html.location, self.user.id) + + orphan_vertical.children.append(multi_parent_html.location) + self.store.update_item(orphan_vertical, self.user.id) + + # create an orphaned html module + orphan_html = self.store.create_item(self.user.id, self.course.id, 'html', "OrphanHtml") + self.store.publish(orphan_html.location, self.user.id) + + self.store.create_child(self.user.id, self.course.location, 'static_tab', "staticuno") + self.store.create_child(self.user.id, self.course.location, 'about', "overview") + self.store.create_child(self.user.id, self.course.location, 'course_info', "updates") + + +class TestOrphan(TestOrphanBase): """ Test finding orphans via view and django config """ def setUp(self): super(TestOrphan, self).setUp() - - runtime = self.course.runtime - - self._create_item('chapter', 'Chapter1', {}, {'display_name': 'Chapter 1'}, 'course', self.course.location.name, runtime) - self._create_item('chapter', 'Chapter2', {}, {'display_name': 'Chapter 2'}, 'course', self.course.location.name, runtime) - self._create_item('chapter', 'OrphanChapter', {}, {'display_name': 'Orphan Chapter'}, None, None, runtime) - self._create_item('vertical', 'Vert1', {}, {'display_name': 'Vertical 1'}, 'chapter', 'Chapter1', runtime) - self._create_item('vertical', 'OrphanVert', {}, {'display_name': 'Orphan Vertical'}, None, None, runtime) - self._create_item('html', 'Html1', "

Goodbye

", {'display_name': 'Parented Html'}, 'vertical', 'Vert1', runtime) - self._create_item('html', 'OrphanHtml', "

Hello

", {'display_name': 'Orphan html'}, None, None, runtime) - self._create_item('static_tab', 'staticuno', "

tab

", {'display_name': 'Tab uno'}, None, None, runtime) - self._create_item('about', 'overview', "

overview

", {}, None, None, runtime) - self._create_item('course_info', 'updates', "
  1. Sep 22

    test

", {}, None, None, runtime) - self.orphan_url = reverse_course_url('orphan_handler', self.course.id) - def _create_item(self, category, name, data, metadata, parent_category, parent_name, runtime): - location = self.course.location.replace(category=category, name=name) - store = modulestore() - store.create_item( - self.user.id, - location.course_key, - location.block_type, - location.block_id, - definition_data=data, - metadata=metadata, - runtime=runtime - ) - if parent_name: - # add child to parent in mongo - parent_location = self.course.location.replace(category=parent_category, name=parent_name) - parent = store.get_item(parent_location) - parent.children.append(location) - store.update_item(parent, self.user.id) - def test_mongo_orphan(self): """ Test that old mongo finds the orphans @@ -77,6 +90,10 @@ class TestOrphan(CourseTestCase): ) self.assertEqual(len(orphans), 0, "Orphans not deleted {}".format(orphans)) + # make sure that any children with one orphan parent and one non-orphan + # parent are not deleted + self.assertTrue(self.store.has_item(self.course.id.make_usage_key('html', "multi_parent_html"))) + def test_not_permitted(self): """ Test that auth restricts get and delete appropriately diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 28c9a5e480..2e6528ed0d 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -604,16 +604,27 @@ def orphan_handler(request, course_key_string): raise PermissionDenied() if request.method == 'DELETE': if request.user.is_staff: - store = modulestore() - items = store.get_orphans(course_usage_key) - for itemloc in items: - # need to delete all versions - store.delete_item(itemloc, request.user.id, revision=ModuleStoreEnum.RevisionOption.all) - return JsonResponse({'deleted': [unicode(item) for item in items]}) + deleted_items = _delete_orphans(course_usage_key, request.user.id, commit=True) + return JsonResponse({'deleted': deleted_items}) else: raise PermissionDenied() +def _delete_orphans(course_usage_key, user_id, commit=False): + """ + Helper function to delete orphans for a given course. + If `commit` is False, this function does not actually remove + the orphans. + """ + store = modulestore() + items = store.get_orphans(course_usage_key) + if commit: + for itemloc in items: + # need to delete all versions + store.delete_item(itemloc, user_id, revision=ModuleStoreEnum.RevisionOption.all) + return [unicode(item) for item in items] + + def _get_xblock(usage_key, user): """ Returns the xblock for the specified usage key. Note: if failing to find a key with a category diff --git a/common/lib/xmodule/xmodule/modulestore/mongo/draft.py b/common/lib/xmodule/xmodule/modulestore/mongo/draft.py index 4f2c1360df..76ce0a6db4 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo/draft.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo/draft.py @@ -541,7 +541,7 @@ class DraftModuleStore(MongoModuleStore): ) self._delete_subtree(location, as_functions) - def _delete_subtree(self, location, as_functions): + def _delete_subtree(self, location, as_functions, draft_only=False): """ Internal method for deleting all of the subtree whose revisions match the as_functions """ @@ -555,10 +555,23 @@ class DraftModuleStore(MongoModuleStore): next_tier = [] for child_loc in current_entry.get('definition', {}).get('children', []): child_loc = course_key.make_usage_key_from_deprecated_string(child_loc) - for rev_func in as_functions: - current_loc = rev_func(child_loc) - current_son = current_loc.to_deprecated_son() - next_tier.append(current_son) + + # single parent can have 2 versions: draft and published + # get draft parents only while deleting draft module + if draft_only: + revision = MongoRevisionKey.draft + else: + revision = ModuleStoreEnum.RevisionOption.all + + parents = self._get_raw_parent_locations(child_loc, revision) + # Don't delete modules if one of its parents shouldn't be deleted + # This should only be an issue for courses have ended up in + # a state where modules have multiple parents + if all(parent.to_deprecated_son() in to_be_deleted for parent in parents): + for rev_func in as_functions: + current_loc = rev_func(child_loc) + current_son = current_loc.to_deprecated_son() + next_tier.append(current_son) return next_tier @@ -738,7 +751,7 @@ class DraftModuleStore(MongoModuleStore): # If 2 versions versions exist, we can assume one is a published version. Go ahead and do the delete # of the draft version. if versions_found.count() > 1: - self._delete_subtree(root_location, [as_draft]) + self._delete_subtree(root_location, [as_draft], draft_only=True) elif versions_found.count() == 1: # Since this method cannot be called on something in DIRECT_ONLY_CATEGORIES and we call # delete_subtree as soon as we find an item with a draft version, if there is only 1 version diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py index 0bdef107b5..2b70cb7584 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py @@ -723,7 +723,7 @@ class TestMixedModuleStore(CourseComparisonTest): # Split: # queries: active_versions, draft and published structures, definition (unnecessary) # sends: update published (why?), draft, and active_versions - @ddt.data(('draft', 8, 2), ('split', 2, 2)) + @ddt.data(('draft', 9, 2), ('split', 2, 2)) @ddt.unpack def test_delete_private_vertical(self, default_ms, max_find, max_send): """ From e9e1d35060564fe7c4482bbb5bed533ca88c3efc Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Mon, 5 Jan 2015 11:50:13 -0500 Subject: [PATCH 02/12] ECOM-869 Update copy and hide supplementary content wrapper and requirement blocks if user is inactive --- .../sass/views/_decoupled-verification.scss | 27 ++++- .../verify_student/intro_step.underscore | 106 +++++++++--------- .../make_payment_step.underscore | 76 ++++++------- .../verify_student/pay_and_verify.html | 2 + .../payment_confirmation_step.underscore | 78 ++++++------- 5 files changed, 156 insertions(+), 133 deletions(-) diff --git a/lms/static/sass/views/_decoupled-verification.scss b/lms/static/sass/views/_decoupled-verification.scss index 5f6a4bf014..7cdef0bf63 100644 --- a/lms/static/sass/views/_decoupled-verification.scss +++ b/lms/static/sass/views/_decoupled-verification.scss @@ -23,6 +23,12 @@ } } + .placeholder-cam { + .copy { + font-weight: bold !important; + } + } + .requirements-container { .list-reqs { @@ -41,7 +47,7 @@ } &.account-not-activated { - width: 990px; + width: 300px; .req { height: 290px; @@ -75,6 +81,7 @@ display: inline; float: left; line-height: 45px; + color: black; } .wizard-steps { @@ -119,11 +126,23 @@ .expandable-area { margin-top: 5px; + padding-bottom: 20px; } } .help-tips { margin-left: 0 !important; + + .title { + font-size: 16px !important; + } + + .list-tips { + .tip { + font-size: 16px; + line-height: 1.5em; + } + } } .photo-tip { @@ -137,6 +156,12 @@ padding-left: 20px; } + .list-faq { + dd { + color: black; + } + } + .wrapper-task { .msg-retake { margin-top: 0; diff --git a/lms/templates/verify_student/intro_step.underscore b/lms/templates/verify_student/intro_step.underscore index b85e35792e..2d467deb84 100644 --- a/lms/templates/verify_student/intro_step.underscore +++ b/lms/templates/verify_student/intro_step.underscore @@ -9,7 +9,13 @@ <% } else { %>

<%- introTitle %>

- <% if ( introMsg ) { %> + <% if ( !isActive ) { %> +
+

+ <%- gettext( "You need to activate your account before you can enroll in courses. Check your inbox for an activation email. After you complete activation you can return and refresh this page." ) %> +

+
+ <% } else if ( introMsg ) { %>

<%- introMsg %>

<% } %> <% } %> @@ -17,69 +23,63 @@
    <% if ( requirements['account-activation-required'] ) { %> -
  • -

    <%- gettext( "Activate Your Account" ) %>

    -
    - -
    +
  • +

    <%- gettext( "Activate Your Account" ) %>

    +
    + +
    -
    -

    - <%- gettext( "Check your email" ) %> - <%- - gettext( "You need to activate your account before you can register for courses. Check your inbox for an activation email." ) - %> - -

    -
    -
  • - <% } %> +
    +

    + <%- gettext( "Check your email" ) %> +

    +
    + + <% } else { %> + <% if ( requirements['photo-id-required'] ) { %> +
  • +

    <%- gettext( "Photo ID" ) %>

    +
    + + +
    - <% if ( requirements['photo-id-required'] ) { %> -
  • -

    <%- gettext( "Photo ID" ) %>

    -
    - - -
    +
    +

    + <%- gettext( "A driver's license, passport, or government-issued ID with your name and picture" ) %> +

    +
    +
  • + <% } %> -
    -

    - <%- gettext( "A driver's license, passport, or government-issued ID with your name and picture" ) %> -

    -
    - - <% } %> + <% if ( requirements['webcam-required'] ) { %> +
  • +

    <%- gettext( "Webcam" ) %>

    +
    + +
    - <% if ( requirements['webcam-required'] ) { %> -
  • -

    <%- gettext( "Webcam" ) %>

    -
    - -
    - -
    -
  • +
    + + <% } %> <% } %>
- <% if ( nextStepTitle ) { %> + <% if ( nextStepTitle && isActive ) { %> diff --git a/lms/templates/verify_student/make_payment_step.underscore b/lms/templates/verify_student/make_payment_step.underscore index e2f6936cb0..389436b232 100644 --- a/lms/templates/verify_student/make_payment_step.underscore +++ b/lms/templates/verify_student/make_payment_step.underscore @@ -105,6 +105,8 @@ gettext( "You can pay now even if you don't have the following items available, but you will need to have these by %(date)s to qualify to earn a Verified Certificate." ), { date: verificationDeadline } ) %> + <% } else if ( !isActive ) { %> + <%- gettext( "You need to activate your account before you can enroll in courses. Check your inbox for an activation email. After you complete activation you can return and refresh this page." ) %> <% } else if ( !upgrade ) { %> <%- gettext( "You can pay now even if you don't have the following items available, but you will need to have these to qualify to earn a Verified Certificate." ) %> <% } %> @@ -116,66 +118,60 @@
    <% if ( requirements['account-activation-required'] ) { %> -
  • -

    <%- gettext( "Activate Your Account" ) %>

    -
    - -
    +
  • +

    <%- gettext( "Activate Your Account" ) %>

    +
    + +
    -
    -

    - <%- gettext( "Check your email" ) %> - <%- - gettext( "You need to activate your account before you can enroll in courses. Check your inbox for an activation email." ) - %> - -

    -
    -
  • - <% } %> +
    +

    + <%- gettext( "Check your email" ) %> +

    +
    + + <% } else {%> + <% if ( requirements['photo-id-required'] ) { %> +
  • +

    <%- gettext( "Government-issued Photo ID" ) %>

    +
    + + +
    - <% if ( requirements['photo-id-required'] ) { %> -
  • -

    <%- gettext( "Government-issued Photo ID" ) %>

    -
    - - -
    +
    +
  • + <% } %> -
    - - <% } %> + <% if ( requirements['webcam-required'] ) { %> +
  • +

    <%- gettext( "Webcam" ) %>

    +
    + +
    - <% if ( requirements['webcam-required'] ) { %> -
  • -

    <%- gettext( "Webcam" ) %>

    -
    - -
    - -
    -
  • +
    + + <% } %> <% } %>
<% } %> + <% if ( isActive ) { %> + <% } %>
diff --git a/lms/templates/verify_student/pay_and_verify.html b/lms/templates/verify_student/pay_and_verify.html index a807722137..d5aef5cf8f 100644 --- a/lms/templates/verify_student/pay_and_verify.html +++ b/lms/templates/verify_student/pay_and_verify.html @@ -76,6 +76,7 @@ from verify_student.views import PayAndVerifyView data-is-active='${is_active}' > + % if is_active: ## Support
+ % endif diff --git a/lms/templates/verify_student/payment_confirmation_step.underscore b/lms/templates/verify_student/payment_confirmation_step.underscore index 615f3d57c2..45d98e5eef 100644 --- a/lms/templates/verify_student/payment_confirmation_step.underscore +++ b/lms/templates/verify_student/payment_confirmation_step.underscore @@ -83,49 +83,49 @@
    <% if ( requirements['account-activation-required'] ) { %> -
  • -

    <%- gettext( "Activate Your Account" ) %>

    -
    - -
    +
  • +

    <%- gettext( "Activate Your Account" ) %>

    +
    + +
    -
    -

    - <%- gettext( "Check your email." ) %> - <%- - gettext( "You need to activate your account before you can enroll in courses. Check your inbox for an activation email." ) - %> - -

    -
    -
  • - <% } %> +
    +

    + <%- gettext( "Check your email" ) %> + <%- + gettext( "You need to activate your account before you can enroll in courses. Check your inbox for an activation email." ) + %> + +

    +
    + + <% } else { %> + <% if ( requirements['photo-id-required'] ) { %> +
  • +

    <%- gettext( "Photo ID" ) %>

    +
    + + +
    - <% if ( requirements['photo-id-required'] ) { %> -
  • -

    <%- gettext( "Photo ID" ) %>

    -
    - - -
    +
    +

    + <%- gettext( "A driver's license, passport, or government-issued ID with your name and picture." ) %> +

    +
    +
  • + <% } %> -
    -

    - <%- gettext( "A driver's license, passport, or government-issued ID with your name and picture." ) %> -

    -
    - - <% } %> + <% if ( requirements['webcam-required'] ) { %> +
  • +

    <%- gettext( "Webcam" ) %>

    +
    + +
    - <% if ( requirements['webcam-required'] ) { %> -
  • -

    <%- gettext( "Webcam" ) %>

    -
    - -
    - -
    -
  • +
    + + <% } %> <% } %>
From fb013272b8bdc1e68fc3abe08dd3f7d5bc80f77e Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Mon, 5 Jan 2015 15:34:32 -0500 Subject: [PATCH 03/12] Wrap full name placeholder in quotes to deal with spaces Also fix failing JS test which was looking for a removed button. --- .../js/spec/verify_student/make_payment_step_view_spec.js | 8 +------- .../js/verify_student/views/make_payment_step_view.js | 5 ----- .../verify_student/review_photos_step.underscore | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/lms/static/js/spec/verify_student/make_payment_step_view_spec.js b/lms/static/js/spec/verify_student/make_payment_step_view_spec.js index 01abb15433..eb0fa26ecb 100644 --- a/lms/static/js/spec/verify_student/make_payment_step_view_spec.js +++ b/lms/static/js/spec/verify_student/make_payment_step_view_spec.js @@ -86,16 +86,10 @@ define([ }; var expectPaymentDisabledBecauseInactive = function() { - var payButton = $( '#pay_button'), - activateButton = $( '#activate_button' ); + var payButton = $( '#pay_button' ); // Payment button should be hidden expect( payButton.length ).toEqual(0); - - // Activate button should be displayed and disabled - expect( activateButton.length ).toEqual(1); - expect( activateButton.hasClass( 'is-disabled' ) ).toBe( true ); - expect( activateButton.prop( 'disabled' ) ).toBe( true ); }; var goToPayment = function( requests, kwargs ) { 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 641f3e15d4..225d2e6e93 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 @@ -38,11 +38,6 @@ var edx = edx || {}; // Set the payment button to disabled by default this.setPaymentEnabled( false ); - // The activate button is always disabled - $( '#activate_button' ) - .addClass( 'is-disabled' ) - .prop( 'disabled', true ); - // Update the contribution amount with the amount the user // selected in a previous screen. if ( templateContext.contributionAmount ) { diff --git a/lms/templates/verify_student/review_photos_step.underscore b/lms/templates/verify_student/review_photos_step.underscore index 351c5c0a0b..087de85b4d 100644 --- a/lms/templates/verify_student/review_photos_step.underscore +++ b/lms/templates/verify_student/review_photos_step.underscore @@ -35,7 +35,7 @@

<%- gettext( "You should change the name on your account to match your ID." ) %>

- > +
From 96d782f2d0a1d7d5a48a52c3fce20095ed5c1f38 Mon Sep 17 00:00:00 2001 From: Muhammad Shoaib Date: Tue, 6 Jan 2015 13:59:26 +0500 Subject: [PATCH 04/12] fix the redemption page css. div selector is not closed properly in the redemption page html --- lms/templates/shoppingcart/registration_code_redemption.html | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/templates/shoppingcart/registration_code_redemption.html b/lms/templates/shoppingcart/registration_code_redemption.html index d8ac9fc56e..f21f0b1b2f 100644 --- a/lms/templates/shoppingcart/registration_code_redemption.html +++ b/lms/templates/shoppingcart/registration_code_redemption.html @@ -22,6 +22,7 @@ from courseware.courses import course_image_url, get_course_about_section course_number=course.display_number_with_default, course_title=get_course_about_section(course, 'title'), )}" /> +
${_("Confirm your enrollment for:")} ${_("course dates")} From 6f593222c4870eb9e05b644a3caa6f9c69364076 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Tue, 6 Jan 2015 07:58:25 -0500 Subject: [PATCH 05/12] Fix ECOM-872 --- lms/djangoapps/shoppingcart/tests/test_views.py | 7 +++++++ lms/djangoapps/shoppingcart/views.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py index d7d7e935b8..287a858b44 100644 --- a/lms/djangoapps/shoppingcart/tests/test_views.py +++ b/lms/djangoapps/shoppingcart/tests/test_views.py @@ -1217,7 +1217,14 @@ class ReceiptRedirectTest(UrlResetMixin, ModuleStoreTestCase): @patch.dict(settings.FEATURES, {'SEPARATE_VERIFICATION_FROM_PAYMENT': True}) def test_show_receipt_redirect_to_verify_student(self): + # Create other carts first + # This ensures that the order ID and order item IDs do not match + Order.get_cart_for_user(self.user).start_purchase() + Order.get_cart_for_user(self.user).start_purchase() + Order.get_cart_for_user(self.user).start_purchase() + # Purchase a verified certificate + self.cart = Order.get_cart_for_user(self.user) CertificateItem.add_to_order( self.cart, self.course_key, diff --git a/lms/djangoapps/shoppingcart/views.py b/lms/djangoapps/shoppingcart/views.py index 1da5c24bfc..f2a54457f5 100644 --- a/lms/djangoapps/shoppingcart/views.py +++ b/lms/djangoapps/shoppingcart/views.py @@ -818,7 +818,7 @@ def _show_receipt_html(request, order): # Add a query string param for the order ID # This allows the view to query for the receipt information later. url += '?payment-order-num={order_num}'.format( - order_num=order_items[0].id + order_num=order_items[0].order.id ) return HttpResponseRedirect(url) From fe691b051b2597f4648f3f6c46285d0e348dcfda Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Tue, 6 Jan 2015 11:31:12 -0500 Subject: [PATCH 06/12] Apply most important copy changes from docs --- .../js/spec/verify_student/webcam_photo_view_spec.js | 2 +- .../verify_student/views/make_payment_step_view.js | 2 +- .../verify_student/views/review_photos_step_view.js | 2 +- lms/static/js/verify_student/views/step_view.js | 2 +- .../js/verify_student/views/webcam_photo_view.js | 6 +++--- .../enrollment_confirmation_step.underscore | 2 +- .../verify_student/face_photo_step.underscore | 12 ++++++------ .../verify_student/id_photo_step.underscore | 10 +++++----- lms/templates/verify_student/intro_step.underscore | 4 ++-- .../verify_student/make_payment_step.underscore | 8 ++++---- .../payment_confirmation_step.underscore | 12 ++++++------ .../verify_student/review_photos_step.underscore | 10 +++++----- lms/templates/verify_student/webcam_photo.underscore | 4 ++-- 13 files changed, 38 insertions(+), 38 deletions(-) diff --git a/lms/static/js/spec/verify_student/webcam_photo_view_spec.js b/lms/static/js/spec/verify_student/webcam_photo_view_spec.js index 5f4d4a99f5..9a4fc13c71 100644 --- a/lms/static/js/spec/verify_student/webcam_photo_view_spec.js +++ b/lms/static/js/spec/verify_student/webcam_photo_view_spec.js @@ -158,7 +158,7 @@ define([ view = createView( backends ); // Expect an error - expect( view.errorModel.get( 'errorTitle' ) ).toEqual( 'No Flash Detected' ); + expect( view.errorModel.get( 'errorTitle' ) ).toEqual( 'Flash Not Detected' ); expect( view.errorModel.get( 'errorMsg' ) ).toContain( 'Get Flash' ); expect( view.errorModel.get( 'shown' ) ).toBe( true ); 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 225d2e6e93..a0ffd1d79b 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 @@ -134,7 +134,7 @@ var edx = edx || {}; }, handleCreateOrderError: function( xhr ) { - var errorMsg = gettext( 'An unexpected error occurred. Please try again.' ); + var errorMsg = gettext( 'An error has occurred. Please try again.' ); if ( xhr.status === 400 ) { errorMsg = xhr.responseText; diff --git a/lms/static/js/verify_student/views/review_photos_step_view.js b/lms/static/js/verify_student/views/review_photos_step_view.js index fbe5067b99..ed76ea27d0 100644 --- a/lms/static/js/verify_student/views/review_photos_step_view.js +++ b/lms/static/js/verify_student/views/review_photos_step_view.js @@ -66,7 +66,7 @@ var edx = edx || {}; }, handleSubmissionError: function( xhr ) { - var errorMsg = gettext( 'An unexpected error occurred. Please try again later.' ); + var errorMsg = gettext( 'An error has occurred. Please try again later.' ); // Re-enable the submit button to allow the user to retry this.setSubmitButtonEnabled( true ); diff --git a/lms/static/js/verify_student/views/step_view.js b/lms/static/js/verify_student/views/step_view.js index 794726a672..96ad8c2d4f 100644 --- a/lms/static/js/verify_student/views/step_view.js +++ b/lms/static/js/verify_student/views/step_view.js @@ -46,7 +46,7 @@ handleError: function( errorTitle, errorMsg ) { this.errorModel.set({ errorTitle: errorTitle || gettext( "Error" ), - errorMsg: errorMsg || gettext( "An unexpected error occurred. Please reload the page to try again." ), + errorMsg: errorMsg || gettext( "An error has occurred. Please try reloading the page." ), shown: true }); }, diff --git a/lms/static/js/verify_student/views/webcam_photo_view.js b/lms/static/js/verify_student/views/webcam_photo_view.js index 13e57dc44d..954729678f 100644 --- a/lms/static/js/verify_student/views/webcam_photo_view.js +++ b/lms/static/js/verify_student/views/webcam_photo_view.js @@ -85,8 +85,8 @@ handleVideoFailure: function() { this.trigger( 'error', - gettext( 'Video capture error' ), - gettext( 'Please check that your webcam is connected and you have allowed access to your webcam.' ) + gettext( 'Video Capture Error' ), + gettext( 'Please verify that your webcam is connected and that you have allowed your browser to access it.' ) ); } }, @@ -211,7 +211,7 @@ if ( !this.backend ) { this.handleError( - gettext( "No Flash Detected" ), + gettext( "Flash Not Detected" ), gettext( "You don't seem to have Flash installed." ) + " " + _.sprintf( gettext( "%(a_start)s Get Flash %(a_end)s to continue your enrollment." ), diff --git a/lms/templates/verify_student/enrollment_confirmation_step.underscore b/lms/templates/verify_student/enrollment_confirmation_step.underscore index b0d2de9d96..55d26ed69a 100644 --- a/lms/templates/verify_student/enrollment_confirmation_step.underscore +++ b/lms/templates/verify_student/enrollment_confirmation_step.underscore @@ -47,7 +47,7 @@
diff --git a/lms/templates/verify_student/face_photo_step.underscore b/lms/templates/verify_student/face_photo_step.underscore index 4d3a626609..57d9950a1b 100644 --- a/lms/templates/verify_student/face_photo_step.underscore +++ b/lms/templates/verify_student/face_photo_step.underscore @@ -2,7 +2,7 @@

<%- gettext( "Take Your Photo" ) %>

-

<%- gettext( "Use your webcam to take a picture of your face so we can match it with the picture on your ID." ) %>

+

<%- gettext( "Use your webcam to take a photo of your face. We will match this photo with the photo on your ID." ) %>

@@ -17,7 +17,7 @@
  • <%- gettext( "Make sure your face is well-lit" ) %>
  • <%- gettext( "Be sure your entire face is inside the frame" ) %>
  • - <%= _.sprintf( gettext( "Once in position, use the camera button %(icon)s to capture your picture" ), { icon: '()' } ) %> + <%= _.sprintf( gettext( "Once in position, use the camera button %(icon)s to capture your photo" ), { icon: '()' } ) %>
  • <%- gettext( "Can we match the photo you took with the one on your ID?" ) %>
  • <%- gettext( "Use the retake photo button if you are not pleased with your photo" ) %>
  • @@ -26,17 +26,17 @@
    -

    <%- gettext( "Common Questions" ) %>

    +

    <%- gettext( "Frequently Asked Questions" ) %>

    <%- _.sprintf( gettext( "Why does %(platformName)s need my photo?" ), { platformName: platformName } ) %>
    -
    <%- gettext( "As part of the verification process, we need your photo to confirm your identity." ) %>
    +
    <%- gettext( "As part of the verification process, you take a photo of both your face and a government-issued photo ID. Our authorization service confirms your identity by comparing the photo you take with the photo on your ID." ) %>
    - <%- _.sprintf( gettext( "What does %(platformName)s do with this picture?" ), { platformName: platformName } ) %> + <%- _.sprintf( gettext( "What does %(platformName)s do with this photo?" ), { platformName: platformName } ) %>
    -
    <%- gettext( "We encrypt it and send it to our secure authorization service for review. We use the highest levels of security and do not save the photo or information anywhere once the match has been completed." ) %>
    +
    <%- _.sprintf( gettext( "We use the highest levels of security available to encrypt your photo and send it to our authorization service for review. Your photo and information are not saved or visible anywhere on %(platformName)s after the verification process is complete." ), { platformName: platformName } ) %>
    diff --git a/lms/templates/verify_student/id_photo_step.underscore b/lms/templates/verify_student/id_photo_step.underscore index 22ef73bff6..265bac5abf 100644 --- a/lms/templates/verify_student/id_photo_step.underscore +++ b/lms/templates/verify_student/id_photo_step.underscore @@ -2,7 +2,7 @@

    <%- gettext( "Take a Photo of Your ID" ) %>

    -

    <%- gettext("Use your webcam to take a picture of your ID so we can match it with your photo and the name on your account.") %>

    +

    <%- gettext("Use your webcam to take a photo of your ID. We will match this photo with the photo of your face and the name on your account.") %>

    @@ -27,19 +27,19 @@
    -

    <%- gettext( "Common Questions" ) %>

    +

    <%- gettext( "Frequently Asked Questions" ) %>

    <%- _.sprintf( gettext( "Why does %(platformName)s need my photo?" ), { platformName: platformName } ) %>
    -
    <%- gettext( "We need to match your ID with your photo and name to confirm your identity." ) %>
    +
    <%- gettext( "As part of the verification process, you take a photo of both your face and a government-issued photo ID. Our authorization service confirms your identity by comparing the photo you take with the photo on your ID." ) %>
    - <%- _.sprintf( gettext( "What does %(platformName)s do with this picture?" ), { platformName: platformName } ) %> + <%- _.sprintf( gettext( "What does %(platformName)s do with this photo?" ), { platformName: platformName } ) %>
    -
    <%- gettext( "We encrypt it and send it to our secure authorization service for review. We use the highest levels of security and do not save the photo or information anywhere once the match has been completed." ) %>
    +
    <%- _.sprintf( gettext( "We use the highest levels of security available to encrypt your photo and send it to our authorization service for review. Your photo and information are not saved or visible anywhere on %(platformName)s after the verification process is complete." ), { platformName: platformName } ) %>
    diff --git a/lms/templates/verify_student/intro_step.underscore b/lms/templates/verify_student/intro_step.underscore index 2d467deb84..fc235907e6 100644 --- a/lms/templates/verify_student/intro_step.underscore +++ b/lms/templates/verify_student/intro_step.underscore @@ -31,7 +31,7 @@

    - <%- gettext( "Check your email" ) %> + <%- gettext( "Check Your Email" ) %>

    @@ -46,7 +46,7 @@

    - <%- gettext( "A driver's license, passport, or government-issued ID with your name and picture" ) %> + <%- gettext( "A driver's license, passport, or other government-issued ID with your name and photo" ) %>

    diff --git a/lms/templates/verify_student/make_payment_step.underscore b/lms/templates/verify_student/make_payment_step.underscore index 389436b232..37166b729f 100644 --- a/lms/templates/verify_student/make_payment_step.underscore +++ b/lms/templates/verify_student/make_payment_step.underscore @@ -15,7 +15,7 @@ ) %>
    - <%- gettext( "We've already verified your identity through the photos of you and your ID you provided earlier. You can now pay and complete registration." ) %> + <%- gettext( "We have successfully verified your identity. You can now enter your payment information and complete your enrollment." ) %>
    <% } %> @@ -79,7 +79,7 @@ <% } else {%>
  • -

    <%- gettext( "Your Course Total" ) %>

    +

    <%- gettext( "Your Total Contribution" ) %>

    <%- gettext( "To complete your enrollment, you will need to pay:" ) %>

    @@ -126,14 +126,14 @@

    - <%- gettext( "Check your email" ) %> + <%- gettext( "Check Your Email" ) %>

  • <% } else {%> <% if ( requirements['photo-id-required'] ) { %>
  • -

    <%- gettext( "Government-issued Photo ID" ) %>

    +

    <%- gettext( "Government-Issued Photo ID" ) %>

    diff --git a/lms/templates/verify_student/payment_confirmation_step.underscore b/lms/templates/verify_student/payment_confirmation_step.underscore index 45d98e5eef..780b715334 100644 --- a/lms/templates/verify_student/payment_confirmation_step.underscore +++ b/lms/templates/verify_student/payment_confirmation_step.underscore @@ -1,7 +1,7 @@

    - <%= _.sprintf( gettext( "Thank you! We have received your payment for %(courseName)s" ), { courseName: '' + courseName + '' } ) %> + <%= _.sprintf( gettext( "Thank you! We have received your payment for %(courseName)s." ), { courseName: '' + courseName + '' } ) %>

    <% if ( receipt ) { %> @@ -55,14 +55,14 @@

    <%- gettext( "Please Note" ) %>:

    -

    <%- gettext( "Items with strikethough have been refunded." ) %>

    +

    <%- gettext( "Crossed out items have been refunded." ) %>

    <% } %>
    -

    <%- gettext( "Billed To" ) %>: +

    <%- gettext( "Billed to" ) %>: <%- receipt.billedTo.firstName %> <%- receipt.billedTo.lastName %> (<%- receipt.billedTo.city %>, @@ -74,7 +74,7 @@

  • <% } else { %> -

    <%- gettext( "No receipt available." ) %>

    +

    <%- gettext( "No receipt available" ) %>

    <% } %> <% if ( nextStepTitle ) { %> @@ -110,7 +110,7 @@

    - <%- gettext( "A driver's license, passport, or government-issued ID with your name and picture." ) %> + <%- gettext( "A driver's license, passport, or government-issued ID with your name and photo." ) %>

    @@ -141,7 +141,7 @@ ) %> - diff --git a/lms/templates/verify_student/review_photos_step.underscore b/lms/templates/verify_student/review_photos_step.underscore index 087de85b4d..f2e24d4651 100644 --- a/lms/templates/verify_student/review_photos_step.underscore +++ b/lms/templates/verify_student/review_photos_step.underscore @@ -1,8 +1,8 @@
    -

    <%- gettext( "Review your photos" ) %>

    +

    <%- gettext( "Review Your Photos" ) %>

    -

    <%- gettext( "Make sure we can verify your identity from the photos below." ) %>

    +

    <%- gettext( "Make sure we can verify your identity with the photos and information you have provided." ) %>

    @@ -30,11 +30,11 @@
  • <%- _.sprintf( gettext( "Does the name on your ID match your account name: %(fullName)s?" ), { fullName: fullName } ) %> @@ -48,7 +48,7 @@

    <%- gettext( "Photos don't meet the requirements?" ) %> - <%- gettext( "Retake your photos" ) %> + <%- gettext( "Retake Your Photos" ) %>

  • diff --git a/lms/templates/verify_student/webcam_photo.underscore b/lms/templates/verify_student/webcam_photo.underscore index cdaaffdbc0..47fa3bebdb 100644 --- a/lms/templates/verify_student/webcam_photo.underscore +++ b/lms/templates/verify_student/webcam_photo.underscore @@ -9,11 +9,11 @@ From 1d928379a1b26704e1b5b3649cafbee316295e1b Mon Sep 17 00:00:00 2001 From: AlasdairSwan Date: Tue, 6 Jan 2015 11:16:59 -0500 Subject: [PATCH 07/12] Updated history.push to include any query params --- lms/static/js/student_account/views/AccessView.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lms/static/js/student_account/views/AccessView.js b/lms/static/js/student_account/views/AccessView.js index 4796834b3a..71385a59cb 100644 --- a/lms/static/js/student_account/views/AccessView.js +++ b/lms/static/js/student_account/views/AccessView.js @@ -172,7 +172,8 @@ var edx = edx || {}; toggleForm: function( e ) { var type = $(e.currentTarget).data('type'), $form = $('#' + type + '-form'), - $anchor = $('#' + type + '-anchor'); + $anchor = $('#' + type + '-anchor'), + queryParams = '?' + url('?'); e.preventDefault(); @@ -190,7 +191,7 @@ var edx = edx || {}; this.element.scrollTop( $anchor ); // Update url without reloading page - History.pushState( null, document.title, '/account/' + type + '/' ); + History.pushState( null, document.title, '/account/' + type + queryParams ); analytics.page( 'login_and_registration', type ); }, From a8a8cfd534883ae94b9d8a14e199a9b63a430374 Mon Sep 17 00:00:00 2001 From: AlasdairSwan Date: Tue, 6 Jan 2015 11:48:05 -0500 Subject: [PATCH 08/12] Updated after Will's comments --- lms/static/js/student_account/views/AccessView.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lms/static/js/student_account/views/AccessView.js b/lms/static/js/student_account/views/AccessView.js index 71385a59cb..7bcc13a4c5 100644 --- a/lms/static/js/student_account/views/AccessView.js +++ b/lms/static/js/student_account/views/AccessView.js @@ -173,7 +173,8 @@ var edx = edx || {}; var type = $(e.currentTarget).data('type'), $form = $('#' + type + '-form'), $anchor = $('#' + type + '-anchor'), - queryParams = '?' + url('?'); + queryParams = url('?'), + queryStr = queryParams.length > 0 ? '?' + queryParams : ''; e.preventDefault(); @@ -191,7 +192,7 @@ var edx = edx || {}; this.element.scrollTop( $anchor ); // Update url without reloading page - History.pushState( null, document.title, '/account/' + type + queryParams ); + History.pushState( null, document.title, '/account/' + type + '/' + queryStr ); analytics.page( 'login_and_registration', type ); }, From 443a552d5f20ca4a631c0c7f7e0fce07a4e670da Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Tue, 6 Jan 2015 19:24:39 -0500 Subject: [PATCH 09/12] remove unused variable --- lms/djangoapps/shoppingcart/tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py index 287a858b44..e85437e09e 100644 --- a/lms/djangoapps/shoppingcart/tests/test_views.py +++ b/lms/djangoapps/shoppingcart/tests/test_views.py @@ -910,7 +910,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): # Two courses in user shopping cart self.login_user() - item1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key) + PaidCourseRegistration.add_to_order(self.cart, self.course_key) item2 = PaidCourseRegistration.add_to_order(self.cart, self.testing_course.id) self.assertEquals(self.cart.orderitem_set.count(), 2) From 1b7d5f347d7a6636a53e27f8e2d9aaeb0b5f317a Mon Sep 17 00:00:00 2001 From: Waqas Khalid Date: Wed, 7 Jan 2015 17:20:10 +0500 Subject: [PATCH 10/12] Test pagination reposition test is failing Test pagination reposition test is failing on release and master. It was flaky test and this problem was caused by the javascript animation. Issue was fixed by adding the promise that we check the value of window top after the animation is completed. --- common/test/acceptance/pages/lms/discussion.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/common/test/acceptance/pages/lms/discussion.py b/common/test/acceptance/pages/lms/discussion.py index b26c1e066a..e923334ddb 100644 --- a/common/test/acceptance/pages/lms/discussion.py +++ b/common/test/acceptance/pages/lms/discussion.py @@ -406,12 +406,24 @@ class DiscussionUserProfilePage(CoursePage): def click_prev_page(self): self._click_pager_with_text(self.TEXT_PREV, self.get_current_page() - 1) + EmptyPromise( + self.is_window_on_top, + "Window is on top" + ).fulfill() def click_next_page(self): self._click_pager_with_text(self.TEXT_NEXT, self.get_current_page() + 1) + EmptyPromise( + self.is_window_on_top, + "Window is on top" + ).fulfill() def click_on_page(self, page_number): self._click_pager_with_text(unicode(page_number), page_number) + EmptyPromise( + self.is_window_on_top, + "Window is on top" + ).fulfill() class DiscussionTabHomePage(CoursePage, DiscussionPageMixin): From 36e879a4f08b68656fb97c18a493fda8b10d3534 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 7 Jan 2015 11:21:27 -0500 Subject: [PATCH 11/12] Need to add new XBlocks to the ADVANCED_COMPONENTS list --- cms/envs/common.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cms/envs/common.py b/cms/envs/common.py index 6b044d388d..be11e8ea89 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -769,10 +769,13 @@ ADVANCED_COMPONENT_TYPES = [ 'audio', # Embed an audio file. See https://github.com/pmitros/AudioXBlock 'recommender', # Crowdsourced recommender. Prototype by dli&pmitros. Intended for roll-out in one place in one course. 'profile', # Prototype user profile XBlock. Used to test XBlock parameter passing. See https://github.com/pmitros/ProfileXBlock + 'split_test', 'combinedopenended', 'peergrading', 'notes', + 'schoolyourself_review', + 'schoolyourself_lesson', ] # Adding components in this list will disable the creation of new problem for those From bea5a3ce64d61fc492d19590fe955b5e384a4a7e Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 7 Jan 2015 12:06:36 -0500 Subject: [PATCH 12/12] Fix for ECOM-876 --- lms/static/js/verify_student/pay_and_verify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/static/js/verify_student/pay_and_verify.js b/lms/static/js/verify_student/pay_and_verify.js index f5fe75c7d7..803596fa11 100644 --- a/lms/static/js/verify_student/pay_and_verify.js +++ b/lms/static/js/verify_student/pay_and_verify.js @@ -55,7 +55,7 @@ var edx = edx || {}; minPrice: el.data('course-mode-min-price'), contributionAmount: el.data('contribution-amount'), suggestedPrices: _.filter( - (el.data('course-mode-suggested-prices') || "").split(","), + (el.data('course-mode-suggested-prices').toString()).split(","), function( price ) { return Boolean( price ); } ), currency: el.data('course-mode-currency'),