diff --git a/cms/djangoapps/contentstore/management/commands/export_convert_format.py b/cms/djangoapps/contentstore/management/commands/export_convert_format.py deleted file mode 100644 index eb0cc575d9..0000000000 --- a/cms/djangoapps/contentstore/management/commands/export_convert_format.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -Script for converting a tar.gz file representing an exported course -to the archive format used by a different version of export. - -Sample invocation: ./manage.py export_convert_format mycourse.tar.gz ~/newformat/ -""" -import os -from path import Path as path -from django.core.management.base import BaseCommand, CommandError -from django.conf import settings - -from tempfile import mkdtemp -import tarfile -import shutil -from openedx.core.lib.extract_tar import safetar_extractall - -from xmodule.modulestore.xml_exporter import convert_between_versions - - -class Command(BaseCommand): - """ - Convert between export formats. - """ - help = 'Convert between versions 0 and 1 of the course export format' - args = ' ' - - def handle(self, *args, **options): - "Execute the command" - if len(args) != 2: - raise CommandError("export requires two arguments: ") - - source_archive = args[0] - output_path = args[1] - - # Create temp directories to extract the source and create the target archive. - temp_source_dir = mkdtemp(dir=settings.DATA_DIR) - temp_target_dir = mkdtemp(dir=settings.DATA_DIR) - try: - extract_source(source_archive, temp_source_dir) - - desired_version = convert_between_versions(temp_source_dir, temp_target_dir) - - # New zip up the target directory. - parts = os.path.basename(source_archive).split('.') - archive_name = path(output_path) / "{source_name}_version_{desired_version}.tar.gz".format( - source_name=parts[0], desired_version=desired_version - ) - with open(archive_name, "w"): - tar_file = tarfile.open(archive_name, mode='w:gz') - try: - for item in os.listdir(temp_target_dir): - tar_file.add(path(temp_target_dir) / item, arcname=item) - - finally: - tar_file.close() - - print "Created archive {0}".format(archive_name) - - except ValueError as err: - raise CommandError(err) - - finally: - shutil.rmtree(temp_source_dir) - shutil.rmtree(temp_target_dir) - - -def extract_source(source_archive, target): - """ - Extract the archive into the given target directory. - """ - with tarfile.open(source_archive) as tar_file: - safetar_extractall(tar_file, target) diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_export_convert_format.py b/cms/djangoapps/contentstore/management/commands/tests/test_export_convert_format.py deleted file mode 100644 index 0160a3feab..0000000000 --- a/cms/djangoapps/contentstore/management/commands/tests/test_export_convert_format.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -Test for export_convert_format. -""" -from unittest import TestCase -from django.core.management import call_command, CommandError -from django.conf import settings -from tempfile import mkdtemp -import shutil -from path import Path as path -from contentstore.management.commands.export_convert_format import Command, extract_source -from xmodule.tests.helpers import directories_equal - - -class ConvertExportFormat(TestCase): - """ - Tests converting between export formats. - """ - def setUp(self): - """ Common setup. """ - super(ConvertExportFormat, self).setUp() - - self.temp_dir = mkdtemp(dir=settings.DATA_DIR) - self.addCleanup(shutil.rmtree, self.temp_dir) - self.data_dir = path(__file__).realpath().parent / 'data' - self.version0 = self.data_dir / "Version0_drafts.tar.gz" - self.version1 = self.data_dir / "Version1_drafts.tar.gz" - - self.command = Command() - - def test_no_args(self): - """ Test error condition of no arguments. """ - errstring = "export requires two arguments" - with self.assertRaisesRegexp(CommandError, errstring): - self.command.handle() - - def test_version1_archive(self): - """ - Smoke test for creating a version 1 archive from a version 0. - """ - call_command('export_convert_format', self.version0, self.temp_dir) - output = path(self.temp_dir) / 'Version0_drafts_version_1.tar.gz' - self.assertTrue(self._verify_archive_equality(output, self.version1)) - - def test_version0_archive(self): - """ - Smoke test for creating a version 0 archive from a version 1. - """ - call_command('export_convert_format', self.version1, self.temp_dir) - output = path(self.temp_dir) / 'Version1_drafts_version_0.tar.gz' - self.assertTrue(self._verify_archive_equality(output, self.version0)) - - def _verify_archive_equality(self, file1, file2): - """ - Helper function for determining if 2 archives are equal. - """ - temp_dir_1 = mkdtemp(dir=settings.DATA_DIR) - temp_dir_2 = mkdtemp(dir=settings.DATA_DIR) - try: - extract_source(file1, temp_dir_1) - extract_source(file2, temp_dir_2) - return directories_equal(temp_dir_1, temp_dir_2) - - finally: - shutil.rmtree(temp_dir_1) - shutil.rmtree(temp_dir_2) diff --git a/cms/djangoapps/contentstore/tests/test_course_listing.py b/cms/djangoapps/contentstore/tests/test_course_listing.py index 4cd54eb356..5800be50b1 100644 --- a/cms/djangoapps/contentstore/tests/test_course_listing.py +++ b/cms/djangoapps/contentstore/tests/test_course_listing.py @@ -29,8 +29,9 @@ from opaque_keys.edx.locations import CourseLocator from xmodule.error_module import ErrorDescriptor from course_action_state.models import CourseRerunState -TOTAL_COURSES_COUNT = 500 -USER_COURSES_COUNT = 50 + +TOTAL_COURSES_COUNT = 10 +USER_COURSES_COUNT = 1 @ddt.ddt @@ -157,8 +158,8 @@ class TestCourseListing(ModuleStoreTestCase, XssTestMixin): self.assertEqual(courses_list_by_groups, []) @ddt.data( - (ModuleStoreEnum.Type.split, 5), - (ModuleStoreEnum.Type.mongo, 3) + (ModuleStoreEnum.Type.split, 3), + (ModuleStoreEnum.Type.mongo, 2) ) @ddt.unpack def test_staff_course_listing(self, default_store, mongo_calls): @@ -265,8 +266,8 @@ class TestCourseListing(ModuleStoreTestCase, XssTestMixin): ) @ddt.data( - (ModuleStoreEnum.Type.split, 150, 505), - (ModuleStoreEnum.Type.mongo, USER_COURSES_COUNT, 3) + (ModuleStoreEnum.Type.split, 3, 13), + (ModuleStoreEnum.Type.mongo, USER_COURSES_COUNT, 2) ) @ddt.unpack def test_course_listing_performance(self, store, courses_list_from_group_calls, courses_list_calls): diff --git a/cms/startup.py b/cms/startup.py index 55244c9045..42c7eb1070 100644 --- a/cms/startup.py +++ b/cms/startup.py @@ -9,7 +9,7 @@ settings.INSTALLED_APPS # pylint: disable=pointless-statement from openedx.core.lib.django_startup import autostartup import django -from monkey_patch import third_party_auth +from monkey_patch import third_party_auth, django_db_models_options import xmodule.x_module import cms.lib.xblock.runtime @@ -22,6 +22,7 @@ def run(): Executed during django startup """ third_party_auth.patch() + django_db_models_options.patch() # Comprehensive theming needs to be set up before django startup, # because modifying django template paths after startup has no effect. diff --git a/common/djangoapps/monkey_patch/django_db_models_options.py b/common/djangoapps/monkey_patch/django_db_models_options.py new file mode 100644 index 0000000000..d67373b3f9 --- /dev/null +++ b/common/djangoapps/monkey_patch/django_db_models_options.py @@ -0,0 +1,39 @@ +""" +Monkey patch implementation of the following _expire_cache performance improvement: + +https://github.com/django/django/commit/7628f87e2b1ab4b8a881f06c8973be4c368aaa3d + +Remove once we upgrade to a version of django which includes this fix natively! +NOTE: This is on django's master branch but is NOT currently part of any django 1.8 or 1.9 release. +""" + +from django.db.models.options import Options + + +def patch(): + """ + Monkey-patch the Options class. + """ + def _expire_cache(self, forward=True, reverse=True): + # pylint: disable=missing-docstring + + # This method is usually called by apps.cache_clear(), when the + # registry is finalized, or when a new field is added. + if forward: + for cache_key in self.FORWARD_PROPERTIES: + if cache_key in self.__dict__: + delattr(self, cache_key) + if reverse and not self.abstract: + for cache_key in self.REVERSE_PROPERTIES: + if cache_key in self.__dict__: + delattr(self, cache_key) + self._get_fields_cache = {} # pylint: disable=protected-access + + # Patch constants as a set instead of a list. + Options.FORWARD_PROPERTIES = {'fields', 'many_to_many', 'concrete_fields', + 'local_concrete_fields', '_forward_fields_map'} + + Options.REVERSE_PROPERTIES = {'related_objects', 'fields_map', '_relation_tree'} + + # Patch the expire_cache method to utilize constant's new set data structure. + Options._expire_cache = _expire_cache # pylint: disable=protected-access diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index 8120590122..5d5859fb90 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -4,6 +4,7 @@ Modulestore configuration for test cases. """ import functools from uuid import uuid4 +from contextlib import contextmanager from mock import patch @@ -269,15 +270,50 @@ class SharedModuleStoreTestCase(TestCase): multi_db = True @classmethod - def setUpClass(cls): - super(SharedModuleStoreTestCase, cls).setUpClass() - + def _setUpModuleStore(cls): # pylint: disable=invalid-name + """ + Set up the modulestore for an entire test class. + """ cls._settings_override = override_settings(MODULESTORE=cls.MODULESTORE) cls._settings_override.__enter__() XMODULE_FACTORY_LOCK.enable() clear_existing_modulestores() cls.store = modulestore() + @classmethod + @contextmanager + def setUpClassAndTestData(cls): # pylint: disable=invalid-name + """ + For use when the test class has a setUpTestData() method that uses variables + that are setup during setUpClass() of the same test class. + + Use it like so: + + @classmethod + def setUpClass(cls): + with super(MyTestClass, cls).setUpClassAndTestData(): + + + @classmethod + def setUpTestData(cls): + + + """ + cls._setUpModuleStore() + # Now yield to allow the test class to run its setUpClass() setup code. + yield + # Now call the base class, which calls back into the test class's setUpTestData(). + super(SharedModuleStoreTestCase, cls).setUpClass() + + @classmethod + def setUpClass(cls): + """ + For use when the test class has no setUpTestData() method -or- + when that method does not use variable set up in setUpClass(). + """ + super(SharedModuleStoreTestCase, cls).setUpClass() + cls._setUpModuleStore() + @classmethod def tearDownClass(cls): drop_mongo_collections() # pylint: disable=no-value-for-parameter diff --git a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py index 0217421b24..dac0e20f91 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py @@ -24,8 +24,6 @@ from opaque_keys.edx.locator import CourseLocator, LibraryLocator DRAFT_DIR = "drafts" PUBLISHED_DIR = "published" -EXPORT_VERSION_FILE = "format.json" -EXPORT_VERSION_KEY = "export_format" DEFAULT_CONTENT_FIELDS = ['metadata', 'data'] @@ -408,90 +406,3 @@ def export_extra_content(export_fs, modulestore, source_course_key, dest_course_ # export content fields other then metadata and data in json format in current directory _export_field_content(item, item_dir) - - -def convert_between_versions(source_dir, target_dir): - """ - Converts a version 0 export format to version 1, and vice versa. - - @param source_dir: the directory structure with the course export that should be converted. - The contents of source_dir will not be altered. - @param target_dir: the directory where the converted export should be written. - @return: the version number of the converted export. - """ - def convert_to_version_1(): - """ Convert a version 0 archive to version 0 """ - os.mkdir(copy_root) - with open(copy_root / EXPORT_VERSION_FILE, 'w') as f: - f.write('{{"{export_key}": 1}}\n'.format(export_key=EXPORT_VERSION_KEY)) - - # If a drafts folder exists, copy it over. - copy_drafts() - - # Now copy everything into the published directory - published_dir = copy_root / PUBLISHED_DIR - shutil.copytree(path(source_dir) / course_name, published_dir) - # And delete the nested drafts directory, if it exists. - nested_drafts_dir = published_dir / DRAFT_DIR - if nested_drafts_dir.isdir(): - shutil.rmtree(nested_drafts_dir) - - def convert_to_version_0(): - """ Convert a version 1 archive to version 0 """ - # Copy everything in "published" up to the top level. - published_dir = path(source_dir) / course_name / PUBLISHED_DIR - if not published_dir.isdir(): - raise ValueError("a version 1 archive must contain a published branch") - - shutil.copytree(published_dir, copy_root) - - # If there is a DRAFT branch, copy it. All other branches are ignored. - copy_drafts() - - def copy_drafts(): - """ - Copy drafts directory from the old archive structure to the new. - """ - draft_dir = path(source_dir) / course_name / DRAFT_DIR - if draft_dir.isdir(): - shutil.copytree(draft_dir, copy_root / DRAFT_DIR) - - root = os.listdir(source_dir) - if len(root) != 1 or (path(source_dir) / root[0]).isfile(): - raise ValueError("source archive does not have single course directory at top level") - - course_name = root[0] - - # For this version of the script, we simply convert back and forth between version 0 and 1. - original_version = get_version(path(source_dir) / course_name) - if original_version not in [0, 1]: - raise ValueError("unknown version: " + str(original_version)) - desired_version = 1 if original_version is 0 else 0 - - copy_root = path(target_dir) / course_name - - if desired_version == 1: - convert_to_version_1() - else: - convert_to_version_0() - - return desired_version - - -def get_version(course_path): - """ - Return the export format version number for the given - archive directory structure (represented as a path instance). - - If the archived file does not correspond to a known export - format, None will be returned. - """ - format_file = course_path / EXPORT_VERSION_FILE - if not format_file.isfile(): - return 0 - with open(format_file, "r") as f: - data = json.load(f) - if EXPORT_VERSION_KEY in data: - return data[EXPORT_VERSION_KEY] - - return None diff --git a/common/lib/xmodule/xmodule/tests/data/EmptyCourse.tar.gz b/common/lib/xmodule/xmodule/tests/data/EmptyCourse.tar.gz deleted file mode 100644 index b81f01a2cb..0000000000 Binary files a/common/lib/xmodule/xmodule/tests/data/EmptyCourse.tar.gz and /dev/null differ diff --git a/common/lib/xmodule/xmodule/tests/data/NoVersionNumber.tar.gz b/common/lib/xmodule/xmodule/tests/data/NoVersionNumber.tar.gz deleted file mode 100644 index ab61274c0a..0000000000 Binary files a/common/lib/xmodule/xmodule/tests/data/NoVersionNumber.tar.gz and /dev/null differ diff --git a/common/lib/xmodule/xmodule/tests/data/Version0_drafts.tar.gz b/common/lib/xmodule/xmodule/tests/data/Version0_drafts.tar.gz deleted file mode 100644 index e55649b1da..0000000000 Binary files a/common/lib/xmodule/xmodule/tests/data/Version0_drafts.tar.gz and /dev/null differ diff --git a/common/lib/xmodule/xmodule/tests/data/Version0_nodrafts.tar.gz b/common/lib/xmodule/xmodule/tests/data/Version0_nodrafts.tar.gz deleted file mode 100644 index 93e501d047..0000000000 Binary files a/common/lib/xmodule/xmodule/tests/data/Version0_nodrafts.tar.gz and /dev/null differ diff --git a/common/lib/xmodule/xmodule/tests/data/Version1_drafts.tar.gz b/common/lib/xmodule/xmodule/tests/data/Version1_drafts.tar.gz deleted file mode 100644 index 4cf81903c8..0000000000 Binary files a/common/lib/xmodule/xmodule/tests/data/Version1_drafts.tar.gz and /dev/null differ diff --git a/common/lib/xmodule/xmodule/tests/data/Version1_drafts_extra_branch.tar.gz b/common/lib/xmodule/xmodule/tests/data/Version1_drafts_extra_branch.tar.gz deleted file mode 100644 index 8c4ec41023..0000000000 Binary files a/common/lib/xmodule/xmodule/tests/data/Version1_drafts_extra_branch.tar.gz and /dev/null differ diff --git a/common/lib/xmodule/xmodule/tests/data/Version1_nodrafts.tar.gz b/common/lib/xmodule/xmodule/tests/data/Version1_nodrafts.tar.gz deleted file mode 100644 index b6673ba1a2..0000000000 Binary files a/common/lib/xmodule/xmodule/tests/data/Version1_nodrafts.tar.gz and /dev/null differ diff --git a/common/lib/xmodule/xmodule/tests/data/Version1_nopublished.tar.gz b/common/lib/xmodule/xmodule/tests/data/Version1_nopublished.tar.gz deleted file mode 100644 index 55e8e13d0f..0000000000 Binary files a/common/lib/xmodule/xmodule/tests/data/Version1_nopublished.tar.gz and /dev/null differ diff --git a/common/lib/xmodule/xmodule/tests/test_export.py b/common/lib/xmodule/xmodule/tests/test_export.py index b4da1df061..082ebc9741 100644 --- a/common/lib/xmodule/xmodule/tests/test_export.py +++ b/common/lib/xmodule/xmodule/tests/test_export.py @@ -25,9 +25,6 @@ from xblock.test.tools import blocks_are_equivalent from opaque_keys.edx.locations import Location from xmodule.modulestore import EdxJSONEncoder from xmodule.modulestore.xml import XMLModuleStore -from xmodule.modulestore.xml_exporter import ( - convert_between_versions, get_version -) from xmodule.tests import DATA_DIR from xmodule.tests.helpers import directories_equal from xmodule.x_module import XModuleMixin @@ -214,173 +211,3 @@ class TestEdxJsonEncoder(unittest.TestCase): with self.assertRaises(TypeError): self.encoder.default({}) - - -class ConvertExportFormat(unittest.TestCase): - """ - Tests converting between export formats. - """ - def setUp(self): - """ Common setup. """ - super(ConvertExportFormat, self).setUp() - - # Directory for expanding all the test archives - self.temp_dir = mkdtemp() - self.addCleanup(shutil.rmtree, self.temp_dir) - - # Directory where new archive will be created - self.result_dir = path(self.temp_dir) / uuid.uuid4().hex - os.mkdir(self.result_dir) - - # Expand all the test archives and store their paths. - self.data_dir = path(__file__).realpath().parent / 'data' - - self._version0_nodrafts = None - self._version1_nodrafts = None - self._version0_drafts = None - self._version1_drafts = None - self._version1_drafts_extra_branch = None - self._no_version = None - - @property - def version0_nodrafts(self): - "lazily expand this" - if self._version0_nodrafts is None: - self._version0_nodrafts = self._expand_archive('Version0_nodrafts.tar.gz') - return self._version0_nodrafts - - @property - def version1_nodrafts(self): - "lazily expand this" - if self._version1_nodrafts is None: - self._version1_nodrafts = self._expand_archive('Version1_nodrafts.tar.gz') - return self._version1_nodrafts - - @property - def version0_drafts(self): - "lazily expand this" - if self._version0_drafts is None: - self._version0_drafts = self._expand_archive('Version0_drafts.tar.gz') - return self._version0_drafts - - @property - def version1_drafts(self): - "lazily expand this" - if self._version1_drafts is None: - self._version1_drafts = self._expand_archive('Version1_drafts.tar.gz') - return self._version1_drafts - - @property - def version1_drafts_extra_branch(self): - "lazily expand this" - if self._version1_drafts_extra_branch is None: - self._version1_drafts_extra_branch = self._expand_archive('Version1_drafts_extra_branch.tar.gz') - return self._version1_drafts_extra_branch - - @property - def no_version(self): - "lazily expand this" - if self._no_version is None: - self._no_version = self._expand_archive('NoVersionNumber.tar.gz') - return self._no_version - - def _expand_archive(self, name): - """ Expand archive into a directory and return the directory. """ - target = path(self.temp_dir) / uuid.uuid4().hex - os.mkdir(target) - with tarfile.open(self.data_dir / name) as tar_file: - tar_file.extractall(path=target) - - return target - - def test_no_version(self): - """ Test error condition of no version number specified. """ - errstring = "unknown version" - with self.assertRaisesRegexp(ValueError, errstring): - convert_between_versions(self.no_version, self.result_dir) - - def test_no_published(self): - """ Test error condition of a version 1 archive with no published branch. """ - errstring = "version 1 archive must contain a published branch" - no_published = self._expand_archive('Version1_nopublished.tar.gz') - with self.assertRaisesRegexp(ValueError, errstring): - convert_between_versions(no_published, self.result_dir) - - def test_empty_course(self): - """ Test error condition of a version 1 archive with no published branch. """ - errstring = "source archive does not have single course directory at top level" - empty_course = self._expand_archive('EmptyCourse.tar.gz') - with self.assertRaisesRegexp(ValueError, errstring): - convert_between_versions(empty_course, self.result_dir) - - def test_convert_to_1_nodrafts(self): - """ - Test for converting from version 0 of export format to version 1 in a course with no drafts. - """ - self._verify_conversion(self.version0_nodrafts, self.version1_nodrafts) - - def test_convert_to_1_drafts(self): - """ - Test for converting from version 0 of export format to version 1 in a course with drafts. - """ - self._verify_conversion(self.version0_drafts, self.version1_drafts) - - def test_convert_to_0_nodrafts(self): - """ - Test for converting from version 1 of export format to version 0 in a course with no drafts. - """ - self._verify_conversion(self.version1_nodrafts, self.version0_nodrafts) - - def test_convert_to_0_drafts(self): - """ - Test for converting from version 1 of export format to version 0 in a course with drafts. - """ - self._verify_conversion(self.version1_drafts, self.version0_drafts) - - def test_convert_to_0_extra_branch(self): - """ - Test for converting from version 1 of export format to version 0 in a course - with drafts and an extra branch. - """ - self._verify_conversion(self.version1_drafts_extra_branch, self.version0_drafts) - - def test_equality_function(self): - """ - Check equality function returns False for unequal directories. - """ - self.assertFalse(directories_equal(self.version1_nodrafts, self.version0_nodrafts)) - self.assertFalse(directories_equal(self.version1_drafts_extra_branch, self.version1_drafts)) - - def test_version_0(self): - """ - Check that get_version correctly identifies a version 0 archive (old format). - """ - self.assertEqual(0, self._version_test(self.version0_nodrafts)) - - def test_version_1(self): - """ - Check that get_version correctly identifies a version 1 archive (new format). - """ - self.assertEqual(1, self._version_test(self.version1_nodrafts)) - - def test_version_missing(self): - """ - Check that get_version returns None if no version number is specified, - and the archive is not version 0. - """ - self.assertIsNone(self._version_test(self.no_version)) - - def _version_test(self, archive_dir): - """ - Helper function for version tests. - """ - root = os.listdir(archive_dir) - course_directory = archive_dir / root[0] - return get_version(course_directory) - - def _verify_conversion(self, source_archive, comparison_archive): - """ - Helper function for conversion tests. - """ - convert_between_versions(source_archive, self.result_dir) - self.assertTrue(directories_equal(self.result_dir, comparison_archive)) diff --git a/lms/djangoapps/ccx/tests/test_ccx_modulestore.py b/lms/djangoapps/ccx/tests/test_ccx_modulestore.py index 6f0ddbe14d..81f1e0a154 100644 --- a/lms/djangoapps/ccx/tests/test_ccx_modulestore.py +++ b/lms/djangoapps/ccx/tests/test_ccx_modulestore.py @@ -25,16 +25,12 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase): @classmethod def setUpClass(cls): super(TestCCXModulestoreWrapper, cls).setUpClass() - cls.course = course = CourseFactory.create() - cls.mooc_start = start = datetime.datetime( - 2010, 5, 12, 2, 42, tzinfo=pytz.UTC - ) - cls.mooc_due = due = datetime.datetime( - 2010, 7, 7, 0, 0, tzinfo=pytz.UTC - ) + cls.course = CourseFactory.create() + start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=pytz.UTC) + due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=pytz.UTC) # Create a course outline cls.chapters = chapters = [ - ItemFactory.create(start=start, parent=course) for _ in xrange(2) + ItemFactory.create(start=start, parent=cls.course) for _ in xrange(2) ] cls.sequentials = sequentials = [ ItemFactory.create(parent=c) for _ in xrange(2) for c in chapters @@ -48,20 +44,24 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase): ItemFactory.create(parent=v, category='html') for _ in xrange(2) for v in verticals ] + @classmethod + def setUpTestData(cls): + """ + Set up models for the whole TestCase. + """ + cls.user = UserFactory.create() + # Create instructor account + cls.coach = AdminFactory.create() + def setUp(self): """ Set up tests """ super(TestCCXModulestoreWrapper, self).setUp() - self.user = UserFactory.create() - - # Create instructor account - coach = AdminFactory.create() - self.ccx = ccx = CustomCourseForEdX( course_id=self.course.id, display_name='Test CCX', - coach=coach + coach=self.coach ) ccx.save() @@ -132,12 +132,13 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase): def test_publication_api(self): """verify that we can correctly discern a published item by ccx key""" - for expected in self.blocks: - block_key = self.ccx_locator.make_usage_key( - expected.location.block_type, expected.location.block_id - ) - self.assertTrue(self.store.has_published_version(expected)) - self.store.unpublish(block_key, self.user.id) - self.assertFalse(self.store.has_published_version(expected)) - self.store.publish(block_key, self.user.id) - self.assertTrue(self.store.has_published_version(expected)) + with self.store.bulk_operations(self.ccx_locator): + for expected in self.blocks: + block_key = self.ccx_locator.make_usage_key( + expected.location.block_type, expected.location.block_id + ) + self.assertTrue(self.store.has_published_version(expected)) + self.store.unpublish(block_key, self.user.id) + self.assertFalse(self.store.has_published_version(expected)) + self.store.publish(block_key, self.user.id) + self.assertTrue(self.store.has_published_version(expected)) diff --git a/lms/djangoapps/ccx/tests/test_overrides.py b/lms/djangoapps/ccx/tests/test_overrides.py index b0acec27d1..98d7c29cb7 100644 --- a/lms/djangoapps/ccx/tests/test_overrides.py +++ b/lms/djangoapps/ccx/tests/test_overrides.py @@ -13,7 +13,7 @@ from lms.djangoapps.courseware.tests.test_field_overrides import inject_field_ov from request_cache.middleware import RequestCache from student.tests.factories import AdminFactory from xmodule.modulestore.tests.django_utils import ( - ModuleStoreTestCase, + SharedModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE) from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -26,26 +26,25 @@ from lms.djangoapps.ccx.tests.utils import flatten, iter_blocks @attr('shard_1') @override_settings(FIELD_OVERRIDE_PROVIDERS=( 'ccx.overrides.CustomCoursesForEdxOverrideProvider',)) -class TestFieldOverrides(ModuleStoreTestCase): +class TestFieldOverrides(SharedModuleStoreTestCase): """ Make sure field overrides behave in the expected manner. """ MODULESTORE = TEST_DATA_SPLIT_MODULESTORE - def setUp(self): + @classmethod + def setUpClass(cls): """ - Set up tests + Course is created here and shared by all the class's tests. """ - super(TestFieldOverrides, self).setUp() - self.course = course = CourseFactory.create() - self.course.enable_ccx = True + super(TestFieldOverrides, cls).setUpClass() + cls.course = CourseFactory.create() + cls.course.enable_ccx = True # Create a course outline - self.mooc_start = start = datetime.datetime( - 2010, 5, 12, 2, 42, tzinfo=pytz.UTC) - self.mooc_due = due = datetime.datetime( - 2010, 7, 7, 0, 0, tzinfo=pytz.UTC) - chapters = [ItemFactory.create(start=start, parent=course) + start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=pytz.UTC) + due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=pytz.UTC) + chapters = [ItemFactory.create(start=start, parent=cls.course) for _ in xrange(2)] sequentials = flatten([ [ItemFactory.create(parent=chapter) for _ in xrange(2)] @@ -57,8 +56,14 @@ class TestFieldOverrides(ModuleStoreTestCase): [ItemFactory.create(parent=vertical) for _ in xrange(2)] for vertical in verticals]) + def setUp(self): + """ + Set up tests + """ + super(TestFieldOverrides, self).setUp() + self.ccx = ccx = CustomCourseForEdX( - course_id=course.id, + course_id=self.course.id, display_name='Test CCX', coach=AdminFactory.create()) ccx.save() @@ -70,7 +75,7 @@ class TestFieldOverrides(ModuleStoreTestCase): self.addCleanup(RequestCache.clear_request_cache) - inject_field_overrides(iter_blocks(ccx.course), course, AdminFactory.create()) + inject_field_overrides(iter_blocks(ccx.course), self.course, AdminFactory.create()) def cleanup_provider_classes(): """ diff --git a/lms/djangoapps/courseware/tests/test_field_overrides.py b/lms/djangoapps/courseware/tests/test_field_overrides.py index 65c263aad8..70eb9660f3 100644 --- a/lms/djangoapps/courseware/tests/test_field_overrides.py +++ b/lms/djangoapps/courseware/tests/test_field_overrides.py @@ -7,7 +7,7 @@ from nose.plugins.attrib import attr from django.test.utils import override_settings from xblock.field_data import DictFieldData from xmodule.modulestore.tests.factories import CourseFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from ..field_overrides import ( disable_overrides, @@ -23,14 +23,21 @@ TESTUSER = "testuser" @attr('shard_1') @override_settings(FIELD_OVERRIDE_PROVIDERS=( 'courseware.tests.test_field_overrides.TestOverrideProvider',)) -class OverrideFieldDataTests(ModuleStoreTestCase): +class OverrideFieldDataTests(SharedModuleStoreTestCase): """ Tests for `OverrideFieldData`. """ + @classmethod + def setUpClass(cls): + """ + Course is created here and shared by all the class's tests. + """ + super(OverrideFieldDataTests, cls).setUpClass() + cls.course = CourseFactory.create(enable_ccx=True) + def setUp(self): super(OverrideFieldDataTests, self).setUp() - self.course = CourseFactory.create(enable_ccx=True) OverrideFieldData.provider_classes = None def tearDown(self): diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py index 0fdecd1cac..407d0d6413 100644 --- a/lms/djangoapps/courseware/tests/test_navigation.py +++ b/lms/djangoapps/courseware/tests/test_navigation.py @@ -11,56 +11,68 @@ from django.test.utils import override_settings from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.factories import GlobalStaffFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from student.tests.factories import UserFactory +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.django import modulestore @attr('shard_1') -class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): +class TestNavigation(SharedModuleStoreTestCase, LoginEnrollmentTestCase): """ Check that navigation state is saved properly. """ STUDENT_INFO = [('view@test.com', 'foo'), ('view2@test.com', 'foo')] - def setUp(self): - super(TestNavigation, self).setUp() + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(TestNavigation, cls).setUpClassAndTestData(): + cls.test_course = CourseFactory.create() + cls.test_course_proctored = CourseFactory.create() + cls.course = CourseFactory.create() - self.test_course = CourseFactory.create() - self.course = CourseFactory.create() - self.chapter0 = ItemFactory.create(parent=self.course, - display_name='Overview') - self.chapter9 = ItemFactory.create(parent=self.course, - display_name='factory_chapter') - self.section0 = ItemFactory.create(parent=self.chapter0, - display_name='Welcome') - self.section9 = ItemFactory.create(parent=self.chapter9, - display_name='factory_section') - self.unit0 = ItemFactory.create(parent=self.section0, - display_name='New Unit') + @classmethod + def setUpTestData(cls): + cls.chapter0 = ItemFactory.create(parent=cls.course, + display_name='Overview') + cls.chapter9 = ItemFactory.create(parent=cls.course, + display_name='factory_chapter') + cls.section0 = ItemFactory.create(parent=cls.chapter0, + display_name='Welcome') + cls.section9 = ItemFactory.create(parent=cls.chapter9, + display_name='factory_section') + cls.unit0 = ItemFactory.create(parent=cls.section0, + display_name='New Unit') - self.chapterchrome = ItemFactory.create(parent=self.course, - display_name='Chrome') - self.chromelesssection = ItemFactory.create(parent=self.chapterchrome, - display_name='chromeless', - chrome='none') - self.accordionsection = ItemFactory.create(parent=self.chapterchrome, - display_name='accordion', - chrome='accordion') - self.tabssection = ItemFactory.create(parent=self.chapterchrome, - display_name='tabs', - chrome='tabs') - self.defaultchromesection = ItemFactory.create( - parent=self.chapterchrome, + cls.chapterchrome = ItemFactory.create(parent=cls.course, + display_name='Chrome') + cls.chromelesssection = ItemFactory.create(parent=cls.chapterchrome, + display_name='chromeless', + chrome='none') + cls.accordionsection = ItemFactory.create(parent=cls.chapterchrome, + display_name='accordion', + chrome='accordion') + cls.tabssection = ItemFactory.create(parent=cls.chapterchrome, + display_name='tabs', + chrome='tabs') + cls.defaultchromesection = ItemFactory.create( + parent=cls.chapterchrome, display_name='defaultchrome', ) - self.fullchromesection = ItemFactory.create(parent=self.chapterchrome, - display_name='fullchrome', - chrome='accordion,tabs') - self.tabtest = ItemFactory.create(parent=self.chapterchrome, - display_name='progress_tab', - default_tab='progress') + cls.fullchromesection = ItemFactory.create(parent=cls.chapterchrome, + display_name='fullchrome', + chrome='accordion,tabs') + cls.tabtest = ItemFactory.create(parent=cls.chapterchrome, + display_name='progress_tab', + default_tab='progress') + + cls.staff_user = GlobalStaffFactory() + cls.user = UserFactory() + + def setUp(self): + super(TestNavigation, self).setUp() # Create student accounts and activate them. for i in range(len(self.STUDENT_INFO)): @@ -69,8 +81,6 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): self.create_account(username, email, password) self.activate_user(email) - self.staff_user = GlobalStaffFactory() - def assertTabActive(self, tabname, response): ''' Check if the progress tab is active in the tab set ''' for line in response.content.split('\n'): @@ -278,9 +288,9 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): email, password = self.STUDENT_INFO[0] self.login(email, password) - self.enroll(self.test_course, True) + self.enroll(self.test_course_proctored, True) - test_course_id = self.test_course.id.to_deprecated_string() + test_course_id = self.test_course_proctored.id.to_deprecated_string() with patch.dict(settings.FEATURES, {'ENABLE_SPECIAL_EXAMS': False}): url = reverse( @@ -302,10 +312,10 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): # now set up a course which is proctored enabled - self.test_course.enable_proctored_exams = True - self.test_course.save() + self.test_course_proctored.enable_proctored_exams = True + self.test_course_proctored.save() - modulestore().update_item(self.test_course, self.user.id) + modulestore().update_item(self.test_course_proctored, self.user.id) resp = self.client.get(url) diff --git a/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py b/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py index 80438aed11..ab7099612f 100644 --- a/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py +++ b/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py @@ -18,7 +18,7 @@ import dashboard.git_import as git_import from dashboard.git_import import GitImportError from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST @@ -37,7 +37,7 @@ FEATURES_WITH_SSL_AUTH['AUTH_USE_CERTIFICATES'] = True @override_settings(MONGODB_LOG=TEST_MONGODB_LOG) @unittest.skipUnless(settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD'), "ENABLE_SYSADMIN_DASHBOARD not set") -class TestGitAddCourse(ModuleStoreTestCase): +class TestGitAddCourse(SharedModuleStoreTestCase): """ Tests the git_add_course management command for proper functions. """ diff --git a/lms/djangoapps/dashboard/sysadmin.py b/lms/djangoapps/dashboard/sysadmin.py index fcbaa9bdc3..3d89a86f77 100644 --- a/lms/djangoapps/dashboard/sysadmin.py +++ b/lms/djangoapps/dashboard/sysadmin.py @@ -59,10 +59,6 @@ class SysadminDashboardView(TemplateView): """ self.def_ms = modulestore() - - self.is_using_mongo = True - if self.def_ms.get_modulestore_type(None) == 'xml': - self.is_using_mongo = False self.msg = u'' self.datatable = [] super(SysadminDashboardView, self).__init__(**kwargs) @@ -374,10 +370,7 @@ class Courses(SysadminDashboardView): return _("The git repo location should end with '.git', " "and be a valid url") - if self.is_using_mongo: - return self.import_mongo_course(gitloc, branch) - - return self.import_xml_course(gitloc, branch) + return self.import_mongo_course(gitloc, branch) def import_mongo_course(self, gitloc, branch): """ @@ -429,80 +422,6 @@ class Courses(SysadminDashboardView): msg += u"
{0}
".format(escape(ret)) return msg - def import_xml_course(self, gitloc, branch): - """Imports a git course into the XMLModuleStore""" - - msg = u'' - if not getattr(settings, 'GIT_IMPORT_WITH_XMLMODULESTORE', False): - # Translators: "GIT_IMPORT_WITH_XMLMODULESTORE" is a variable name. - # "XMLModuleStore" and "MongoDB" are database systems. You should not - # translate these names. - return _('Refusing to import. GIT_IMPORT_WITH_XMLMODULESTORE is ' - 'not turned on, and it is generally not safe to import ' - 'into an XMLModuleStore with multithreaded. We ' - 'recommend you enable the MongoDB based module store ' - 'instead, unless this is a development environment.') - cdir = (gitloc.rsplit('/', 1)[1])[:-4] - gdir = settings.DATA_DIR / cdir - if os.path.exists(gdir): - msg += _("The course {0} already exists in the data directory! " - "(reloading anyway)").format(cdir) - cmd = ['git', 'pull', ] - cwd = gdir - else: - cmd = ['git', 'clone', gitloc, ] - cwd = settings.DATA_DIR - cwd = os.path.abspath(cwd) - try: - cmd_output = escape( - subprocess.check_output(cmd, stderr=subprocess.STDOUT, cwd=cwd) - ) - except subprocess.CalledProcessError as ex: - log.exception('Git pull or clone output was: %r', ex.output) - # Translators: unable to download the course content from - # the source git repository. Clone occurs if this is brand - # new, and pull is when it is being updated from the - # source. - return _('Unable to clone or pull repository. Please check ' - 'your url. Output was: {0!r}').format(ex.output) - - msg += u'
{0}
'.format(cmd_output) - if not os.path.exists(gdir): - msg += _('Failed to clone repository to {directory_name}').format(directory_name=gdir) - return msg - # Change branch if specified - if branch: - try: - git_import.switch_branch(branch, gdir) - except GitImportError as ex: - return str(ex) - # Translators: This is a git repository branch, which is a - # specific version of a courses content - msg += u'

{0}

'.format( - _('Successfully switched to branch: ' - '{branch_name}').format(branch_name=branch)) - - self.def_ms.try_load_course(os.path.abspath(gdir)) - errlog = self.def_ms.errored_courses.get(cdir, '') - if errlog: - msg += u'
{0}
'.format(escape(errlog)) - else: - course = self.def_ms.courses[os.path.abspath(gdir)] - msg += _('Loaded course {course_name}
Errors:').format( - course_name="{} {}".format(cdir, course.display_name) - ) - errors = self.def_ms.get_course_errors(course.id) - if not errors: - msg += u'None' - else: - msg += u'
    ' - for (summary, err) in errors: - msg += u'
  • {0}: {1}
  • '.format(escape(summary), - escape(err)) - msg += u'
' - - return msg - def make_datatable(self): """Creates course information datatable""" diff --git a/lms/djangoapps/dashboard/tests/test_sysadmin.py b/lms/djangoapps/dashboard/tests/test_sysadmin.py index ee0355cf68..a3a954c1d9 100644 --- a/lms/djangoapps/dashboard/tests/test_sysadmin.py +++ b/lms/djangoapps/dashboard/tests/test_sysadmin.py @@ -20,8 +20,6 @@ from django.utils.translation import ugettext as _ import mongoengine from opaque_keys.edx.locations import SlashSeparatedCourseKey -from xmodule.modulestore.tests.django_utils import TEST_DATA_XML_MODULESTORE - from dashboard.models import CourseImportLog from dashboard.sysadmin import Users from dashboard.git_import import GitImportError @@ -30,7 +28,7 @@ from external_auth.models import ExternalAuthMap from student.roles import CourseStaffRole, GlobalStaff from student.tests.factories import UserFactory from xmodule.modulestore.django import modulestore -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST @@ -46,7 +44,7 @@ FEATURES_WITH_SSL_AUTH = settings.FEATURES.copy() FEATURES_WITH_SSL_AUTH['AUTH_USE_CERTIFICATES'] = True -class SysadminBaseTestCase(ModuleStoreTestCase): +class SysadminBaseTestCase(SharedModuleStoreTestCase): """ Base class with common methods used in XML and Mongo tests """ @@ -57,7 +55,7 @@ class SysadminBaseTestCase(ModuleStoreTestCase): def setUp(self): """Setup test case by adding primary user.""" - super(SysadminBaseTestCase, self).setUp(create_user=False) + super(SysadminBaseTestCase, self).setUp() self.user = UserFactory.create(username='test_user', email='test_user+sysadmin@edx.org', password='foo') @@ -115,297 +113,6 @@ class SysadminBaseTestCase(ModuleStoreTestCase): self.addCleanup(shutil.rmtree, path) -@attr('shard_1') -@unittest.skipUnless(settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD'), - "ENABLE_SYSADMIN_DASHBOARD not set") -@override_settings(GIT_IMPORT_WITH_XMLMODULESTORE=True) -class TestSysadmin(SysadminBaseTestCase): - """ - Test sysadmin dashboard features using XMLModuleStore - """ - MODULESTORE = TEST_DATA_XML_MODULESTORE - - def test_staff_access(self): - """Test access controls.""" - - test_views = ['sysadmin', 'sysadmin_courses', 'sysadmin_staffing', ] - for view in test_views: - response = self.client.get(reverse(view)) - self.assertEqual(response.status_code, 302) - - self.user.is_staff = False - self.user.save() - - logged_in = self.client.login(username=self.user.username, - password='foo') - self.assertTrue(logged_in) - - for view in test_views: - response = self.client.get(reverse(view)) - self.assertEqual(response.status_code, 404) - - response = self.client.get(reverse('gitlogs')) - self.assertEqual(response.status_code, 404) - - self.user.is_staff = True - self.user.save() - - self.client.logout() - self.client.login(username=self.user.username, password='foo') - - for view in test_views: - response = self.client.get(reverse(view)) - self.assertTrue(response.status_code, 200) - - response = self.client.get(reverse('gitlogs')) - self.assertTrue(response.status_code, 200) - - def test_user_mod(self): - """Create and delete a user""" - - self._setstaff_login() - - self.client.login(username=self.user.username, password='foo') - - # Create user tests - - # No uname - response = self.client.post(reverse('sysadmin'), - {'action': 'create_user', - 'student_fullname': 'blah', - 'student_password': 'foozor', }) - self.assertIn('Must provide username', response.content.decode('utf-8')) - # no full name - response = self.client.post(reverse('sysadmin'), - {'action': 'create_user', - 'student_uname': 'test_cuser+sysadmin@edx.org', - 'student_password': 'foozor', }) - self.assertIn('Must provide full name', response.content.decode('utf-8')) - - # Test create valid user - self.client.post(reverse('sysadmin'), - {'action': 'create_user', - 'student_uname': 'test_cuser+sysadmin@edx.org', - 'student_fullname': 'test cuser', - 'student_password': 'foozor', }) - - self.assertIsNotNone( - User.objects.get(username='test_cuser+sysadmin@edx.org', - email='test_cuser+sysadmin@edx.org')) - - # login as new user to confirm - self.assertTrue(self.client.login( - username='test_cuser+sysadmin@edx.org', password='foozor')) - - self.client.logout() - self.client.login(username=self.user.username, password='foo') - - # Delete user tests - - # Try no username - response = self.client.post(reverse('sysadmin'), - {'action': 'del_user', }) - self.assertIn('Must provide username', response.content.decode('utf-8')) - - # Try bad usernames - response = self.client.post(reverse('sysadmin'), - {'action': 'del_user', - 'student_uname': 'flabbergast@example.com', - 'student_fullname': 'enigma jones', }) - self.assertIn('Cannot find user with email address', response.content.decode('utf-8')) - - response = self.client.post(reverse('sysadmin'), - {'action': 'del_user', - 'student_uname': 'flabbergast', - 'student_fullname': 'enigma jones', }) - self.assertIn('Cannot find user with username', response.content.decode('utf-8')) - - self.client.post(reverse('sysadmin'), - {'action': 'del_user', - 'student_uname': 'test_cuser+sysadmin@edx.org', - 'student_fullname': 'test cuser', }) - - self.assertEqual(0, len(User.objects.filter( - username='test_cuser+sysadmin@edx.org', - email='test_cuser+sysadmin@edx.org'))) - - self.assertEqual(1, len(User.objects.all())) - - def test_user_csv(self): - """Download and validate user CSV""" - - num_test_users = 100 - self._setstaff_login() - - # Stuff full of users to test streaming - for user_num in xrange(num_test_users): - Users().create_user('testingman_with_long_name{}'.format(user_num), - 'test test') - - response = self.client.post(reverse('sysadmin'), - {'action': 'download_users', }) - - self.assertIn('attachment', response['Content-Disposition']) - self.assertEqual('text/csv', response['Content-Type']) - self.assertIn('test_user', response.content) - self.assertTrue(num_test_users + 2, len(response.content.splitlines())) - - # Clean up - User.objects.filter( - username__startswith='testingman_with_long_name').delete() - - @override_settings(FEATURES=FEATURES_WITH_SSL_AUTH) - def test_authmap_repair(self): - """Run authmap check and repair""" - - self._setstaff_login() - - Users().create_user('test0', 'test test') - # Will raise exception, so no assert needed - eamap = ExternalAuthMap.objects.get(external_name='test test') - mitu = User.objects.get(username='test0') - - self.assertTrue(check_password(eamap.internal_password, mitu.password)) - mitu.set_password('not autogenerated') - mitu.save() - self.assertFalse(check_password(eamap.internal_password, mitu.password)) - - # Create really non user AuthMap - ExternalAuthMap(external_id='ll', - external_domain='ll', - external_credentials='{}', - external_email='a@b.c', - external_name='c', - internal_password='').save() - - response = self.client.post(reverse('sysadmin'), - {'action': 'repair_eamap', }) - - self.assertIn('{0} test0'.format('Failed in authenticating'), - response.content) - self.assertIn('fixed password', response.content.decode('utf-8')) - - self.assertTrue(self.client.login(username='test0', - password=eamap.internal_password)) - - # Check for all OK - self._setstaff_login() - response = self.client.post(reverse('sysadmin'), - {'action': 'repair_eamap', }) - self.assertIn('All ok!', response.content.decode('utf-8')) - - def test_xml_course_add_delete(self): - """add and delete course from xml module store""" - - self._setstaff_login() - - # Try bad git repo - response = self.client.post(reverse('sysadmin_courses'), { - 'repo_location': 'github.com/mitocw/edx4edx_lite', - 'action': 'add_course', }) - self.assertIn(_("The git repo location should end with '.git', " - "and be a valid url"), response.content.decode('utf-8')) - - response = self.client.post(reverse('sysadmin_courses'), { - 'repo_location': 'http://example.com/not_real.git', - 'action': 'add_course', }) - self.assertIn('Unable to clone or pull repository', - response.content.decode('utf-8')) - # Create git loaded course - response = self._add_edx4edx() - - def_ms = modulestore() - - self.assertEqual('xml', def_ms.get_modulestore_type(None)) - course = def_ms.courses.get('{0}/edx4edx_lite'.format( - os.path.abspath(settings.DATA_DIR)), None) - self.assertIsNotNone(course) - - # Delete a course - self._rm_edx4edx() - course = def_ms.courses.get('{0}/edx4edx_lite'.format( - os.path.abspath(settings.DATA_DIR)), None) - self.assertIsNone(course) - - # Load a bad git branch - response = self._add_edx4edx('asdfasdfasdf') - self.assertIn(GitImportError.REMOTE_BRANCH_MISSING, - response.content.decode('utf-8')) - - # Load a course from a git branch - self._add_edx4edx(self.TEST_BRANCH) - course = def_ms.courses.get('{0}/edx4edx_lite'.format( - os.path.abspath(settings.DATA_DIR)), None) - self.assertIsNotNone(course) - self.assertEqual(self.TEST_BRANCH_COURSE, course.id) - self._rm_edx4edx() - - # Try and delete a non-existent course - response = self.client.post(reverse('sysadmin_courses'), - {'course_id': 'foobar/foo/blah', - 'action': 'del_course', }) - self.assertIn('Error - cannot get course with ID', - response.content.decode('utf-8')) - - @override_settings(GIT_IMPORT_WITH_XMLMODULESTORE=False) - def test_xml_safety_flag(self): - """Make sure the settings flag to disable xml imports is working""" - - self._setstaff_login() - response = self._add_edx4edx() - self.assertIn('GIT_IMPORT_WITH_XMLMODULESTORE', response.content) - - def_ms = modulestore() - course = def_ms.courses.get('{0}/edx4edx_lite'.format( - os.path.abspath(settings.DATA_DIR)), None) - self.assertIsNone(course) - - def test_git_pull(self): - """Make sure we can pull""" - - self._setstaff_login() - - response = self._add_edx4edx() - response = self._add_edx4edx() - self.assertIn(_("The course {0} already exists in the data directory! " - "(reloading anyway)").format('edx4edx_lite'), - response.content.decode('utf-8')) - self._rm_edx4edx() - - def test_staff_csv(self): - """Download and validate staff CSV""" - - self._setstaff_login() - self._add_edx4edx() - - def_ms = modulestore() - course = def_ms.get_course(SlashSeparatedCourseKey('MITx', 'edx4edx', 'edx4edx')) - CourseStaffRole(course.id).add_users(self.user) - - response = self.client.post(reverse('sysadmin_staffing'), - {'action': 'get_staff_csv', }) - self.assertIn('attachment', response['Content-Disposition']) - self.assertEqual('text/csv', response['Content-Type']) - columns = ['course_id', 'role', 'username', - 'email', 'full_name', ] - self.assertIn(','.join('"' + c + '"' for c in columns), - response.content) - - self._rm_edx4edx() - - def test_enrollment_page(self): - """ - Adds a course and makes sure that it shows up on the staffing and - enrollment page - """ - - self._setstaff_login() - self._add_edx4edx() - response = self.client.get(reverse('sysadmin_staffing')) - self.assertIn('edx4edx', response.content) - self._rm_edx4edx() - - @attr('shard_1') @override_settings(MONGODB_LOG=TEST_MONGODB_LOG) @unittest.skipUnless(settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD'), diff --git a/lms/djangoapps/django_comment_client/base/tests.py b/lms/djangoapps/django_comment_client/base/tests.py index 464750dda4..abda87d29c 100644 --- a/lms/djangoapps/django_comment_client/base/tests.py +++ b/lms/djangoapps/django_comment_client/base/tests.py @@ -6,7 +6,7 @@ import ddt from django.conf import settings from django.core.cache import caches -from django.test.client import Client, RequestFactory +from django.test.client import RequestFactory from django.contrib.auth.models import User from django.core.management import call_command from django.core.urlresolvers import reverse @@ -27,7 +27,7 @@ from student.tests.factories import CourseEnrollmentFactory, UserFactory, Course from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMembershipFactory from util.testing import UrlResetMixin from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import check_mongo_calls from xmodule.modulestore.django import modulestore from xmodule.modulestore import ModuleStoreEnum @@ -177,12 +177,6 @@ class ThreadActionGroupIdTestCase( class ViewsTestCaseMixin(object): - """ - This class is used by both ViewsQueryCountTestCase and ViewsTestCase. By - breaking out set_up_course into its own method, ViewsQueryCountTestCase - can build a course in a particular modulestore, while ViewsTestCase can - just run it in setUp for all tests. - """ def set_up_course(self, module_count=0): """ @@ -234,7 +228,6 @@ class ViewsTestCaseMixin(object): CourseEnrollmentFactory(user=self.moderator, course_id=self.course.id) self.moderator.roles.add(Role.objects.get(name="Moderator", course_id=self.course.id)) - self.client = Client() assert_true(self.client.login(username='student', password=self.password)) def _setup_mock_request(self, mock_request, include_depth=False): @@ -379,9 +372,7 @@ class ViewsQueryCountTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSet @ddt.data( (ModuleStoreEnum.Type.mongo, 3, 4, 26), - (ModuleStoreEnum.Type.mongo, 20, 4, 26), (ModuleStoreEnum.Type.split, 3, 13, 26), - (ModuleStoreEnum.Type.split, 20, 13, 26), ) @ddt.unpack @count_queries @@ -390,9 +381,7 @@ class ViewsQueryCountTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSet @ddt.data( (ModuleStoreEnum.Type.mongo, 3, 3, 20), - (ModuleStoreEnum.Type.mongo, 20, 3, 20), (ModuleStoreEnum.Type.split, 3, 10, 20), - (ModuleStoreEnum.Type.split, 20, 10, 20), ) @ddt.unpack @count_queries @@ -404,19 +393,62 @@ class ViewsQueryCountTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSet @patch('lms.lib.comment_client.utils.requests.request', autospec=True) class ViewsTestCase( UrlResetMixin, - ModuleStoreTestCase, + SharedModuleStoreTestCase, MockRequestSetupMixin, ViewsTestCaseMixin, MockSignalHandlerMixin ): + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(ViewsTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create( + org='MITx', course='999', + discussion_topics={"Some Topic": {"id": "some_topic"}}, + display_name='Robot Super Course', + ) + + @classmethod + def setUpTestData(cls): + super(ViewsTestCase, cls).setUpTestData() + + cls.course_id = cls.course.id + + # seed the forums permissions and roles + call_command('seed_permissions_roles', unicode(cls.course_id)) + @patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): # Patching the ENABLE_DISCUSSION_SERVICE value affects the contents of urls.py, # so we need to call super.setUp() which reloads urls.py (because # of the UrlResetMixin) - super(ViewsTestCase, self).setUp(create_user=False) - self.set_up_course() + super(ViewsTestCase, self).setUp() + + # Patch the comment client user save method so it does not try + # to create a new cc user when creating a django user + with patch('student.models.cc.User.save'): + uname = 'student' + email = 'student@edx.org' + self.password = 'test' # pylint: disable=attribute-defined-outside-init + + # Create the user and make them active so we can log them in. + self.student = User.objects.create_user(uname, email, self.password) # pylint: disable=attribute-defined-outside-init + self.student.is_active = True + self.student.save() + + # Add a discussion moderator + self.moderator = UserFactory.create(password=self.password) # pylint: disable=attribute-defined-outside-init + + # Enroll the student in the course + CourseEnrollmentFactory(user=self.student, + course_id=self.course_id) + + # Enroll the moderator and give them the appropriate roles + CourseEnrollmentFactory(user=self.moderator, course_id=self.course.id) + self.moderator.roles.add(Role.objects.get(name="Moderator", course_id=self.course.id)) + + assert_true(self.client.login(username='student', password=self.password)) @contextmanager def assert_discussion_signals(self, signal, user=None): @@ -986,18 +1018,32 @@ class ViewsTestCase( @patch("lms.lib.comment_client.utils.requests.request", autospec=True) @disable_signal(views, 'comment_endorsed') -class ViewPermissionsTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSetupMixin): +class ViewPermissionsTestCase(UrlResetMixin, SharedModuleStoreTestCase, MockRequestSetupMixin): + + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(ViewPermissionsTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() + + @classmethod + def setUpTestData(cls): + super(ViewPermissionsTestCase, cls).setUpTestData() + + seed_permissions_roles(cls.course.id) + + cls.password = "test password" + cls.student = UserFactory.create(password=cls.password) + cls.moderator = UserFactory.create(password=cls.password) + + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) + CourseEnrollmentFactory(user=cls.moderator, course_id=cls.course.id) + + cls.moderator.roles.add(Role.objects.get(name="Moderator", course_id=cls.course.id)) + @patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(ViewPermissionsTestCase, self).setUp() - self.password = "test password" - self.course = CourseFactory.create() - seed_permissions_roles(self.course.id) - self.student = UserFactory.create(password=self.password) - self.moderator = UserFactory.create(password=self.password) - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) - CourseEnrollmentFactory(user=self.moderator, course_id=self.course.id) - self.moderator.roles.add(Role.objects.get(name="Moderator", course_id=self.course.id)) def test_pin_thread_as_student(self, mock_request): self._set_mock_request_data(mock_request, {}) @@ -1079,14 +1125,21 @@ class ViewPermissionsTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSet self.assertEqual(response.status_code, 200) -class CreateThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin): - def setUp(self): - super(CreateThreadUnicodeTestCase, self).setUp() +class CreateThreadUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin): - self.course = CourseFactory.create() - seed_permissions_roles(self.course.id) - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(CreateThreadUnicodeTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() + + @classmethod + def setUpTestData(cls): + super(CreateThreadUnicodeTestCase, cls).setUpTestData() + + seed_permissions_roles(cls.course.id) + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) @patch('lms.lib.comment_client.utils.requests.request', autospec=True) def _test_unicode_data(self, text, mock_request,): @@ -1108,14 +1161,21 @@ class CreateThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockReq @disable_signal(views, 'thread_edited') -class UpdateThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin): - def setUp(self): - super(UpdateThreadUnicodeTestCase, self).setUp() +class UpdateThreadUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin): - self.course = CourseFactory.create() - seed_permissions_roles(self.course.id) - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(UpdateThreadUnicodeTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() + + @classmethod + def setUpTestData(cls): + super(UpdateThreadUnicodeTestCase, cls).setUpTestData() + + seed_permissions_roles(cls.course.id) + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) @patch('django_comment_client.utils.get_discussion_categories_ids', return_value=["test_commentable"]) @patch('lms.lib.comment_client.utils.requests.request', autospec=True) @@ -1138,14 +1198,21 @@ class UpdateThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockReq @disable_signal(views, 'comment_created') -class CreateCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin): - def setUp(self): - super(CreateCommentUnicodeTestCase, self).setUp() +class CreateCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin): - self.course = CourseFactory.create() - seed_permissions_roles(self.course.id) - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(CreateCommentUnicodeTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() + + @classmethod + def setUpTestData(cls): + super(CreateCommentUnicodeTestCase, cls).setUpTestData() + + seed_permissions_roles(cls.course.id) + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) @patch('lms.lib.comment_client.utils.requests.request', autospec=True) def _test_unicode_data(self, text, mock_request): @@ -1173,14 +1240,21 @@ class CreateCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRe @disable_signal(views, 'comment_edited') -class UpdateCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin): - def setUp(self): - super(UpdateCommentUnicodeTestCase, self).setUp() +class UpdateCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin): - self.course = CourseFactory.create() - seed_permissions_roles(self.course.id) - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(UpdateCommentUnicodeTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() + + @classmethod + def setUpTestData(cls): + super(UpdateCommentUnicodeTestCase, cls).setUpTestData() + + seed_permissions_roles(cls.course.id) + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) @patch('lms.lib.comment_client.utils.requests.request', autospec=True) def _test_unicode_data(self, text, mock_request): @@ -1199,17 +1273,23 @@ class UpdateCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRe @disable_signal(views, 'comment_created') -class CreateSubCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin): +class CreateSubCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin): """ Make sure comments under a response can handle unicode. """ - def setUp(self): - super(CreateSubCommentUnicodeTestCase, self).setUp() + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(CreateSubCommentUnicodeTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() - self.course = CourseFactory.create() - seed_permissions_roles(self.course.id) - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) + @classmethod + def setUpTestData(cls): + super(CreateSubCommentUnicodeTestCase, cls).setUpTestData() + + seed_permissions_roles(cls.course.id) + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) @patch('lms.lib.comment_client.utils.requests.request', autospec=True) def _test_unicode_data(self, text, mock_request): @@ -1245,7 +1325,7 @@ class CreateSubCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, Moc @disable_signal(views, 'comment_created') @disable_signal(views, 'comment_voted') @disable_signal(views, 'comment_deleted') -class TeamsPermissionsTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSetupMixin): +class TeamsPermissionsTestCase(UrlResetMixin, SharedModuleStoreTestCase, MockRequestSetupMixin): # Most of the test points use the same ddt data. # args: user, commentable_id, status_code ddt_permissions_args = [ @@ -1261,38 +1341,48 @@ class TeamsPermissionsTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSe ('moderator', 'team_commentable_id', 200) ] + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(TeamsPermissionsTestCase, cls).setUpClassAndTestData(): + teams_configuration = { + 'topics': [{'id': "topic_id", 'name': 'Solar Power', 'description': 'Solar power is hot'}] + } + cls.course = CourseFactory.create(teams_configuration=teams_configuration) + + @classmethod + def setUpTestData(cls): + super(TeamsPermissionsTestCase, cls).setUpTestData() + + cls.password = "test password" + seed_permissions_roles(cls.course.id) + + # Create 3 users-- student in team, student not in team, discussion moderator + cls.student_in_team = UserFactory.create(password=cls.password) + cls.student_not_in_team = UserFactory.create(password=cls.password) + cls.moderator = UserFactory.create(password=cls.password) + CourseEnrollmentFactory(user=cls.student_in_team, course_id=cls.course.id) + CourseEnrollmentFactory(user=cls.student_not_in_team, course_id=cls.course.id) + CourseEnrollmentFactory(user=cls.moderator, course_id=cls.course.id) + cls.moderator.roles.add(Role.objects.get(name="Moderator", course_id=cls.course.id)) + + # Create a team. + cls.team_commentable_id = "team_discussion_id" + cls.team = CourseTeamFactory.create( + name=u'The Only Team', + course_id=cls.course.id, + topic_id='topic_id', + discussion_topic_id=cls.team_commentable_id + ) + + cls.team.add_user(cls.student_in_team) + + # Dummy commentable ID not linked to a team + cls.course_commentable_id = "course_level_commentable" + @patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(TeamsPermissionsTestCase, self).setUp() - self.password = "test password" - teams_configuration = { - 'topics': [{'id': "topic_id", 'name': 'Solar Power', 'description': 'Solar power is hot'}] - } - self.course = CourseFactory.create(teams_configuration=teams_configuration) - seed_permissions_roles(self.course.id) - - # Create 3 users-- student in team, student not in team, discussion moderator - self.student_in_team = UserFactory.create(password=self.password) - self.student_not_in_team = UserFactory.create(password=self.password) - self.moderator = UserFactory.create(password=self.password) - CourseEnrollmentFactory(user=self.student_in_team, course_id=self.course.id) - CourseEnrollmentFactory(user=self.student_not_in_team, course_id=self.course.id) - CourseEnrollmentFactory(user=self.moderator, course_id=self.course.id) - self.moderator.roles.add(Role.objects.get(name="Moderator", course_id=self.course.id)) - - # Create a team. - self.team_commentable_id = "team_discussion_id" - self.team = CourseTeamFactory.create( - name=u'The Only Team', - course_id=self.course.id, - topic_id='topic_id', - discussion_topic_id=self.team_commentable_id - ) - - self.team.add_user(self.student_in_team) - - # Dummy commentable ID not linked to a team - self.course_commentable_id = "course_level_commentable" def _setup_mock(self, user, mock_request, data): user = getattr(self, user) @@ -1501,18 +1591,26 @@ TEAM_COMMENTABLE_ID = 'test-team-discussion' @disable_signal(views, 'comment_created') @ddt.ddt -class ForumEventTestCase(ModuleStoreTestCase, MockRequestSetupMixin): +class ForumEventTestCase(SharedModuleStoreTestCase, MockRequestSetupMixin): """ Forum actions are expected to launch analytics events. Test these here. """ - def setUp(self): - super(ForumEventTestCase, self).setUp() - self.course = CourseFactory.create() - seed_permissions_roles(self.course.id) - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) - self.student.roles.add(Role.objects.get(name="Student", course_id=self.course.id)) - CourseAccessRoleFactory(course_id=self.course.id, user=self.student, role='Wizard') + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(ForumEventTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() + + @classmethod + def setUpTestData(cls): + super(ForumEventTestCase, cls).setUpTestData() + + seed_permissions_roles(cls.course.id) + + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) + cls.student.roles.add(Role.objects.get(name="Student", course_id=cls.course.id)) + CourseAccessRoleFactory(course_id=cls.course.id, user=cls.student, role='Wizard') @patch('eventtracking.tracker.emit') @patch('lms.lib.comment_client.utils.requests.request', autospec=True) @@ -1676,7 +1774,24 @@ class ForumEventTestCase(ModuleStoreTestCase, MockRequestSetupMixin): self.assertEqual(event['vote_value'], 'up') -class UsersEndpointTestCase(ModuleStoreTestCase, MockRequestSetupMixin): +class UsersEndpointTestCase(SharedModuleStoreTestCase, MockRequestSetupMixin): + + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(UsersEndpointTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() + + @classmethod + def setUpTestData(cls): + super(UsersEndpointTestCase, cls).setUpTestData() + + seed_permissions_roles(cls.course.id) + + cls.student = UserFactory.create() + cls.enrollment = CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) + cls.other_user = UserFactory.create(username="other") + CourseEnrollmentFactory(user=cls.other_user, course_id=cls.course.id) def set_post_counts(self, mock_request, threads_count=1, comments_count=1): """ @@ -1687,16 +1802,6 @@ class UsersEndpointTestCase(ModuleStoreTestCase, MockRequestSetupMixin): "comments_count": comments_count, }) - def setUp(self): - super(UsersEndpointTestCase, self).setUp() - - self.course = CourseFactory.create() - seed_permissions_roles(self.course.id) - self.student = UserFactory.create() - self.enrollment = CourseEnrollmentFactory(user=self.student, course_id=self.course.id) - self.other_user = UserFactory.create(username="other") - CourseEnrollmentFactory(user=self.other_user, course_id=self.course.id) - def make_request(self, method='get', course_id=None, **kwargs): course_id = course_id or self.course.id request = getattr(RequestFactory(), method)("dummy_url", kwargs) diff --git a/lms/djangoapps/django_comment_client/forum/tests.py b/lms/djangoapps/django_comment_client/forum/tests.py index 2ac39cc644..66323a1968 100644 --- a/lms/djangoapps/django_comment_client/forum/tests.py +++ b/lms/djangoapps/django_comment_client/forum/tests.py @@ -26,7 +26,8 @@ from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ( ModuleStoreTestCase, - TEST_DATA_MONGO_MODULESTORE + SharedModuleStoreTestCase, + TEST_DATA_MONGO_MODULESTORE, ) from xmodule.modulestore.tests.factories import check_mongo_calls, CourseFactory, ItemFactory @@ -1282,13 +1283,20 @@ class CommentsServiceRequestHeadersTestCase(UrlResetMixin, ModuleStoreTestCase): self.assert_all_calls_have_header(mock_request, "X-Edx-Api-Key", "test_api_key") -class InlineDiscussionUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin): - def setUp(self): - super(InlineDiscussionUnicodeTestCase, self).setUp() +class InlineDiscussionUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin): - self.course = CourseFactory.create() - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(InlineDiscussionUnicodeTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() + + @classmethod + def setUpTestData(cls): + super(InlineDiscussionUnicodeTestCase, cls).setUpTestData() + + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) @patch('lms.lib.comment_client.utils.requests.request', autospec=True) def _test_unicode_data(self, text, mock_request): @@ -1305,13 +1313,19 @@ class InlineDiscussionUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin): self.assertEqual(response_data["discussion_data"][0]["body"], text) -class ForumFormDiscussionUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin): - def setUp(self): - super(ForumFormDiscussionUnicodeTestCase, self).setUp() +class ForumFormDiscussionUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin): + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(ForumFormDiscussionUnicodeTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() - self.course = CourseFactory.create() - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) + @classmethod + def setUpTestData(cls): + super(ForumFormDiscussionUnicodeTestCase, cls).setUpTestData() + + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) @patch('lms.lib.comment_client.utils.requests.request', autospec=True) def _test_unicode_data(self, text, mock_request): @@ -1377,13 +1391,20 @@ class ForumDiscussionXSSTestCase(UrlResetMixin, ModuleStoreTestCase): self.assertNotIn(malicious_code, resp.content) -class ForumDiscussionSearchUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin): - def setUp(self): - super(ForumDiscussionSearchUnicodeTestCase, self).setUp() +class ForumDiscussionSearchUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin): - self.course = CourseFactory.create() - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(ForumDiscussionSearchUnicodeTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() + + @classmethod + def setUpTestData(cls): + super(ForumDiscussionSearchUnicodeTestCase, cls).setUpTestData() + + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) @patch('lms.lib.comment_client.utils.requests.request', autospec=True) def _test_unicode_data(self, text, mock_request): @@ -1403,13 +1424,20 @@ class ForumDiscussionSearchUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin self.assertEqual(response_data["discussion_data"][0]["body"], text) -class SingleThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin): - def setUp(self): - super(SingleThreadUnicodeTestCase, self).setUp() +class SingleThreadUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin): - self.course = CourseFactory.create(discussion_topics={'dummy_discussion_id': {'id': 'dummy_discussion_id'}}) - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(SingleThreadUnicodeTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create(discussion_topics={'dummy_discussion_id': {'id': 'dummy_discussion_id'}}) + + @classmethod + def setUpTestData(cls): + super(SingleThreadUnicodeTestCase, cls).setUpTestData() + + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) @patch('lms.lib.comment_client.utils.requests.request', autospec=True) def _test_unicode_data(self, text, mock_request): @@ -1426,13 +1454,20 @@ class SingleThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin): self.assertEqual(response_data["content"]["body"], text) -class UserProfileUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin): - def setUp(self): - super(UserProfileUnicodeTestCase, self).setUp() +class UserProfileUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin): - self.course = CourseFactory.create() - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(UserProfileUnicodeTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() + + @classmethod + def setUpTestData(cls): + super(UserProfileUnicodeTestCase, cls).setUpTestData() + + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) @patch('lms.lib.comment_client.utils.requests.request', autospec=True) def _test_unicode_data(self, text, mock_request): @@ -1448,13 +1483,20 @@ class UserProfileUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin): self.assertEqual(response_data["discussion_data"][0]["body"], text) -class FollowedThreadsUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin): - def setUp(self): - super(FollowedThreadsUnicodeTestCase, self).setUp() +class FollowedThreadsUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin): - self.course = CourseFactory.create() - self.student = UserFactory.create() - CourseEnrollmentFactory(user=self.student, course_id=self.course.id) + @classmethod + def setUpClass(cls): + # pylint: disable=super-method-not-called + with super(FollowedThreadsUnicodeTestCase, cls).setUpClassAndTestData(): + cls.course = CourseFactory.create() + + @classmethod + def setUpTestData(cls): + super(FollowedThreadsUnicodeTestCase, cls).setUpTestData() + + cls.student = UserFactory.create() + CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id) @patch('lms.lib.comment_client.utils.requests.request', autospec=True) def _test_unicode_data(self, text, mock_request): diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py index 4f0673103e..bb2402aca8 100644 --- a/lms/djangoapps/teams/tests/test_views.py +++ b/lms/djangoapps/teams/tests/test_views.py @@ -200,89 +200,90 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): @classmethod def setUpClass(cls): - super(TeamAPITestCase, cls).setUpClass() - teams_configuration_1 = { - 'topics': - [ - { - 'id': 'topic_{}'.format(i), - 'name': name, - 'description': 'Description for topic {}.'.format(i) - } for i, name in enumerate([u'Sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power']) - ] - } - cls.test_course_1 = CourseFactory.create( - org='TestX', - course='TS101', - display_name='Test Course', - teams_configuration=teams_configuration_1 - ) + with super(TeamAPITestCase, cls).setUpClassAndTestData(): + teams_configuration_1 = { + 'topics': + [ + { + 'id': 'topic_{}'.format(i), + 'name': name, + 'description': 'Description for topic {}.'.format(i) + } for i, name in enumerate([u'Sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power']) + ] + } + cls.test_course_1 = CourseFactory.create( + org='TestX', + course='TS101', + display_name='Test Course', + teams_configuration=teams_configuration_1 + ) - teams_configuration_2 = { - 'topics': - [ - { - 'id': 'topic_5', - 'name': 'Other Interests', - 'description': 'Description for topic 5.' - }, - { - 'id': 'topic_6', - 'name': 'Public Profiles', - 'description': 'Description for topic 6.' - }, - { - 'id': 'Topic_6.5', - 'name': 'Test Accessibility Topic', - 'description': 'Description for Topic_6.5' - }, - ], - 'max_team_size': 1 - } - cls.test_course_2 = CourseFactory.create( - org='MIT', - course='6.002x', - display_name='Circuits', - teams_configuration=teams_configuration_2 - ) + teams_configuration_2 = { + 'topics': + [ + { + 'id': 'topic_5', + 'name': 'Other Interests', + 'description': 'Description for topic 5.' + }, + { + 'id': 'topic_6', + 'name': 'Public Profiles', + 'description': 'Description for topic 6.' + }, + { + 'id': 'Topic_6.5', + 'name': 'Test Accessibility Topic', + 'description': 'Description for Topic_6.5' + }, + ], + 'max_team_size': 1 + } + cls.test_course_2 = CourseFactory.create( + org='MIT', + course='6.002x', + display_name='Circuits', + teams_configuration=teams_configuration_2 + ) - def setUp(self): - super(TeamAPITestCase, self).setUp() - self.topics_count = 4 - self.users = { - 'staff': AdminFactory.create(password=self.test_password), - 'course_staff': StaffFactory.create(course_key=self.test_course_1.id, password=self.test_password) + @classmethod + def setUpTestData(cls): + super(TeamAPITestCase, cls).setUpTestData() + cls.topics_count = 4 + cls.users = { + 'staff': AdminFactory.create(password=cls.test_password), + 'course_staff': StaffFactory.create(course_key=cls.test_course_1.id, password=cls.test_password) } - self.create_and_enroll_student(username='student_enrolled') - self.create_and_enroll_student(username='student_enrolled_not_on_team') - self.create_and_enroll_student(username='student_unenrolled', courses=[]) + cls.create_and_enroll_student(username='student_enrolled') + cls.create_and_enroll_student(username='student_enrolled_not_on_team') + cls.create_and_enroll_student(username='student_unenrolled', courses=[]) # Make this student a community TA. - self.create_and_enroll_student(username='community_ta') - seed_permissions_roles(self.test_course_1.id) - community_ta_role = Role.objects.get(name=FORUM_ROLE_COMMUNITY_TA, course_id=self.test_course_1.id) - community_ta_role.users.add(self.users['community_ta']) + cls.create_and_enroll_student(username='community_ta') + seed_permissions_roles(cls.test_course_1.id) + community_ta_role = Role.objects.get(name=FORUM_ROLE_COMMUNITY_TA, course_id=cls.test_course_1.id) + community_ta_role.users.add(cls.users['community_ta']) # This student is enrolled in both test courses and is a member of a team in each course, but is not on the # same team as student_enrolled. - self.create_and_enroll_student( - courses=[self.test_course_1, self.test_course_2], + cls.create_and_enroll_student( + courses=[cls.test_course_1, cls.test_course_2], username='student_enrolled_both_courses_other_team' ) # Make this student have a public profile - self.create_and_enroll_student( - courses=[self.test_course_2], + cls.create_and_enroll_student( + courses=[cls.test_course_2], username='student_enrolled_public_profile' ) - profile = self.users['student_enrolled_public_profile'].profile + profile = cls.users['student_enrolled_public_profile'].profile profile.year_of_birth = 1970 profile.save() # This student is enrolled in the other course, but not yet a member of a team. This is to allow # course_2 to use a max_team_size of 1 without breaking other tests on course_1 - self.create_and_enroll_student( - courses=[self.test_course_2], + cls.create_and_enroll_student( + courses=[cls.test_course_2], username='student_enrolled_other_course_not_on_team' ) @@ -292,58 +293,58 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): sender=CourseTeam, dispatch_uid='teams.signals.course_team_post_save_callback' ): - self.solar_team = CourseTeamFactory.create( + cls.solar_team = CourseTeamFactory.create( name=u'Sólar team', - course_id=self.test_course_1.id, + course_id=cls.test_course_1.id, topic_id='topic_0' ) - self.wind_team = CourseTeamFactory.create(name='Wind Team', course_id=self.test_course_1.id) - self.nuclear_team = CourseTeamFactory.create(name='Nuclear Team', course_id=self.test_course_1.id) - self.another_team = CourseTeamFactory.create(name='Another Team', course_id=self.test_course_2.id) - self.public_profile_team = CourseTeamFactory.create( + cls.wind_team = CourseTeamFactory.create(name='Wind Team', course_id=cls.test_course_1.id) + cls.nuclear_team = CourseTeamFactory.create(name='Nuclear Team', course_id=cls.test_course_1.id) + cls.another_team = CourseTeamFactory.create(name='Another Team', course_id=cls.test_course_2.id) + cls.public_profile_team = CourseTeamFactory.create( name='Public Profile Team', - course_id=self.test_course_2.id, + course_id=cls.test_course_2.id, topic_id='topic_6' ) - self.search_team = CourseTeamFactory.create( + cls.search_team = CourseTeamFactory.create( name='Search', description='queryable text', country='GS', language='to', - course_id=self.test_course_2.id, + course_id=cls.test_course_2.id, topic_id='topic_7' ) - self.chinese_team = CourseTeamFactory.create( + cls.chinese_team = CourseTeamFactory.create( name=u'著文企臺個', description=u'共樣地面較,件展冷不護者這與民教過住意,國制銀產物助音是勢一友', country='CN', language='zh_HANS', - course_id=self.test_course_2.id, + course_id=cls.test_course_2.id, topic_id='topic_7' ) - self.test_team_name_id_map = {team.name: team for team in ( - self.solar_team, - self.wind_team, - self.nuclear_team, - self.another_team, - self.public_profile_team, - self.search_team, - self.chinese_team, + cls.test_team_name_id_map = {team.name: team for team in ( + cls.solar_team, + cls.wind_team, + cls.nuclear_team, + cls.another_team, + cls.public_profile_team, + cls.search_team, + cls.chinese_team, )} - for user, course in [('staff', self.test_course_1), ('course_staff', self.test_course_1)]: + for user, course in [('staff', cls.test_course_1), ('course_staff', cls.test_course_1)]: CourseEnrollment.enroll( - self.users[user], course.id, check_access=True + cls.users[user], course.id, check_access=True ) # Django Rest Framework v3 requires us to pass a request to serializers # that have URL fields. Since we're invoking this code outside the context # of a request, we need to simulate that there's a request. - self.solar_team.add_user(self.users['student_enrolled']) - self.nuclear_team.add_user(self.users['student_enrolled_both_courses_other_team']) - self.another_team.add_user(self.users['student_enrolled_both_courses_other_team']) - self.public_profile_team.add_user(self.users['student_enrolled_public_profile']) + cls.solar_team.add_user(cls.users['student_enrolled']) + cls.nuclear_team.add_user(cls.users['student_enrolled_both_courses_other_team']) + cls.another_team.add_user(cls.users['student_enrolled_both_courses_other_team']) + cls.public_profile_team.add_user(cls.users['student_enrolled_public_profile']) def build_membership_data_raw(self, username, team): """Assembles a membership creation payload based on the raw values provided.""" @@ -353,21 +354,22 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): """Assembles a membership creation payload based on the username and team model provided.""" return self.build_membership_data_raw(self.users[username].username, team.team_id) - def create_and_enroll_student(self, courses=None, username=None): + @classmethod + def create_and_enroll_student(cls, courses=None, username=None): """ Creates a new student and enrolls that student in the course. - Adds the new user to the self.users dictionary with the username as the key. + Adds the new user to the cls.users dictionary with the username as the key. Returns the username once the user has been created. """ if username is not None: - user = UserFactory.create(password=self.test_password, username=username) + user = UserFactory.create(password=cls.test_password, username=username) else: - user = UserFactory.create(password=self.test_password) - courses = courses if courses is not None else [self.test_course_1] + user = UserFactory.create(password=cls.test_password) + courses = courses if courses is not None else [cls.test_course_1] for course in courses: CourseEnrollment.enroll(user, course.id, check_access=True) - self.users[user.username] = user + cls.users[user.username] = user return user.username diff --git a/lms/startup.py b/lms/startup.py index ab7a20adc3..15582012ab 100644 --- a/lms/startup.py +++ b/lms/startup.py @@ -12,7 +12,7 @@ from openedx.core.lib.django_startup import autostartup import edxmako import logging import analytics -from monkey_patch import third_party_auth +from monkey_patch import third_party_auth, django_db_models_options import xmodule.x_module @@ -29,6 +29,7 @@ def run(): Executed during django startup """ third_party_auth.patch() + django_db_models_options.patch() # To override the settings before executing the autostartup() for python-social-auth if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False):