merging from master

This commit is contained in:
John Jarvis
2014-11-26 16:21:16 -05:00
2397 changed files with 54271 additions and 359163 deletions

2
.gitignore vendored
View File

@@ -41,6 +41,8 @@ conf/locale/en/LC_MESSAGES/*.po
conf/locale/en/LC_MESSAGES/*.mo
conf/locale/fake*/LC_MESSAGES/*.po
conf/locale/fake*/LC_MESSAGES/*.mo
conf/locale/eo/LC_MESSAGES/*.po
conf/locale/eo/LC_MESSAGES/*.mo
conf/locale/messages.mo
### Testing artifacts

View File

@@ -179,3 +179,5 @@ Henry Tareque <henry.tareque@gmail.com>
Eugeny Kolpakov <eugeny.kolpakov@gmail.com>
Omar Al-Ithawi <oithawi@qrf.org>
Louis Pilfold <louis@lpil.uk>
Akiva Leffert <akiva@edx.org>
Mike Bifulco <mbifulco@aquent.com>

View File

@@ -7,6 +7,8 @@ the top. Include a label indicating the component affected.
Common: Add configurable reset button to units
Studio: Add support xblock validation messages on Studio unit/container page. TNL-683
LMS: Support adding cohorts from the instructor dashboard. TNL-162
LMS: Support adding students to a cohort via the instructor dashboard. TNL-163

View File

@@ -1 +0,0 @@

View File

@@ -286,8 +286,15 @@ def _do_studio_prompt_action(intent, action):
Wait for a studio prompt to appear and press the specified action button
See cms/static/js/views/feedback_prompt.js for implementation
"""
assert intent in ['warning', 'error', 'confirmation', 'announcement',
'step-required', 'help', 'mini']
assert intent in [
'warning',
'error',
'confirmation',
'announcement',
'step-required',
'help',
'mini',
]
assert action in ['primary', 'secondary']
world.wait_for_present('div.wrapper-prompt.is-shown#prompt-{}'.format(intent))
@@ -333,7 +340,6 @@ def get_codemirror_value(index=0, find_prefix="$"):
)
def attach_file(filename, sub_path):
path = os.path.join(TEST_ROOT, sub_path, filename)
world.browser.execute_script("$('input.file-input').css('display', 'block')")
@@ -388,4 +394,3 @@ def create_other_user(_step, name, has_extra_perms, role_name):
@step('I log out')
def log_out(_step):
world.visit('logout')

View File

@@ -52,19 +52,13 @@ def see_a_multi_step_component(step, category):
world.wait_for(lambda _: len(world.css_find(selector)) == len(step.hashes))
for idx, step_hash in enumerate(step.hashes):
if category == 'HTML':
html_matcher = {
'Text':
'\n \n',
'Announcement':
'<p> Words of encouragement! This is a short note that most students will read. </p>',
'Zooming Image':
'<h2>ZOOMING DIAGRAMS</h2>',
'E-text Written in LaTeX':
'<h2>Example: E-text page</h2>',
'Raw HTML':
'<p>This template is similar to the Text template. The only difference is',
'Text': '\n \n',
'Announcement': '<p> Words of encouragement! This is a short note that most students will read. </p>',
'Zooming Image': '<h2>ZOOMING DIAGRAMS</h2>',
'E-text Written in LaTeX': '<h2>Example: E-text page</h2>',
'Raw HTML': '<p>This template is similar to the Text template. The only difference is',
}
actual_html = world.css_html(selector, index=idx)
assert_in(html_matcher[step_hash['Component']], actual_html)

View File

@@ -93,8 +93,10 @@ def click_component_from_menu(category, component_type, is_advanced):
"""
if is_advanced:
# Sometimes this click does not work if you go too fast.
world.retry_on_exception(_click_advanced,
ignored_exceptions=AssertionError)
world.retry_on_exception(
_click_advanced,
ignored_exceptions=AssertionError,
)
# Retry this in case the list is empty because you tried too fast.
link = world.retry_on_exception(

View File

@@ -24,7 +24,8 @@ def i_export_the_course(step):
@step('I edit and enter bad XML$')
def i_enter_bad_xml(step):
enter_xml_in_advanced_problem(step,
enter_xml_in_advanced_problem(
step,
"""<problem><h1>Smallest Canvas</h1>
<p>You want to make the smallest canvas you can.</p>
<multiplechoiceresponse>

View File

@@ -1,4 +1,4 @@
# pylint: disable=C0111
# pylint: disable=missing-docstring
from lettuce import world, step
from selenium.webdriver.common.keys import Keys
@@ -20,9 +20,10 @@ SELECTORS = {
# We should wait 300 ms for event handler invocation + 200ms for safety.
DELAY = 0.5
@step('youtube stub server (.*) YouTube API')
def configure_youtube_api(_step, action):
action=action.strip()
action = action.strip()
if action == 'proxies':
world.youtube.config['youtube_api_blocked'] = False
elif action == 'blocks':
@@ -30,6 +31,7 @@ def configure_youtube_api(_step, action):
else:
raise ValueError('Parameter `action` should be one of "proxies" or "blocks".')
@step('I have created a Video component$')
def i_created_a_video_component(step):
step.given('I am in Studio editing a new unit')
@@ -47,6 +49,7 @@ def i_created_a_video_component(step):
if not world.youtube.config.get('youtube_api_blocked'):
world.wait_for_visible(SELECTORS['controls'])
@step('I have created a Video component with subtitles$')
def i_created_a_video_with_subs(_step):
_step.given('I have created a Video component with subtitles "OEoXaMPEzfM"')
@@ -221,7 +224,7 @@ def see_a_range_slider_with_proper_range(_step):
def do_not_see_or_not_button_video(_step, action, button_type):
world.wait(DELAY)
world.wait_for_ajax_complete()
action=action.strip()
action = action.strip()
button = button_type.strip()
if action == 'do not':
assert not world.is_css_present(VIDEO_BUTTONS[button])

View File

@@ -3,7 +3,7 @@
# pylint: disable=C0111
from lettuce import world, step
from nose.tools import assert_true # pylint: disable=E0611
from nose.tools import assert_true # pylint: disable=E0611
from video_editor import RequestHandlerWithSessionId, success_upload_file

View File

@@ -9,6 +9,7 @@ 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'''

View File

@@ -62,7 +62,7 @@ class Command(BaseCommand):
try:
course_key = SlashSeparatedCourseKey.from_deprecated_string(args[0])
except InvalidKeyError:
raise CommandError(GitExportError.BAD_COURSE)
raise CommandError(_(GitExportError.BAD_COURSE))
try:
git_export_utils.export_to_git(
@@ -72,4 +72,4 @@ class Command(BaseCommand):
options.get('rdir', None)
)
except git_export_utils.GitExportError as ex:
raise CommandError(str(ex))
raise CommandError(_(ex.message))

View File

@@ -11,6 +11,8 @@ from django.db.utils import IntegrityError
from student.roles import CourseInstructorRole, CourseStaffRole
#------------ to run: ./manage.py cms populate_creators --settings=dev
class Command(BaseCommand):
"""
Script for granting existing course instructors course creator privileges.

View File

@@ -11,8 +11,13 @@ def query_yes_no(question, default="yes"):
The "answer" return value is one of "yes" or "no".
"""
valid = {"yes": True, "y": True, "ye": True,
"no": False, "n": False}
valid = {
"yes": True,
"y": True,
"ye": True,
"no": False,
"n": False,
}
if default is None:
prompt = " [y/n] "
elif default == "yes":

View File

@@ -10,4 +10,3 @@ class Command(BaseCommand):
raise CommandError("restore_asset_from_trashcan requires one argument: <location>")
restore_asset_from_trashcan(args[0])

View File

@@ -11,6 +11,9 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.mongo.base import location_to_query
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.xml_importer import import_from_xml
from django.conf import settings
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
class ExportAllCourses(ModuleStoreTestCase):
@@ -30,7 +33,7 @@ class ExportAllCourses(ModuleStoreTestCase):
import_from_xml(
self.module_store,
'**replace_user**',
'common/test/data/',
TEST_DATA_DIR,
['dot-underscore'],
static_content_store=self.content_store,
do_import_static=True,

View File

@@ -80,6 +80,34 @@ class TestGitExport(CourseTestCase):
stderr=StringIO.StringIO())
self.assertEqual(ex.exception.code, 1)
def test_error_output(self):
"""
Verify that error output is actually resolved as the correct string
"""
output = StringIO.StringIO()
with self.assertRaises(SystemExit):
with self.assertRaisesRegexp(CommandError, GitExportError.BAD_COURSE):
call_command(
'git_export', 'foo/bar:baz', 'silly',
stdout=output, stderr=output
)
self.assertIn('Bad course location provided', output.getvalue())
output.close()
output = StringIO.StringIO()
with self.assertRaises(SystemExit):
with self.assertRaisesRegexp(CommandError, GitExportError.URL_BAD):
call_command(
'git_export', 'foo/bar/baz', 'silly',
stdout=output, stderr=output
)
self.assertIn(
'Non writable git url provided. Expecting something like:'
' git@github.com:mitocw/edx4edx_lite.git',
output.getvalue()
)
output.close()
def test_bad_git_url(self):
"""
Test several bad URLs for validation

View File

@@ -4,6 +4,7 @@
import copy
import mock
from mock import patch
import shutil
import lxml
@@ -56,6 +57,8 @@ from xmodule.contentstore.content import StaticContent
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE)
class ContentStoreTestCase(CourseTestCase):
@@ -69,7 +72,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
Tests which legitimately need to import a course
"""
def test_no_static_link_rewrites_on_import(self):
course_items = import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'])
course_items = import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'])
course = course_items[0]
handouts_usage_key = course.id.make_usage_key('course_info', 'handouts')
@@ -81,7 +84,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
self.assertIn('/static/', handouts.data)
def test_xlint_fails(self):
err_cnt = perform_xlint('common/test/data', ['toy'])
err_cnt = perform_xlint(TEST_DATA_DIR, ['toy'])
self.assertGreater(err_cnt, 0)
def test_about_overrides(self):
@@ -90,7 +93,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
e.g. /about/Fall_2012/effort.html
while there is a base definition in /about/effort.html
'''
course_items = import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'])
course_items = import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'])
course_key = course_items[0].id
effort = self.store.get_item(course_key.make_usage_key('about', 'effort'))
self.assertEqual(effort.data, '6 hours')
@@ -105,7 +108,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
'''
content_store = contentstore()
import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store, verbose=True)
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], static_content_store=content_store, verbose=True)
course = self.store.get_course(SlashSeparatedCourseKey('edX', 'toy', '2012_Fall'))
@@ -153,7 +156,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
Test that course info updates are imported and exported with all content fields ('data', 'items')
"""
content_store = contentstore()
data_dir = "common/test/data/"
data_dir = TEST_DATA_DIR
courses = import_from_xml(
self.store, self.user.id, data_dir, ['course_info_updates'],
static_content_store=content_store, verbose=True,
@@ -166,7 +169,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
self.assertIsNotNone(course_updates)
# check that course which is imported has files 'updates.html' and 'updates.items.json'
filesystem = OSFS(data_dir + 'course_info_updates/info')
filesystem = OSFS(data_dir + '/course_info_updates/info')
self.assertTrue(filesystem.exists('updates.html'))
self.assertTrue(filesystem.exists('updates.items.json'))
@@ -204,7 +207,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
def test_rewrite_nonportable_links_on_import(self):
content_store = contentstore()
import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store)
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], static_content_store=content_store)
# first check a static asset link
course_key = SlashSeparatedCourseKey('edX', 'toy', 'run')
@@ -323,7 +326,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
def test_export_course_with_metadata_only_video(self):
content_store = contentstore()
import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'])
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'])
course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
# create a new video module and add it as a child to a vertical
@@ -352,7 +355,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
"""
content_store = contentstore()
import_from_xml(self.store, self.user.id, 'common/test/data/', ['word_cloud'])
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['word_cloud'])
course_id = SlashSeparatedCourseKey('HarvardX', 'ER22x', '2013_Spring')
verticals = self.store.get_items(course_id, qualifiers={'category': 'vertical'})
@@ -379,7 +382,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
"""
content_store = contentstore()
import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'])
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'])
course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
verticals = self.store.get_items(course_id, qualifiers={'category': 'vertical'})
@@ -410,7 +413,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
"""
content_store = contentstore()
import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'])
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'])
course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
@@ -432,7 +435,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
def test_export_course_without_content_store(self):
# Create toy course
course_items = import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'])
course_items = import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'])
course_id = course_items[0].id
root_dir = path(mkdtemp_clean())
@@ -534,6 +537,7 @@ class MiscCourseTests(ContentStoreTestCase):
for expected in expected_types:
self.assertIn(expected, resp.content)
@patch('django.conf.settings.DEPRECATED_ADVANCED_COMPONENT_TYPES', [])
def test_advanced_components_in_edit_unit(self):
# This could be made better, but for now let's just assert that we see the advanced modules mentioned in the page
# response HTML
@@ -853,15 +857,14 @@ class MiscCourseTests(ContentStoreTestCase):
self.assertContains(resp, unicode(asset_key))
def test_prefetch_children(self):
# make sure we haven't done too many round trips to DB
# note we say 4 round trips here for:
# make sure we haven't done too many round trips to DB:
# 1) the course,
# 2 & 3) for the chapters and sequentials
# Because we're querying from the top of the tree, we cache information needed for inheritance,
# so we don't need to make an extra query to compute it.
# set the branch to 'publish' in order to prevent extra lookups of draft versions
with self.store.branch_setting(ModuleStoreEnum.Branch.published_only, self.course.id):
with check_mongo_calls(3, 0):
with check_mongo_calls(3):
course = self.store.get_course(self.course.id, depth=2)
# make sure we pre-fetched a known sequential which should be at depth=2
@@ -873,7 +876,7 @@ class MiscCourseTests(ContentStoreTestCase):
# Now, test with the branch set to draft. No extra round trips b/c it doesn't go deep enough to get
# beyond direct only categories
with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, self.course.id):
with check_mongo_calls(3, 0):
with check_mongo_calls(3):
self.store.get_course(self.course.id, depth=2)
def _check_verticals(self, locations):
@@ -1008,7 +1011,7 @@ class ContentStoreTest(ContentStoreTestCase):
def test_create_course_duplicate_course(self):
"""Test new course creation - error path"""
self.client.ajax_post('/course/', self.course_data)
self.assert_course_creation_failed('There is already a course defined with the same organization, course number, and course run. Please change either organization or course number to be unique.')
self.assert_course_creation_failed('There is already a course defined with the same organization and course number. Please change either organization or course number to be unique.')
def assert_course_creation_failed(self, error_message):
"""
@@ -1037,7 +1040,7 @@ class ContentStoreTest(ContentStoreTestCase):
self.course_data['display_name'] = 'Robot Super Course Two'
self.course_data['run'] = '2013_Summer'
self.assert_course_creation_failed('There is already a course defined with the same organization, course number, and course run. Please change either organization or course number to be unique.')
self.assert_course_creation_failed('There is already a course defined with the same organization and course number. Please change either organization or course number to be unique.')
def test_create_course_case_change(self):
"""Test new course creation - error path due to case insensitive name equality"""
@@ -1045,13 +1048,13 @@ class ContentStoreTest(ContentStoreTestCase):
self.client.ajax_post('/course/', self.course_data)
cache_current = self.course_data['org']
self.course_data['org'] = self.course_data['org'].lower()
self.assert_course_creation_failed('There is already a course defined with the same organization, course number, and course run. Please change either organization or course number to be unique.')
self.assert_course_creation_failed('There is already a course defined with the same organization and course number. Please change either organization or course number to be unique.')
self.course_data['org'] = cache_current
self.client.ajax_post('/course/', self.course_data)
cache_current = self.course_data['number']
self.course_data['number'] = self.course_data['number'].upper()
self.assert_course_creation_failed('There is already a course defined with the same organization, course number, and course run. Please change either organization or course number to be unique.')
self.assert_course_creation_failed('There is already a course defined with the same organization and course number. Please change either organization or course number to be unique.')
def test_course_substring(self):
"""
@@ -1221,7 +1224,7 @@ class ContentStoreTest(ContentStoreTestCase):
)
self.assertEqual(resp.status_code, 200)
course_items = import_from_xml(self.store, self.user.id, 'common/test/data/', ['simple'])
course_items = import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['simple'])
course_key = course_items[0].id
resp = self._show_course_overview(course_key)
@@ -1268,7 +1271,7 @@ class ContentStoreTest(ContentStoreTestCase):
target_course_id = _get_course_id(self.course_data)
_create_course(self, target_course_id, self.course_data)
import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'], target_course_id=target_course_id)
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], target_course_id=target_course_id)
modules = self.store.get_items(target_course_id)
@@ -1303,7 +1306,7 @@ class ContentStoreTest(ContentStoreTestCase):
course_module.save()
# Import a course with wiki_slug == location.course
import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'], target_course_id=target_course_id)
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], target_course_id=target_course_id)
course_module = self.store.get_course(target_course_id)
self.assertEquals(course_module.wiki_slug, 'toy')
@@ -1318,17 +1321,17 @@ class ContentStoreTest(ContentStoreTestCase):
_create_course(self, target_course_id, course_data)
# Import a course with wiki_slug == location.course
import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'], target_course_id=target_course_id)
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], target_course_id=target_course_id)
course_module = self.store.get_course(target_course_id)
self.assertEquals(course_module.wiki_slug, 'MITx.111.2013_Spring')
# Now try importing a course with wiki_slug == '{0}.{1}.{2}'.format(location.org, location.course, location.run)
import_from_xml(self.store, self.user.id, 'common/test/data/', ['two_toys'], target_course_id=target_course_id)
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['two_toys'], target_course_id=target_course_id)
course_module = self.store.get_course(target_course_id)
self.assertEquals(course_module.wiki_slug, 'MITx.111.2013_Spring')
def test_import_metadata_with_attempts_empty_string(self):
import_from_xml(self.store, self.user.id, 'common/test/data/', ['simple'])
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['simple'])
did_load_item = False
try:
course_key = SlashSeparatedCourseKey('edX', 'simple', 'problem')
@@ -1350,7 +1353,7 @@ class ContentStoreTest(ContentStoreTestCase):
self.assertNotEquals(new_discussion_item.discussion_id, '$$GUID$$')
def test_metadata_inheritance(self):
course_items = import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'])
course_items = import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'])
course = course_items[0]
verticals = self.store.get_items(course.id, qualifiers={'category': 'vertical'})
@@ -1419,7 +1422,7 @@ class ContentStoreTest(ContentStoreTestCase):
courses = import_from_xml(
self.store,
self.user.id,
'common/test/data/',
TEST_DATA_DIR,
['conditional_and_poll'],
static_content_store=content_store
)

View File

@@ -607,11 +607,13 @@ class CourseMetadataEditingTest(CourseTestCase):
def test_correct_http_status(self):
json_data = json.dumps({
"advertised_start": {"value": 1, "display_name": "Course Advertised Start Date", },
"days_early_for_beta": {"value": "supposed to be an integer",
"display_name": "Days Early for Beta Users", },
"advanced_modules": {"value": 1, "display_name": "Advanced Module List", },
})
"advertised_start": {"value": 1, "display_name": "Course Advertised Start Date", },
"days_early_for_beta": {
"value": "supposed to be an integer",
"display_name": "Days Early for Beta Users",
},
"advanced_modules": {"value": 1, "display_name": "Advanced Module List", },
})
response = self.client.ajax_post(self.course_setting_url, json_data)
self.assertEqual(400, response.status_code)
@@ -623,7 +625,7 @@ class CourseMetadataEditingTest(CourseTestCase):
"days_early_for_beta": {"value": 2},
},
user=self.user
)
)
self.update_check(test_model)
# try fresh fetch to ensure persistence
fresh = modulestore().get_course(self.course.id)

View File

@@ -66,8 +66,10 @@ class TemplateTests(unittest.TestCase):
self.assertEqual(index_info['course'], 'course')
self.assertEqual(index_info['run'], '2014')
test_chapter = persistent_factories.ItemFactory.create(display_name='chapter 1',
parent_location=test_course.location)
test_chapter = persistent_factories.ItemFactory.create(
display_name='chapter 1',
parent_location=test_course.location
)
self.assertIsInstance(test_chapter, SequenceDescriptor)
# refetch parent which should now point to child
test_course = self.split_store.get_course(test_course.id.version_agnostic())
@@ -156,8 +158,10 @@ class TemplateTests(unittest.TestCase):
course='history', run='doomed', org='edu.harvard',
display_name='doomed test course',
user_id='testbot')
persistent_factories.ItemFactory.create(display_name='chapter 1',
parent_location=test_course.location)
persistent_factories.ItemFactory.create(
display_name='chapter 1',
parent_location=test_course.location
)
id_locator = test_course.id.for_branch(ModuleStoreEnum.BranchName.draft)
guid_locator = test_course.location.course_agnostic()
@@ -180,10 +184,17 @@ class TemplateTests(unittest.TestCase):
display_name='history test course',
user_id='testbot'
)
chapter = persistent_factories.ItemFactory.create(display_name='chapter 1',
parent_location=test_course.location, user_id='testbot')
sub = persistent_factories.ItemFactory.create(display_name='subsection 1',
parent_location=chapter.location, user_id='testbot', category='vertical')
chapter = persistent_factories.ItemFactory.create(
display_name='chapter 1',
parent_location=test_course.location,
user_id='testbot'
)
sub = persistent_factories.ItemFactory.create(
display_name='subsection 1',
parent_location=chapter.location,
user_id='testbot',
category='vertical'
)
first_problem = persistent_factories.ItemFactory.create(
display_name='problem 1', parent_location=sub.location, user_id='testbot', category='problem',
data="<problem></problem>"

View File

@@ -55,10 +55,11 @@ class InternationalizationTest(ModuleStoreTestCase):
self.client = AjaxEnabledTestClient()
self.client.login(username=self.uname, password=self.password)
resp = self.client.get_html('/course/',
{},
HTTP_ACCEPT_LANGUAGE='en'
)
resp = self.client.get_html(
'/course/',
{},
HTTP_ACCEPT_LANGUAGE='en',
)
self.assertContains(resp,
'<h1 class="page-header">My Courses</h1>',

View File

@@ -24,6 +24,8 @@ from uuid import uuid4
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
@ddt.ddt
@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE)
@@ -48,7 +50,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
import_from_xml(
module_store,
self.user.id,
'common/test/data/',
TEST_DATA_DIR,
['test_import_course'],
static_content_store=content_store,
do_import_static=False,
@@ -70,7 +72,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
course_items = import_from_xml(
module_store,
self.user.id,
'common/test/data',
TEST_DATA_DIR,
['test_import_course_2'],
target_course_id=course.id,
verbose=True,
@@ -86,7 +88,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
import_from_xml(
module_store,
self.user.id,
'common/test/data/',
TEST_DATA_DIR,
['2014_Uni'],
target_course_id=course_id
)
@@ -131,7 +133,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
content_store = contentstore()
module_store = modulestore()
import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store, do_import_static=False, verbose=True)
import_from_xml(module_store, self.user.id, TEST_DATA_DIR, ['toy'], static_content_store=content_store, do_import_static=False, verbose=True)
course = module_store.get_course(SlashSeparatedCourseKey('edX', 'toy', '2012_Fall'))
@@ -142,7 +144,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
def test_no_static_link_rewrites_on_import(self):
module_store = modulestore()
courses = import_from_xml(module_store, self.user.id, 'common/test/data/', ['toy'], do_import_static=False, verbose=True)
courses = import_from_xml(module_store, self.user.id, TEST_DATA_DIR, ['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'))
@@ -183,7 +185,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
import_from_xml(
module_store,
self.user.id,
'common/test/data/',
TEST_DATA_DIR,
['conditional'],
target_course_id=target_course_id
)
@@ -213,7 +215,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
import_from_xml(
module_store,
self.user.id,
'common/test/data/',
TEST_DATA_DIR,
['open_ended'],
target_course_id=target_course_id
)
@@ -254,7 +256,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
import_from_xml(
module_store,
self.user.id,
'common/test/data/',
TEST_DATA_DIR,
[source_course_name],
target_course_id=target_course_id
)

View File

@@ -2,6 +2,9 @@ from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.django import modulestore
from django.conf import settings
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
# This test is in the CMS module because the test configuration to use a draft
@@ -10,7 +13,7 @@ class DraftReorderTestCase(ModuleStoreTestCase):
def test_order(self):
store = modulestore()
course_items = import_from_xml(store, self.user.id, 'common/test/data/', ['import_draft_order'])
course_items = import_from_xml(store, self.user.id, TEST_DATA_DIR, ['import_draft_order'])
course_key = course_items[0].id
sequential = store.get_item(course_key.make_usage_key('sequential', '0f4f7649b10141b0bdc9922dcf94515a'))
verticals = sequential.children

View File

@@ -8,6 +8,9 @@ from xblock.fields import String
from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.mongo.draft import as_draft
from django.conf import settings
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
class StubXBlock(XBlock):
@@ -58,7 +61,7 @@ class XBlockImportTest(ModuleStoreTestCase):
"""
courses = import_from_xml(
self.store, self.user.id, 'common/test/data', [course_dir]
self.store, self.user.id, TEST_DATA_DIR, [course_dir]
)
xblock_location = courses[0].id.make_usage_key('stubxblock', 'xblock_test')

View File

@@ -32,13 +32,14 @@ class TestCourseAccess(ModuleStoreTestCase):
# 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')
course_url = reverse_url('course_handler')
self.client.ajax_post(course_url,
self.client.ajax_post(
course_url,
{
'org': self.course_key.org,
'number': self.course_key.course,
'display_name': 'My favorite course',
'run': self.course_key.run,
}
},
)
self.users = self._create_users()

View File

@@ -161,7 +161,6 @@ class TestDownloadYoutubeSubs(ModuleStoreTestCase):
number = '999'
display_name = 'Test course'
def clear_sub_content(self, subs_id):
"""
Remove, if subtitle content exists.
@@ -472,6 +471,7 @@ class TestYoutubeTranscripts(unittest.TestCase):
self.assertEqual(transcripts, expected_transcripts)
mock_get.assert_called_with('http://video.google.com/timedtext', params={'lang': 'en', 'v': 'good_youtube_id'})
class TestTranscript(unittest.TestCase):
"""
Tests for Transcript class e.g. different transcript conversions.
@@ -489,7 +489,6 @@ class TestTranscript(unittest.TestCase):
""")
self.sjson_transcript = textwrap.dedent("""\
{
"start": [

View File

@@ -86,6 +86,7 @@ class LMSLinksTestCase(TestCase):
link = utils.get_lms_link_for_item(location)
self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/course/test")
class ExtraPanelTabTestCase(TestCase):
""" Tests adding and removing extra course tabs. """

View File

@@ -18,6 +18,9 @@ from student.models import Registration
from opaque_keys.edx.locations import SlashSeparatedCourseKey, AssetLocation
from contentstore.utils import reverse_url
from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
from django.conf import settings
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
def parse_json(response):
@@ -137,7 +140,7 @@ class CourseTestCase(ModuleStoreTestCase):
Imports the test toy course and populates it with additional test data
"""
content_store = contentstore()
import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store)
import_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], static_content_store=content_store)
course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
# create an Orphan

View File

@@ -190,8 +190,8 @@ def _upload_asset(request, course_key):
# first let's see if a thumbnail can be created
(thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail(
content,
tempfile_path=tempfile_path
content,
tempfile_path=tempfile_path,
)
# delete cached thumbnail even if one couldn't be created this time (else

View File

@@ -124,6 +124,7 @@ def expand_checklist_action_url(course_module, checklist):
return expanded_checklist
def localize_checklist_text(checklist):
"""
Localize texts for a given checklist and returns the modified version.

View File

@@ -56,6 +56,13 @@ ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules'
ADVANCED_PROBLEM_TYPES = settings.ADVANCED_PROBLEM_TYPES
def _advanced_component_types():
"""
Return advanced component types which can be created.
"""
return [c_type for c_type in ADVANCED_COMPONENT_TYPES if c_type not in settings.DEPRECATED_ADVANCED_COMPONENT_TYPES]
@require_GET
@login_required
def subsection_handler(request, usage_key_string):
@@ -292,10 +299,11 @@ def get_component_templates(course):
# enabled for the course.
course_advanced_keys = course.advanced_modules
advanced_component_templates = {"type": "advanced", "templates": [], "display_name": _("Advanced")}
advanced_component_types = _advanced_component_types()
# Set component types according to course policy file
if isinstance(course_advanced_keys, list):
for category in course_advanced_keys:
if category in ADVANCED_COMPONENT_TYPES and not category in categories:
if category in advanced_component_types and not category in categories:
# boilerplates not supported for advanced components
try:
component_display_name = xblock_type_display_name(category, default_display_name=category)

View File

@@ -261,6 +261,7 @@ def course_rerun_handler(request, course_key_string):
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False)
})
def _course_outline_json(request, course_module):
"""
Returns a JSON representation of the course module and recursively all of its children.
@@ -386,10 +387,13 @@ def course_listing(request):
'run': uca.course_key.run,
'is_failed': True if uca.state == CourseRerunUIStateManager.State.FAILED else False,
'is_in_progress': True if uca.state == CourseRerunUIStateManager.State.IN_PROGRESS else False,
'dismiss_link':
reverse_course_url('course_notifications_handler', uca.course_key, kwargs={
'dismiss_link': reverse_course_url(
'course_notifications_handler',
uca.course_key,
kwargs={
'action_state_id': uca.id,
}) if uca.state == CourseRerunUIStateManager.State.FAILED else ''
},
) if uca.state == CourseRerunUIStateManager.State.FAILED else ''
}
# remove any courses in courses that are also in the in_process_course_actions list
@@ -455,10 +459,13 @@ def course_index(request, course_key):
'rerun_notification_id': current_action.id if current_action else None,
'course_release_date': course_release_date,
'settings_url': settings_url,
'notification_dismiss_url':
reverse_course_url('course_notifications_handler', current_action.course_key, kwargs={
'notification_dismiss_url': reverse_course_url(
'course_notifications_handler',
current_action.course_key,
kwargs={
'action_state_id': current_action.id,
}) if current_action else None,
},
) if current_action else None,
})
@@ -542,7 +549,7 @@ def _create_or_rerun_course(request):
return JsonResponse({
'ErrMsg': _(
'There is already a course defined with the same '
'organization, course number, and course run. Please '
'organization and course number. Please '
'change either organization or course number to be unique.'
),
'OrgErrMsg': _(
@@ -1290,10 +1297,12 @@ class GroupConfiguration(object):
'container_handler',
course.location.course_key.make_usage_key(unit.location.block_type, unit.location.name)
)
validation_summary = split_test.general_validation_message()
usage_info[split_test.user_partition_id].append({
'label': '{} / {}'.format(unit.display_name, split_test.display_name),
'url': unit_url,
'validation': split_test.general_validation_message,
'validation': validation_summary.to_json() if validation_summary else None,
})
return usage_info

View File

@@ -31,8 +31,6 @@ from xmodule.modulestore.xml_exporter import export_to_xml
from .access import has_course_access
from extract_tar import safetar_extractall
from student import auth
from student.roles import CourseInstructorRole, CourseStaffRole, GlobalStaff
from util.json_request import JsonResponse
from util.views import ensure_valid_course_key

View File

@@ -151,7 +151,7 @@ def _preview_module_system(request, descriptor):
replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_id=course_id),
user=request.user,
can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
get_python_lib_zip=(lambda :get_python_lib_zip(contentstore, course_id)),
get_python_lib_zip=(lambda: get_python_lib_zip(contentstore, course_id)),
mixins=settings.XBLOCK_MIXINS,
course_id=course_id,
anonymous_student_id='student',

View File

@@ -20,6 +20,7 @@ from ..utils import get_lms_link_for_item
__all__ = ['tabs_handler']
@expect_json
@login_required
@ensure_csrf_cookie
@@ -203,4 +204,3 @@ def primitive_insert(course, num, tab_type, name):
tabs = course.tabs
tabs.insert(num, new_tab)
modulestore().update_item(course, ModuleStoreEnum.UserID.primitive_command)

View File

@@ -1,11 +1,6 @@
"""
Unit tests for the asset upload endpoint.
"""
# pylint: disable=C0111
# pylint: disable=W0621
# pylint: disable=W0212
from datetime import datetime
from io import BytesIO
from pytz import UTC
@@ -13,7 +8,7 @@ import json
from contentstore.tests.utils import CourseTestCase
from contentstore.views import assets
from contentstore.utils import reverse_course_url
from xmodule.assetstore.assetmgr import UnknownAssetType, AssetMetadataFoundTemporary
from xmodule.assetstore.assetmgr import AssetMetadataFoundTemporary
from xmodule.assetstore import AssetMetadata
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
@@ -21,6 +16,9 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.xml_importer import import_from_xml
from django.test.utils import override_settings
from opaque_keys.edx.locations import SlashSeparatedCourseKey, AssetLocation
from django.conf import settings
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
class AssetsTestCase(CourseTestCase):
@@ -32,12 +30,18 @@ class AssetsTestCase(CourseTestCase):
self.url = reverse_course_url('assets_handler', self.course.id)
def upload_asset(self, name="asset-1"):
"""
Post to the asset upload url
"""
f = BytesIO(name)
f.name = name + ".txt"
return self.client.post(self.url, {"name": name, "file": f})
class BasicAssetsTestCase(AssetsTestCase):
"""
Test getting assets via html w/o additional args
"""
def test_basic(self):
resp = self.client.get(self.url, HTTP_ACCEPT='text/html')
self.assertEquals(resp.status_code, 200)
@@ -54,7 +58,7 @@ class BasicAssetsTestCase(AssetsTestCase):
course_items = import_from_xml(
module_store,
self.user.id,
'common/test/data/',
TEST_DATA_DIR,
['toy'],
static_content_store=contentstore(),
verbose=True
@@ -78,6 +82,9 @@ class PaginationTestCase(AssetsTestCase):
Tests the pagination of assets returned from the REST API.
"""
def test_json_responses(self):
"""
Test the ajax asset interfaces
"""
self.upload_asset("asset-1")
self.upload_asset("asset-2")
self.upload_asset("asset-3")
@@ -97,20 +104,26 @@ class PaginationTestCase(AssetsTestCase):
self.assert_correct_asset_response(self.url + "?page_size=3&page=1", 0, 3, 3)
def assert_correct_asset_response(self, url, expected_start, expected_length, expected_total):
"""
Get from the url and ensure it contains the expected number of responses
"""
resp = self.client.get(url, HTTP_ACCEPT='application/json')
json_response = json.loads(resp.content)
assets = json_response['assets']
assets_response = json_response['assets']
self.assertEquals(json_response['start'], expected_start)
self.assertEquals(len(assets), expected_length)
self.assertEquals(len(assets_response), expected_length)
self.assertEquals(json_response['totalCount'], expected_total)
def assert_correct_sort_response(self, url, sort, direction):
"""
Get from the url w/ a sort option and ensure items honor that sort
"""
resp = self.client.get(url + '?sort=' + sort + '&direction=' + direction, HTTP_ACCEPT='application/json')
json_response = json.loads(resp.content)
assets = json_response['assets']
name1 = assets[0][sort]
name2 = assets[1][sort]
name3 = assets[2][sort]
assets_response = json_response['assets']
name1 = assets_response[0][sort]
name2 = assets_response[1][sort]
name3 = assets_response[2][sort]
if direction == 'asc':
self.assertLessEqual(name1, name2)
self.assertLessEqual(name2, name3)
@@ -160,12 +173,6 @@ class DownloadTestCase(AssetsTestCase):
resp = self.client.get(url, HTTP_ACCEPT='text/html')
self.assertEquals(resp.status_code, 404)
def test_download_unknown_asset_type(self):
# Change the asset type to something unknown.
url = self.uploaded_url.replace('/asset/', '/unknown_type/')
with self.assertRaises((UnknownAssetType, NameError)):
self.client.get(url, HTTP_ACCEPT='text/html')
def test_metadata_found_in_modulestore(self):
# Insert asset metadata into the modulestore (with no accompanying asset).
asset_key = self.course.id.make_asset_key(AssetMetadata.ASSET_TYPE, 'pic1.jpg')
@@ -176,7 +183,7 @@ class DownloadTestCase(AssetsTestCase):
'curr_version': '14',
'prev_version': '13'
})
modulestore().save_asset_metadata(self.course.id, asset_md, 15)
modulestore().save_asset_metadata(asset_md, 15)
# Get the asset metadata and have it be found in the modulestore.
# Currently, no asset metadata should be found in the modulestore. The code is not yet storing it there.
# If asset metadata *is* found there, an exception is raised. This test ensures the exception is indeed raised.
@@ -198,6 +205,7 @@ class AssetToJsonTestCase(AssetsTestCase):
location = course_key.make_asset_key('asset', 'my_file_name.jpg')
thumbnail_location = course_key.make_asset_key('thumbnail', 'my_file_name_thumb.jpg')
# pylint: disable=protected-access
output = assets._get_asset_json("my_file", upload_date, location, thumbnail_location, True)
self.assertEquals(output["display_name"], "my_file")
@@ -236,6 +244,7 @@ class LockAssetTestCase(AssetsTestCase):
resp = self.client.post(
url,
# pylint: disable=protected-access
json.dumps(assets._get_asset_json("sample_static.txt", upload_date, asset_location, None, lock)),
"application/json"
)
@@ -247,7 +256,7 @@ class LockAssetTestCase(AssetsTestCase):
course_items = import_from_xml(
module_store,
self.user.id,
'common/test/data/',
TEST_DATA_DIR,
['toy'],
static_content_store=contentstore(),
verbose=True

View File

@@ -8,20 +8,17 @@ import datetime
from contentstore.tests.utils import CourseTestCase
from contentstore.utils import reverse_course_url, add_instructor
from contentstore.views.access import has_course_access
from contentstore.views.course import course_outline_initial_state, _course_outline_json
from contentstore.views.course import course_outline_initial_state
from contentstore.views.item import create_xblock_info, VisibilityState
from course_action_state.models import CourseRerunState
from util.date_utils import get_default_time_display
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls, \
mongo_uses_error_check
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from opaque_keys.edx.locator import CourseLocator
from student.tests.factories import UserFactory
from course_action_state.managers import CourseRerunUIStateManager
from django.conf import settings
import ddt
import threading
import pytz
@@ -312,28 +309,3 @@ class TestCourseOutline(CourseTestCase):
self.assertEqual(_get_release_date(response), get_default_time_display(self.course.start))
_assert_settings_link_present(response)
@ddt.ddt
class OutlinePerfTest(TestCourseOutline):
def setUp(self):
with modulestore().default_store(ModuleStoreEnum.Type.split):
super(OutlinePerfTest, self).setUp()
@ddt.data(1, 2, 4, 8)
def test_query_counts(self, num_threads):
"""
Test that increasing threads does not increase query counts
"""
def test_client():
with modulestore().default_store(ModuleStoreEnum.Type.split):
with modulestore().bulk_operations(self.course.id):
course = modulestore().get_course(self.course.id, depth=0)
return _course_outline_json(None, course)
per_thread = 4
with check_mongo_calls(per_thread * num_threads, 0):
outline_threads = [threading.Thread(target=test_client) for __ in xrange(num_threads)]
[thread.start() for thread in outline_threads]
# now wait until they all finish
[thread.join() for thread in outline_threads]

View File

@@ -9,7 +9,7 @@ from contentstore.views.course import GroupConfiguration
from contentstore.tests.utils import CourseTestCase
from xmodule.partitions.partitions import Group, UserPartition
from xmodule.modulestore.tests.factories import ItemFactory
from xmodule.split_test_module import ValidationMessage, ValidationMessageType
from xmodule.validation import StudioValidation, StudioValidationMessage
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import ModuleStoreEnum
@@ -541,87 +541,75 @@ class GroupConfigurationsValidationTestCase(CourseTestCase, HelperMethods):
def setUp(self):
super(GroupConfigurationsValidationTestCase, self).setUp()
@patch('xmodule.split_test_module.SplitTestDescriptor.validation_messages')
def test_error_message_present(self, mocked_validation_messages):
@patch('xmodule.split_test_module.SplitTestDescriptor.validate_split_test')
def verify_validation_add_usage_info(self, expected_result, mocked_message, mocked_validation_messages):
"""
Tests if validation message is present.
Helper method for testing validation information present after add_usage_info.
"""
self._add_user_partitions()
split_test = self._create_content_experiment(cid=0, name_suffix='0')[1]
mocked_validation_messages.return_value = [
ValidationMessage(
split_test,
u"Validation message",
ValidationMessageType.error
)
]
group_configuration = GroupConfiguration.add_usage_info(self.course, self.store)[0]
self.assertEqual(
group_configuration['usage'][0]['validation'],
{
'message': u'This content experiment has issues that affect content visibility.',
'type': 'error'
}
)
validation = StudioValidation(split_test.location)
validation.add(mocked_message)
mocked_validation_messages.return_value = validation
@patch('xmodule.split_test_module.SplitTestDescriptor.validation_messages')
def test_warning_message_present(self, mocked_validation_messages):
group_configuration = GroupConfiguration.add_usage_info(self.course, self.store)[0]
self.assertEqual(expected_result.to_json(), group_configuration['usage'][0]['validation'])
def test_error_message_present(self):
"""
Tests if validation message is present.
Tests if validation message is present (error case).
"""
mocked_message = StudioValidationMessage(StudioValidationMessage.ERROR, u"Validation message")
expected_result = StudioValidationMessage(
StudioValidationMessage.ERROR, u"This content experiment has issues that affect content visibility."
)
self.verify_validation_add_usage_info(expected_result, mocked_message) # pylint: disable=no-value-for-parameter
def test_warning_message_present(self):
"""
Tests if validation message is present (warning case).
"""
mocked_message = StudioValidationMessage(StudioValidationMessage.WARNING, u"Validation message")
expected_result = StudioValidationMessage(
StudioValidationMessage.WARNING, u"This content experiment has issues that affect content visibility."
)
self.verify_validation_add_usage_info(expected_result, mocked_message) # pylint: disable=no-value-for-parameter
@patch('xmodule.split_test_module.SplitTestDescriptor.validate_split_test')
def verify_validation_update_usage_info(self, expected_result, mocked_message, mocked_validation_messages):
"""
Helper method for testing validation information present after update_usage_info.
"""
self._add_user_partitions()
split_test = self._create_content_experiment(cid=0, name_suffix='0')[1]
mocked_validation_messages.return_value = [
ValidationMessage(
split_test,
u"Validation message",
ValidationMessageType.warning
)
]
group_configuration = GroupConfiguration.add_usage_info(self.course, self.store)[0]
validation = StudioValidation(split_test.location)
if mocked_message is not None:
validation.add(mocked_message)
mocked_validation_messages.return_value = validation
group_configuration = GroupConfiguration.update_usage_info(
self.store, self.course, self.course.user_partitions[0]
)
self.assertEqual(
group_configuration['usage'][0]['validation'],
{
'message': u'This content experiment has issues that affect content visibility.',
'type': 'warning'
}
expected_result.to_json() if expected_result is not None else None,
group_configuration['usage'][0]['validation']
)
@patch('xmodule.split_test_module.SplitTestDescriptor.validation_messages')
def test_update_usage_info(self, mocked_validation_messages):
def test_update_usage_info(self):
"""
Tests if validation message is present when updating usage info.
"""
self._add_user_partitions()
split_test = self._create_content_experiment(cid=0, name_suffix='0')[1]
mocked_validation_messages.return_value = [
ValidationMessage(
split_test,
u"Validation message",
ValidationMessageType.warning
)
]
group_configuration = GroupConfiguration.update_usage_info(self.store, self.course, self.course.user_partitions[0])
self.assertEqual(
group_configuration['usage'][0]['validation'],
{
'message': u'This content experiment has issues that affect content visibility.',
'type': 'warning'
}
mocked_message = StudioValidationMessage(StudioValidationMessage.WARNING, u"Validation message")
expected_result = StudioValidationMessage(
StudioValidationMessage.WARNING, u"This content experiment has issues that affect content visibility."
)
# pylint: disable=no-value-for-parameter
self.verify_validation_update_usage_info(expected_result, mocked_message)
@patch('xmodule.split_test_module.SplitTestDescriptor.validation_messages')
def test_update_usage_info_no_message(self, mocked_validation_messages):
def test_update_usage_info_no_message(self):
"""
Tests if validation message is not present when updating usage info.
"""
self._add_user_partitions()
self._create_content_experiment(cid=0, name_suffix='0')
mocked_validation_messages.return_value = []
group_configuration = GroupConfiguration.update_usage_info(self.store, self.course, self.course.user_partitions[0])
self.assertEqual(group_configuration['usage'][0]['validation'], None)
self.verify_validation_update_usage_info(None, None) # pylint: disable=no-value-for-parameter

View File

@@ -130,8 +130,10 @@ class GetItemTest(ItemTest):
root_usage_key = self._create_vertical()
html, __ = self._get_container_preview(root_usage_key)
# Verify that the Studio wrapper is not added
self.assertNotIn('wrapper-xblock', html)
# XBlock messages are added by the Studio wrapper.
self.assertIn('wrapper-xblock-message', html)
# Make sure that "wrapper-xblock" does not appear by itself (without -message at end).
self.assertNotRegexpMatches(html, r'wrapper-xblock[^-]+')
# Verify that the header and article tags are still added
self.assertIn('<header class="xblock-header xblock-header-vertical">', html)
@@ -1201,6 +1203,43 @@ class TestComponentTemplates(CourseTestCase):
self.assertEqual(ora_template.get('category'), 'openassessment')
self.assertIsNone(ora_template.get('boilerplate_name', None))
@patch('django.conf.settings.DEPRECATED_ADVANCED_COMPONENT_TYPES', ["combinedopenended", "peergrading"])
def test_ora1_no_advance_component_button(self):
"""
Test that there will be no `Advanced` button on unit page if `combinedopenended` and `peergrading` are
deprecated provided that there are only 'combinedopenended', 'peergrading' modules in `Advanced Module List`
"""
self.course.advanced_modules.extend(['combinedopenended', 'peergrading'])
templates = get_component_templates(self.course)
button_names = [template['display_name'] for template in templates]
self.assertNotIn('Advanced', button_names)
@patch('django.conf.settings.DEPRECATED_ADVANCED_COMPONENT_TYPES', ["combinedopenended", "peergrading"])
def test_cannot_create_ora1_problems(self):
"""
Test that we can't create ORA1 problems if `combinedopenended` and `peergrading` are deprecated
"""
self.course.advanced_modules.extend(['annotatable', 'combinedopenended', 'peergrading'])
templates = get_component_templates(self.course)
button_names = [template['display_name'] for template in templates]
self.assertIn('Advanced', button_names)
self.assertEqual(len(templates[0]['templates']), 1)
template_display_names = [template['display_name'] for template in templates[0]['templates']]
self.assertEqual(template_display_names, ['Annotation'])
@patch('django.conf.settings.DEPRECATED_ADVANCED_COMPONENT_TYPES', [])
def test_create_ora1_problems(self):
"""
Test that we can create ORA1 problems if `combinedopenended` and `peergrading` are not deprecated
"""
self.course.advanced_modules.extend(['annotatable', 'combinedopenended', 'peergrading'])
templates = get_component_templates(self.course)
button_names = [template['display_name'] for template in templates]
self.assertIn('Advanced', button_names)
self.assertEqual(len(templates[0]['templates']), 3)
template_display_names = [template['display_name'] for template in templates[0]['templates']]
self.assertEqual(template_display_names, ['Annotation', 'Open Response Assessment', 'Peer Grading Interface'])
class TestXBlockInfo(ItemTest):
"""

View File

@@ -377,7 +377,10 @@ def choose_transcripts(request):
if item.sub != html5_id: # update sub value
item.sub = html5_id
item.save_with_metadata(request.user)
response = {'status': 'Success', 'subs': item.sub}
response = {
'status': 'Success',
'subs': item.sub,
}
return JsonResponse(response)
@@ -408,7 +411,10 @@ def replace_transcripts(request):
item.sub = youtube_id
item.save_with_metadata(request.user)
response = {'status': 'Success', 'subs': item.sub}
response = {
'status': 'Success',
'subs': item.sub,
}
return JsonResponse(response)

View File

@@ -100,7 +100,6 @@ def _course_team_user(request, course_key, email):
}
return JsonResponse(msg, 400)
try:
user = User.objects.get(email=email)
except Exception:

View File

@@ -11,6 +11,7 @@ from models.settings import course_grading
from xmodule.fields import Date
from xmodule.modulestore.django import modulestore
class CourseDetails(object):
def __init__(self, org, course_id, run):
# still need these for now b/c the client's screen shows these 3 fields

View File

@@ -14,23 +14,24 @@ class CourseMetadata(object):
# The list of fields that wouldn't be shown in Advanced Settings.
# Should not be used directly. Instead the filtered_list method should be used if the field needs to be filtered
# depending on the feature flag.
FILTERED_LIST = ['xml_attributes',
'start',
'end',
'enrollment_start',
'enrollment_end',
'tabs',
'graceperiod',
'checklists',
'show_timezone',
'format',
'graded',
'hide_from_toc',
'pdf_textbooks',
'user_partitions',
'name', # from xblock
'tags', # from xblock
'visible_to_staff_only'
FILTERED_LIST = [
'xml_attributes',
'start',
'end',
'enrollment_start',
'enrollment_end',
'tabs',
'graceperiod',
'checklists',
'show_timezone',
'format',
'graded',
'hide_from_toc',
'pdf_textbooks',
'user_partitions',
'name', # from xblock
'tags', # from xblock
'visible_to_staff_only',
]
@classmethod

View File

@@ -215,7 +215,8 @@ with open(CONFIG_ROOT / CONFIG_PREFIX + "auth.json") as auth_file:
AUTH_TOKENS = json.load(auth_file)
############### XBlock filesystem field config ##########
DJFS = AUTH_TOKENS.get('DJFS', None)
if 'DJFS' in AUTH_TOKENS and AUTH_TOKENS['DJFS'] is not None:
DJFS = AUTH_TOKENS['DJFS']
EMAIL_HOST_USER = AUTH_TOKENS.get('EMAIL_HOST_USER', EMAIL_HOST_USER)
EMAIL_HOST_PASSWORD = AUTH_TOKENS.get('EMAIL_HOST_PASSWORD', EMAIL_HOST_PASSWORD)

View File

@@ -26,7 +26,7 @@
"default": {
"ENGINE": "django.db.backends.mysql",
"HOST": "localhost",
"NAME": "test",
"NAME": "edxtest",
"PASSWORD": "",
"PORT": "3306",
"USER": "root"

View File

@@ -292,7 +292,6 @@ MANAGERS = ADMINS
# Static content
STATIC_URL = '/static/' + git.revision + "/"
ADMIN_MEDIA_PREFIX = '/static/admin/'
STATIC_ROOT = ENV_ROOT / "staticfiles" / git.revision
STATICFILES_DIRS = [
@@ -737,13 +736,17 @@ ADVANCED_COMPONENT_TYPES = [
'done', # Lets students mark things as done. See https://github.com/pmitros/DoneXBlock
'audio', # Embed an audio file. See https://github.com/pmitros/AudioXBlock
'recommender', # Crowdsourced recommender. Prototype by dli&pmitros. Intended for roll-out in one place in one course.
'profile', # Prototype user profile XBlock. Used to test XBlock parameter passing. See https://github.com/pmitros/ProfileXBlock
'profile', # Prototype user profile XBlock. Used to test XBlock parameter passing. See https://github.com/pmitros/ProfileXBlock
'split_test',
'combinedopenended',
'peergrading',
'notes',
]
# Adding components in this list will disable the creation of new problem for those
# compoenents in studio. Existing problems will work fine and one can edit them in studio
DEPRECATED_ADVANCED_COMPONENT_TYPES = []
# Specify xblocks that should be treated as advanced problems. Each entry is a tuple
# specifying the xblock name and an optional YAML template to be used.
ADVANCED_PROBLEM_TYPES = [

View File

@@ -2,7 +2,7 @@
Specific overrides to the base prod settings to make development easier.
"""
from .aws import * # pylint: disable=wildcard-import, unused-wildcard-import
from .aws import * # pylint: disable=wildcard-import, unused-wildcard-import
# Don't use S3 in devstack, fall back to filesystem
del DEFAULT_FILE_STORAGE

View File

@@ -69,9 +69,9 @@ STATICFILES_DIRS += [
# If we don't add these settings, then Django templates that can't
# find pipelined assets will raise a ValueError.
# http://stackoverflow.com/questions/12816941/unit-testing-with-django-pipeline
STATICFILES_STORAGE='pipeline.storage.NonPackagingPipelineStorage'
STATICFILES_STORAGE = 'pipeline.storage.NonPackagingPipelineStorage'
STATIC_URL = "/static/"
PIPELINE_ENABLED=False
PIPELINE_ENABLED = False
# Update module store settings per defaults for tests
update_module_store_settings(

View File

@@ -45,7 +45,8 @@
'js/factories/settings',
'js/factories/settings_advanced',
'js/factories/settings_graders',
'js/factories/textbooks'
'js/factories/textbooks',
'js/factories/xblock_validation'
]),
/**
* By default all the configuration for optimization happens from the command

View File

@@ -43,7 +43,7 @@ requirejs.config({
"domReady": "xmodule_js/common_static/js/vendor/domReady",
"URI": "xmodule_js/common_static/js/vendor/URI.min",
"mathjax": "//edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured",
"mathjax": "//cdn.mathjax.org/mathjax/2.2-latest/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured",
"youtube": "//www.youtube.com/player_api?noext",
"tender": "//edxedge.tenderapp.com/tender_widget",
@@ -213,6 +213,7 @@ define([
"js/spec/models/component_template_spec",
"js/spec/models/explicit_url_spec",
"js/spec/models/xblock_info_spec",
"js/spec/models/xblock_validation_spec",
"js/spec/utils/drag_and_drop_spec",
"js/spec/utils/handle_iframe_binding_spec",
@@ -228,6 +229,7 @@ define([
"js/spec/views/xblock_spec",
"js/spec/views/xblock_editor_spec",
"js/spec/views/xblock_string_field_editor_spec",
"js/spec/views/xblock_validation_spec",
"js/spec/views/utils/view_utils_spec",
@@ -242,6 +244,10 @@ define([
"js/spec/views/modals/edit_xblock_spec",
"js/spec/views/modals/validation_error_modal_spec",
"js/spec/views/settings/main_spec",
"js/spec/factories/xblock_validation_spec",
"js/spec/xblock/cms.runtime.v1_spec",
# these tests are run separately in the cms-squire suite, due to process

View File

@@ -38,7 +38,7 @@ requirejs.config({
"domReady": "xmodule_js/common_static/js/vendor/domReady",
"URI": "xmodule_js/common_static/js/vendor/URI.min",
"mathjax": "//edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured",
"mathjax": "//cdn.mathjax.org/mathjax/2.2-latest/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured",
"youtube": "//www.youtube.com/player_api?noext",
"tender": "//edxedge.tenderapp.com/tender_widget.js"

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 114 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 119 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 113 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 118 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 115 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 114 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 119 KiB

View File

Before

Width:  |  Height:  |  Size: 117 B

After

Width:  |  Height:  |  Size: 117 B

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 954 B

After

Width:  |  Height:  |  Size: 954 B

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 633 B

After

Width:  |  Height:  |  Size: 633 B

View File

Before

Width:  |  Height:  |  Size: 737 B

After

Width:  |  Height:  |  Size: 737 B

View File

Before

Width:  |  Height:  |  Size: 581 B

After

Width:  |  Height:  |  Size: 581 B

View File

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 797 B

View File

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 234 B

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Some files were not shown because too many files have changed in this diff Show More