diff --git a/cms/djangoapps/contentstore/features/course_import.py b/cms/djangoapps/contentstore/features/course_import.py new file mode 100644 index 0000000000..0d26124d79 --- /dev/null +++ b/cms/djangoapps/contentstore/features/course_import.py @@ -0,0 +1,20 @@ +import os +from lettuce import world +from django.conf import settings + +def import_file(filename): + world.browser.execute_script("$('input.file-input').css('display', 'block')") + path = os.path.join(settings.COMMON_TEST_DATA_ROOT, "imports", filename) + world.browser.attach_file('course-data', os.path.abspath(path)) + world.css_click('input.submit-button') + # Go to course outline + world.click_course_content() + outline_css = 'li.nav-course-courseware-outline a' + world.css_click(outline_css) + + +def go_to_import(): + menu_css = 'li.nav-course-tools' + import_css = 'li.nav-course-tools-import a' + world.css_click(menu_css) + world.css_click(import_css) diff --git a/cms/djangoapps/contentstore/features/problem-editor.feature b/cms/djangoapps/contentstore/features/problem-editor.feature index e3f659a929..f3b75ebf7e 100644 --- a/cms/djangoapps/contentstore/features/problem-editor.feature +++ b/cms/djangoapps/contentstore/features/problem-editor.feature @@ -89,3 +89,13 @@ Feature: CMS.Problem Editor When I edit and compile the High Level Source Then my change to the High Level Source is persisted And when I view the High Level Source I see my changes + + Scenario: Exceptions don't cause problem to be uneditable (bug STUD-786) + Given I have an empty course + And I go to the import page + And I import the file "get_html_exception_test.tar.gz" + When I go to the unit "Probability and BMI" + And I click on "edit a draft" + Then I see a message that says "We're having trouble rendering your component" + And I can edit the problem + diff --git a/cms/djangoapps/contentstore/features/problem-editor.py b/cms/djangoapps/contentstore/features/problem-editor.py index fca2249066..262d1bfc10 100644 --- a/cms/djangoapps/contentstore/features/problem-editor.py +++ b/cms/djangoapps/contentstore/features/problem-editor.py @@ -1,9 +1,13 @@ # disable missing docstring #pylint: disable=C0111 +import os +import json from lettuce import world, step from nose.tools import assert_equal, assert_true # pylint: disable=E0611 -from common import type_in_codemirror +from common import type_in_codemirror, open_new_course +from course_import import import_file, go_to_import + DISPLAY_NAME = "Display Name" MAXIMUM_ATTEMPTS = "Maximum Attempts" @@ -24,7 +28,7 @@ def i_created_blank_common_problem(step): @step('I edit and select Settings$') -def i_edit_and_select_settings(step): +def i_edit_and_select_settings(_step): world.edit_component_and_select_settings() @@ -41,7 +45,7 @@ def i_see_advanced_settings_with_values(step): @step('I can modify the display name') -def i_can_modify_the_display_name(step): +def i_can_modify_the_display_name(_step): # Verifying that the display name can be a string containing a floating point value # (to confirm that we don't throw an error because it is of the wrong type). index = world.get_setting_entry_index(DISPLAY_NAME) @@ -58,7 +62,7 @@ def my_display_name_change_is_persisted_on_save(step): @step('I can specify special characters in the display name') -def i_can_modify_the_display_name_with_special_chars(step): +def i_can_modify_the_display_name_with_special_chars(_step): index = world.get_setting_entry_index(DISPLAY_NAME) world.css_fill('.wrapper-comp-setting .setting-input', "updated ' \" &", index=index) if world.is_firefox(): @@ -73,7 +77,7 @@ def special_chars_persisted_on_save(step): @step('I can revert the display name to unset') -def can_revert_display_name_to_unset(step): +def can_revert_display_name_to_unset(_step): world.revert_setting_entry(DISPLAY_NAME) verify_unset_display_name() @@ -85,7 +89,7 @@ def my_display_name_is_persisted_on_save(step): @step('I can select Per Student for Randomization') -def i_can_select_per_student_for_randomization(step): +def i_can_select_per_student_for_randomization(_step): world.browser.select(RANDOMIZATION, "Per Student") verify_modified_randomization() @@ -104,7 +108,7 @@ def i_can_revert_to_default_for_randomization(step): @step('I can set the weight to "(.*)"?') -def i_can_set_weight(step, weight): +def i_can_set_weight(_step, weight): set_weight(weight) verify_modified_weight() @@ -175,14 +179,14 @@ def create_latex_problem(step): @step('I edit and compile the High Level Source') -def edit_latex_source(step): +def edit_latex_source(_step): open_high_level_source() type_in_codemirror(1, "hi") world.css_click('.hls-compile') @step('my change to the High Level Source is persisted') -def high_level_source_persisted(step): +def high_level_source_persisted(_step): def verify_text(driver): css_sel = '.problem div>span' return world.css_text(css_sel) == 'hi' @@ -191,11 +195,53 @@ def high_level_source_persisted(step): @step('I view the High Level Source I see my changes') -def high_level_source_in_editor(step): +def high_level_source_in_editor(_step): open_high_level_source() assert_equal('hi', world.css_value('.source-edit-box')) +@step(u'I have an empty course') +def i_have_empty_course(step): + open_new_course() + + +@step(u'I go to the import page') +def i_go_to_import(_step): + go_to_import() + + +@step(u'I import the file "([^"]*)"$') +def i_import_the_file(_step, filename): + import_file(filename) + + +@step(u'I click on "edit a draft"$') +def i_edit_a_draft(_step): + world.css_click("a.create-draft") + + +@step(u'I go to the vertical "([^"]*)"$') +def i_go_to_vertical(_step, vertical): + world.css_click("span:contains('{0}')".format(vertical)) + + +@step(u'I go to the unit "([^"]*)"$') +def i_go_to_unit(_step, unit): + loc = "window.location = $(\"span:contains('{0}')\").closest('a').attr('href')".format(unit) + world.browser.execute_script(loc) + + +@step(u'I see a message that says "([^"]*)"$') +def i_can_see_message(_step, msg): + msg = json.dumps(msg) # escape quotes + world.css_has_text("h2.title", msg) + + +@step(u'I can edit the problem$') +def i_can_edit_problem(_step): + world.edit_component() + + def verify_high_level_source_links(step, visible): if visible: assert_true(world.is_css_present('.launch-latex-compiler'), diff --git a/cms/djangoapps/contentstore/views/import_export.py b/cms/djangoapps/contentstore/views/import_export.py index 67e64d67d0..6252da0d0e 100644 --- a/cms/djangoapps/contentstore/views/import_export.py +++ b/cms/djangoapps/contentstore/views/import_export.py @@ -193,6 +193,7 @@ def import_course(request, org, course, name): if not dirpath: return JsonResponse( { + 'ErrMsg': _('Could not find the course.xml file in the package.'), 'Stage': 2 }, diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index 03545c9077..12df3fcf7a 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -6,7 +6,7 @@ from django.conf import settings from django.http import HttpResponse, Http404, HttpResponseBadRequest, HttpResponseForbidden from django.core.urlresolvers import reverse from django.contrib.auth.decorators import login_required -from mitxmako.shortcuts import render_to_response +from mitxmako.shortcuts import render_to_response, render_to_string from xmodule_modifiers import replace_static_urls, wrap_xmodule from xmodule.error_module import ErrorDescriptor @@ -79,9 +79,17 @@ def preview_component(request, location): # can bind to it correctly component.runtime.wrappers.append(partial(wrap_xmodule, 'xmodule_edit.html')) + try: + content = component.render('studio_view').content + # catch exceptions indiscriminately, since after this point they escape the + # dungeon and surface as uneditable, unsaveable, and undeletable + # component-goblins. + except Exception as exc: # pylint: disable=W0703 + content = render_to_string('html_error.html', {'message': str(exc)}) + return render_to_response('component.html', { 'preview': get_preview_html(request, component, 0), - 'editor': component.render('studio_view').content, + 'editor': content }) @@ -157,4 +165,8 @@ def get_preview_html(request, descriptor, idx): specified by the descriptor and idx. """ module = load_preview_module(request, str(idx), descriptor) - return module.render("student_view").content + try: + content = module.render("student_view").content + except Exception as exc: # pylint: disable=W0703 + content = render_to_string('html_error.html', {'message': str(exc)}) + return content diff --git a/cms/static/sass/views/_unit.scss b/cms/static/sass/views/_unit.scss index 7c8a531cdc..50685123a2 100644 --- a/cms/static/sass/views/_unit.scss +++ b/cms/static/sass/views/_unit.scss @@ -355,6 +355,20 @@ body.course.unit,.view-unit { } } + + .wrapper-alert-error { + margin-top: ($baseline*1.25); + box-shadow: none; + border-top: 5px solid $red-l1; + + .copy, + .title { + color: $white; + } + + } + + } } diff --git a/cms/templates/html_error.html b/cms/templates/html_error.html new file mode 100644 index 0000000000..65bf870029 --- /dev/null +++ b/cms/templates/html_error.html @@ -0,0 +1,26 @@ +<%! from django.utils.translation import ugettext as _ %> +<%! from django.core.urlresolvers import reverse %> + +<%block name="content"> +
${_("Students will not be able to access this component. Re-edit your component to fix the error.")}
+ + % if message: ++ ${_("Error:")} + ${message | h} +
+ % endif + + +