Merge pull request #4339 from edx/nimisha/fix-user-ids
Nimisha/fix user ids and LMS tests branch setting
This commit is contained in:
@@ -7,6 +7,7 @@ from student.roles import CourseInstructorRole, CourseStaffRole
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
|
||||
|
||||
#
|
||||
@@ -38,7 +39,7 @@ class Command(BaseCommand):
|
||||
print("Cloning course {0} to {1}".format(source_course_id, dest_course_id))
|
||||
|
||||
with mstore.bulk_write_operations(dest_course_id):
|
||||
if mstore.clone_course(source_course_id, dest_course_id, None):
|
||||
if mstore.clone_course(source_course_id, dest_course_id, ModuleStoreEnum.UserID.mgmt_command):
|
||||
print("copying User permissions...")
|
||||
# purposely avoids auth.add_user b/c it doesn't have a caller to authorize
|
||||
CourseInstructorRole(dest_course_id).add_users(
|
||||
|
||||
@@ -7,7 +7,7 @@ from contentstore.utils import delete_course_and_groups
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Delete a MongoDB backed course'''
|
||||
@@ -28,6 +28,6 @@ class Command(BaseCommand):
|
||||
if commit:
|
||||
print('Actually going to delete the course from DB....')
|
||||
|
||||
if query_yes_no("Deleting course {0}. Confirm?".format(course_key), default="no"):
|
||||
if query_yes_no("Are you sure. This action cannot be undone!", default="no"):
|
||||
delete_course_and_groups(course_key, commit)
|
||||
if query_yes_no("Deleting course {0}. Confirm?".format(course_key), default="no"):
|
||||
if query_yes_no("Are you sure. This action cannot be undone!", default="no"):
|
||||
delete_course_and_groups(course_key, ModuleStoreEnum.UserID.mgmt_command)
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.core.management.base import BaseCommand, CommandError, make_option
|
||||
from django_comment_common.utils import (seed_permissions_roles,
|
||||
are_permissions_roles_seeded)
|
||||
from xmodule.modulestore.xml_importer import import_from_xml
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.contentstore.django import contentstore
|
||||
|
||||
@@ -40,7 +41,7 @@ class Command(BaseCommand):
|
||||
mstore = modulestore()
|
||||
|
||||
_, course_items = import_from_xml(
|
||||
mstore, "**replace_user**", data_dir, course_dirs, load_error_modules=False,
|
||||
mstore, ModuleStoreEnum.UserID.mgmt_command, data_dir, course_dirs, load_error_modules=False,
|
||||
static_content_store=contentstore(), verbose=True,
|
||||
do_import_static=do_import_static,
|
||||
create_new_course_if_not_present=True,
|
||||
|
||||
@@ -30,7 +30,6 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey, AssetLocation
|
||||
from xmodule.modulestore.store_utilities import delete_course
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.modulestore.xml_exporter import export_to_xml
|
||||
from xmodule.modulestore.xml_importer import import_from_xml, perform_xlint
|
||||
@@ -632,7 +631,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
|
||||
self.store.convert_to_draft(vertical.location, self.user.id)
|
||||
|
||||
# delete the course
|
||||
delete_course(self.store, content_store, course_id, commit=True)
|
||||
self.store.delete_course(course_id, self.user.id)
|
||||
|
||||
# assert that there's absolutely no non-draft modules in the course
|
||||
# this should also include all draft items
|
||||
@@ -697,7 +696,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
|
||||
self.assertEqual(on_disk['course/2012_Fall'], own_metadata(course))
|
||||
|
||||
# remove old course
|
||||
delete_course(self.store, content_store, course_id, commit=True)
|
||||
self.store.delete_course(course_id, self.user.id)
|
||||
|
||||
# reimport over old course
|
||||
self.check_import(root_dir, content_store, course_id)
|
||||
@@ -909,7 +908,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
|
||||
|
||||
# Delete the course from module store and reimport it
|
||||
|
||||
delete_course(self.store, content_store, course_id, commit=True)
|
||||
self.store.delete_course(course_id, self.user.id)
|
||||
|
||||
import_from_xml(
|
||||
self.store, self.user.id, root_dir, ['test_export_no_content_store'],
|
||||
@@ -997,7 +996,7 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
test_course_data = self.assert_created_course(number_suffix=uuid4().hex)
|
||||
course_id = _get_course_id(test_course_data)
|
||||
self.assertTrue(are_permissions_roles_seeded(course_id))
|
||||
delete_course_and_groups(course_id, commit=True)
|
||||
delete_course_and_groups(course_id, self.user.id)
|
||||
# should raise an exception for checking permissions on deleted course
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
are_permissions_roles_seeded(course_id)
|
||||
@@ -1009,7 +1008,7 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
|
||||
# unseed the forums for the first course
|
||||
course_id = _get_course_id(test_course_data)
|
||||
delete_course_and_groups(course_id, commit=True)
|
||||
delete_course_and_groups(course_id, self.user.id)
|
||||
# should raise an exception for checking permissions on deleted course
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
are_permissions_roles_seeded(course_id)
|
||||
@@ -1029,7 +1028,7 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
self.assertTrue(CourseEnrollment.is_enrolled(self.user, course_id))
|
||||
self.assertTrue(self.user.roles.filter(name="Student", course_id=course_id)) # pylint: disable=no-member
|
||||
|
||||
delete_course_and_groups(course_id, commit=True)
|
||||
delete_course_and_groups(course_id, self.user.id)
|
||||
# check that user's enrollment for this course is not deleted
|
||||
self.assertTrue(CourseEnrollment.is_enrolled(self.user, course_id))
|
||||
# check that user has form role "Student" for this course even after deleting it
|
||||
@@ -1051,7 +1050,7 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
self.assertTrue(len(instructor_role.users_with_role()) > 0)
|
||||
|
||||
# Now delete course and check that user not in instructor groups of this course
|
||||
delete_course_and_groups(course_id, commit=True)
|
||||
delete_course_and_groups(course_id, self.user.id)
|
||||
|
||||
# Update our cached user since its roles have changed
|
||||
self.user = User.objects.get_by_natural_key(self.user.natural_key()[0])
|
||||
|
||||
@@ -145,7 +145,7 @@ class TestCourseListing(ModuleStoreTestCase):
|
||||
self.assertEqual(courses_list, courses_list_by_groups)
|
||||
|
||||
# now delete this course and re-add user to instructor group of this course
|
||||
delete_course_and_groups(course_key, commit=True)
|
||||
delete_course_and_groups(course_key, self.user.id)
|
||||
|
||||
CourseInstructorRole(course_key).add_users(self.user)
|
||||
|
||||
@@ -237,7 +237,7 @@ class TestCourseListing(ModuleStoreTestCase):
|
||||
self.assertEqual(len(courses_list_by_groups), 2)
|
||||
|
||||
# now delete first course (course_location_caps) and check that it is no longer accessible
|
||||
delete_course_and_groups(course_location_caps, commit=True)
|
||||
delete_course_and_groups(course_location_caps, self.user.id)
|
||||
|
||||
# test that get courses through iterating all courses now returns one course
|
||||
courses_list = _accessible_courses_list(self.request)
|
||||
@@ -269,7 +269,7 @@ class TestCourseListing(ModuleStoreTestCase):
|
||||
|
||||
course_location = SlashSeparatedCourseKey('testOrg', 'doomedCourse', 'RunBabyRun')
|
||||
self._create_course_with_access_groups(course_location, self.user)
|
||||
store.delete_course(course_location)
|
||||
store.delete_course(course_location, self.user.id)
|
||||
|
||||
course_location = SlashSeparatedCourseKey('testOrg', 'erroredCourse', 'RunBabyRun')
|
||||
course = self._create_course_with_access_groups(course_location, self.user)
|
||||
|
||||
@@ -162,7 +162,7 @@ class TemplateTests(unittest.TestCase):
|
||||
self.assertIsInstance(self.split_store.get_course(id_locator), CourseDescriptor)
|
||||
# and by guid
|
||||
self.assertIsInstance(self.split_store.get_item(guid_locator), CourseDescriptor)
|
||||
self.split_store.delete_course(id_locator)
|
||||
self.split_store.delete_course(id_locator, ModuleStoreEnum.UserID.test)
|
||||
# test can no longer retrieve by id
|
||||
self.assertRaises(ItemNotFoundError, self.split_store.get_course, id_locator)
|
||||
# but can by guid
|
||||
@@ -187,11 +187,11 @@ class TemplateTests(unittest.TestCase):
|
||||
)
|
||||
first_problem.max_attempts = 3
|
||||
first_problem.save() # decache the above into the kvs
|
||||
updated_problem = self.split_store.update_item(first_problem, '**replace_user**')
|
||||
updated_problem = self.split_store.update_item(first_problem, ModuleStoreEnum.UserID.test)
|
||||
self.assertIsNotNone(updated_problem.previous_version)
|
||||
self.assertEqual(updated_problem.previous_version, first_problem.update_version)
|
||||
self.assertNotEqual(updated_problem.update_version, first_problem.update_version)
|
||||
updated_loc = self.split_store.delete_item(updated_problem.location, '**replace_user**', 'testbot')
|
||||
updated_loc = self.split_store.delete_item(updated_problem.location, ModuleStoreEnum.UserID.test, 'testbot')
|
||||
|
||||
second_problem = persistent_factories.ItemFactory.create(
|
||||
display_name='problem 2',
|
||||
|
||||
@@ -65,7 +65,7 @@ class TestExportGit(CourseTestCase):
|
||||
Test failed course export response.
|
||||
"""
|
||||
self.course_module.giturl = 'foobar'
|
||||
modulestore().update_item(self.course_module, '**replace_user**')
|
||||
modulestore().update_item(self.course_module, self.user.id)
|
||||
|
||||
response = self.client.get('{}?action=push'.format(self.test_url))
|
||||
self.assertIn('Export Failed:', response.content)
|
||||
@@ -75,7 +75,7 @@ class TestExportGit(CourseTestCase):
|
||||
Regression test for making sure errors are properly stringified
|
||||
"""
|
||||
self.course_module.giturl = 'foobar'
|
||||
modulestore().update_item(self.course_module, '**replace_user**')
|
||||
modulestore().update_item(self.course_module, self.user.id)
|
||||
|
||||
response = self.client.get('{}?action=push'.format(self.test_url))
|
||||
self.assertNotIn('django.utils.functional.__proxy__', response.content)
|
||||
@@ -98,7 +98,7 @@ class TestExportGit(CourseTestCase):
|
||||
|
||||
self.populate_course()
|
||||
self.course_module.giturl = 'file://{}'.format(bare_repo_dir)
|
||||
modulestore().update_item(self.course_module, '**replace_user**')
|
||||
modulestore().update_item(self.course_module, self.user.id)
|
||||
|
||||
response = self.client.get('{}?action=push'.format(self.test_url))
|
||||
self.assertIn('Export Succeeded', response.content)
|
||||
|
||||
@@ -33,25 +33,10 @@ class ContentStoreImportTest(ModuleStoreTestCase):
|
||||
NOTE: refactor using CourseFactory so they do not.
|
||||
"""
|
||||
def setUp(self):
|
||||
|
||||
uname = 'testuser'
|
||||
email = 'test+courses@edx.org'
|
||||
password = 'foo'
|
||||
|
||||
# Create the use so we can log them in.
|
||||
self.user = User.objects.create_user(uname, email, password)
|
||||
|
||||
# Note that we do not actually need to do anything
|
||||
# for registration if we directly mark them active.
|
||||
self.user.is_active = True
|
||||
# Staff has access to view all courses
|
||||
self.user.is_staff = True
|
||||
|
||||
# Save the data that we've just changed to the db.
|
||||
self.user.save()
|
||||
|
||||
password = super(ContentStoreImportTest, self).setUp()
|
||||
|
||||
self.client = Client()
|
||||
self.client.login(username=uname, password=password)
|
||||
self.client.login(username=self.user.username, password=password)
|
||||
|
||||
def tearDown(self):
|
||||
contentstore().drop_database()
|
||||
@@ -66,7 +51,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
|
||||
module_store = modulestore()
|
||||
import_from_xml(
|
||||
module_store,
|
||||
'**replace_user**',
|
||||
self.user.id,
|
||||
'common/test/data/',
|
||||
['test_import_course'],
|
||||
static_content_store=content_store,
|
||||
@@ -86,7 +71,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
|
||||
module_store, __, course = self.load_test_import_course()
|
||||
__, course_items = import_from_xml(
|
||||
module_store,
|
||||
'**replace_user**',
|
||||
self.user.id,
|
||||
'common/test/data',
|
||||
['test_import_course_2'],
|
||||
target_course_id=course.id,
|
||||
@@ -102,7 +87,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
|
||||
course_id = SlashSeparatedCourseKey(u'Юникода', u'unicode_course', u'échantillon')
|
||||
import_from_xml(
|
||||
module_store,
|
||||
'**replace_user**',
|
||||
self.user.id,
|
||||
'common/test/data/',
|
||||
['2014_Uni'],
|
||||
target_course_id=course_id
|
||||
@@ -148,7 +133,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
|
||||
content_store = contentstore()
|
||||
|
||||
module_store = modulestore()
|
||||
import_from_xml(module_store, '**replace_user**', 'common/test/data/', ['toy'], static_content_store=content_store, do_import_static=False, verbose=True)
|
||||
import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store, do_import_static=False, verbose=True)
|
||||
|
||||
course = module_store.get_course(SlashSeparatedCourseKey('edX', 'toy', '2012_Fall'))
|
||||
|
||||
@@ -159,7 +144,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
|
||||
|
||||
def test_no_static_link_rewrites_on_import(self):
|
||||
module_store = modulestore()
|
||||
_, courses = import_from_xml(module_store, '**replace_user**', 'common/test/data/', ['toy'], do_import_static=False, verbose=True)
|
||||
_, courses = import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], do_import_static=False, verbose=True)
|
||||
course_key = courses[0].id
|
||||
|
||||
handouts = module_store.get_item(course_key.make_usage_key('course_info', 'handouts'))
|
||||
@@ -178,7 +163,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
|
||||
target_course_id = SlashSeparatedCourseKey('testX', 'conditional_copy', 'copy_run')
|
||||
import_from_xml(
|
||||
module_store,
|
||||
'**replace_user**',
|
||||
self.user.id,
|
||||
'common/test/data/',
|
||||
['conditional'],
|
||||
target_course_id=target_course_id
|
||||
@@ -208,7 +193,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
|
||||
target_course_id = SlashSeparatedCourseKey('testX', 'peergrading_copy', 'copy_run')
|
||||
import_from_xml(
|
||||
module_store,
|
||||
'**replace_user**',
|
||||
self.user.id,
|
||||
'common/test/data/',
|
||||
['open_ended'],
|
||||
target_course_id=target_course_id
|
||||
@@ -227,7 +212,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
|
||||
target_course_id = SlashSeparatedCourseKey('testX', 'split_test_copy', 'copy_run')
|
||||
import_from_xml(
|
||||
module_store,
|
||||
'**replace_user**',
|
||||
self.user.id,
|
||||
'common/test/data/',
|
||||
['split_test_module'],
|
||||
target_course_id=target_course_id
|
||||
|
||||
@@ -10,7 +10,7 @@ class DraftReorderTestCase(ModuleStoreTestCase):
|
||||
|
||||
def test_order(self):
|
||||
store = modulestore()
|
||||
_, course_items = import_from_xml(store, '**replace_user**', 'common/test/data/', ['import_draft_order'])
|
||||
_, course_items = import_from_xml(store, self.user.id, 'common/test/data/', ['import_draft_order'])
|
||||
course_key = course_items[0].id
|
||||
sequential = store.get_item(course_key.make_usage_key('sequential', '0f4f7649b10141b0bdc9922dcf94515a'))
|
||||
verticals = sequential.children
|
||||
|
||||
@@ -28,9 +28,6 @@ class StubXBlock(XBlock):
|
||||
|
||||
class XBlockImportTest(ModuleStoreTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.store = modulestore()
|
||||
|
||||
@XBlock.register_temp_plugin(StubXBlock)
|
||||
def test_import_public(self):
|
||||
self._assert_import(
|
||||
@@ -62,7 +59,7 @@ class XBlockImportTest(ModuleStoreTestCase):
|
||||
|
||||
"""
|
||||
_, courses = import_from_xml(
|
||||
self.store, '**replace_user**', 'common/test/data', [course_dir]
|
||||
self.store, self.user.id, 'common/test/data', [course_dir]
|
||||
)
|
||||
|
||||
xblock_location = courses[0].id.make_usage_key('stubxblock', 'xblock_test')
|
||||
|
||||
@@ -24,23 +24,10 @@ class TestCourseAccess(ModuleStoreTestCase):
|
||||
|
||||
Create a pool of users w/o granting them any permissions
|
||||
"""
|
||||
super(TestCourseAccess, self).setUp()
|
||||
uname = 'testuser'
|
||||
email = 'test+courses@edx.org'
|
||||
password = 'foo'
|
||||
|
||||
# Create the use so we can log them in.
|
||||
self.user = User.objects.create_user(uname, email, password)
|
||||
|
||||
# Note that we do not actually need to do anything
|
||||
# for registration if we directly mark them active.
|
||||
self.user.is_active = True
|
||||
# Staff has access to view all courses
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
user_password = super(TestCourseAccess, self).setUp()
|
||||
|
||||
self.client = AjaxEnabledTestClient()
|
||||
self.client.login(username=uname, password=password)
|
||||
self.client.login(username=self.user.username, password=user_password)
|
||||
|
||||
# create a course via the view handler which has a different strategy for permissions than the factory
|
||||
self.course_key = SlashSeparatedCourseKey('myu', 'mydept.mycourse', 'myrun')
|
||||
|
||||
@@ -62,7 +62,7 @@ class TestUsersDefaultRole(ModuleStoreTestCase):
|
||||
# check that user has his default "Student" forum role for this course
|
||||
self.assertTrue(self.user.roles.filter(name="Student", course_id=self.course_key)) # pylint: disable=no-member
|
||||
|
||||
delete_course_and_groups(self.course_key, commit=True)
|
||||
delete_course_and_groups(self.course_key, self.user.id)
|
||||
|
||||
# check that user's enrollment for this course is not deleted
|
||||
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_key))
|
||||
@@ -80,7 +80,7 @@ class TestUsersDefaultRole(ModuleStoreTestCase):
|
||||
self.assertTrue(self.user.roles.filter(name="Student", course_id=self.course_key)) # pylint: disable=no-member
|
||||
|
||||
# delete this course and recreate this course with same user
|
||||
delete_course_and_groups(self.course_key, commit=True)
|
||||
delete_course_and_groups(self.course_key, self.user.id)
|
||||
resp = self._create_course_with_given_location(self.course_key)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
@@ -98,7 +98,7 @@ class TestUsersDefaultRole(ModuleStoreTestCase):
|
||||
# check that user has enrollment and his default "Student" forum role for this course
|
||||
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_key))
|
||||
# delete this course and recreate this course with same user
|
||||
delete_course_and_groups(self.course_key, commit=True)
|
||||
delete_course_and_groups(self.course_key, self.user.id)
|
||||
|
||||
# now create same course with different name case ('uppercase')
|
||||
new_course_key = self.course_key.replace(course=self.course_key.course.upper())
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from contentstore import utils
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey, Location
|
||||
|
||||
@@ -193,7 +194,7 @@ class XBlockVisibilityTestCase(TestCase):
|
||||
"""Tests for xblock visibility for students."""
|
||||
|
||||
def setUp(self):
|
||||
self.dummy_user = 123
|
||||
self.dummy_user = ModuleStoreEnum.UserID.test
|
||||
self.past = datetime(1970, 1, 1)
|
||||
self.future = datetime.now(UTC) + timedelta(days=1)
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ Utilities for contentstore tests
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test.client import Client
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
@@ -68,53 +68,32 @@ class AjaxEnabledTestClient(Client):
|
||||
class CourseTestCase(ModuleStoreTestCase):
|
||||
def setUp(self):
|
||||
"""
|
||||
These tests need a user in the DB so that the django Test Client
|
||||
can log them in.
|
||||
These tests need a user in the DB so that the django Test Client can log them in.
|
||||
The test user is created in the ModuleStoreTestCase setUp method.
|
||||
They inherit from the ModuleStoreTestCase class so that the mongodb collection
|
||||
will be cleared out before each test case execution and deleted
|
||||
afterwards.
|
||||
"""
|
||||
uname = 'testuser'
|
||||
email = 'test+courses@edx.org'
|
||||
password = 'foo'
|
||||
|
||||
# Create the user so we can log them in.
|
||||
self.user = User.objects.create_user(uname, email, password)
|
||||
|
||||
# Note that we do not actually need to do anything
|
||||
# for registration if we directly mark them active.
|
||||
self.user.is_active = True
|
||||
# Staff has access to view all courses
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
user_password = super(CourseTestCase, self).setUp()
|
||||
|
||||
self.client = AjaxEnabledTestClient()
|
||||
self.client.login(username=uname, password=password)
|
||||
self.client.login(username=self.user.username, password=user_password)
|
||||
|
||||
self.course = CourseFactory.create(
|
||||
org='MITx',
|
||||
number='999',
|
||||
display_name='Robot Super Course',
|
||||
)
|
||||
self.store = modulestore()
|
||||
|
||||
def create_non_staff_authed_user_client(self, authenticate=True):
|
||||
"""
|
||||
Create a non-staff user, log them in (if authenticate=True), and return the client, user to use for testing.
|
||||
"""
|
||||
uname = 'teststudent'
|
||||
password = 'foo'
|
||||
nonstaff = User.objects.create_user(uname, 'test+student@edx.org', password)
|
||||
|
||||
# Note that we do not actually need to do anything
|
||||
# for registration if we directly mark them active.
|
||||
nonstaff.is_active = True
|
||||
nonstaff.is_staff = False
|
||||
nonstaff.save()
|
||||
nonstaff, password = self.create_non_staff_user()
|
||||
|
||||
client = Client()
|
||||
if authenticate:
|
||||
client.login(username=uname, password=password)
|
||||
client.login(username=nonstaff.username, password=password)
|
||||
return client, nonstaff
|
||||
|
||||
def populate_course(self):
|
||||
|
||||
@@ -11,12 +11,10 @@ from django.utils.translation import ugettext as _
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey, Location
|
||||
from xmodule.modulestore.store_utilities import delete_course
|
||||
from student.roles import CourseInstructorRole, CourseStaffRole
|
||||
|
||||
|
||||
@@ -28,27 +26,25 @@ NOTES_PANEL = {"name": _("My Notes"), "type": "notes"}
|
||||
EXTRA_TAB_PANELS = dict([(p['type'], p) for p in [OPEN_ENDED_PANEL, NOTES_PANEL]])
|
||||
|
||||
|
||||
def delete_course_and_groups(course_id, commit=False):
|
||||
def delete_course_and_groups(course_id, user_id):
|
||||
"""
|
||||
This deletes the courseware associated with a course_id as well as cleaning update_item
|
||||
the various user table stuff (groups, permissions, etc.)
|
||||
"""
|
||||
module_store = modulestore()
|
||||
content_store = contentstore()
|
||||
|
||||
with module_store.bulk_write_operations(course_id):
|
||||
if delete_course(module_store, content_store, course_id, commit):
|
||||
module_store.delete_course(course_id, user_id)
|
||||
|
||||
print 'removing User permissions from course....'
|
||||
# in the django layer, we need to remove all the user permissions groups associated with this course
|
||||
if commit:
|
||||
try:
|
||||
staff_role = CourseStaffRole(course_id)
|
||||
staff_role.remove_users(*staff_role.users_with_role())
|
||||
instructor_role = CourseInstructorRole(course_id)
|
||||
instructor_role.remove_users(*instructor_role.users_with_role())
|
||||
except Exception as err:
|
||||
log.error("Error in deleting course groups for {0}: {1}".format(course_id, err))
|
||||
print 'removing User permissions from course....'
|
||||
# in the django layer, we need to remove all the user permissions groups associated with this course
|
||||
try:
|
||||
staff_role = CourseStaffRole(course_id)
|
||||
staff_role.remove_users(*staff_role.users_with_role())
|
||||
instructor_role = CourseInstructorRole(course_id)
|
||||
instructor_role.remove_users(*instructor_role.users_with_role())
|
||||
except Exception as err:
|
||||
log.error("Error in deleting course groups for {0}: {1}".format(course_id, err))
|
||||
|
||||
|
||||
def get_lms_link_for_item(location, preview=False):
|
||||
|
||||
@@ -449,8 +449,8 @@ def component_handler(request, usage_key_string, handler, suffix=''):
|
||||
log.info("XBlock %s attempted to access missing handler %r", descriptor, handler, exc_info=True)
|
||||
raise Http404
|
||||
|
||||
# unintentional update to handle any side effects of handle call; so, request user didn't author
|
||||
# the change
|
||||
modulestore().update_item(descriptor, None)
|
||||
# unintentional update to handle any side effects of handle call
|
||||
# could potentially be updating actual course data or simply caching its values
|
||||
modulestore().update_item(descriptor, request.user.id)
|
||||
|
||||
return webob_to_django_response(resp)
|
||||
|
||||
@@ -337,6 +337,7 @@ def create_new_course(request):
|
||||
new_course = modulestore().create_course(
|
||||
course_key.org,
|
||||
course_key.offering,
|
||||
request.user.id,
|
||||
fields=fields,
|
||||
)
|
||||
|
||||
|
||||
@@ -140,8 +140,8 @@ def xblock_handler(request, usage_key_string):
|
||||
dest_usage_key = _duplicate_item(
|
||||
parent_usage_key,
|
||||
duplicate_source_usage_key,
|
||||
request.json.get('display_name'),
|
||||
request.user,
|
||||
request.json.get('display_name'),
|
||||
)
|
||||
|
||||
return JsonResponse({"locator": unicode(dest_usage_key), "courseKey": unicode(dest_usage_key.course_key)})
|
||||
@@ -193,8 +193,7 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
log.debug("unable to render studio_view for %r", xblock, exc_info=True)
|
||||
fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)}))
|
||||
|
||||
# change not authored by requestor but by xblocks.
|
||||
store.update_item(xblock, None)
|
||||
store.update_item(xblock, request.user.id)
|
||||
elif view_name in (unit_views + container_views):
|
||||
is_container_view = (view_name in container_views)
|
||||
|
||||
@@ -432,7 +431,7 @@ def _create_item(request):
|
||||
return JsonResponse({"locator": unicode(created_block.location), "courseKey": unicode(created_block.location.course_key)})
|
||||
|
||||
|
||||
def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=None, user=None):
|
||||
def _duplicate_item(parent_usage_key, duplicate_source_usage_key, user, display_name=None):
|
||||
"""
|
||||
Duplicate an existing xblock as a child of the supplied parent_usage_key.
|
||||
"""
|
||||
@@ -454,6 +453,8 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N
|
||||
|
||||
dest_module = store.create_and_save_xmodule(
|
||||
dest_usage_key,
|
||||
|
||||
|
||||
user.id,
|
||||
definition_data=source_item.get_explicitly_set_fields_by_scope(Scope.content),
|
||||
metadata=duplicate_metadata,
|
||||
@@ -467,7 +468,7 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N
|
||||
for child in source_item.children:
|
||||
dupe = _duplicate_item(dest_module.location, child, user=user)
|
||||
dest_module.children.append(dupe)
|
||||
store.update_item(dest_module, user.id if user else None)
|
||||
store.update_item(dest_module, user.id)
|
||||
|
||||
if not 'detached' in source_item.runtime.load_block_type(category)._class_tags:
|
||||
parent = store.get_item(parent_usage_key)
|
||||
@@ -478,7 +479,7 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N
|
||||
parent.children.insert(source_index + 1, dest_module.location)
|
||||
else:
|
||||
parent.children.append(dest_module.location)
|
||||
store.update_item(parent, user.id if user else None)
|
||||
store.update_item(parent, user.id)
|
||||
|
||||
return dest_module.location
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from django_future.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.tabs import CourseTabList, StaticTab, CourseTab, InvalidTabsException
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
|
||||
@@ -192,7 +193,7 @@ def primitive_delete(course, num):
|
||||
# Note for future implementations: if you delete a static_tab, then Chris Dodge
|
||||
# points out that there's other stuff to delete beyond this element.
|
||||
# This code happens to not delete static_tab so it doesn't come up.
|
||||
modulestore().update_item(course, '**replace_user**')
|
||||
modulestore().update_item(course, ModuleStoreEnum.UserID.primitive_command)
|
||||
|
||||
|
||||
def primitive_insert(course, num, tab_type, name):
|
||||
@@ -201,5 +202,5 @@ def primitive_insert(course, num, tab_type, name):
|
||||
new_tab = CourseTab.from_json({u'type': unicode(tab_type), u'name': unicode(name)})
|
||||
tabs = course.tabs
|
||||
tabs.insert(num, new_tab)
|
||||
modulestore().update_item(course, '**replace_user**')
|
||||
modulestore().update_item(course, ModuleStoreEnum.UserID.primitive_command)
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class BasicAssetsTestCase(AssetsTestCase):
|
||||
module_store = modulestore()
|
||||
_, course_items = import_from_xml(
|
||||
module_store,
|
||||
'**replace_user**',
|
||||
self.user.id,
|
||||
'common/test/data/',
|
||||
['toy'],
|
||||
static_content_store=contentstore(),
|
||||
@@ -195,7 +195,7 @@ class LockAssetTestCase(AssetsTestCase):
|
||||
module_store = modulestore()
|
||||
_, course_items = import_from_xml(
|
||||
module_store,
|
||||
'**replace_user**',
|
||||
self.user.id,
|
||||
'common/test/data/',
|
||||
['toy'],
|
||||
static_content_store=contentstore(),
|
||||
|
||||
@@ -20,16 +20,11 @@ class ContainerPageTestCase(StudioPageTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ContainerPageTestCase, self).setUp()
|
||||
self.vertical = ItemFactory.create(parent_location=self.sequential.location,
|
||||
category='vertical', display_name='Unit')
|
||||
self.html = ItemFactory.create(parent_location=self.vertical.location,
|
||||
category="html", display_name="HTML")
|
||||
self.child_container = ItemFactory.create(parent_location=self.vertical.location,
|
||||
category='split_test', display_name='Split Test')
|
||||
self.child_vertical = ItemFactory.create(parent_location=self.child_container.location,
|
||||
category='vertical', display_name='Child Vertical')
|
||||
self.video = ItemFactory.create(parent_location=self.child_vertical.location,
|
||||
category="video", display_name="My Video")
|
||||
self.vertical = self._create_item(self.sequential.location, 'vertical', 'Unit')
|
||||
self.html = self._create_item(self.vertical.location, "html", "HTML")
|
||||
self.child_container = self._create_item(self.vertical.location, 'split_test', 'Split Test')
|
||||
self.child_vertical = self._create_item(self.child_container.location, 'vertical', 'Child Vertical')
|
||||
self.video = self._create_item(self.child_vertical.location, "video", "My Video")
|
||||
self.store = modulestore()
|
||||
|
||||
def test_container_html(self):
|
||||
@@ -51,14 +46,8 @@ class ContainerPageTestCase(StudioPageTestCase):
|
||||
Create the scenario of an xblock with children (non-vertical) on the container page.
|
||||
This should create a container page that is a child of another container page.
|
||||
"""
|
||||
draft_container = ItemFactory.create(
|
||||
parent_location=self.child_container.location,
|
||||
category="wrapper", display_name="Wrapper"
|
||||
)
|
||||
ItemFactory.create(
|
||||
parent_location=draft_container.location,
|
||||
category="html", display_name="Child HTML"
|
||||
)
|
||||
draft_container = self._create_item(self.child_container.location, "wrapper", "Wrapper")
|
||||
self._create_item(draft_container.location, "html", "Child HTML")
|
||||
|
||||
def test_container_html(xblock):
|
||||
self._test_html_content(
|
||||
@@ -135,9 +124,8 @@ class ContainerPageTestCase(StudioPageTestCase):
|
||||
"""
|
||||
Verify that a public container rendered as a child of the container page returns the expected HTML.
|
||||
"""
|
||||
empty_child_container = ItemFactory.create(parent_location=self.vertical.location,
|
||||
category='split_test', display_name='Split Test')
|
||||
published_empty_child_container = self.store.publish(empty_child_container.location, '**replace_user**')
|
||||
empty_child_container = self._create_item(self.vertical.location, 'split_test', 'Split Test')
|
||||
published_empty_child_container = self.store.publish(empty_child_container.location, self.user.id)
|
||||
self.validate_preview_html(published_empty_child_container, self.reorderable_child_view,
|
||||
can_reorder=False, can_edit=False, can_add=False)
|
||||
|
||||
@@ -145,7 +133,18 @@ class ContainerPageTestCase(StudioPageTestCase):
|
||||
"""
|
||||
Verify that a draft container rendered as a child of the container page returns the expected HTML.
|
||||
"""
|
||||
empty_child_container = ItemFactory.create(parent_location=self.vertical.location,
|
||||
category='split_test', display_name='Split Test')
|
||||
empty_child_container = self._create_item(self.vertical.location, 'split_test', 'Split Test')
|
||||
self.validate_preview_html(empty_child_container, self.reorderable_child_view,
|
||||
can_reorder=True, can_edit=True, can_add=False)
|
||||
|
||||
def _create_item(self, parent_location, category, display_name, **kwargs):
|
||||
"""
|
||||
creates an item in the module store, without publishing it.
|
||||
"""
|
||||
return ItemFactory.create(
|
||||
parent_location=parent_location,
|
||||
category=category,
|
||||
display_name=display_name,
|
||||
publish_item=False,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@@ -25,10 +25,9 @@ from contentstore.tests.utils import CourseTestCase
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.capa_module import CapaDescriptor
|
||||
from xmodule.modulestore import PublishState
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.x_module import STUDIO_VIEW, STUDENT_VIEW
|
||||
from xblock.exceptions import NoSuchHandlerError
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
from opaque_keys.edx.keys import UsageKey, CourseKey
|
||||
from opaque_keys.edx.locations import Location
|
||||
from xmodule.partitions.partitions import Group, UserPartition
|
||||
|
||||
@@ -40,7 +39,6 @@ class ItemTest(CourseTestCase):
|
||||
|
||||
self.course_key = self.course.id
|
||||
self.usage_key = self.course.location
|
||||
self.store = modulestore()
|
||||
|
||||
def get_item_from_modulestore(self, usage_key, verify_is_draft=False):
|
||||
"""
|
||||
|
||||
@@ -40,7 +40,7 @@ class UnitPageTestCase(StudioPageTestCase):
|
||||
"""
|
||||
Verify that a public xblock's preview returns the expected HTML.
|
||||
"""
|
||||
published_video = self.store.publish(self.video.location, '**replace_user**')
|
||||
published_video = self.store.publish(self.video.location, self.user.id)
|
||||
self.validate_preview_html(self.video, STUDENT_VIEW,
|
||||
can_edit=True, can_reorder=True, can_add=False)
|
||||
|
||||
@@ -60,7 +60,7 @@ class UnitPageTestCase(StudioPageTestCase):
|
||||
category='split_test', display_name='Split Test')
|
||||
ItemFactory.create(parent_location=child_container.location,
|
||||
category='html', display_name='grandchild')
|
||||
published_child_container = self.store.publish(child_container.location, '**replace_user**')
|
||||
published_child_container = self.store.publish(child_container.location, self.user.id)
|
||||
self.validate_preview_html(published_child_container, STUDENT_VIEW,
|
||||
can_reorder=True, can_edit=True, can_add=False)
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class CourseMetadata(object):
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def update_from_json(cls, descriptor, jsondict, filter_tabs=True, user=None):
|
||||
def update_from_json(cls, descriptor, jsondict, user, filter_tabs=True):
|
||||
"""
|
||||
Decode the json into CourseMetadata and save any changed attrs to the db.
|
||||
|
||||
@@ -84,6 +84,6 @@ class CourseMetadata(object):
|
||||
setattr(descriptor, key, value)
|
||||
|
||||
if len(key_values) > 0:
|
||||
modulestore().update_item(descriptor, user.id if user else None)
|
||||
modulestore().update_item(descriptor, user.id)
|
||||
|
||||
return cls.fetch(descriptor)
|
||||
|
||||
@@ -5,7 +5,6 @@ import copy
|
||||
import logging
|
||||
from uuid import uuid4
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
@@ -34,12 +33,16 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Create user and login.
|
||||
"""
|
||||
self.staff_pwd = super(ContentStoreToyCourseTest, self).setUp()
|
||||
self.staff_usr = self.user
|
||||
self.non_staff_usr, self.non_staff_pwd = self.create_non_staff_user()
|
||||
|
||||
self.client = Client()
|
||||
self.contentstore = contentstore()
|
||||
|
||||
self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
|
||||
|
||||
import_from_xml(modulestore(), '**replace_user**', 'common/test/data/', ['toy'],
|
||||
import_from_xml(modulestore(), self.user.id, 'common/test/data/', ['toy'],
|
||||
static_content_store=self.contentstore, verbose=True)
|
||||
|
||||
# A locked asset
|
||||
@@ -52,24 +55,6 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
|
||||
self.contentstore.set_attr(self.locked_asset, 'locked', True)
|
||||
|
||||
# Create user
|
||||
self.usr = 'testuser'
|
||||
self.pwd = 'foo'
|
||||
email = 'test+courses@edx.org'
|
||||
self.user = User.objects.create_user(self.usr, email, self.pwd)
|
||||
self.user.is_active = True
|
||||
self.user.save()
|
||||
|
||||
# Create staff user
|
||||
self.staff_usr = 'stafftestuser'
|
||||
self.staff_pwd = 'foo'
|
||||
staff_email = 'stafftest+courses@edx.org'
|
||||
self.staff_user = User.objects.create_user(self.staff_usr, staff_email,
|
||||
self.staff_pwd)
|
||||
self.staff_user.is_active = True
|
||||
self.staff_user.is_staff = True
|
||||
self.staff_user.save()
|
||||
|
||||
def tearDown(self):
|
||||
|
||||
contentstore().drop_database()
|
||||
@@ -97,7 +82,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
Test that locked assets behave appropriately in case user is logged in
|
||||
in but not registered for the course.
|
||||
"""
|
||||
self.client.login(username=self.usr, password=self.pwd)
|
||||
self.client.login(username=self.non_staff_usr, password=self.non_staff_pwd)
|
||||
resp = self.client.get(self.url_locked)
|
||||
self.assertEqual(resp.status_code, 403) # pylint: disable=E1103
|
||||
|
||||
@@ -106,10 +91,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
Test that locked assets behave appropriately in case user is logged in
|
||||
and registered for the course.
|
||||
"""
|
||||
CourseEnrollment.enroll(self.user, self.course_key)
|
||||
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_key))
|
||||
CourseEnrollment.enroll(self.non_staff_usr, self.course_key)
|
||||
self.assertTrue(CourseEnrollment.is_enrolled(self.non_staff_usr, self.course_key))
|
||||
|
||||
self.client.login(username=self.usr, password=self.pwd)
|
||||
self.client.login(username=self.non_staff_usr, password=self.non_staff_pwd)
|
||||
resp = self.client.get(self.url_locked)
|
||||
self.assertEqual(resp.status_code, 200) # pylint: disable=E1103
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ from django.utils.importlib import import_module
|
||||
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, mixed_store_config
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
from external_auth.models import ExternalAuthMap
|
||||
@@ -80,7 +80,8 @@ class ShibSPTest(ModuleStoreTestCase):
|
||||
request_factory = RequestFactory()
|
||||
|
||||
def setUp(self):
|
||||
self.store = modulestore()
|
||||
super(ShibSPTest, self).setUp(create_user=False)
|
||||
self.test_user_id = ModuleStoreEnum.UserID.test
|
||||
|
||||
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
|
||||
def test_exception_shib_login(self):
|
||||
@@ -377,13 +378,21 @@ class ShibSPTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests that the correct course specific login and registration urls work for shib
|
||||
"""
|
||||
course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
|
||||
course = CourseFactory.create(
|
||||
org='MITx',
|
||||
number='999',
|
||||
display_name='Robot Super Course',
|
||||
user_id=self.test_user_id,
|
||||
)
|
||||
|
||||
# Test for cases where course is found
|
||||
for domain in ["", "shib:https://idp.stanford.edu/"]:
|
||||
# set domains
|
||||
course.enrollment_domain = domain
|
||||
self.store.update_item(course, '**replace_user**')
|
||||
|
||||
# temporarily set the branch to draft-preferred so we can update the course
|
||||
with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, course.id):
|
||||
course.enrollment_domain = domain
|
||||
self.store.update_item(course, self.test_user_id)
|
||||
|
||||
# setting location to test that GET params get passed through
|
||||
login_request = self.request_factory.get('/course_specific_login/MITx/999/Robot_Super_Course' +
|
||||
@@ -450,13 +459,21 @@ class ShibSPTest(ModuleStoreTestCase):
|
||||
"""
|
||||
|
||||
# create 2 course, one with limited enrollment one without
|
||||
shib_course = CourseFactory.create(org='Stanford', number='123', display_name='Shib Only')
|
||||
shib_course.enrollment_domain = 'shib:https://idp.stanford.edu/'
|
||||
self.store.update_item(shib_course, '**replace_user**')
|
||||
shib_course = CourseFactory.create(
|
||||
org='Stanford',
|
||||
number='123',
|
||||
display_name='Shib Only',
|
||||
enrollment_domain='shib:https://idp.stanford.edu/',
|
||||
user_id=self.test_user_id,
|
||||
)
|
||||
|
||||
open_enroll_course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
|
||||
open_enroll_course.enrollment_domain = ''
|
||||
self.store.update_item(open_enroll_course, '**replace_user**')
|
||||
open_enroll_course = CourseFactory.create(
|
||||
org='MITx',
|
||||
number='999',
|
||||
display_name='Robot Super Course',
|
||||
enrollment_domain='',
|
||||
user_id=self.test_user_id,
|
||||
)
|
||||
|
||||
# create 3 kinds of students, external_auth matching shib_course, external_auth not matching, no external auth
|
||||
shib_student = UserFactory.create()
|
||||
@@ -520,9 +537,13 @@ class ShibSPTest(ModuleStoreTestCase):
|
||||
student.save()
|
||||
extauth.save()
|
||||
|
||||
course = CourseFactory.create(org='Stanford', number='123', display_name='Shib Only')
|
||||
course.enrollment_domain = 'shib:https://idp.stanford.edu/'
|
||||
self.store.update_item(course, '**replace_user**')
|
||||
course = CourseFactory.create(
|
||||
org='Stanford',
|
||||
number='123',
|
||||
display_name='Shib Only',
|
||||
enrollment_domain='shib:https://idp.stanford.edu/',
|
||||
user_id=self.test_user_id,
|
||||
)
|
||||
|
||||
# use django test client for sessions and url processing
|
||||
# no enrollment before trying
|
||||
|
||||
@@ -97,7 +97,7 @@ class TestCourseListing(ModuleStoreTestCase):
|
||||
|
||||
course_location = SlashSeparatedCourseKey('testOrg', 'doomedCourse', 'RunBabyRun')
|
||||
self._create_course_with_access_groups(course_location)
|
||||
mongo_store.delete_course(course_location)
|
||||
mongo_store.delete_course(course_location, ModuleStoreEnum.UserID.test)
|
||||
|
||||
course_location = SlashSeparatedCourseKey('testOrg', 'erroredCourse', 'RunBabyRun')
|
||||
course = self._create_course_with_access_groups(course_location)
|
||||
|
||||
@@ -17,7 +17,6 @@ from student.views import _parse_course_id_from_string, _get_course_enrollment_d
|
||||
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, mixed_store_config
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from external_auth.models import ExternalAuthMap
|
||||
@@ -346,11 +345,20 @@ class ExternalAuthShibTest(ModuleStoreTestCase):
|
||||
Tests how login_user() interacts with ExternalAuth, in particular Shib
|
||||
"""
|
||||
def setUp(self):
|
||||
self.store = modulestore()
|
||||
self.course = CourseFactory.create(org='Stanford', number='456', display_name='NO SHIB')
|
||||
self.shib_course = CourseFactory.create(org='Stanford', number='123', display_name='Shib Only')
|
||||
self.shib_course.enrollment_domain = 'shib:https://idp.stanford.edu/'
|
||||
self.store.update_item(self.shib_course, '**replace_user**')
|
||||
super(ExternalAuthShibTest, self).setUp()
|
||||
self.course = CourseFactory.create(
|
||||
org='Stanford',
|
||||
number='456',
|
||||
display_name='NO SHIB',
|
||||
user_id=self.user.id,
|
||||
)
|
||||
self.shib_course = CourseFactory.create(
|
||||
org='Stanford',
|
||||
number='123',
|
||||
display_name='Shib Only',
|
||||
enrollment_domain='shib:https://idp.stanford.edu/',
|
||||
user_id=self.user.id,
|
||||
)
|
||||
self.user_w_map = UserFactory.create(email='withmap@stanford.edu')
|
||||
self.extauth = ExternalAuthMap(external_id='withmap@stanford.edu',
|
||||
external_email='withmap@stanford.edu',
|
||||
|
||||
@@ -30,8 +30,8 @@ class UrlResetMixin(object):
|
||||
# Resolve a URL so that the new urlconf gets loaded
|
||||
resolve('/')
|
||||
|
||||
def setUp(self):
|
||||
def setUp(self, **kwargs):
|
||||
"""Reset django default urlconf before tests and after tests"""
|
||||
super(UrlResetMixin, self).setUp()
|
||||
super(UrlResetMixin, self).setUp(**kwargs)
|
||||
self._reset_urls()
|
||||
self.addCleanup(self._reset_urls)
|
||||
|
||||
@@ -72,6 +72,21 @@ class ModuleStoreEnum(object):
|
||||
draft = 'draft-branch'
|
||||
published = 'published-branch'
|
||||
|
||||
class UserID(object):
|
||||
"""
|
||||
Values for user ID defaults
|
||||
"""
|
||||
# Note: we use negative values here to (try to) not collide
|
||||
# with user identifiers provided by actual user services.
|
||||
|
||||
# user ID to use for all management commands
|
||||
mgmt_command = -1
|
||||
|
||||
# user ID to use for primitive commands
|
||||
primitive_command = -2
|
||||
|
||||
# user ID to use for tests that do not have a django user available
|
||||
test = -3
|
||||
|
||||
class PublishState(object):
|
||||
"""
|
||||
@@ -270,6 +285,18 @@ class ModuleStoreRead(object):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def compute_publish_state(self, xblock):
|
||||
"""
|
||||
Returns whether this xblock is draft, public, or private.
|
||||
|
||||
Returns:
|
||||
PublishState.draft - content is in the process of being edited, but still has a previous
|
||||
version deployed to LMS
|
||||
PublishState.public - content is locked and deployed to LMS
|
||||
PublishState.private - content is editable and not deployed to LMS
|
||||
"""
|
||||
pass
|
||||
|
||||
class ModuleStoreWrite(ModuleStoreRead):
|
||||
"""
|
||||
@@ -280,7 +307,7 @@ class ModuleStoreWrite(ModuleStoreRead):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def update_item(self, xblock, user_id=None, allow_not_found=False, force=False):
|
||||
def update_item(self, xblock, user_id, allow_not_found=False, force=False):
|
||||
"""
|
||||
Update the given xblock's persisted repr. Pass the user's unique id which the persistent store
|
||||
should save with the update if it has that ability.
|
||||
@@ -296,7 +323,7 @@ class ModuleStoreWrite(ModuleStoreRead):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_item(self, location, user_id=None, **kwargs):
|
||||
def delete_item(self, location, user_id, **kwargs):
|
||||
"""
|
||||
Delete an item and its subtree from persistence. Remove the item from any parents (Note, does not
|
||||
affect parents from other branches or logical branches; thus, in old mongo, deleting something
|
||||
@@ -315,7 +342,7 @@ class ModuleStoreWrite(ModuleStoreRead):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_course(self, org, offering, user_id=None, fields=None, **kwargs):
|
||||
def create_course(self, org, offering, user_id, fields=None, **kwargs):
|
||||
"""
|
||||
Creates and returns the course.
|
||||
|
||||
@@ -348,7 +375,7 @@ class ModuleStoreWrite(ModuleStoreRead):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_course(self, course_key, user_id=None):
|
||||
def delete_course(self, course_key, user_id):
|
||||
"""
|
||||
Deletes the course. It may be a soft or hard delete. It may or may not remove the xblock definitions
|
||||
depending on the persistence layer and how tightly bound the xblocks are to the course.
|
||||
@@ -441,6 +468,12 @@ class ModuleStoreReadBase(ModuleStoreRead):
|
||||
None
|
||||
)
|
||||
|
||||
def compute_publish_state(self, xblock):
|
||||
"""
|
||||
Returns PublishState.public since this is a read-only store.
|
||||
"""
|
||||
return PublishState.public
|
||||
|
||||
def heartbeat(self):
|
||||
"""
|
||||
Is this modulestore ready?
|
||||
@@ -499,7 +532,7 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
|
||||
result[field.scope][field_name] = value
|
||||
return result
|
||||
|
||||
def update_item(self, xblock, user_id=None, allow_not_found=False, force=False):
|
||||
def update_item(self, xblock, user_id, allow_not_found=False, force=False):
|
||||
"""
|
||||
Update the given xblock's persisted repr. Pass the user's unique id which the persistent store
|
||||
should save with the update if it has that ability.
|
||||
@@ -514,7 +547,7 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_item(self, location, user_id=None, force=False):
|
||||
def delete_item(self, location, user_id, force=False):
|
||||
"""
|
||||
Delete an item from persistence. Pass the user's unique id which the persistent store
|
||||
should save with the update if it has that ability.
|
||||
@@ -553,6 +586,15 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
|
||||
super(ModuleStoreWriteBase, self).clone_course(source_course_id, dest_course_id, user_id)
|
||||
return dest_course_id
|
||||
|
||||
def delete_course(self, course_key, user_id):
|
||||
"""
|
||||
This base method just deletes the assets. The lower level impls must do the actual deleting of
|
||||
content.
|
||||
"""
|
||||
# delete the assets
|
||||
self.contentstore.delete_all_course_assets(course_key)
|
||||
super(ModuleStoreWriteBase, self).delete_course(course_key, user_id)
|
||||
|
||||
@contextmanager
|
||||
def bulk_write_operations(self, course_id):
|
||||
"""
|
||||
|
||||
@@ -229,16 +229,13 @@ class MixedModuleStore(ModuleStoreWriteBase):
|
||||
store = self._get_modulestore_for_courseid(course_id)
|
||||
return store.has_course(course_id, ignore_case)
|
||||
|
||||
def delete_course(self, course_key, user_id=None):
|
||||
def delete_course(self, course_key, user_id):
|
||||
"""
|
||||
See xmodule.modulestore.__init__.ModuleStoreWrite.delete_course
|
||||
"""
|
||||
assert(isinstance(course_key, CourseKey))
|
||||
store = self._get_modulestore_for_courseid(course_key)
|
||||
if hasattr(store, 'delete_course'):
|
||||
return store.delete_course(course_key, user_id)
|
||||
else:
|
||||
raise NotImplementedError(u"Cannot delete a course on store {}".format(store))
|
||||
return store.delete_course(course_key, user_id)
|
||||
|
||||
def get_parent_location(self, location, **kwargs):
|
||||
"""
|
||||
@@ -276,7 +273,7 @@ class MixedModuleStore(ModuleStoreWriteBase):
|
||||
errs.update(store.get_errored_courses())
|
||||
return errs
|
||||
|
||||
def create_course(self, org, offering, user_id=None, fields=None, **kwargs):
|
||||
def create_course(self, org, offering, user_id, fields=None, **kwargs):
|
||||
"""
|
||||
Creates and returns the course.
|
||||
|
||||
@@ -290,10 +287,6 @@ class MixedModuleStore(ModuleStoreWriteBase):
|
||||
Returns: a CourseDescriptor
|
||||
"""
|
||||
store = self._get_modulestore_for_courseid(None)
|
||||
|
||||
if not hasattr(store, 'create_course'):
|
||||
raise NotImplementedError(u"Cannot create a course on store {}".format(store))
|
||||
|
||||
return store.create_course(org, offering, user_id, fields, **kwargs)
|
||||
|
||||
def clone_course(self, source_course_id, dest_course_id, user_id):
|
||||
@@ -326,7 +319,7 @@ class MixedModuleStore(ModuleStoreWriteBase):
|
||||
source_course_id, user_id, dest_course_id.org, dest_course_id.offering
|
||||
)
|
||||
|
||||
def create_item(self, course_or_parent_loc, category, user_id=None, **kwargs):
|
||||
def create_item(self, course_or_parent_loc, category, user_id, **kwargs):
|
||||
"""
|
||||
Create and return the item. If parent_loc is a specific location v a course id,
|
||||
it installs the new item as a child of the parent (if the parent_loc is a specific
|
||||
@@ -353,7 +346,7 @@ class MixedModuleStore(ModuleStoreWriteBase):
|
||||
if parent_loc is not None and not 'detached' in xblock._class_tags:
|
||||
parent = store.get_item(parent_loc)
|
||||
parent.children.append(location)
|
||||
store.update_item(parent)
|
||||
store.update_item(parent, user_id)
|
||||
elif isinstance(store, SplitMongoModuleStore):
|
||||
if not isinstance(course_or_parent_loc, (CourseLocator, BlockUsageLocator)):
|
||||
raise ValueError(u"Cannot create a child of {} in split. Wrong repr.".format(course_or_parent_loc))
|
||||
@@ -378,7 +371,7 @@ class MixedModuleStore(ModuleStoreWriteBase):
|
||||
store = self._verify_modulestore_support(xblock.location, 'update_item')
|
||||
return store.update_item(xblock, user_id, allow_not_found)
|
||||
|
||||
def delete_item(self, location, user_id=None, **kwargs):
|
||||
def delete_item(self, location, user_id, **kwargs):
|
||||
"""
|
||||
Delete the given item from persistence. kwargs allow modulestore specific parameters.
|
||||
"""
|
||||
@@ -456,13 +449,7 @@ class MixedModuleStore(ModuleStoreWriteBase):
|
||||
"""
|
||||
course_id = xblock.scope_ids.usage_id.course_key
|
||||
store = self._get_modulestore_for_courseid(course_id)
|
||||
if hasattr(store, 'compute_publish_state'):
|
||||
return store.compute_publish_state(xblock)
|
||||
elif hasattr(store, 'publish'):
|
||||
raise NotImplementedError(u"Cannot compute_publish_state on store {}".format(store))
|
||||
else:
|
||||
# read-only store; so, everything's public
|
||||
return PublishState.public
|
||||
return store.compute_publish_state(xblock)
|
||||
|
||||
def publish(self, location, user_id):
|
||||
"""
|
||||
|
||||
@@ -841,7 +841,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
modules = self._load_items(course_id, list(items))
|
||||
return modules
|
||||
|
||||
def create_course(self, org, offering, user_id=None, fields=None, **kwargs):
|
||||
def create_course(self, org, offering, user_id, fields=None, **kwargs):
|
||||
"""
|
||||
Creates and returns the course.
|
||||
|
||||
@@ -892,15 +892,6 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
|
||||
return course
|
||||
|
||||
def delete_course(self, course_key, user_id=None):
|
||||
"""
|
||||
The impl removes all of the db records for the course.
|
||||
:param course_key:
|
||||
:param user_id:
|
||||
"""
|
||||
course_query = self._course_key_to_son(course_key)
|
||||
self.collection.remove(course_query, multi=True)
|
||||
|
||||
def create_xmodule(self, location, definition_data=None, metadata=None, runtime=None, fields={}):
|
||||
"""
|
||||
Create the new xmodule but don't save it. Returns the new module.
|
||||
@@ -992,7 +983,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
self._update_single_item(parent, update)
|
||||
self._update_ancestors(parent, update)
|
||||
|
||||
def update_item(self, xblock, user_id=None, allow_not_found=False, force=False, isPublish=False,
|
||||
def update_item(self, xblock, user_id, allow_not_found=False, force=False, isPublish=False,
|
||||
is_publish_root=True):
|
||||
"""
|
||||
Update the persisted version of xblock to reflect its current values.
|
||||
|
||||
@@ -144,6 +144,18 @@ class DraftModuleStore(MongoModuleStore):
|
||||
del key['_id.revision']
|
||||
return self.collection.find(key).count() > 0
|
||||
|
||||
def delete_course(self, course_key, user_id):
|
||||
"""
|
||||
:param course_key: which course to delete
|
||||
:param user_id: id of the user deleting the course
|
||||
"""
|
||||
# delete the assets
|
||||
super(DraftModuleStore, self).delete_course(course_key, user_id)
|
||||
|
||||
# delete all of the db records for the course
|
||||
course_query = self._course_key_to_son(course_key)
|
||||
self.collection.remove(course_query, multi=True)
|
||||
|
||||
def clone_course(self, source_course_id, dest_course_id, user_id):
|
||||
"""
|
||||
Only called if cloning within this store or if env doesn't set up mixed.
|
||||
@@ -419,7 +431,7 @@ class DraftModuleStore(MongoModuleStore):
|
||||
# get_item will wrap_draft so don't call it here (otherwise, it would override the is_draft attribute)
|
||||
return self.get_item(location)
|
||||
|
||||
def update_item(self, xblock, user_id=None, allow_not_found=False, force=False, isPublish=False):
|
||||
def update_item(self, xblock, user_id, allow_not_found=False, force=False, isPublish=False):
|
||||
"""
|
||||
See superclass doc.
|
||||
In addition to the superclass's behavior, this method converts the unit to draft if it's not
|
||||
|
||||
@@ -1380,7 +1380,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
|
||||
return result
|
||||
|
||||
def delete_course(self, course_key, user_id=None):
|
||||
def delete_course(self, course_key, user_id):
|
||||
"""
|
||||
Remove the given course from the course index.
|
||||
|
||||
@@ -1395,6 +1395,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
log.info(u"deleting course from split-mongo: %s", course_key)
|
||||
self.db_connection.delete_course_index(index)
|
||||
|
||||
# We do NOT call the super class here since we need to keep the assets
|
||||
# in case the course is later restored.
|
||||
# super(SplitMongoModuleStore, self).delete_course(course_key, user_id)
|
||||
|
||||
def revert_to_published(self, location, user_id=None):
|
||||
"""
|
||||
Reverts an item to its last published version (recursively traversing all of its descendants).
|
||||
@@ -1407,13 +1411,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_errored_courses(self):
|
||||
"""
|
||||
This function doesn't make sense for the mongo modulestore, as structures
|
||||
are loaded on demand, rather than up front
|
||||
"""
|
||||
return {}
|
||||
|
||||
def inherit_settings(self, block_map, block_json, inheriting_settings=None):
|
||||
"""
|
||||
Updates block_json with any inheritable setting set by an ancestor and recurses to children.
|
||||
|
||||
@@ -85,25 +85,3 @@ def rewrite_nonportable_content_links(source_course_id, dest_course_id, text):
|
||||
logging.warning("Error producing regex substitution %r for text = %r.\n\nError msg = %s", source_course_id, text, str(exc))
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def delete_course(modulestore, contentstore, course_key, commit=False):
|
||||
"""
|
||||
This method will actually do the work to delete all content in a course in a MongoDB backed
|
||||
courseware store. BE VERY CAREFUL, this is not reversable.
|
||||
"""
|
||||
|
||||
# check to see if the source course is actually there
|
||||
if not modulestore.has_course(course_key):
|
||||
raise Exception("Cannot find a course at {0}. Aborting".format(course_key))
|
||||
|
||||
if commit:
|
||||
print "Deleting assets and thumbnails {}".format(course_key)
|
||||
contentstore.delete_all_course_assets(course_key)
|
||||
|
||||
# finally delete the course
|
||||
print "Deleting {0}...".format(course_key)
|
||||
if commit:
|
||||
modulestore.delete_course(course_key, '**replace-user**')
|
||||
|
||||
return True
|
||||
|
||||
@@ -4,8 +4,10 @@ Modulestore configuration for test cases.
|
||||
|
||||
from uuid import uuid4
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
from xmodule.modulestore.django import (
|
||||
modulestore, clear_existing_modulestores, loc_mapper)
|
||||
modulestore, clear_existing_modulestores, loc_mapper
|
||||
)
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
|
||||
|
||||
@@ -126,18 +128,62 @@ class ModuleStoreTestCase(TestCase):
|
||||
`clear_existing_modulestores()` directly in
|
||||
your `setUp()` method.
|
||||
"""
|
||||
def setUp(self, **kwargs):
|
||||
"""
|
||||
Creates a test User if `create_user` is True.
|
||||
Returns the password for the test User.
|
||||
|
||||
@staticmethod
|
||||
def update_course(course):
|
||||
Args:
|
||||
create_user - specifies whether or not to create a test User. Default is True.
|
||||
"""
|
||||
super(ModuleStoreTestCase, self).setUp()
|
||||
|
||||
self.store = modulestore()
|
||||
|
||||
uname = 'testuser'
|
||||
email = 'test+courses@edx.org'
|
||||
password = 'foo'
|
||||
|
||||
if kwargs.pop('create_user', True):
|
||||
# Create the user so we can log them in.
|
||||
self.user = User.objects.create_user(uname, email, password)
|
||||
|
||||
# Note that we do not actually need to do anything
|
||||
# for registration if we directly mark them active.
|
||||
self.user.is_active = True
|
||||
|
||||
# Staff has access to view all courses
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
|
||||
return password
|
||||
|
||||
def create_non_staff_user(self):
|
||||
"""
|
||||
Creates a non-staff test user.
|
||||
Returns the non-staff test user and its password.
|
||||
"""
|
||||
uname = 'teststudent'
|
||||
password = 'foo'
|
||||
nonstaff_user = User.objects.create_user(uname, 'test+student@edx.org', password)
|
||||
|
||||
# Note that we do not actually need to do anything
|
||||
# for registration if we directly mark them active.
|
||||
nonstaff_user.is_active = True
|
||||
nonstaff_user.is_staff = False
|
||||
nonstaff_user.save()
|
||||
return nonstaff_user, password
|
||||
|
||||
def update_course(self, course, user_id):
|
||||
"""
|
||||
Updates the version of course in the modulestore
|
||||
|
||||
'course' is an instance of CourseDescriptor for which we want
|
||||
to update metadata.
|
||||
"""
|
||||
store = modulestore()
|
||||
store.update_item(course, '**replace_user**')
|
||||
updated_course = store.get_course(course.id)
|
||||
with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, course.id):
|
||||
self.store.update_item(course, user_id)
|
||||
updated_course = self.store.get_course(course.id)
|
||||
return updated_course
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -2,7 +2,7 @@ from factory import Factory, lazy_attribute_sequence, lazy_attribute
|
||||
from factory.containers import CyclicDefinitionError
|
||||
from uuid import uuid4
|
||||
|
||||
from xmodule.modulestore import prefer_xmodules
|
||||
from xmodule.modulestore import prefer_xmodules, ModuleStoreEnum
|
||||
from opaque_keys.edx.locations import Location
|
||||
from xblock.core import XBlock
|
||||
from xmodule.tabs import StaticTab
|
||||
@@ -52,21 +52,23 @@ class CourseFactory(XModuleFactory):
|
||||
store = kwargs.pop('modulestore')
|
||||
name = kwargs.get('name', kwargs.get('run', Location.clean(kwargs.get('display_name'))))
|
||||
run = kwargs.get('run', name)
|
||||
user_id = kwargs.pop('user_id', ModuleStoreEnum.UserID.test)
|
||||
|
||||
location = Location(org, number, run, 'course', name)
|
||||
|
||||
# Write the data to the mongo datastore
|
||||
new_course = store.create_xmodule(location, metadata=kwargs.get('metadata', None))
|
||||
with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred):
|
||||
# Write the data to the mongo datastore
|
||||
new_course = store.create_xmodule(location, metadata=kwargs.get('metadata', None))
|
||||
|
||||
# The rest of kwargs become attributes on the course:
|
||||
for k, v in kwargs.iteritems():
|
||||
setattr(new_course, k, v)
|
||||
# The rest of kwargs become attributes on the course:
|
||||
for k, v in kwargs.iteritems():
|
||||
setattr(new_course, k, v)
|
||||
|
||||
# Save the attributes we just set
|
||||
new_course.save()
|
||||
# Update the data in the mongo datastore
|
||||
store.update_item(new_course, '**replace_user**')
|
||||
return new_course
|
||||
# Save the attributes we just set
|
||||
new_course.save()
|
||||
# Update the data in the mongo datastore
|
||||
store.update_item(new_course, user_id)
|
||||
return new_course
|
||||
|
||||
|
||||
class ItemFactory(XModuleFactory):
|
||||
@@ -128,6 +130,8 @@ class ItemFactory(XModuleFactory):
|
||||
|
||||
:boilerplate: (optional) the boilerplate for overriding field values
|
||||
|
||||
:publish_item: (optional) whether or not to publish the item (default is True)
|
||||
|
||||
:target_class: is ignored
|
||||
"""
|
||||
|
||||
@@ -143,7 +147,8 @@ class ItemFactory(XModuleFactory):
|
||||
display_name = kwargs.pop('display_name', None)
|
||||
metadata = kwargs.pop('metadata', {})
|
||||
location = kwargs.pop('location')
|
||||
user_id = kwargs.pop('user_id', 999)
|
||||
user_id = kwargs.pop('user_id', ModuleStoreEnum.UserID.test)
|
||||
publish_item = kwargs.pop('publish_item', True)
|
||||
|
||||
assert isinstance(location, Location)
|
||||
assert location != parent_location
|
||||
@@ -153,47 +158,55 @@ class ItemFactory(XModuleFactory):
|
||||
# This code was based off that in cms/djangoapps/contentstore/views.py
|
||||
parent = kwargs.pop('parent', None) or store.get_item(parent_location)
|
||||
|
||||
if 'boilerplate' in kwargs:
|
||||
template_id = kwargs.pop('boilerplate')
|
||||
clz = XBlock.load_class(category, select=prefer_xmodules)
|
||||
template = clz.get_template(template_id)
|
||||
assert template is not None
|
||||
metadata.update(template.get('metadata', {}))
|
||||
if not isinstance(data, basestring):
|
||||
data.update(template.get('data'))
|
||||
with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred):
|
||||
|
||||
# replace the display name with an optional parameter passed in from the caller
|
||||
if display_name is not None:
|
||||
metadata['display_name'] = display_name
|
||||
runtime = parent.runtime if parent else None
|
||||
store.create_and_save_xmodule(location, user_id, metadata=metadata, definition_data=data, runtime=runtime)
|
||||
if 'boilerplate' in kwargs:
|
||||
template_id = kwargs.pop('boilerplate')
|
||||
clz = XBlock.load_class(category, select=prefer_xmodules)
|
||||
template = clz.get_template(template_id)
|
||||
assert template is not None
|
||||
metadata.update(template.get('metadata', {}))
|
||||
if not isinstance(data, basestring):
|
||||
data.update(template.get('data'))
|
||||
|
||||
module = store.get_item(location)
|
||||
# replace the display name with an optional parameter passed in from the caller
|
||||
if display_name is not None:
|
||||
metadata['display_name'] = display_name
|
||||
runtime = parent.runtime if parent else None
|
||||
store.create_and_save_xmodule(location, user_id, metadata=metadata, definition_data=data, runtime=runtime)
|
||||
|
||||
for attr, val in kwargs.items():
|
||||
setattr(module, attr, val)
|
||||
# Save the attributes we just set
|
||||
module.save()
|
||||
module = store.get_item(location)
|
||||
|
||||
store.update_item(module, '**replace_user**')
|
||||
for attr, val in kwargs.items():
|
||||
setattr(module, attr, val)
|
||||
# Save the attributes we just set
|
||||
module.save()
|
||||
|
||||
if 'detached' not in module._class_tags:
|
||||
parent.children.append(location)
|
||||
store.update_item(parent, '**replace_user**')
|
||||
store.update_item(module, user_id)
|
||||
|
||||
# VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so
|
||||
# if we add one then we need to also add it to the policy information (i.e. metadata)
|
||||
# we should remove this once we can break this reference from the course to static tabs
|
||||
if category == 'static_tab':
|
||||
course = store.get_course(location.course_key)
|
||||
course.tabs.append(
|
||||
StaticTab(
|
||||
name=display_name,
|
||||
url_slug=location.name,
|
||||
# VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so
|
||||
# if we add one then we need to also add it to the policy information (i.e. metadata)
|
||||
# we should remove this once we can break this reference from the course to static tabs
|
||||
if category == 'static_tab':
|
||||
course = store.get_course(location.course_key)
|
||||
course.tabs.append(
|
||||
StaticTab(
|
||||
name=display_name,
|
||||
url_slug=location.name,
|
||||
)
|
||||
)
|
||||
)
|
||||
store.update_item(course, '**replace_user**')
|
||||
store.update_item(course, user_id)
|
||||
|
||||
# parent and publish the item, so it can be accessed
|
||||
if 'detached' not in module._class_tags:
|
||||
parent.children.append(location)
|
||||
store.update_item(parent, user_id)
|
||||
if publish_item:
|
||||
store.publish(parent.location, user_id)
|
||||
elif publish_item:
|
||||
store.publish(location, user_id)
|
||||
|
||||
# return the published item
|
||||
return store.get_item(location)
|
||||
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class PersistentCourseFactory(SplitFactory):
|
||||
|
||||
# pylint: disable=W0613
|
||||
@classmethod
|
||||
def _create(cls, target_class, offering='999', org='testX', user_id='test_user',
|
||||
def _create(cls, target_class, offering='999', org='testX', user_id=ModuleStoreEnum.UserID.test,
|
||||
master_branch=ModuleStoreEnum.BranchName.draft, **kwargs):
|
||||
|
||||
modulestore = kwargs.pop('modulestore')
|
||||
@@ -59,7 +59,7 @@ class ItemFactory(SplitFactory):
|
||||
# pylint: disable=W0613
|
||||
@classmethod
|
||||
def _create(cls, target_class, parent_location, category='chapter',
|
||||
user_id='test_user', block_id=None, definition_locator=None, force=False,
|
||||
user_id=ModuleStoreEnum.UserID.test, block_id=None, definition_locator=None, force=False,
|
||||
continue_version=False, **kwargs):
|
||||
"""
|
||||
passes *kwargs* as the new item's field values:
|
||||
|
||||
@@ -116,7 +116,7 @@ class TestMixedModuleStore(LocMapperSetupSansDjango):
|
||||
self.writable_chapter_location = self.store = self.fake_location = self.xml_chapter_location = None
|
||||
self.course_locations = []
|
||||
|
||||
self.user_id = 0
|
||||
self.user_id = ModuleStoreEnum.UserID.test
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def _create_course(self, default, course_key):
|
||||
@@ -127,12 +127,12 @@ class TestMixedModuleStore(LocMapperSetupSansDjango):
|
||||
offering = course_key.offering.replace('/', '.')
|
||||
else:
|
||||
offering = course_key.offering
|
||||
course = self.store.create_course(course_key.org, offering)
|
||||
course = self.store.create_course(course_key.org, offering, self.user_id)
|
||||
category = self.writable_chapter_location.category
|
||||
block_id = self.writable_chapter_location.name
|
||||
chapter = self.store.create_item(
|
||||
# don't use course_location as it may not be the repr
|
||||
course.location, category, location=self.writable_chapter_location, block_id=block_id
|
||||
course.location, category, self.user_id, location=self.writable_chapter_location, block_id=block_id
|
||||
)
|
||||
if isinstance(course.id, CourseLocator):
|
||||
self.course_locations[self.MONGO_COURSEID] = course.location.version_agnostic()
|
||||
@@ -184,7 +184,7 @@ class TestMixedModuleStore(LocMapperSetupSansDjango):
|
||||
]
|
||||
|
||||
def create_sub_tree(parent, block_info):
|
||||
block = self.store.create_item(parent.location, category=block_info.category, block_id=block_info.display_name)
|
||||
block = self.store.create_item(parent.location, block_info.category, self.user_id, block_id=block_info.display_name)
|
||||
for tree in block_info.sub_tree:
|
||||
create_sub_tree(block, tree)
|
||||
# reload the block to update its children field
|
||||
@@ -306,13 +306,13 @@ class TestMixedModuleStore(LocMapperSetupSansDjango):
|
||||
self.assertFalse(course.show_calculator, "Default changed making test meaningless")
|
||||
course.show_calculator = True
|
||||
with self.assertRaises(NotImplementedError): # ensure it doesn't allow writing
|
||||
self.store.update_item(course, None)
|
||||
self.store.update_item(course, self.user_id)
|
||||
# now do it for a r/w db
|
||||
course = self.store.get_course(self.course_locations[self.MONGO_COURSEID].course_key)
|
||||
# if following raised, then the test is really a noop, change it
|
||||
self.assertFalse(course.show_calculator, "Default changed making test meaningless")
|
||||
course.show_calculator = True
|
||||
self.store.update_item(course, None)
|
||||
self.store.update_item(course, self.user_id)
|
||||
course = self.store.get_course(self.course_locations[self.MONGO_COURSEID].course_key)
|
||||
self.assertTrue(course.show_calculator)
|
||||
|
||||
@@ -324,8 +324,8 @@ class TestMixedModuleStore(LocMapperSetupSansDjango):
|
||||
self.initdb(default_ms)
|
||||
# r/o try deleting the course (is here to ensure it can't be deleted)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.store.delete_item(self.xml_chapter_location, 13)
|
||||
self.store.delete_item(self.writable_chapter_location, 9)
|
||||
self.store.delete_item(self.xml_chapter_location, self.user_id)
|
||||
self.store.delete_item(self.writable_chapter_location, self.user_id)
|
||||
# verify it's gone
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
self.store.get_item(self.writable_chapter_location)
|
||||
@@ -367,7 +367,7 @@ class TestMixedModuleStore(LocMapperSetupSansDjango):
|
||||
xml_store = self.store._get_modulestore_by_type(ModuleStoreEnum.Type.xml)
|
||||
# the important thing is not which exception it raises but that it raises an exception
|
||||
with self.assertRaises(AttributeError):
|
||||
xml_store.create_course("org", "course/run", 999)
|
||||
xml_store.create_course("org", "course/run", self.user_id)
|
||||
|
||||
@ddt.data('draft', 'split')
|
||||
def test_get_course(self, default_ms):
|
||||
@@ -547,7 +547,7 @@ class TestMixedModuleStore(LocMapperSetupSansDjango):
|
||||
self.initdb(default_ms)
|
||||
# create an orphan
|
||||
course_id = self.course_locations[self.MONGO_COURSEID].course_key
|
||||
orphan = self.store.create_item(course_id, 'problem', block_id='orphan')
|
||||
orphan = self.store.create_item(course_id, 'problem', self.user_id, block_id='orphan')
|
||||
found_orphans = self.store.get_orphans(self.course_locations[self.MONGO_COURSEID].course_key)
|
||||
if default_ms == 'split':
|
||||
self.assertEqual(found_orphans, [orphan.location.version_agnostic()])
|
||||
@@ -561,7 +561,7 @@ class TestMixedModuleStore(LocMapperSetupSansDjango):
|
||||
new location for the child
|
||||
"""
|
||||
self.initdb(default_ms)
|
||||
self.store.create_item(self.course_locations[self.MONGO_COURSEID], 'problem', block_id='orphan')
|
||||
self.store.create_item(self.course_locations[self.MONGO_COURSEID], 'problem', self.user_id, block_id='orphan')
|
||||
orphans = self.store.get_orphans(self.course_locations[self.MONGO_COURSEID].course_key)
|
||||
self.assertEqual(len(orphans), 0, "unexpected orphans: {}".format(orphans))
|
||||
|
||||
|
||||
@@ -134,6 +134,14 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
# Destroy the test db.
|
||||
connection.drop_database(DB)
|
||||
|
||||
def setUp(self):
|
||||
# make a copy for convenience
|
||||
self.connection = TestMongoModuleStore.connection
|
||||
self.dummy_user = ModuleStoreEnum.UserID.test
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_init(self):
|
||||
'''Make sure the db loads'''
|
||||
ids = list(self.connection[DB][COLLECTION].find({}, {'_id': True}))
|
||||
@@ -351,7 +359,7 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
# set toy course to share the wiki with simple course
|
||||
toy_course = self.draft_store.get_course(SlashSeparatedCourseKey('edX', 'toy', '2012_Fall'))
|
||||
toy_course.wiki_slug = 'simple'
|
||||
self.draft_store.update_item(toy_course)
|
||||
self.draft_store.update_item(toy_course, ModuleStoreEnum.UserID.test)
|
||||
|
||||
# now toy_course should not be retrievable with old wiki_slug
|
||||
course_locations = self.draft_store.get_courses_for_wiki('toy')
|
||||
@@ -366,7 +374,7 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
# configure simple course to use unique wiki_slug.
|
||||
simple_course = self.draft_store.get_course(SlashSeparatedCourseKey('edX', 'simple', '2012_Fall'))
|
||||
simple_course.wiki_slug = 'edX.simple.2012_Fall'
|
||||
self.draft_store.update_item(simple_course)
|
||||
self.draft_store.update_item(simple_course, ModuleStoreEnum.UserID.test)
|
||||
# it should be retrievable with its new wiki_slug
|
||||
course_locations = self.draft_store.get_courses_for_wiki('edX.simple.2012_Fall')
|
||||
assert_equals(len(course_locations), 1)
|
||||
@@ -491,11 +499,10 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
"""
|
||||
course_location = Location('edx', 'direct', '2012_Fall', 'course', 'test_course')
|
||||
chapter_location = Location('edx', 'direct', '2012_Fall', 'chapter', 'test_chapter')
|
||||
dummy_user = 123
|
||||
|
||||
# Create dummy direct only xblocks
|
||||
self.draft_store.create_and_save_xmodule(course_location, user_id=dummy_user)
|
||||
self.draft_store.create_and_save_xmodule(chapter_location, user_id=dummy_user)
|
||||
self.draft_store.create_and_save_xmodule(course_location, user_id=self.dummy_user)
|
||||
self.draft_store.create_and_save_xmodule(chapter_location, user_id=self.dummy_user)
|
||||
|
||||
# Check that neither xblock has changes
|
||||
self.assertFalse(self.draft_store.has_changes(course_location))
|
||||
@@ -506,29 +513,28 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
Tests that has_changes() only returns true when changes are present
|
||||
"""
|
||||
location = Location('edX', 'changes', '2012_Fall', 'vertical', 'test_vertical')
|
||||
dummy_user = 123
|
||||
|
||||
# Create a dummy component to test against
|
||||
self.draft_store.create_and_save_xmodule(location, user_id=dummy_user)
|
||||
self.draft_store.create_and_save_xmodule(location, user_id=self.dummy_user)
|
||||
|
||||
# Not yet published, so changes are present
|
||||
self.assertTrue(self.draft_store.has_changes(location))
|
||||
|
||||
# Publish and verify that there are no unpublished changes
|
||||
self.draft_store.publish(location, dummy_user)
|
||||
self.draft_store.publish(location, self.dummy_user)
|
||||
self.assertFalse(self.draft_store.has_changes(location))
|
||||
|
||||
# Change the component, then check that there now are changes
|
||||
component = self.draft_store.get_item(location)
|
||||
component.display_name = 'Changed Display Name'
|
||||
self.draft_store.update_item(component, dummy_user)
|
||||
self.draft_store.update_item(component, self.dummy_user)
|
||||
self.assertTrue(self.draft_store.has_changes(location))
|
||||
|
||||
# Publish and verify again
|
||||
self.draft_store.publish(location, dummy_user)
|
||||
self.draft_store.publish(location, self.dummy_user)
|
||||
self.assertFalse(self.draft_store.has_changes(location))
|
||||
|
||||
def _create_test_tree(self, name, user_id=123):
|
||||
def _create_test_tree(self, name, user_id=None):
|
||||
"""
|
||||
Creates and returns a tree with the following structure:
|
||||
Grandparent
|
||||
@@ -538,6 +544,9 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
Child Sibling
|
||||
|
||||
"""
|
||||
if user_id is None:
|
||||
user_id = self.dummy_user
|
||||
|
||||
locations = {
|
||||
'grandparent': Location('edX', 'tree', name, 'chapter', 'grandparent'),
|
||||
'parent_sibling': Location('edX', 'tree', name, 'sequential', 'parent_sibling'),
|
||||
@@ -566,7 +575,6 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
"""
|
||||
Tests that has_changes() returns true on ancestors when a child is changed
|
||||
"""
|
||||
dummy_user = 123
|
||||
locations = self._create_test_tree('has_changes_ancestors')
|
||||
|
||||
# Verify that there are no unpublished changes
|
||||
@@ -576,7 +584,7 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
# Change the child
|
||||
child = self.draft_store.get_item(locations['child'])
|
||||
child.display_name = 'Changed Display Name'
|
||||
self.draft_store.update_item(child, user_id=dummy_user)
|
||||
self.draft_store.update_item(child, user_id=self.dummy_user)
|
||||
|
||||
# All ancestors should have changes, but not siblings
|
||||
self.assertTrue(self.draft_store.has_changes(locations['grandparent']))
|
||||
@@ -586,7 +594,7 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
self.assertFalse(self.draft_store.has_changes(locations['child_sibling']))
|
||||
|
||||
# Publish the unit with changes
|
||||
self.draft_store.publish(locations['parent'], dummy_user)
|
||||
self.draft_store.publish(locations['parent'], self.dummy_user)
|
||||
|
||||
# Verify that there are no unpublished changes
|
||||
for key in locations:
|
||||
@@ -596,7 +604,6 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
"""
|
||||
Tests that has_changes() returns false after a child is published only if all children are unchanged
|
||||
"""
|
||||
dummy_user = 123
|
||||
locations = self._create_test_tree('has_changes_publish_ancestors')
|
||||
|
||||
# Verify that there are no unpublished changes
|
||||
@@ -608,22 +615,22 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
child_sibling = self.draft_store.get_item(locations['child_sibling'])
|
||||
child.display_name = 'Changed Display Name'
|
||||
child_sibling.display_name = 'Changed Display Name'
|
||||
self.draft_store.update_item(child, user_id=dummy_user)
|
||||
self.draft_store.update_item(child_sibling, user_id=dummy_user)
|
||||
self.draft_store.update_item(child, user_id=self.dummy_user)
|
||||
self.draft_store.update_item(child_sibling, user_id=self.dummy_user)
|
||||
|
||||
# Verify that ancestors have changes
|
||||
self.assertTrue(self.draft_store.has_changes(locations['grandparent']))
|
||||
self.assertTrue(self.draft_store.has_changes(locations['parent']))
|
||||
|
||||
# Publish one child
|
||||
self.draft_store.publish(locations['child_sibling'], dummy_user)
|
||||
self.draft_store.publish(locations['child_sibling'], self.dummy_user)
|
||||
|
||||
# Verify that ancestors still have changes
|
||||
self.assertTrue(self.draft_store.has_changes(locations['grandparent']))
|
||||
self.assertTrue(self.draft_store.has_changes(locations['parent']))
|
||||
|
||||
# Publish the other child
|
||||
self.draft_store.publish(locations['child'], dummy_user)
|
||||
self.draft_store.publish(locations['child'], self.dummy_user)
|
||||
|
||||
# Verify that ancestors now have no changes
|
||||
self.assertFalse(self.draft_store.has_changes(locations['grandparent']))
|
||||
@@ -634,7 +641,6 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
Tests that has_changes() returns true for the parent when a child with changes is added
|
||||
and false when that child is removed.
|
||||
"""
|
||||
dummy_user = 123
|
||||
locations = self._create_test_tree('has_changes_add_remove_child')
|
||||
|
||||
# Test that the ancestors don't have changes
|
||||
@@ -643,10 +649,10 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
|
||||
# Create a new child and attach it to parent
|
||||
new_child_location = Location('edX', 'tree', 'has_changes_add_remove_child', 'vertical', 'new_child')
|
||||
self.draft_store.create_and_save_xmodule(new_child_location, user_id=dummy_user)
|
||||
self.draft_store.create_and_save_xmodule(new_child_location, user_id=self.dummy_user)
|
||||
parent = self.draft_store.get_item(locations['parent'])
|
||||
parent.children += [new_child_location]
|
||||
self.draft_store.update_item(parent, user_id=dummy_user)
|
||||
self.draft_store.update_item(parent, user_id=self.dummy_user)
|
||||
|
||||
# Verify that the ancestors now have changes
|
||||
self.assertTrue(self.draft_store.has_changes(locations['grandparent']))
|
||||
@@ -655,7 +661,7 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
# Remove the child from the parent
|
||||
parent = self.draft_store.get_item(locations['parent'])
|
||||
parent.children = [locations['child'], locations['child_sibling']]
|
||||
self.draft_store.update_item(parent, user_id=dummy_user)
|
||||
self.draft_store.update_item(parent, user_id=self.dummy_user)
|
||||
|
||||
# Verify that ancestors now have no changes
|
||||
self.assertFalse(self.draft_store.has_changes(locations['grandparent']))
|
||||
@@ -665,15 +671,14 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
"""
|
||||
Tests that has_changes() returns true after editing the child of a vertical (both not direct only categories).
|
||||
"""
|
||||
dummy_user = 123
|
||||
parent_location = Location('edX', 'test', 'non_direct_only_children', 'vertical', 'parent')
|
||||
child_location = Location('edX', 'test', 'non_direct_only_children', 'html', 'child')
|
||||
|
||||
parent = self.draft_store.create_and_save_xmodule(parent_location, user_id=dummy_user)
|
||||
child = self.draft_store.create_and_save_xmodule(child_location, user_id=dummy_user)
|
||||
parent = self.draft_store.create_and_save_xmodule(parent_location, user_id=self.dummy_user)
|
||||
child = self.draft_store.create_and_save_xmodule(child_location, user_id=self.dummy_user)
|
||||
parent.children += [child_location]
|
||||
self.draft_store.update_item(parent, user_id=dummy_user)
|
||||
self.draft_store.publish(parent_location, dummy_user)
|
||||
self.draft_store.update_item(parent, user_id=self.dummy_user)
|
||||
self.draft_store.publish(parent_location, self.dummy_user)
|
||||
|
||||
# Verify that there are no changes
|
||||
self.assertFalse(self.draft_store.has_changes(parent_location))
|
||||
@@ -681,7 +686,7 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
|
||||
# Change the child
|
||||
child.display_name = 'Changed Display Name'
|
||||
self.draft_store.update_item(child, user_id=123)
|
||||
self.draft_store.update_item(child, user_id=self.dummy_user)
|
||||
|
||||
# Verify that both parent and child have changes
|
||||
self.assertTrue(self.draft_store.has_changes(parent_location))
|
||||
@@ -693,7 +698,7 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
"""
|
||||
create_user = 123
|
||||
edit_user = 456
|
||||
locations = self._create_test_tree('update_edit_info_ancestors', create_user)
|
||||
locations =self._create_test_tree('update_edit_info_ancestors', create_user)
|
||||
|
||||
def check_node(location_key, after, before, edited_by, subtree_after, subtree_before, subtree_by):
|
||||
"""
|
||||
@@ -739,24 +744,23 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
Tests that edited_on and edited_by are set correctly during an update
|
||||
"""
|
||||
location = Location('edX', 'editInfoTest', '2012_Fall', 'html', 'test_html')
|
||||
dummy_user = 123
|
||||
|
||||
# Create a dummy component to test against
|
||||
self.draft_store.create_and_save_xmodule(location, user_id=dummy_user)
|
||||
self.draft_store.create_and_save_xmodule(location, user_id=self.dummy_user)
|
||||
|
||||
# Store the current edit time and verify that dummy_user created the component
|
||||
component = self.draft_store.get_item(location)
|
||||
self.assertEqual(component.edited_by, dummy_user)
|
||||
self.assertEqual(component.edited_by, self.dummy_user)
|
||||
old_edited_on = component.edited_on
|
||||
|
||||
# Change the component
|
||||
component.display_name = component.display_name + ' Changed'
|
||||
self.draft_store.update_item(component, dummy_user)
|
||||
self.draft_store.update_item(component, self.dummy_user)
|
||||
updated_component = self.draft_store.get_item(location)
|
||||
|
||||
# Verify the ordering of edit times and that dummy_user made the edit
|
||||
self.assertLess(old_edited_on, updated_component.edited_on)
|
||||
self.assertEqual(updated_component.edited_by, dummy_user)
|
||||
self.assertEqual(updated_component.edited_by, self.dummy_user)
|
||||
|
||||
def test_update_published_info(self):
|
||||
"""
|
||||
|
||||
@@ -5,6 +5,7 @@ Tests for split_migrator
|
||||
import uuid
|
||||
import random
|
||||
import mock
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.mongo.base import MongoRevisionKey
|
||||
from xmodule.modulestore.loc_mapper_store import LocMapperStore
|
||||
from xmodule.modulestore.split_migrator import SplitMigrator
|
||||
@@ -63,7 +64,7 @@ class TestMigration(SplitWMongoCourseBoostrapper):
|
||||
self.create_random_units(False, both_vert_loc)
|
||||
draft_both = self.draft_mongo.get_item(both_vert_loc)
|
||||
draft_both.display_name = 'Both vertical renamed'
|
||||
self.draft_mongo.update_item(draft_both)
|
||||
self.draft_mongo.update_item(draft_both, ModuleStoreEnum.UserID.test)
|
||||
self.create_random_units(True, both_vert_loc)
|
||||
# vertical in draft only (x2)
|
||||
draft_vert_loc = self.old_course_key.make_usage_key('vertical', uuid.uuid4().hex)
|
||||
|
||||
@@ -441,6 +441,7 @@ class SplitModuleTest(unittest.TestCase):
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def bootstrapDB():
|
||||
'''
|
||||
@@ -489,6 +490,9 @@ class SplitModuleTest(unittest.TestCase):
|
||||
destination = CourseLocator(org="testx", offering="wonderful", branch=BRANCH_NAME_PUBLISHED)
|
||||
split_store.xblock_publish("test@edx.org", to_publish, destination, [to_publish], None)
|
||||
|
||||
def setUp(self):
|
||||
self.user_id = random.getrandbits(32)
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Clear persistence between each test.
|
||||
@@ -817,7 +821,7 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
draft_course = CourseLocator(org='testx', offering='GreekHero', branch=BRANCH_NAME_DRAFT)
|
||||
published_course = CourseLocator(org='testx', offering='GreekHero', branch=BRANCH_NAME_PUBLISHED)
|
||||
head = draft_course.make_usage_key('course', 'head12345')
|
||||
dummy_user = 'testUser'
|
||||
dummy_user = ModuleStoreEnum.UserID.test
|
||||
|
||||
# Not yet published, so changes are present
|
||||
self.assertTrue(modulestore().has_changes(head))
|
||||
@@ -1217,7 +1221,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
|
||||
problem.max_attempts = 4
|
||||
problem.save() # decache above setting into the kvs
|
||||
updated_problem = modulestore().update_item(problem, '**replace_user**')
|
||||
updated_problem = modulestore().update_item(problem, self.user_id)
|
||||
# check that course version changed and course's previous is the other one
|
||||
self.assertEqual(updated_problem.definition_locator.definition_id, pre_def_id)
|
||||
self.assertNotEqual(updated_problem.location.version_guid, pre_version_guid)
|
||||
@@ -1232,7 +1236,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
|
||||
history_info = modulestore().get_course_history_info(current_course.location)
|
||||
self.assertEqual(history_info['previous_version'], pre_version_guid)
|
||||
self.assertEqual(history_info['edited_by'], "**replace_user**")
|
||||
self.assertEqual(history_info['edited_by'], self.user_id)
|
||||
|
||||
def test_update_children(self):
|
||||
"""
|
||||
@@ -1249,7 +1253,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
self.assertGreater(len(block.children), 0, "meaningless test")
|
||||
moved_child = block.children.pop()
|
||||
block.save() # decache model changes
|
||||
updated_problem = modulestore().update_item(block, '**replace_user**')
|
||||
updated_problem = modulestore().update_item(block, self.user_id)
|
||||
# check that course version changed and course's previous is the other one
|
||||
self.assertEqual(updated_problem.definition_locator.definition_id, pre_def_id)
|
||||
self.assertNotEqual(updated_problem.location.version_guid, pre_version_guid)
|
||||
@@ -1258,7 +1262,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
locator = locator.course_key.make_usage_key('Chapter', "chapter1")
|
||||
other_block = modulestore().get_item(locator)
|
||||
other_block.children.append(moved_child)
|
||||
other_updated = modulestore().update_item(other_block, '**replace_user**')
|
||||
other_updated = modulestore().update_item(other_block, self.user_id)
|
||||
self.assertIn(moved_child.version_agnostic(), version_agnostic(other_updated.children))
|
||||
|
||||
def test_update_definition(self):
|
||||
@@ -1274,7 +1278,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
|
||||
block.grading_policy['GRADER'][0]['min_count'] = 13
|
||||
block.save() # decache model changes
|
||||
updated_block = modulestore().update_item(block, '**replace_user**')
|
||||
updated_block = modulestore().update_item(block, self.user_id)
|
||||
|
||||
self.assertNotEqual(updated_block.definition_locator.definition_id, pre_def_id)
|
||||
self.assertNotEqual(updated_block.location.version_guid, pre_version_guid)
|
||||
@@ -1320,7 +1324,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
block.advertised_start = "Soon"
|
||||
|
||||
block.save() # decache model changes
|
||||
updated_block = modulestore().update_item(block, "**replace_user**")
|
||||
updated_block = modulestore().update_item(block, self.user_id)
|
||||
self.assertNotEqual(updated_block.definition_locator.definition_id, pre_def_id)
|
||||
self.assertNotEqual(updated_block.location.version_guid, pre_version_guid)
|
||||
self.assertEqual(updated_block.grading_policy['GRADER'][0]['min_count'], 13)
|
||||
@@ -1330,13 +1334,13 @@ class TestItemCrud(SplitModuleTest):
|
||||
def test_delete_item(self):
|
||||
course = self.create_course_for_deletion()
|
||||
with self.assertRaises(ValueError):
|
||||
modulestore().delete_item(course.location, 'deleting_user')
|
||||
modulestore().delete_item(course.location, self.user_id)
|
||||
reusable_location = course.id.version_agnostic().for_branch(BRANCH_NAME_DRAFT)
|
||||
|
||||
# delete a leaf
|
||||
problems = modulestore().get_items(reusable_location, category='problem')
|
||||
locn_to_del = problems[0].location
|
||||
new_course_loc = modulestore().delete_item(locn_to_del, 'deleting_user')
|
||||
new_course_loc = modulestore().delete_item(locn_to_del, self.user_id)
|
||||
deleted = locn_to_del.version_agnostic()
|
||||
self.assertFalse(modulestore().has_item(deleted))
|
||||
with self.assertRaises(VersionConflictError):
|
||||
@@ -1347,7 +1351,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
|
||||
# delete a subtree
|
||||
nodes = modulestore().get_items(reusable_location, category='chapter')
|
||||
new_course_loc = modulestore().delete_item(nodes[0].location, 'deleting_user')
|
||||
new_course_loc = modulestore().delete_item(nodes[0].location, self.user_id)
|
||||
# check subtree
|
||||
|
||||
def check_subtree(node):
|
||||
@@ -1582,7 +1586,6 @@ class TestPublish(SplitModuleTest):
|
||||
"""
|
||||
def setUp(self):
|
||||
SplitModuleTest.setUp(self)
|
||||
self.user = random.getrandbits(32)
|
||||
|
||||
def tearDown(self):
|
||||
SplitModuleTest.tearDown(self)
|
||||
@@ -1597,14 +1600,14 @@ class TestPublish(SplitModuleTest):
|
||||
chapter1 = source_course.make_usage_key('chapter', 'chapter1')
|
||||
chapter2 = source_course.make_usage_key('chapter', 'chapter2')
|
||||
chapter3 = source_course.make_usage_key('chapter', 'chapter3')
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, [head], [chapter2, chapter3])
|
||||
modulestore().xblock_publish(self.user_id, source_course, dest_course, [head], [chapter2, chapter3])
|
||||
expected = [head.block_id, chapter1.block_id]
|
||||
self._check_course(
|
||||
source_course, dest_course, expected, [chapter2.block_id, chapter3.block_id, "problem1", "problem3_2"]
|
||||
)
|
||||
# add a child under chapter1
|
||||
new_module = modulestore().create_item(
|
||||
chapter1, "sequential", self.user,
|
||||
chapter1, "sequential", self.user_id,
|
||||
fields={'display_name': 'new sequential'},
|
||||
)
|
||||
# remove chapter1 from expected b/c its pub'd version != the source anymore since source changed
|
||||
@@ -1613,7 +1616,7 @@ class TestPublish(SplitModuleTest):
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().get_item(new_module.location.map_into_course(dest_course))
|
||||
# publish it
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location], None)
|
||||
modulestore().xblock_publish(self.user_id, source_course, dest_course, [new_module.location], None)
|
||||
expected.append(new_module.location.block_id)
|
||||
# check that it is in the published course and that its parent is the chapter
|
||||
pub_module = modulestore().get_item(new_module.location.map_into_course(dest_course))
|
||||
@@ -1622,10 +1625,10 @@ class TestPublish(SplitModuleTest):
|
||||
)
|
||||
# ensure intentionally orphaned blocks work (e.g., course_info)
|
||||
new_module = modulestore().create_item(
|
||||
source_course, "course_info", self.user, block_id="handouts"
|
||||
source_course, "course_info", self.user_id, block_id="handouts"
|
||||
)
|
||||
# publish it
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location], None)
|
||||
modulestore().xblock_publish(self.user_id, source_course, dest_course, [new_module.location], None)
|
||||
expected.append(new_module.location.block_id)
|
||||
# check that it is in the published course (no error means it worked)
|
||||
pub_module = modulestore().get_item(new_module.location.map_into_course(dest_course))
|
||||
@@ -1644,15 +1647,15 @@ class TestPublish(SplitModuleTest):
|
||||
chapter3 = source_course.make_usage_key('chapter', 'chapter3')
|
||||
problem1 = source_course.make_usage_key('problem', 'problem1')
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().xblock_publish(self.user, source_course, destination_course, [chapter3], None)
|
||||
modulestore().xblock_publish(self.user_id, source_course, destination_course, [chapter3], None)
|
||||
# publishing into a new branch w/o publishing the root
|
||||
destination_course = CourseLocator(org='testx', offering='GreekHero', branch=BRANCH_NAME_PUBLISHED)
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().xblock_publish(self.user, source_course, destination_course, [chapter3], None)
|
||||
modulestore().xblock_publish(self.user_id, source_course, destination_course, [chapter3], None)
|
||||
# publishing a subdag w/o the parent already in course
|
||||
modulestore().xblock_publish(self.user, source_course, destination_course, [head], [chapter3])
|
||||
modulestore().xblock_publish(self.user_id, source_course, destination_course, [head], [chapter3])
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().xblock_publish(self.user, source_course, destination_course, [problem1], [])
|
||||
modulestore().xblock_publish(self.user_id, source_course, destination_course, [problem1], [])
|
||||
|
||||
def test_move_delete(self):
|
||||
"""
|
||||
@@ -1663,7 +1666,7 @@ class TestPublish(SplitModuleTest):
|
||||
head = source_course.make_usage_key('course', "head12345")
|
||||
chapter2 = source_course.make_usage_key('chapter', 'chapter2')
|
||||
problem1 = source_course.make_usage_key('problem', 'problem1')
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, [head], [chapter2])
|
||||
modulestore().xblock_publish(self.user_id, source_course, dest_course, [head], [chapter2])
|
||||
expected = ["head12345", "chapter1", "chapter3", "problem1", "problem3_2"]
|
||||
self._check_course(source_course, dest_course, expected, ["chapter2"])
|
||||
# now move problem1 and delete problem3_2
|
||||
@@ -1671,8 +1674,8 @@ class TestPublish(SplitModuleTest):
|
||||
chapter3 = modulestore().get_item(source_course.make_usage_key("chapter", "chapter3"))
|
||||
chapter1.children.append(problem1)
|
||||
chapter3.children.remove(problem1.map_into_course(chapter3.location.course_key))
|
||||
modulestore().delete_item(source_course.make_usage_key("problem", "problem3_2"), self.user)
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, [head], [chapter2])
|
||||
modulestore().delete_item(source_course.make_usage_key("problem", "problem3_2"), self.user_id)
|
||||
modulestore().xblock_publish(self.user_id, source_course, dest_course, [head], [chapter2])
|
||||
expected = ["head12345", "chapter1", "chapter3", "problem1"]
|
||||
self._check_course(source_course, dest_course, expected, ["chapter2", "problem3_2"])
|
||||
|
||||
@@ -1681,7 +1684,7 @@ class TestPublish(SplitModuleTest):
|
||||
Check that the course has the expected blocks and does not have the unexpected blocks
|
||||
"""
|
||||
history_info = modulestore().get_course_history_info(dest_course_loc)
|
||||
self.assertEqual(history_info['edited_by'], self.user)
|
||||
self.assertEqual(history_info['edited_by'], self.user_id)
|
||||
for expected in expected_blocks:
|
||||
# since block_type has no impact on identity, we can just provide an empty string
|
||||
source = modulestore().get_item(source_course_loc.make_usage_key("", expected))
|
||||
@@ -1690,8 +1693,8 @@ class TestPublish(SplitModuleTest):
|
||||
self.assertEqual(source.category, pub_copy.category)
|
||||
self.assertEqual(source.update_version, pub_copy.update_version)
|
||||
self.assertEqual(
|
||||
self.user, pub_copy.edited_by,
|
||||
"{} edited_by {} not {}".format(pub_copy.location, pub_copy.edited_by, self.user)
|
||||
self.user_id, pub_copy.edited_by,
|
||||
"{} edited_by {} not {}".format(pub_copy.location, pub_copy.edited_by, self.user_id)
|
||||
)
|
||||
for field in source.fields.values():
|
||||
if field.name == 'children':
|
||||
|
||||
@@ -10,7 +10,6 @@ from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
|
||||
from xmodule.modulestore.mongo import MongoModuleStore, DraftMongoModuleStore
|
||||
from xmodule.modulestore.mongo.draft import DIRECT_ONLY_CATEGORIES
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from mock import Mock
|
||||
|
||||
|
||||
class SplitWMongoCourseBoostrapper(unittest.TestCase):
|
||||
@@ -138,6 +137,6 @@ class SplitWMongoCourseBoostrapper(unittest.TestCase):
|
||||
self.split_mongo.create_course(
|
||||
self.split_course_key.org, self.split_course_key.offering, self.userid, fields=fields, root_block_id='runid'
|
||||
)
|
||||
old_course = self.old_mongo.create_course(self.split_course_key.org, 'test_course/runid', fields=fields)
|
||||
old_course = self.old_mongo.create_course(self.split_course_key.org, 'test_course/runid', self.userid, fields=fields)
|
||||
self.old_course_key = old_course.id
|
||||
self.runtime = old_course.runtime
|
||||
|
||||
@@ -17,6 +17,7 @@ import xblock
|
||||
from xmodule.tabs import CourseTabList
|
||||
from xmodule.modulestore.exceptions import InvalidLocationError
|
||||
from xmodule.modulestore.mongo.base import MongoRevisionKey
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -159,165 +160,167 @@ def import_from_xml(
|
||||
# of course modules. It will be left as a TBD to implement that
|
||||
# method on XmlModuleStore.
|
||||
course_items = []
|
||||
for course_key in xml_module_store.modules.keys():
|
||||
|
||||
if target_course_id is not None:
|
||||
dest_course_id = target_course_id
|
||||
else:
|
||||
dest_course_id = course_key
|
||||
with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred):
|
||||
for course_key in xml_module_store.modules.keys():
|
||||
|
||||
# Creates a new course if it doesn't already exist
|
||||
if create_new_course_if_not_present and not store.has_course(dest_course_id, ignore_case=True):
|
||||
try:
|
||||
store.create_course(dest_course_id.org, dest_course_id.offering)
|
||||
except InvalidLocationError:
|
||||
# course w/ same org and course exists
|
||||
log.debug(
|
||||
"Skipping import of course with id, {0},"
|
||||
"since it collides with an existing one".format(dest_course_id)
|
||||
)
|
||||
continue
|
||||
if target_course_id is not None:
|
||||
dest_course_id = target_course_id
|
||||
else:
|
||||
dest_course_id = course_key
|
||||
|
||||
with store.bulk_write_operations(dest_course_id):
|
||||
course_data_path = None
|
||||
|
||||
if verbose:
|
||||
log.debug("Scanning {0} for course module...".format(course_key))
|
||||
|
||||
# Quick scan to get course module as we need some info from there.
|
||||
# Also we need to make sure that the course module is committed
|
||||
# first into the store
|
||||
for module in xml_module_store.modules[course_key].itervalues():
|
||||
if module.scope_ids.block_type == 'course':
|
||||
course_data_path = path(data_dir) / module.data_dir
|
||||
|
||||
log.debug(u'======> IMPORTING course {course_key}'.format(
|
||||
course_key=course_key,
|
||||
))
|
||||
|
||||
if not do_import_static:
|
||||
# for old-style xblock where this was actually linked to kvs
|
||||
module.static_asset_path = module.data_dir
|
||||
module.save()
|
||||
log.debug('course static_asset_path={path}'.format(
|
||||
path=module.static_asset_path
|
||||
))
|
||||
|
||||
log.debug('course data_dir={0}'.format(module.data_dir))
|
||||
|
||||
course = _import_module_and_update_references(
|
||||
module, store, user_id,
|
||||
course_key,
|
||||
dest_course_id,
|
||||
do_import_static=do_import_static
|
||||
# Creates a new course if it doesn't already exist
|
||||
if create_new_course_if_not_present and not store.has_course(dest_course_id, ignore_case=True):
|
||||
try:
|
||||
store.create_course(dest_course_id.org, dest_course_id.offering, user_id)
|
||||
except InvalidLocationError:
|
||||
# course w/ same org and course exists
|
||||
log.debug(
|
||||
"Skipping import of course with id, {0},"
|
||||
"since it collides with an existing one".format(dest_course_id)
|
||||
)
|
||||
|
||||
for entry in course.pdf_textbooks:
|
||||
for chapter in entry.get('chapters', []):
|
||||
if StaticContent.is_c4x_path(chapter.get('url', '')):
|
||||
asset_key = StaticContent.get_location_from_path(chapter['url'])
|
||||
chapter['url'] = StaticContent.get_static_path_from_location(asset_key)
|
||||
|
||||
# Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'.
|
||||
# If we are importing into a course with a different course_id and wiki_slug is equal to either of these default
|
||||
# values then remap it so that the wiki does not point to the old wiki.
|
||||
if course_key != course.id:
|
||||
original_unique_wiki_slug = u'{0}.{1}.{2}'.format(
|
||||
course_key.org,
|
||||
course_key.course,
|
||||
course_key.run
|
||||
)
|
||||
if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course:
|
||||
course.wiki_slug = u'{0}.{1}.{2}'.format(
|
||||
course.id.org,
|
||||
course.id.course,
|
||||
course.id.run,
|
||||
)
|
||||
|
||||
# cdodge: more hacks (what else). Seems like we have a
|
||||
# problem when importing a course (like 6.002) which
|
||||
# does not have any tabs defined in the policy file.
|
||||
# The import goes fine and then displays fine in LMS,
|
||||
# but if someone tries to add a new tab in the CMS, then
|
||||
# the LMS barfs because it expects that -- if there are
|
||||
# *any* tabs -- then there at least needs to be
|
||||
# some predefined ones
|
||||
if course.tabs is None or len(course.tabs) == 0:
|
||||
CourseTabList.initialize_default(course)
|
||||
|
||||
store.update_item(course, user_id)
|
||||
|
||||
course_items.append(course)
|
||||
break
|
||||
|
||||
# TODO: shouldn't this raise an exception if course wasn't found?
|
||||
|
||||
# then import all the static content
|
||||
if static_content_store is not None and do_import_static:
|
||||
# first pass to find everything in /static/
|
||||
import_static_content(
|
||||
course_data_path, static_content_store,
|
||||
dest_course_id, subpath='static', verbose=verbose
|
||||
)
|
||||
|
||||
elif verbose and not do_import_static:
|
||||
log.debug(
|
||||
"Skipping import of static content, "
|
||||
"since do_import_static={0}".format(do_import_static)
|
||||
)
|
||||
|
||||
# no matter what do_import_static is, import "static_import" directory
|
||||
|
||||
# This is needed because the "about" pages (eg "overview") are
|
||||
# loaded via load_extra_content, and do not inherit the lms
|
||||
# metadata from the course module, and thus do not get
|
||||
# "static_content_store" properly defined. Static content
|
||||
# referenced in those extra pages thus need to come through the
|
||||
# c4x:// contentstore, unfortunately. Tell users to copy that
|
||||
# content into the "static_import" subdir.
|
||||
|
||||
simport = 'static_import'
|
||||
if os.path.exists(course_data_path / simport):
|
||||
import_static_content(
|
||||
course_data_path, static_content_store,
|
||||
dest_course_id, subpath=simport, verbose=verbose
|
||||
)
|
||||
|
||||
# now loop through all the modules
|
||||
for module in xml_module_store.modules[course_key].itervalues():
|
||||
if module.scope_ids.block_type == 'course':
|
||||
# we've already saved the course module up at the top
|
||||
# of the loop so just skip over it in the inner loop
|
||||
continue
|
||||
|
||||
if verbose:
|
||||
log.debug('importing module location {loc}'.format(
|
||||
loc=module.location
|
||||
))
|
||||
with store.bulk_write_operations(dest_course_id):
|
||||
course_data_path = None
|
||||
|
||||
_import_module_and_update_references(
|
||||
module, store,
|
||||
if verbose:
|
||||
log.debug("Scanning {0} for course module...".format(course_key))
|
||||
|
||||
# Quick scan to get course module as we need some info from there.
|
||||
# Also we need to make sure that the course module is committed
|
||||
# first into the store
|
||||
for module in xml_module_store.modules[course_key].itervalues():
|
||||
if module.scope_ids.block_type == 'course':
|
||||
course_data_path = path(data_dir) / module.data_dir
|
||||
|
||||
log.debug(u'======> IMPORTING course {course_key}'.format(
|
||||
course_key=course_key,
|
||||
))
|
||||
|
||||
if not do_import_static:
|
||||
# for old-style xblock where this was actually linked to kvs
|
||||
module.static_asset_path = module.data_dir
|
||||
module.save()
|
||||
log.debug('course static_asset_path={path}'.format(
|
||||
path=module.static_asset_path
|
||||
))
|
||||
|
||||
log.debug('course data_dir={0}'.format(module.data_dir))
|
||||
|
||||
course = _import_module_and_update_references(
|
||||
module, store, user_id,
|
||||
course_key,
|
||||
dest_course_id,
|
||||
do_import_static=do_import_static
|
||||
)
|
||||
|
||||
for entry in course.pdf_textbooks:
|
||||
for chapter in entry.get('chapters', []):
|
||||
if StaticContent.is_c4x_path(chapter.get('url', '')):
|
||||
asset_key = StaticContent.get_location_from_path(chapter['url'])
|
||||
chapter['url'] = StaticContent.get_static_path_from_location(asset_key)
|
||||
|
||||
# Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'.
|
||||
# If we are importing into a course with a different course_id and wiki_slug is equal to either of these default
|
||||
# values then remap it so that the wiki does not point to the old wiki.
|
||||
if course_key != course.id:
|
||||
original_unique_wiki_slug = u'{0}.{1}.{2}'.format(
|
||||
course_key.org,
|
||||
course_key.course,
|
||||
course_key.run
|
||||
)
|
||||
if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course:
|
||||
course.wiki_slug = u'{0}.{1}.{2}'.format(
|
||||
course.id.org,
|
||||
course.id.course,
|
||||
course.id.run,
|
||||
)
|
||||
|
||||
# cdodge: more hacks (what else). Seems like we have a
|
||||
# problem when importing a course (like 6.002) which
|
||||
# does not have any tabs defined in the policy file.
|
||||
# The import goes fine and then displays fine in LMS,
|
||||
# but if someone tries to add a new tab in the CMS, then
|
||||
# the LMS barfs because it expects that -- if there are
|
||||
# *any* tabs -- then there at least needs to be
|
||||
# some predefined ones
|
||||
if course.tabs is None or len(course.tabs) == 0:
|
||||
CourseTabList.initialize_default(course)
|
||||
|
||||
store.update_item(course, user_id)
|
||||
|
||||
course_items.append(course)
|
||||
break
|
||||
|
||||
# TODO: shouldn't this raise an exception if course wasn't found?
|
||||
|
||||
# then import all the static content
|
||||
if static_content_store is not None and do_import_static:
|
||||
# first pass to find everything in /static/
|
||||
import_static_content(
|
||||
course_data_path, static_content_store,
|
||||
dest_course_id, subpath='static', verbose=verbose
|
||||
)
|
||||
|
||||
elif verbose and not do_import_static:
|
||||
log.debug(
|
||||
"Skipping import of static content, "
|
||||
"since do_import_static={0}".format(do_import_static)
|
||||
)
|
||||
|
||||
# no matter what do_import_static is, import "static_import" directory
|
||||
|
||||
# This is needed because the "about" pages (eg "overview") are
|
||||
# loaded via load_extra_content, and do not inherit the lms
|
||||
# metadata from the course module, and thus do not get
|
||||
# "static_content_store" properly defined. Static content
|
||||
# referenced in those extra pages thus need to come through the
|
||||
# c4x:// contentstore, unfortunately. Tell users to copy that
|
||||
# content into the "static_import" subdir.
|
||||
|
||||
simport = 'static_import'
|
||||
if os.path.exists(course_data_path / simport):
|
||||
import_static_content(
|
||||
course_data_path, static_content_store,
|
||||
dest_course_id, subpath=simport, verbose=verbose
|
||||
)
|
||||
|
||||
# now loop through all the modules
|
||||
for module in xml_module_store.modules[course_key].itervalues():
|
||||
if module.scope_ids.block_type == 'course':
|
||||
# we've already saved the course module up at the top
|
||||
# of the loop so just skip over it in the inner loop
|
||||
continue
|
||||
|
||||
if verbose:
|
||||
log.debug('importing module location {loc}'.format(
|
||||
loc=module.location
|
||||
))
|
||||
|
||||
_import_module_and_update_references(
|
||||
module, store,
|
||||
user_id,
|
||||
course_key,
|
||||
dest_course_id,
|
||||
do_import_static=do_import_static,
|
||||
runtime=course.runtime
|
||||
)
|
||||
|
||||
# finally, publish the course
|
||||
store.publish(course.location, user_id)
|
||||
|
||||
# now import any DRAFT items
|
||||
_import_course_draft(
|
||||
xml_module_store,
|
||||
store,
|
||||
user_id,
|
||||
course_data_path,
|
||||
course_key,
|
||||
dest_course_id,
|
||||
do_import_static=do_import_static,
|
||||
runtime=course.runtime
|
||||
course.runtime
|
||||
)
|
||||
|
||||
# finally, publish the course
|
||||
store.publish(course.location, user_id)
|
||||
|
||||
# now import any DRAFT items
|
||||
_import_course_draft(
|
||||
xml_module_store,
|
||||
store,
|
||||
user_id,
|
||||
course_data_path,
|
||||
course_key,
|
||||
dest_course_id,
|
||||
course.runtime
|
||||
)
|
||||
|
||||
return xml_module_store, course_items
|
||||
|
||||
|
||||
|
||||
@@ -559,7 +559,7 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor, StudioEditableDes
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
# request does not have a user attribute, so pass None for user.
|
||||
# TODO user.id - to be fixed by Publishing team
|
||||
self.system.modulestore.update_item(self, None)
|
||||
return Response()
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ class VideoDescriptor(VideoFields, VideoStudioViewHandlers, TabsEditingDescripto
|
||||
Save module with updated metadata to database."
|
||||
"""
|
||||
self.save()
|
||||
self.runtime.modulestore.update_item(self, user.id if user else None)
|
||||
self.runtime.modulestore.update_item(self, user.id)
|
||||
|
||||
@property
|
||||
def editable_metadata_fields(self):
|
||||
|
||||
@@ -28,12 +28,13 @@ class AnonymousIndexPageTest(ModuleStoreTestCase):
|
||||
Tests that anonymous users can access the '/' page, Need courses with start date
|
||||
"""
|
||||
def setUp(self):
|
||||
self.store = modulestore()
|
||||
super(AnonymousIndexPageTest, self).setUp()
|
||||
self.factory = RequestFactory()
|
||||
self.course = CourseFactory.create()
|
||||
self.course.days_early_for_beta = 5
|
||||
self.course.enrollment_start = datetime.datetime.now(UTC) + datetime.timedelta(days=3)
|
||||
self.store.update_item(self.course, '**replace_user**')
|
||||
self.course = CourseFactory.create(
|
||||
days_early_for_beta=5,
|
||||
enrollment_start=datetime.datetime.now(UTC)+datetime.timedelta(days=3),
|
||||
user_id=self.user.id,
|
||||
)
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_STARTDATE)
|
||||
def test_none_user_index_access_with_startdate_fails(self):
|
||||
|
||||
@@ -3,7 +3,7 @@ import textwrap
|
||||
from lettuce import world, steps
|
||||
from nose.tools import assert_in, assert_equals, assert_true
|
||||
|
||||
from common import i_am_registered_for_the_course, visit_scenario_item, publish
|
||||
from common import i_am_registered_for_the_course, visit_scenario_item
|
||||
|
||||
DATA_TEMPLATE = textwrap.dedent("""\
|
||||
<annotatable>
|
||||
@@ -78,9 +78,6 @@ class AnnotatableSteps(object):
|
||||
display_name="Test Annotation Module",
|
||||
data=DATA_TEMPLATE.format("\n".join(ANNOTATION_TEMPLATE.format(i) for i in xrange(count)))
|
||||
)
|
||||
|
||||
publish(world.scenario_dict['ANNOTATION_VERTICAL'].location)
|
||||
|
||||
self.annotations_count = count
|
||||
|
||||
def view_component(self, step):
|
||||
@@ -125,7 +122,6 @@ class AnnotatableSteps(object):
|
||||
)
|
||||
)
|
||||
)
|
||||
publish(world.scenario_dict['ANNOTATION_VERTICAL'].location)
|
||||
|
||||
def click_reply(self, step, problem):
|
||||
r"""I click "Reply to annotation" on passage (?P<problem>\d+)$"""
|
||||
|
||||
@@ -134,10 +134,6 @@ def section_location(course_num):
|
||||
return world.scenario_dict['SECTION'].location.replace(course=course_num)
|
||||
|
||||
|
||||
def publish(location):
|
||||
modulestore().publish(location, '**replace_user**')
|
||||
|
||||
|
||||
def visit_scenario_item(item_key):
|
||||
"""
|
||||
Go to the courseware page containing the item stored in `world.scenario_dict`
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from lettuce import world, steps
|
||||
from nose.tools import assert_in, assert_true # pylint: disable=no-name-in-module
|
||||
|
||||
from common import i_am_registered_for_the_course, visit_scenario_item, publish
|
||||
from common import i_am_registered_for_the_course, visit_scenario_item
|
||||
from problems_setup import add_problem_to_course, answer_problem
|
||||
|
||||
@steps
|
||||
@@ -67,9 +67,6 @@ class ConditionalSteps(object):
|
||||
data='<html><div class="hidden-contents">Hidden Contents</p></html>'
|
||||
)
|
||||
|
||||
publish(world.scenario_dict['VERTICAL'].location)
|
||||
|
||||
|
||||
def setup_problem_attempts(self, step, not_attempted=None):
|
||||
r'that the conditioned problem has (?P<not_attempted>not )?been attempted$'
|
||||
visit_scenario_item('CONDITION_SOURCE')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from lettuce import world, steps
|
||||
from nose.tools import assert_in, assert_equals, assert_true
|
||||
|
||||
from common import i_am_registered_for_the_course, visit_scenario_item, publish
|
||||
from common import i_am_registered_for_the_course, visit_scenario_item
|
||||
from problems_setup import add_problem_to_course, answer_problem
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from courseware.tests.modulestore_config import TEST_DATA_XML_MODULESTORE
|
||||
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
|
||||
from courseware.tests.modulestore_config import TEST_DATA_MONGO_MODULESTORE
|
||||
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
@@ -56,7 +57,7 @@ class CommandsTestBase(TestCase):
|
||||
courses = store.get_courses()
|
||||
# NOTE: if xml store owns these, it won't import them into mongo
|
||||
if SlashSeparatedCourseKey.from_deprecated_string(TEST_COURSE_ID) not in [c.id for c in courses]:
|
||||
import_from_xml(store, "**replace_user**", DATA_DIR, ['toy', 'simple'])
|
||||
import_from_xml(store, ModuleStoreEnum.UserID.mgmt_command, DATA_DIR, ['toy', 'simple'])
|
||||
|
||||
return [course.id for course in store.get_courses()]
|
||||
|
||||
|
||||
@@ -132,6 +132,7 @@ class BaseTestXmodule(ModuleStoreTestCase):
|
||||
self.assertTrue(all(self.login_statuses))
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestXmodule, self).setUp()
|
||||
self.setup_course()
|
||||
self.initialize_module(metadata=self.METADATA, data=self.DATA)
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class LoginEnrollmentTestCase(TestCase):
|
||||
self.email = 'foo@test.com'
|
||||
self.password = 'bar'
|
||||
self.username = 'test'
|
||||
self.create_account(self.username,
|
||||
self.user = self.create_account(self.username,
|
||||
self.email, self.password)
|
||||
self.activate_user(self.email)
|
||||
self.login(self.email, self.password)
|
||||
@@ -107,7 +107,9 @@ class LoginEnrollmentTestCase(TestCase):
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['success'], True)
|
||||
# Check both that the user is created, and inactive
|
||||
self.assertFalse(User.objects.get(email=email).is_active)
|
||||
user = User.objects.get(email=email)
|
||||
self.assertFalse(user.is_active)
|
||||
return user
|
||||
|
||||
def activate_user(self, email):
|
||||
"""
|
||||
|
||||
@@ -10,7 +10,8 @@ from django.test.utils import override_settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
|
||||
from xmodule.modulestore.mongo.base import MongoRevisionKey
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
@@ -160,7 +161,8 @@ class TestLTIModuleListing(ModuleStoreTestCase):
|
||||
parent_location=self.section2.location,
|
||||
display_name="lti draft",
|
||||
category="lti",
|
||||
location=self.course.id.make_usage_key('lti', 'lti_published').replace(revision=MongoRevisionKey.draft),
|
||||
location=self.course.id.make_usage_key('lti', 'lti_published'),
|
||||
publish_item=False,
|
||||
)
|
||||
|
||||
def expected_handler_url(self, handler):
|
||||
|
||||
@@ -19,8 +19,6 @@ from django.test.utils import override_settings
|
||||
from courseware import grades
|
||||
from courseware.models import StudentModule
|
||||
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
#import factories and parent testcase modules
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
@@ -46,7 +44,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
super(TestSubmittingProblems, self).setUp()
|
||||
super(TestSubmittingProblems, self).setUp(create_user=False)
|
||||
# Create course
|
||||
self.course = CourseFactory.create(display_name=self.COURSE_NAME, number=self.COURSE_SLUG)
|
||||
assert self.course, "Couldn't load course %r" % self.COURSE_NAME
|
||||
@@ -64,7 +62,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
"""
|
||||
Re-fetch the course from the database so that the object being dealt with has everything added to it.
|
||||
"""
|
||||
self.course = modulestore().get_course(self.course.id)
|
||||
self.course = self.store.get_course(self.course.id)
|
||||
|
||||
def problem_location(self, problem_url_name):
|
||||
"""
|
||||
@@ -230,8 +228,7 @@ class TestCourseGrader(TestSubmittingProblems):
|
||||
"""
|
||||
|
||||
self.course.grading_policy = grading_policy
|
||||
store = modulestore()
|
||||
store.update_item(self.course, '**replace_user**')
|
||||
self.update_course(self.course, self.student_user.id)
|
||||
self.refresh_course()
|
||||
|
||||
def get_grade_summary(self):
|
||||
|
||||
@@ -12,6 +12,7 @@ from webob import Request
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
from . import BaseTestXmodule
|
||||
from .test_video_xml import SOURCE_XML
|
||||
@@ -411,7 +412,8 @@ class TestTranscriptTranslationGetDispatch(TestVideo):
|
||||
self.course.static_asset_path = 'dummy/static'
|
||||
self.course.save()
|
||||
store = modulestore()
|
||||
store.update_item(self.course, 'OEoXaMPEzfM')
|
||||
with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, self.course.id):
|
||||
store.update_item(self.course, self.user.id)
|
||||
|
||||
# Test youtube style en
|
||||
request = Request.blank('/translation/en?videoId=12345')
|
||||
|
||||
@@ -110,6 +110,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
return super(TestViewAuth, self).login(user.email, 'test')
|
||||
|
||||
def setUp(self):
|
||||
super(TestViewAuth, self).setUp()
|
||||
|
||||
self.course = CourseFactory.create(number='999', display_name='Robot_Super_Course')
|
||||
self.courseware_chapter = ItemFactory.create(display_name='courseware')
|
||||
@@ -296,8 +297,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
tomorrow = now + datetime.timedelta(days=1)
|
||||
self.course.start = tomorrow
|
||||
self.test_course.start = tomorrow
|
||||
self.course = self.update_course(self.course)
|
||||
self.test_course = self.update_course(self.test_course)
|
||||
self.course = self.update_course(self.course, self.user.id)
|
||||
self.test_course = self.update_course(self.test_course, self.user.id)
|
||||
|
||||
self.assertFalse(self.course.has_started())
|
||||
self.assertFalse(self.test_course.has_started())
|
||||
@@ -321,8 +322,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
tomorrow = now + datetime.timedelta(days=1)
|
||||
self.course.start = tomorrow
|
||||
self.test_course.start = tomorrow
|
||||
self.course = self.update_course(self.course)
|
||||
self.test_course = self.update_course(self.test_course)
|
||||
self.course = self.update_course(self.course, self.user.id)
|
||||
self.test_course = self.update_course(self.test_course, self.user.id)
|
||||
|
||||
self.login(self.instructor_user)
|
||||
# Enroll in the classes---can't see courseware otherwise.
|
||||
@@ -345,8 +346,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
|
||||
self.course.start = tomorrow
|
||||
self.test_course.start = tomorrow
|
||||
self.course = self.update_course(self.course)
|
||||
self.test_course = self.update_course(self.test_course)
|
||||
self.course = self.update_course(self.course, self.user.id)
|
||||
self.test_course = self.update_course(self.test_course, self.user.id)
|
||||
|
||||
self.login(self.global_staff_user)
|
||||
self.enroll(self.course, True)
|
||||
@@ -373,8 +374,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
# test_course course's has
|
||||
self.test_course.enrollment_start = yesterday
|
||||
self.test_course.enrollment_end = tomorrow
|
||||
self.course = self.update_course(self.course)
|
||||
self.test_course = self.update_course(self.test_course)
|
||||
self.course = self.update_course(self.course, self.user.id)
|
||||
self.test_course = self.update_course(self.test_course, self.user.id)
|
||||
|
||||
# First, try with an enrolled student
|
||||
self.login(self.unenrolled_user)
|
||||
|
||||
@@ -142,9 +142,8 @@ class TestMongoCoursesLoad(ModuleStoreTestCase, PageLoaderTestCase):
|
||||
super(TestMongoCoursesLoad, self).setUp()
|
||||
self.setup_user()
|
||||
|
||||
# Import the toy course into a Mongo-backed modulestore
|
||||
self.store = modulestore()
|
||||
import_from_xml(self.store, "**replace_user**", TEST_DATA_DIR, ['toy'])
|
||||
# Import the toy course
|
||||
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'])
|
||||
|
||||
@mock.patch('xmodule.course_module.requests.get')
|
||||
def test_toy_textbooks_loads(self, mock_get):
|
||||
|
||||
@@ -15,10 +15,9 @@ from django.core.management.base import CommandError
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
from xmodule.modulestore.store_utilities import delete_course
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
import dashboard.git_import as git_import
|
||||
from dashboard.git_import import GitImportError
|
||||
@@ -161,9 +160,7 @@ class TestGitAddCourse(ModuleStoreTestCase):
|
||||
self.TEST_BRANCH)
|
||||
|
||||
# Delete to test branching back to master
|
||||
delete_course(def_ms, contentstore(),
|
||||
self.TEST_BRANCH_COURSE,
|
||||
True)
|
||||
def_ms.delete_course(self.TEST_BRANCH_COURSE, ModuleStoreEnum.UserID.test)
|
||||
self.assertIsNone(def_ms.get_course(self.TEST_BRANCH_COURSE))
|
||||
git_import.add_repo(self.TEST_REPO,
|
||||
repo_dir / 'edx4edx_lite',
|
||||
|
||||
@@ -38,10 +38,8 @@ from external_auth.models import ExternalAuthMap
|
||||
from external_auth.views import generate_password
|
||||
from student.models import CourseEnrollment, UserProfile, Registration
|
||||
import track.views
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.store_utilities import delete_course
|
||||
from xmodule.modulestore.xml import XMLModuleStore
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
@@ -591,9 +589,7 @@ class Courses(SysadminDashboardView):
|
||||
|
||||
elif course_found and not is_xml_course:
|
||||
# delete course that is stored with mongodb backend
|
||||
content_store = contentstore()
|
||||
commit = True
|
||||
delete_course(self.def_ms, content_store, course.id, commit)
|
||||
self.def_ms.delete_course(course.id, request.user.id)
|
||||
# don't delete user permission groups, though
|
||||
self.msg += \
|
||||
u"<font color='red'>{0} {1} = {2} ({3})</font>".format(
|
||||
|
||||
@@ -54,7 +54,7 @@ class SysadminBaseTestCase(ModuleStoreTestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Setup test case by adding primary user."""
|
||||
super(SysadminBaseTestCase, self).setUp()
|
||||
super(SysadminBaseTestCase, self).setUp(create_user=False)
|
||||
self.user = UserFactory.create(username='test_user',
|
||||
email='test_user+sysadmin@edx.org',
|
||||
password='foo')
|
||||
@@ -129,6 +129,9 @@ class TestSysadmin(SysadminBaseTestCase):
|
||||
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)
|
||||
|
||||
@@ -41,7 +41,7 @@ class ViewsTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSetupMixin):
|
||||
# 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()
|
||||
super(ViewsTestCase, self).setUp(create_user=False)
|
||||
|
||||
# create a course
|
||||
self.course = CourseFactory.create(org='MITx', course='999',
|
||||
|
||||
@@ -13,6 +13,7 @@ from django.contrib.auth.models import User
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from capa.tests.response_xml_factory import OptionResponseXMLFactory
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
@@ -207,8 +208,10 @@ class InstructorTaskModuleTestCase(InstructorTaskCourseTestCase):
|
||||
problem_xml = factory.build_xml(**factory_args)
|
||||
location = InstructorTaskTestCase.problem_location(problem_url_name)
|
||||
item = self.module_store.get_item(location)
|
||||
item.data = problem_xml
|
||||
self.module_store.update_item(item, '**replace_user**')
|
||||
with self.module_store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, location.course_key):
|
||||
item.data = problem_xml
|
||||
self.module_store.update_item(item, self.user.id)
|
||||
self.module_store.publish(location, self.user.id)
|
||||
|
||||
def get_student_module(self, username, descriptor):
|
||||
"""Get StudentModule object for test course, given the `username` and the problem's `descriptor`."""
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.core.urlresolvers import reverse
|
||||
from capa.tests.response_xml_factory import (CodeResponseXMLFactory,
|
||||
CustomResponseXMLFactory)
|
||||
from xmodule.modulestore.tests.factories import ItemFactory
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
|
||||
from courseware.model_data import StudentModule
|
||||
|
||||
@@ -105,6 +106,9 @@ class TestRescoringTask(TestIntegrationTask):
|
||||
self.create_student('u4')
|
||||
self.logout()
|
||||
|
||||
# set up test user for performing test operations
|
||||
self.setup_user()
|
||||
|
||||
def render_problem(self, username, problem_url_name):
|
||||
"""
|
||||
Use ajax interface to request html for a problem.
|
||||
@@ -294,7 +298,9 @@ class TestRescoringTask(TestIntegrationTask):
|
||||
InstructorTaskModuleTestCase.problem_location(problem_url_name)
|
||||
)
|
||||
descriptor.data = problem_xml
|
||||
self.module_store.update_item(descriptor, '**replace_user**')
|
||||
with self.module_store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, descriptor.location.course_key):
|
||||
self.module_store.update_item(descriptor, self.user.id)
|
||||
self.module_store.publish(descriptor.location, self.user.id)
|
||||
else:
|
||||
# Use "per-student" rerandomization so that check-problem can be called more than once.
|
||||
# Using "always" means we cannot check a problem twice, but we want to call once to get the
|
||||
|
||||
@@ -109,7 +109,6 @@ STATICFILES_DIRS += [
|
||||
if os.path.isdir(COMMON_TEST_DATA_ROOT / course_dir)
|
||||
]
|
||||
|
||||
MODULESTORE_BRANCH = 'draft-preferred'
|
||||
update_module_store_settings(
|
||||
MODULESTORE,
|
||||
module_store_options={
|
||||
|
||||
Reference in New Issue
Block a user