diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_fix_not_found.py b/cms/djangoapps/contentstore/management/commands/tests/test_fix_not_found.py new file mode 100644 index 0000000000..bd519a4167 --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/tests/test_fix_not_found.py @@ -0,0 +1,47 @@ +""" +Tests for the fix_not_found management command +""" + +from django.core.management import call_command +from xmodule.modulestore import ModuleStoreEnum +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory + + +class TestFixNotFound(ModuleStoreTestCase): + """ + Tests for the fix_not_found management command + """ + def test_fix_not_found_non_split(self): + """ + The management command doesn't work on non split courses + """ + course = CourseFactory(default_store=ModuleStoreEnum.Type.mongo) + with self.assertRaises(SystemExit): + call_command("fix_not_found", unicode(course.id)) + + def test_fix_not_found(self): + course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) + ItemFactory.create(category='chapter', parent_location=course.location) + + # get course again in order to update its children list + course = self.store.get_course(course.id) + + # create a dangling usage key that we'll add to the course's children list + dangling_pointer = course.id.make_usage_key('chapter', 'DanglingPointer') + + course.children.append(dangling_pointer) + self.store.update_item(course, self.user.id) + + # the course block should now point to two children, one of which + # doesn't actually exist + self.assertEqual(len(course.children), 2) + self.assertIn(dangling_pointer, course.children) + + call_command("fix_not_found", unicode(course.id)) + + # make sure the dangling pointer was removed from + # the course block's children + course = self.store.get_course(course.id) + self.assertEqual(len(course.children), 1) + self.assertNotIn(dangling_pointer, course.children) diff --git a/cms/static/js/certificates/models/signatory.js b/cms/static/js/certificates/models/signatory.js index ba478d5ece..416607d222 100644 --- a/cms/static/js/certificates/models/signatory.js +++ b/cms/static/js/certificates/models/signatory.js @@ -46,8 +46,8 @@ function(_, str, Backbone, BackboneRelational, gettext) { 'title': gettext('Signatory title should span over maximum of 2 lines.') }, errors); } - else if ((lines.length > 1 && (lines[0].length > 40 || lines[1].length > 40)) || - (lines.length === 1 && title.length > 40)) { + else if ((lines.length > 1 && (lines[0].length > 53 && lines[1].length > 53)) || + (lines.length === 1 && title.length > 106)) { errors = _.extend({ 'title': gettext('Signatory title should have maximum of 40 characters per line.') }, errors); diff --git a/cms/static/js/certificates/spec/views/certificate_details_spec.js b/cms/static/js/certificates/spec/views/certificate_details_spec.js index 75c3f84d51..b4044182fa 100644 --- a/cms/static/js/certificates/spec/views/certificate_details_spec.js +++ b/cms/static/js/certificates/spec/views/certificate_details_spec.js @@ -246,7 +246,7 @@ function(_, Course, CertificatesCollection, CertificateModel, CertificateDetails }); setValuesToInputs(this.view, { - inputSignatoryTitle: 'New Signatory Test Title longer than 40 characters in length' + inputSignatoryTitle: 'This is a certificate signatory title that has waaaaaaay more than 106 characters, in order to cause an exception.' }); setValuesToInputs(this.view, { diff --git a/cms/static/js/certificates/spec/views/certificate_editor_spec.js b/cms/static/js/certificates/spec/views/certificate_editor_spec.js index af3681d2de..5ccd3794f2 100644 --- a/cms/static/js/certificates/spec/views/certificate_editor_spec.js +++ b/cms/static/js/certificates/spec/views/certificate_editor_spec.js @@ -228,7 +228,7 @@ function(_, Course, CertificateModel, SignatoryModel, CertificatesCollection, Ce } ); - it('signatories should not save when title has more than 40 characters per line', function() { + it('signatories should not save when fields have too many characters per line', function() { this.view.$(SELECTORS.addSignatoryButton).click(); setValuesToInputs(this.view, { inputCertificateName: 'New Certificate Name' @@ -239,7 +239,7 @@ function(_, Course, CertificateModel, SignatoryModel, CertificatesCollection, Ce }); setValuesToInputs(this.view, { - inputSignatoryTitle: 'New Signatory title longer than 40 characters on one line' + inputSignatoryTitle: 'This is a certificate signatory title that has waaaaaaay more than 106 characters, in order to cause an exception.' }); setValuesToInputs(this.view, { diff --git a/cms/static/js/spec/views/pages/container_spec.js b/cms/static/js/spec/views/pages/container_spec.js index cb738ac959..c9eb7c270d 100644 --- a/cms/static/js/spec/views/pages/container_spec.js +++ b/cms/static/js/spec/views/pages/container_spec.js @@ -574,6 +574,25 @@ define(["jquery", "underscore", "underscore.string", "common/js/spec_helpers/aja }); }); + it('also works for older-style add component links', function () { + // Some third party xblocks (problem-builder in particular) expect add + // event handlers on custom add buttons which is what the platform + // used to use instead of - + diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py index db6ff974f3..d76823e9bb 100644 --- a/common/djangoapps/course_modes/models.py +++ b/common/djangoapps/course_modes/models.py @@ -100,6 +100,9 @@ class CourseMode(models.Model): # Modes that allow a student to pursue a verified certificate VERIFIED_MODES = [VERIFIED, PROFESSIONAL] + # Modes that allow a student to pursue a non-verified certificate + NON_VERIFIED_MODES = [HONOR, AUDIT, NO_ID_PROFESSIONAL_MODE] + # Modes that allow a student to earn credit with a university partner CREDIT_MODES = [CREDIT_MODE] diff --git a/common/djangoapps/student/tests/test_certificates.py b/common/djangoapps/student/tests/test_certificates.py index 539dc456b9..af4a6db8f1 100644 --- a/common/djangoapps/student/tests/test_certificates.py +++ b/common/djangoapps/student/tests/test_certificates.py @@ -13,6 +13,7 @@ from xmodule.modulestore.tests.factories import CourseFactory from student.tests.factories import UserFactory, CourseEnrollmentFactory from certificates.tests.factories import GeneratedCertificateFactory # pylint: disable=import-error from certificates.api import get_certificate_url # pylint: disable=import-error +from course_modes.models import CourseMode # pylint: disable=no-member @@ -42,6 +43,15 @@ class CertificateDisplayTest(ModuleStoreTestCase): self._create_certificate(enrollment_mode) self._check_can_download_certificate() + @patch.dict('django.conf.settings.FEATURES', {'CERTIFICATES_HTML_VIEW': False}) + def test_display_verified_certificate_no_id(self): + """ + Confirm that if we get a certificate with a no-id-professional mode + we still can download our certificate + """ + self._create_certificate(CourseMode.NO_ID_PROFESSIONAL_MODE) + self._check_can_download_certificate_no_id() + @ddt.data('verified', 'honor') @override_settings(CERT_NAME_SHORT='Test_Certificate') @patch.dict('django.conf.settings.FEATURES', {'CERTIFICATES_HTML_VIEW': True}) @@ -105,6 +115,16 @@ class CertificateDisplayTest(ModuleStoreTestCase): self.assertContains(response, u'Download Your ID Verified') self.assertContains(response, self.DOWNLOAD_URL) + def _check_can_download_certificate_no_id(self): + """ + Inspects the dashboard to see if a certificate for a non verified course enrollment + is present + """ + response = self.client.get(reverse('dashboard')) + self.assertContains(response, u'Download') + self.assertContains(response, u'(PDF)') + self.assertContains(response, self.DOWNLOAD_URL) + def _check_can_not_download_certificate(self): """ Make sure response does not have any of the download certificate buttons diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index 89bd27a9c7..8c134991bd 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -97,13 +97,10 @@ class DiscussionModule(DiscussionFields, XModule): def get_course(self): """ - Return the CourseDescriptor at the root of the tree we're in. + Return CourseDescriptor by course id. """ - block = self - while block.parent: - block = block.get_parent() - - return block + course = self.runtime.modulestore.get_course(self.course_id) + return course class DiscussionDescriptor(DiscussionFields, MetadataOnlyEditingDescriptor, RawDescriptor): diff --git a/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py b/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py index 95cf99e00c..379f5f04ba 100644 --- a/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py +++ b/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py @@ -312,7 +312,7 @@ class SplitBulkWriteMixin(BulkOperationsMixin): if bulk_write_record.active: bulk_write_record.index = updated_index_entry else: - self.db_connection.update_course_index(updated_index_entry, course_key) + self.db_connection.update_course_index(updated_index_entry, course_context=course_key) def get_structure(self, course_key, version_guid): bulk_write_record = self._get_bulk_ops_record(course_key) diff --git a/common/static/common/js/components/collections/paging_collection.js b/common/static/common/js/components/collections/paging_collection.js index 4bf4ff3066..a8af909897 100644 --- a/common/static/common/js/components/collections/paging_collection.js +++ b/common/static/common/js/components/collections/paging_collection.js @@ -90,16 +90,20 @@ */ setPage: function (page) { var oldPage = this.currentPage, - self = this; - return this.goTo(page - (this.isZeroIndexed ? 1 : 0), {reset: true}).then( + self = this, + deferred = $.Deferred(); + this.goTo(page - (this.isZeroIndexed ? 1 : 0), {reset: true}).then( function () { self.isStale = false; self.trigger('page_changed'); + deferred.resolve(); }, function () { self.currentPage = oldPage; + deferred.fail(); } ); + return deferred.promise(); }, diff --git a/common/static/common/templates/components/search-field.underscore b/common/static/common/templates/components/search-field.underscore index aac29f640d..c25d8640f6 100644 --- a/common/static/common/templates/components/search-field.underscore +++ b/common/static/common/templates/components/search-field.underscore @@ -1,7 +1,7 @@