diff --git a/.jshintignore b/.jshintignore index 91523ae0d2..cd0d011354 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,2 +1,2 @@ -common/static/js/vendor -lms/static/js/vendor +**/vendor +node_modules diff --git a/.jshintrc b/.jshintrc index 4ff0d97920..d3b6256345 100644 --- a/.jshintrc +++ b/.jshintrc @@ -140,6 +140,7 @@ "spyOnEvent", // Miscellaneous globals - "JSON" + "JSON", + "gettext" ] } diff --git a/AUTHORS b/AUTHORS index 0c07f8fcd3..c04dc2c40e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -253,4 +253,6 @@ Justin Abrahms Arbab Nazar Douglas Hall Awais Jibran -Muhammad Rehan \ No newline at end of file +Muhammad Rehan +Shawn Milochik +Afeef Janjua diff --git a/cms/djangoapps/contentstore/courseware_index.py b/cms/djangoapps/contentstore/courseware_index.py index a3cc375422..21cb2b2398 100644 --- a/cms/djangoapps/contentstore/courseware_index.py +++ b/cms/djangoapps/contentstore/courseware_index.py @@ -640,3 +640,22 @@ class CourseAboutSearchIndexer(object): "Successfully added %s course to the course discovery index", course_id ) + + @classmethod + def _get_location_info(cls, normalized_structure_key): + """ Builds location info dictionary """ + return {"course": unicode(normalized_structure_key), "org": normalized_structure_key.org} + + @classmethod + def remove_deleted_items(cls, structure_key): + """ Remove item from Course About Search_index """ + searcher = SearchEngine.get_search_engine(cls.INDEX_NAME) + if not searcher: + return + + response = searcher.search( + doc_type=cls.DISCOVERY_DOCUMENT_TYPE, + field_dictionary=cls._get_location_info(structure_key) + ) + result_ids = [result["data"]["id"] for result in response["results"]] + searcher.remove(cls.DISCOVERY_DOCUMENT_TYPE, result_ids) diff --git a/cms/djangoapps/contentstore/features/advanced_settings.py b/cms/djangoapps/contentstore/features/advanced_settings.py index f85659bfa9..56cad31ad6 100644 --- a/cms/djangoapps/contentstore/features/advanced_settings.py +++ b/cms/djangoapps/contentstore/features/advanced_settings.py @@ -2,7 +2,7 @@ # pylint: disable=redefined-outer-name from lettuce import world, step -from nose.tools import assert_false, assert_equal, assert_regexp_matches # pylint: disable=no-name-in-module +from nose.tools import assert_false, assert_equal, assert_regexp_matches from common import type_in_codemirror, press_the_notification_button, get_codemirror_value KEY_CSS = '.key h3.title' diff --git a/cms/djangoapps/contentstore/features/checklists.py b/cms/djangoapps/contentstore/features/checklists.py index 43945658fb..4e246a7517 100644 --- a/cms/djangoapps/contentstore/features/checklists.py +++ b/cms/djangoapps/contentstore/features/checklists.py @@ -2,7 +2,7 @@ # pylint: disable=redefined-outer-name from lettuce import world, step -from nose.tools import assert_true, assert_equal # pylint: disable=no-name-in-module +from nose.tools import assert_true, assert_equal from selenium.common.exceptions import StaleElementReferenceException diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 98a774fcc2..cb7ad41be0 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -3,7 +3,7 @@ import os from lettuce import world, step -from nose.tools import assert_true, assert_in # pylint: disable=no-name-in-module +from nose.tools import assert_true, assert_in from django.conf import settings from student.roles import CourseStaffRole, CourseInstructorRole, GlobalStaff diff --git a/cms/djangoapps/contentstore/features/component.py b/cms/djangoapps/contentstore/features/component.py index c2f62e69e4..b5aa8195d1 100644 --- a/cms/djangoapps/contentstore/features/component.py +++ b/cms/djangoapps/contentstore/features/component.py @@ -6,7 +6,7 @@ # pylint: disable=unused-argument from lettuce import world, step -from nose.tools import assert_true, assert_in, assert_equal # pylint: disable=no-name-in-module +from nose.tools import assert_true, assert_in, assert_equal DISPLAY_NAME = "Display Name" diff --git a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py index fa6c7a31b3..aebe907468 100644 --- a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py +++ b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py @@ -2,7 +2,7 @@ # pylint: disable=missing-docstring from lettuce import world -from nose.tools import assert_equal, assert_in # pylint: disable=no-name-in-module +from nose.tools import assert_equal, assert_in from terrain.steps import reload_the_page from common import type_in_codemirror from selenium.webdriver.common.keys import Keys diff --git a/cms/djangoapps/contentstore/features/course-outline.py b/cms/djangoapps/contentstore/features/course-outline.py index 702364b1a0..650f4d6475 100644 --- a/cms/djangoapps/contentstore/features/course-outline.py +++ b/cms/djangoapps/contentstore/features/course-outline.py @@ -3,7 +3,7 @@ from lettuce import world, step from common import * -from nose.tools import assert_true, assert_false # pylint: disable=no-name-in-module +from nose.tools import assert_true, assert_false from logging import getLogger logger = getLogger(__name__) diff --git a/cms/djangoapps/contentstore/features/course-settings.py b/cms/djangoapps/contentstore/features/course-settings.py index 7970a0e1f2..4e11b393d1 100644 --- a/cms/djangoapps/contentstore/features/course-settings.py +++ b/cms/djangoapps/contentstore/features/course-settings.py @@ -6,7 +6,7 @@ from selenium.webdriver.common.keys import Keys from common import type_in_codemirror, upload_file from django.conf import settings -from nose.tools import assert_true, assert_false # pylint: disable=no-name-in-module +from nose.tools import assert_true, assert_false TEST_ROOT = settings.COMMON_TEST_DATA_ROOT diff --git a/cms/djangoapps/contentstore/features/course-updates.py b/cms/djangoapps/contentstore/features/course-updates.py index afc02aa424..c3fb3e178b 100644 --- a/cms/djangoapps/contentstore/features/course-updates.py +++ b/cms/djangoapps/contentstore/features/course-updates.py @@ -1,10 +1,9 @@ # pylint: disable=missing-docstring -# pylint: disable=redefined-outer-name from lettuce import world, step from selenium.webdriver.common.keys import Keys from common import type_in_codemirror, get_codemirror_value -from nose.tools import assert_in # pylint: disable=no-name-in-module +from nose.tools import assert_in @step(u'I go to the course updates page') diff --git a/cms/djangoapps/contentstore/features/grading.py b/cms/djangoapps/contentstore/features/grading.py index bd509e6d5e..7f9b315ce0 100644 --- a/cms/djangoapps/contentstore/features/grading.py +++ b/cms/djangoapps/contentstore/features/grading.py @@ -6,7 +6,7 @@ from common import * from terrain.steps import reload_the_page from selenium.common.exceptions import InvalidElementStateException from contentstore.utils import reverse_course_url -from nose.tools import assert_in, assert_equal, assert_not_equal # pylint: disable=no-name-in-module +from nose.tools import assert_in, assert_equal, assert_not_equal @step(u'I am viewing the grading settings') diff --git a/cms/djangoapps/contentstore/features/html-editor.py b/cms/djangoapps/contentstore/features/html-editor.py index 193af0cf2e..b5a0163298 100644 --- a/cms/djangoapps/contentstore/features/html-editor.py +++ b/cms/djangoapps/contentstore/features/html-editor.py @@ -4,7 +4,7 @@ from collections import OrderedDict from lettuce import world, step -from nose.tools import assert_in, assert_false, assert_true, assert_equal # pylint: disable=no-name-in-module +from nose.tools import assert_in, assert_false, assert_true, assert_equal from common import type_in_codemirror, get_codemirror_value CODEMIRROR_SELECTOR_PREFIX = "$('iframe').contents().find" diff --git a/cms/djangoapps/contentstore/features/pages.py b/cms/djangoapps/contentstore/features/pages.py index 0bacc737c6..464e76d7f8 100644 --- a/cms/djangoapps/contentstore/features/pages.py +++ b/cms/djangoapps/contentstore/features/pages.py @@ -3,7 +3,7 @@ # pylint: disable=unused-argument from lettuce import world, step -from nose.tools import assert_equal, assert_in # pylint: disable=no-name-in-module +from nose.tools import assert_equal, assert_in CSS_FOR_TAB_ELEMENT = "li[data-tab-id='{0}'] input.toggle-checkbox" diff --git a/cms/djangoapps/contentstore/features/problem-editor.py b/cms/djangoapps/contentstore/features/problem-editor.py index a1135d06c7..92f9fda6f9 100644 --- a/cms/djangoapps/contentstore/features/problem-editor.py +++ b/cms/djangoapps/contentstore/features/problem-editor.py @@ -3,7 +3,7 @@ import json from lettuce import world, step -from nose.tools import assert_equal, assert_true # pylint: disable=no-name-in-module +from nose.tools import assert_equal, assert_true from common import type_in_codemirror, open_new_course from advanced_settings import change_value, ADVANCED_MODULES_KEY from course_import import import_file diff --git a/cms/djangoapps/contentstore/features/signup.py b/cms/djangoapps/contentstore/features/signup.py index b1c65edea5..1a8d9c2002 100644 --- a/cms/djangoapps/contentstore/features/signup.py +++ b/cms/djangoapps/contentstore/features/signup.py @@ -2,7 +2,7 @@ # pylint: disable=redefined-outer-name from lettuce import world, step -from nose.tools import assert_true, assert_false # pylint: disable=no-name-in-module +from nose.tools import assert_true, assert_false @step('I fill in the registration form$') diff --git a/cms/djangoapps/contentstore/features/textbooks.py b/cms/djangoapps/contentstore/features/textbooks.py index 4cfba5dca5..cd756b2242 100644 --- a/cms/djangoapps/contentstore/features/textbooks.py +++ b/cms/djangoapps/contentstore/features/textbooks.py @@ -1,5 +1,4 @@ # pylint: disable=missing-docstring -# pylint: disable=redefined-outer-name from lettuce import world, step from django.conf import settings diff --git a/cms/djangoapps/contentstore/features/upload.py b/cms/djangoapps/contentstore/features/upload.py index a852b8e264..d46c29eff9 100644 --- a/cms/djangoapps/contentstore/features/upload.py +++ b/cms/djangoapps/contentstore/features/upload.py @@ -10,7 +10,7 @@ import random import os from django.contrib.auth.models import User from student.models import CourseEnrollment -from nose.tools import assert_equal, assert_not_equal # pylint: disable=no-name-in-module +from nose.tools import assert_equal, assert_not_equal TEST_ROOT = settings.COMMON_TEST_DATA_ROOT ASSET_NAMES_CSS = 'td.name-col > span.title > a.filename' diff --git a/cms/djangoapps/contentstore/features/video_editor.py b/cms/djangoapps/contentstore/features/video_editor.py index 353d716708..cf96e099c9 100644 --- a/cms/djangoapps/contentstore/features/video_editor.py +++ b/cms/djangoapps/contentstore/features/video_editor.py @@ -4,7 +4,7 @@ import requests from lettuce import world, step -from nose.tools import assert_true, assert_equal, assert_in, assert_not_equal # pylint: disable=no-name-in-module +from nose.tools import assert_true, assert_equal, assert_in, assert_not_equal from terrain.steps import reload_the_page from django.conf import settings from common import upload_file, attach_file diff --git a/cms/djangoapps/contentstore/features/video_handout.py b/cms/djangoapps/contentstore/features/video_handout.py index ccf74680d7..233ea30b56 100644 --- a/cms/djangoapps/contentstore/features/video_handout.py +++ b/cms/djangoapps/contentstore/features/video_handout.py @@ -3,7 +3,7 @@ # pylint: disable=missing-docstring from lettuce import world, step -from nose.tools import assert_true # pylint: disable=no-name-in-module +from nose.tools import assert_true from video_editor import RequestHandlerWithSessionId, success_upload_file diff --git a/cms/djangoapps/contentstore/management/commands/delete_orphans.py b/cms/djangoapps/contentstore/management/commands/delete_orphans.py index c0971bbc08..48e2689e9f 100644 --- a/cms/djangoapps/contentstore/management/commands/delete_orphans.py +++ b/cms/djangoapps/contentstore/management/commands/delete_orphans.py @@ -11,12 +11,12 @@ class Command(BaseCommand): 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. + |--commit|: optional argument. If not provided, will dry run delete ''' def add_arguments(self, parser): parser.add_argument('course_id') - parser.add_argument('--commit', action='store') + parser.add_argument('--commit', action='store_true', help='Commit to deleting the orphans') def handle(self, *args, **options): try: @@ -24,20 +24,16 @@ class Command(BaseCommand): except InvalidKeyError: raise CommandError("Invalid course key.") - commit = False if options['commit']: - commit = options['commit'] == 'commit' - - if commit: print 'Deleting orphans from the course:' deleted_items = _delete_orphans( - course_key, ModuleStoreEnum.UserID.mgmt_command, commit + course_key, ModuleStoreEnum.UserID.mgmt_command, options['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 + course_key, ModuleStoreEnum.UserID.mgmt_command, options['commit'] ) print "\n".join(deleted_items) diff --git a/cms/djangoapps/contentstore/management/commands/force_publish.py b/cms/djangoapps/contentstore/management/commands/force_publish.py index 4795e10560..96f68334b4 100644 --- a/cms/djangoapps/contentstore/management/commands/force_publish.py +++ b/cms/djangoapps/contentstore/management/commands/force_publish.py @@ -17,39 +17,37 @@ class Command(BaseCommand): help = ''' Force publish a course. Takes two arguments: : the course id of the course you want to publish forcefully - commit: do the force publish + --commit: do the force publish - If you do not specify 'commit', the command will print out what changes would be made. + If you do not specify '--commit', the command will print out what changes would be made. ''' + def add_arguments(self, parser): + parser.add_argument('course_key', help="ID of the Course to force publish") + parser.add_argument('--commit', action='store_true', help="Pull updated metadata from external IDPs") + def handle(self, *args, **options): """Execute the command""" - if len(args) not in {1, 2}: - raise CommandError("force_publish requires 1 or more argument: |commit|") try: - course_key = CourseKey.from_string(args[0]) + course_key = CourseKey.from_string(options['course_key']) except InvalidKeyError: raise CommandError("Invalid course key.") if not modulestore().get_course(course_key): raise CommandError("Course not found.") - commit = False - if len(args) == 2: - commit = args[1] == 'commit' - # for now only support on split mongo owning_store = modulestore()._get_modulestore_for_courselike(course_key) # pylint: disable=protected-access if hasattr(owning_store, 'force_publish_course'): - versions = get_course_versions(args[0]) + versions = get_course_versions(options['course_key']) print "Course versions : {0}".format(versions) - if commit: + if options['commit']: if query_yes_no("Are you sure to publish the {0} course forcefully?".format(course_key), default="no"): # publish course forcefully updated_versions = owning_store.force_publish_course( - course_key, ModuleStoreEnum.UserID.mgmt_command, commit + course_key, ModuleStoreEnum.UserID.mgmt_command, options['commit'] ) if updated_versions: # if publish and draft were different diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py b/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py index d15698719d..21b4bbb37a 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py @@ -7,8 +7,8 @@ import mock from opaque_keys.edx.locations import SlashSeparatedCourseKey from django.core.management import CommandError -from contentstore.management.commands.delete_course import Command # pylint: disable=import-error -from contentstore.tests.utils import CourseTestCase # pylint: disable=import-error +from contentstore.management.commands.delete_course import Command +from contentstore.tests.utils import CourseTestCase from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.django import modulestore diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py b/cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py index 502cd7b49f..816d579eb6 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py @@ -24,7 +24,7 @@ class TestDeleteOrphan(TestOrphanBase): @ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo) def test_delete_orphans_no_commit(self, default_store): """ - Tests that running the command without a 'commit' argument + Tests that running the command without a '--commit' argument results in no orphans being deleted """ course = self.create_course_with_orphans(default_store) @@ -37,12 +37,12 @@ class TestDeleteOrphan(TestOrphanBase): @ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo) def test_delete_orphans_commit(self, default_store): """ - Tests that running the command WITH the 'commit' argument + Tests that running the command WITH the '--commit' argument results in the orphans being deleted """ course = self.create_course_with_orphans(default_store) - call_command('delete_orphans', unicode(course.id), commit='commit') + call_command('delete_orphans', unicode(course.id), '--commit') # make sure this module wasn't deleted self.assertTrue(self.store.has_item(course.id.make_usage_key('html', 'multi_parent_html'))) @@ -66,7 +66,7 @@ class TestDeleteOrphan(TestOrphanBase): # call delete orphans, specifying the published branch # of the course - call_command('delete_orphans', unicode(published_branch), commit='commit') + call_command('delete_orphans', unicode(published_branch), '--commit') # now all orphans should be deleted self.assertOrphanCount(course.id, 0) diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_force_publish.py b/cms/djangoapps/contentstore/management/commands/tests/test_force_publish.py index f4766ddfd9..7358210b31 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_force_publish.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_force_publish.py @@ -2,7 +2,7 @@ Tests for the force_publish management command """ import mock -from django.core.management.base import CommandError +from django.core.management import call_command, CommandError from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -25,9 +25,9 @@ class TestForcePublish(SharedModuleStoreTestCase): """ Test 'force_publish' command with no arguments """ - errstring = "force_publish requires 1 or more argument: |commit" + errstring = "Error: too few arguments" with self.assertRaisesRegexp(CommandError, errstring): - self.command.handle() + call_command('force_publish') def test_invalid_course_key(self): """ @@ -35,15 +35,15 @@ class TestForcePublish(SharedModuleStoreTestCase): """ errstring = "Invalid course key." with self.assertRaisesRegexp(CommandError, errstring): - self.command.handle('TestX/TS01') + call_command('force_publish', 'TestX/TS01') def test_too_many_arguments(self): """ Test 'force_publish' command with more than 2 arguments """ - errstring = "force_publish requires 1 or more argument: |commit" + errstring = "Error: unrecognized arguments: invalid-arg" with self.assertRaisesRegexp(CommandError, errstring): - self.command.handle(unicode(self.course.id), 'commit', 'invalid-arg') + call_command('force_publish', unicode(self.course.id), '--commit', 'invalid-arg') def test_course_key_not_found(self): """ @@ -51,7 +51,7 @@ class TestForcePublish(SharedModuleStoreTestCase): """ errstring = "Course not found." with self.assertRaisesRegexp(CommandError, errstring): - self.command.handle(unicode('course-v1:org+course+run')) + call_command('force_publish', unicode('course-v1:org+course+run')) def test_force_publish_non_split(self): """ @@ -60,7 +60,7 @@ class TestForcePublish(SharedModuleStoreTestCase): course = CourseFactory.create(default_store=ModuleStoreEnum.Type.mongo) errstring = 'The owning modulestore does not support this command.' with self.assertRaisesRegexp(CommandError, errstring): - self.command.handle(unicode(course.id)) + call_command('force_publish', unicode(course.id)) @SharedModuleStoreTestCase.modifies_courseware def test_force_publish(self): @@ -91,7 +91,7 @@ class TestForcePublish(SharedModuleStoreTestCase): patched_yes_no.return_value = True # force publish course - self.command.handle(unicode(self.course.id), 'commit') + call_command('force_publish', unicode(self.course.id), '--commit') # verify that course has no changes self.assertFalse(self.store.has_changes(self.store.get_item(self.course.location))) diff --git a/cms/djangoapps/contentstore/models.py b/cms/djangoapps/contentstore/models.py index d2112bd9f1..12435dd9c2 100644 --- a/cms/djangoapps/contentstore/models.py +++ b/cms/djangoapps/contentstore/models.py @@ -1,7 +1,6 @@ """ Models for contentstore """ -# pylint: disable=no-member from django.db.models.fields import TextField diff --git a/cms/djangoapps/contentstore/tests/test_course_create_rerun.py b/cms/djangoapps/contentstore/tests/test_course_create_rerun.py index a7c5df8bc7..4db95d3bed 100644 --- a/cms/djangoapps/contentstore/tests/test_course_create_rerun.py +++ b/cms/djangoapps/contentstore/tests/test_course_create_rerun.py @@ -1,11 +1,15 @@ """ Test view handler for rerun (and eventually create) """ +import ddt + from django.test.client import RequestFactory from opaque_keys.edx.keys import CourseKey from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory +from xmodule.modulestore import ModuleStoreEnum +from xmodule.modulestore.django import modulestore from student.roles import CourseInstructorRole, CourseStaffRole from student.tests.factories import UserFactory from contentstore.tests.utils import AjaxEnabledTestClient, parse_json @@ -13,6 +17,7 @@ from datetime import datetime from xmodule.course_module import CourseFields +@ddt.ddt class TestCourseListing(ModuleStoreTestCase): """ Unit tests for getting the list of courses for a logged in user @@ -64,3 +69,21 @@ class TestCourseListing(ModuleStoreTestCase): self.assertEqual(dest_course_key.run, 'copy') dest_course = self.store.get_course(dest_course_key) self.assertEqual(dest_course.start, CourseFields.start.default) + + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) + def test_newly_created_course_has_web_certs_enabled(self, store): + """ + Tests newly created course has web certs enabled by default. + """ + with modulestore().default_store(store): + response = self.client.ajax_post('/course/', { + 'org': 'orgX', + 'number': 'CS101', + 'display_name': 'Course with web certs enabled', + 'run': '2015_T2' + }) + self.assertEqual(response.status_code, 200) + data = parse_json(response) + new_course_key = CourseKey.from_string(data['course_key']) + course = self.store.get_course(new_course_key) + self.assertTrue(course.cert_html_view_enabled) diff --git a/cms/djangoapps/contentstore/tests/test_courseware_index.py b/cms/djangoapps/contentstore/tests/test_courseware_index.py index e146cb914c..1b290a29d6 100644 --- a/cms/djangoapps/contentstore/tests/test_courseware_index.py +++ b/cms/djangoapps/contentstore/tests/test_courseware_index.py @@ -17,7 +17,7 @@ from django.conf import settings from course_modes.models import CourseMode from xmodule.library_tools import normalize_key_for_search from xmodule.modulestore import ModuleStoreEnum -from xmodule.modulestore.django import SignalHandler +from xmodule.modulestore.django import SignalHandler, modulestore from xmodule.modulestore.edit_info import EditInfoMixin from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.inheritance import InheritanceMixin @@ -335,6 +335,25 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase): response = self.search() self.assertEqual(response["total"], 5) + def _test_delete_course_from_search_index_after_course_deletion(self, store): # pylint: disable=invalid-name + """ + Test that course will also be delete from search_index after course deletion. + """ + self.DOCUMENT_TYPE = 'course_info' # pylint: disable=invalid-name + response = self.search() + self.assertEqual(response["total"], 0) + + # index the course in search_index + self.reindex_course(store) + response = self.search() + self.assertEqual(response["total"], 1) + + # delete the course and look course in search_index + modulestore().delete_course(self.course.id, self.user_id) + self.assertIsNone(modulestore().get_course(self.course.id)) + response = self.search() + self.assertEqual(response["total"], 0) + def _test_deleting_item(self, store): """ test deleting an item """ # Publish the vertical to start with @@ -604,6 +623,11 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase): def test_course_location_null(self, store_type): self._perform_test_using_store(store_type, self._test_course_location_null) + @ddt.data(*WORKS_WITH_STORES) + def test_delete_course_from_search_index_after_course_deletion(self, store_type): + """ Test for removing course from CourseAboutSearchIndexer """ + self._perform_test_using_store(store_type, self._test_delete_course_from_search_index_after_course_deletion) + @patch('django.conf.settings.SEARCH_ENGINE', 'search.tests.utils.ForceRefreshElasticSearchEngine') @ddt.ddt diff --git a/cms/djangoapps/contentstore/tests/test_libraries.py b/cms/djangoapps/contentstore/tests/test_libraries.py index c28be47d45..969aec1323 100644 --- a/cms/djangoapps/contentstore/tests/test_libraries.py +++ b/cms/djangoapps/contentstore/tests/test_libraries.py @@ -105,7 +105,7 @@ class LibraryTestCase(ModuleStoreTestCase): if user not in self.session_data: self.session_data[user] = {} request = Mock(user=user, session=self.session_data[user]) - _load_preview_module(request, descriptor) # pylint: disable=protected-access + _load_preview_module(request, descriptor) def _update_item(self, usage_key, metadata): """ diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py index 7f0546f166..237d04a756 100644 --- a/cms/djangoapps/contentstore/tests/utils.py +++ b/cms/djangoapps/contentstore/tests/utils.py @@ -10,8 +10,8 @@ from django.contrib.auth.models import User from django.test.client import Client from opaque_keys.edx.locations import SlashSeparatedCourseKey, AssetLocation -from contentstore.utils import reverse_url # pylint: disable=import-error -from student.models import Registration # pylint: disable=import-error +from contentstore.utils import reverse_url +from student.models import Registration from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore from xmodule.contentstore.django import contentstore from xmodule.modulestore import ModuleStoreEnum diff --git a/cms/djangoapps/contentstore/views/__init__.py b/cms/djangoapps/contentstore/views/__init__.py index 2f582ef498..2ab6283322 100644 --- a/cms/djangoapps/contentstore/views/__init__.py +++ b/cms/djangoapps/contentstore/views/__init__.py @@ -1,4 +1,4 @@ -# pylint: disable=wildcard-import, fixme +# pylint: disable=wildcard-import "All view functions for contentstore, broken out into submodules" diff --git a/cms/djangoapps/contentstore/views/certificates.py b/cms/djangoapps/contentstore/views/certificates.py index ca453c9f7d..2b409ddb29 100644 --- a/cms/djangoapps/contentstore/views/certificates.py +++ b/cms/djangoapps/contentstore/views/certificates.py @@ -245,7 +245,7 @@ class CertificateManager(object): """ for cert_index, cert in enumerate(course.certificates['certificates']): # pylint: disable=unused-variable if int(cert['id']) == int(certificate_id): - for sig_index, signatory in enumerate(cert.get('signatories')): # pylint: disable=unused-variable + for sig_index, signatory in enumerate(cert.get('signatories')): if int(signatory_id) == int(signatory['id']): _delete_asset(course.id, signatory['signature_image_path']) del cert['signatories'][sig_index] diff --git a/cms/djangoapps/contentstore/views/checklist.py b/cms/djangoapps/contentstore/views/checklist.py index 9b40a3d88a..cb315d6b30 100644 --- a/cms/djangoapps/contentstore/views/checklist.py +++ b/cms/djangoapps/contentstore/views/checklist.py @@ -21,7 +21,6 @@ from django.utils.translation import ugettext __all__ = ['checklists_handler'] -# pylint: disable=unused-argument @require_http_methods(("GET", "POST", "PUT")) @login_required @ensure_csrf_cookie diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index 3e131378b1..0cc6011024 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -82,7 +82,6 @@ def _load_mixed_class(category): return mixologist.mix(component_class) -# pylint: disable=unused-argument @require_GET @login_required def container_handler(request, usage_key_string): diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 6d4dba3649..473fe058d3 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -6,7 +6,7 @@ from django.shortcuts import redirect import json import random import logging -import string # pylint: disable=deprecated-module +import string from django.utils.translation import ugettext as _ import django.utils from django.contrib.auth.decorators import login_required @@ -219,7 +219,6 @@ def _dismiss_notification(request, course_action_state_id): # pylint: disable=u return JsonResponse({'success': True}) -# pylint: disable=unused-argument @login_required def course_handler(request, course_key_string=None): """ @@ -741,8 +740,11 @@ def create_new_course_in_store(store, user, org, number, run, fields): Separated out b/c command line course creation uses this as well as the web interface. """ - # Set default language from settings - fields.update({'language': getattr(settings, 'DEFAULT_COURSE_LANGUAGE', 'en')}) + # Set default language from settings and enable web certs + fields.update({ + 'language': getattr(settings, 'DEFAULT_COURSE_LANGUAGE', 'en'), + 'cert_html_view_enabled': True, + }) with modulestore().default_store(store): # Creating the course raises DuplicateCourseError if an existing course with this org/name is found @@ -803,7 +805,6 @@ def _rerun_course(request, org, number, run, fields): }) -# pylint: disable=unused-argument @login_required @ensure_csrf_cookie @require_http_methods(["GET"]) @@ -836,7 +837,6 @@ def course_info_handler(request, course_key_string): return HttpResponseBadRequest("Only supports html requests") -# pylint: disable=unused-argument @login_required @ensure_csrf_cookie @require_http_methods(("GET", "POST", "PUT", "DELETE")) diff --git a/cms/djangoapps/contentstore/views/entrance_exam.py b/cms/djangoapps/contentstore/views/entrance_exam.py index 400ce362d3..21be96119e 100644 --- a/cms/djangoapps/contentstore/views/entrance_exam.py +++ b/cms/djangoapps/contentstore/views/entrance_exam.py @@ -177,7 +177,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613 course = modulestore().get_course(course_key) if course is None: return HttpResponse(status=400) - if not getattr(course, 'entrance_exam_id'): + if not course.entrance_exam_id: return HttpResponse(status=404) try: exam_key = UsageKey.from_string(course.entrance_exam_id) @@ -228,7 +228,7 @@ def _delete_entrance_exam(request, course_key): # Reset the entrance exam flags on the course # Reload the course so we have the latest state course = store.get_course(course_key) - if getattr(course, 'entrance_exam_id'): + if course.entrance_exam_id: metadata = { 'entrance_exam_enabled': False, 'entrance_exam_minimum_score_pct': None, diff --git a/cms/djangoapps/contentstore/views/import_export.py b/cms/djangoapps/contentstore/views/import_export.py index 58187d3b53..f6b56f8829 100644 --- a/cms/djangoapps/contentstore/views/import_export.py +++ b/cms/djangoapps/contentstore/views/import_export.py @@ -59,7 +59,6 @@ log = logging.getLogger(__name__) CONTENT_RE = re.compile(r"(?P\d{1,11})-(?P\d{1,11})/(?P\d{1,11})") -# pylint: disable=unused-argument @login_required @ensure_csrf_cookie @require_http_methods(("GET", "POST", "PUT")) @@ -359,7 +358,6 @@ def _save_request_status(request, key, status): request.session.save() -# pylint: disable=unused-argument @require_GET @ensure_csrf_cookie @login_required @@ -458,7 +456,6 @@ def send_tarball(tarball): return response -# pylint: disable=unused-argument @ensure_csrf_cookie @login_required @require_http_methods(("GET",)) diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index e0f5537bde..824d1bda76 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -88,7 +88,6 @@ def _filter_entrance_exam_grader(graders): return graders -# pylint: disable=unused-argument @require_http_methods(("DELETE", "GET", "PUT", "POST", "PATCH")) @login_required @expect_json @@ -196,7 +195,6 @@ def xblock_handler(request, usage_key_string): ) -# pylint: disable=unused-argument @require_http_methods(("GET")) @login_required @expect_json @@ -259,7 +257,6 @@ def xblock_view_handler(request, usage_key_string, view_name): 'page_size': int(request.REQUEST.get('page_size', 0)), } except ValueError: - # pylint: disable=too-many-format-args return HttpResponse( content="Couldn't parse paging parameters: enable_paging: " "{0}, page_number: {1}, page_size: {2}".format( @@ -312,7 +309,6 @@ def xblock_view_handler(request, usage_key_string, view_name): return HttpResponse(status=406) -# pylint: disable=unused-argument @require_http_methods(("GET")) @login_required @expect_json @@ -663,7 +659,6 @@ def _delete_item(usage_key, user): store.delete_item(usage_key, user.id) -# pylint: disable=unused-argument @login_required @require_http_methods(("GET", "DELETE")) def orphan_handler(request, course_key_string): diff --git a/cms/djangoapps/contentstore/views/tests/test_certificates.py b/cms/djangoapps/contentstore/views/tests/test_certificates.py index d7bcd1b1be..404e9a979c 100644 --- a/cms/djangoapps/contentstore/views/tests/test_certificates.py +++ b/cms/djangoapps/contentstore/views/tests/test_certificates.py @@ -230,7 +230,7 @@ class CertificatesListHandlerTestCase(EventTestMixin, CourseTestCase, Certificat self.assertEqual(response.status_code, 201) self.assertIn("Location", response) content = json.loads(response.content) - certificate_id = self._remove_ids(content) # pylint: disable=unused-variable + certificate_id = self._remove_ids(content) self.assertEqual(content, expected) self.assert_event_emitted( 'edx.certificate.configuration.created', diff --git a/cms/djangoapps/contentstore/views/tests/test_videos.py b/cms/djangoapps/contentstore/views/tests/test_videos.py index e8bed3c2b2..9733433c97 100644 --- a/cms/djangoapps/contentstore/views/tests/test_videos.py +++ b/cms/djangoapps/contentstore/views/tests/test_videos.py @@ -2,7 +2,6 @@ """ Unit tests for video-related REST APIs. """ -# pylint: disable=attribute-defined-outside-init import csv import json import dateutil.parser diff --git a/cms/djangoapps/contentstore/views/user.py b/cms/djangoapps/contentstore/views/user.py index 7ee92c0ba4..e5da0a8194 100644 --- a/cms/djangoapps/contentstore/views/user.py +++ b/cms/djangoapps/contentstore/views/user.py @@ -34,7 +34,6 @@ def request_course_creator(request): return JsonResponse({"Status": "OK"}) -# pylint: disable=unused-argument @login_required @ensure_csrf_cookie @require_http_methods(("GET", "POST", "PUT", "DELETE")) diff --git a/cms/envs/aws.py b/cms/envs/aws.py index 44f1861b05..80f2718b53 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -269,6 +269,8 @@ else: DATABASES = AUTH_TOKENS['DATABASES'] # Enable automatic transaction management on all databases +# https://docs.djangoproject.com/en/1.8/topics/db/transactions/#tying-transactions-to-http-requests +# This needs to be true for all databases for database_name in DATABASES: DATABASES[database_name]['ATOMIC_REQUESTS'] = True diff --git a/cms/envs/bok_choy.py b/cms/envs/bok_choy.py index c34ae18f45..20671f9c7b 100644 --- a/cms/envs/bok_choy.py +++ b/cms/envs/bok_choy.py @@ -13,11 +13,6 @@ from the same directory. import os from path import Path as path -# Pylint gets confused by path.py instances, which report themselves as class -# objects. As a result, pylint applies the wrong regex in validating names, -# and throws spurious errors. Therefore, we disable invalid-name checking. -# pylint: disable=invalid-name - ########################## Prod-like settings ################################### # These should be as close as possible to the settings we use in production. diff --git a/cms/envs/common.py b/cms/envs/common.py index 63ae7caaa0..930ab32438 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -22,12 +22,7 @@ Longer TODO: # We intentionally define lots of variables that aren't used, and # want to import all variables from base settings files -# pylint: disable=wildcard-import, unused-import, unused-wildcard-import - -# Pylint gets confused by path.py instances, which report themselves as class -# objects. As a result, pylint applies the wrong regex in validating names, -# and throws spurious errors. Therefore, we disable invalid-name checking. -# pylint: disable=invalid-name +# pylint: disable=unused-import import imp import os @@ -62,7 +57,7 @@ from xmodule.mixin import LicenseMixin # Dummy secret key for dev/test -SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd' +SECRET_KEY = 'dev key' STUDIO_NAME = "Studio" STUDIO_SHORT_NAME = "Studio" @@ -1014,6 +1009,8 @@ ADVANCED_COMPONENT_TYPES = [ 'notes', 'schoolyourself_review', 'schoolyourself_lesson', + # Office Mix + 'officemix', # Google Drive embedded components. These XBlocks allow one to # embed public google drive documents and calendars within edX units diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js index 9703977314..99db4d68a1 100644 --- a/cms/static/js/models/settings/course_details.js +++ b/cms/static/js/models/settings/course_details.js @@ -35,9 +35,6 @@ var CourseDetails = Backbone.Model.extend({ if (newattrs.start_date === null) { errors.start_date = gettext("The course must have an assigned start date."); } - if (this.hasChanged("start_date") && this.get("has_cert_config") === false){ - errors.start_date = gettext("The course must have at least one active certificate configuration before it can be started."); - } if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) { errors.end_date = gettext("The course end date must be later than the course start date."); } diff --git a/cms/static/js/spec/views/settings/main_spec.js b/cms/static/js/spec/views/settings/main_spec.js index 636ed93c0a..e8284620f8 100644 --- a/cms/static/js/spec/views/settings/main_spec.js +++ b/cms/static/js/spec/views/settings/main_spec.js @@ -72,13 +72,6 @@ define([ ); }); - it('Changing course start date without active certificate configuration should result in error', function () { - this.view.$el.find('#course-start-date') - .val('10/06/2014') - .trigger('change'); - expect(this.view.$el.find('span.message-error').text()).toContain("course must have at least one active certificate configuration"); - }); - it('Selecting a course in pre-requisite drop down should save it as part of course details', function () { var pre_requisite_courses = ['test/CSS101/2012_T1']; var requests = AjaxHelpers.requests(this), diff --git a/cms/static/js/views/metadata.js b/cms/static/js/views/metadata.js index daa6e4d207..e8aca4b5d4 100644 --- a/cms/static/js/views/metadata.js +++ b/cms/static/js/views/metadata.js @@ -288,7 +288,7 @@ function(BaseView, _, MetadataModel, AbstractEditor, FileUpload, UploadDialog, var template = _.template( '
  • ' + '' + - 'Remove' + + 'Remove' + '
  • ' ); list.append($(template({'ele': ele, 'index': index}))); @@ -455,7 +455,7 @@ function(BaseView, _, MetadataModel, AbstractEditor, FileUpload, UploadDialog, '
  • ' + '' + '' + - 'Remove' + + 'Remove' + '
  • ' ); diff --git a/cms/templates/js/basic-modal.underscore b/cms/templates/js/basic-modal.underscore index 009dd57a32..a145db642c 100644 --- a/cms/templates/js/basic-modal.underscore +++ b/cms/templates/js/basic-modal.underscore @@ -1,7 +1,5 @@