diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f2454f5cc6..1a7454e1ec 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,8 @@ the Check/Final Check buttons with keys: custom_check and custom_final_check LMS: Add PaidCourseRegistration mode, where payment is required before course registration. +Studio: Switched to loading Javascript using require.js + LMS: Add split testing functionality for internal use. CMS: Add edit_course_tabs management command, providing a primitive @@ -36,7 +38,7 @@ new post dropdown as well as response and comment area labeling. LMS: enhanced shib support, including detection of linked shib account at login page and support for the ?next= GET parameter. -LMS: Experimental feature using the ICE change tracker JS pkg to allow peer +LMS: Experimental feature using the ICE change tracker JS pkg to allow peer assessors to edit the original submitter's work. LMS: Fixed a bug that caused links from forum user profile pages to @@ -341,4 +343,4 @@ Common: Allow setting of authentication session cookie name. LMS: Option to email students when enroll/un-enroll them. Blades: Added WAI-ARIA markup to the video player controls. These are now fully -accessible by screen readers. +accessible by screen readers. diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py index 664d8d00ae..201d87f029 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.py +++ b/cms/djangoapps/contentstore/features/advanced-settings.py @@ -16,6 +16,11 @@ def i_select_advanced_settings(step): world.click_course_settings() link_css = 'li.nav-course-settings-advanced a' world.css_click(link_css) + world.wait_for_requirejs( + ["jquery", "js/models/course", "js/models/settings/advanced", + "js/views/settings/advanced", "codemirror"]) + # this shouldn't be necessary, but we experience sporadic failures otherwise + world.wait(1) @step('I am on the Advanced Course Settings page in Studio$') @@ -91,8 +96,10 @@ def assert_policy_entries(expected_keys, expected_values): index = get_index_of(key) assert_false(index == -1, "Could not find key: {key}".format(key=key)) found_value = world.css_find(VALUE_CSS)[index].value - assert_equal(value, found_value, - "Expected {} to have value {} but found {}".format(key, value, found_value)) + assert_equal( + value, found_value, + "Expected {} to have value {} but found {}".format(key, value, found_value) + ) def get_index_of(expected_key): @@ -116,4 +123,6 @@ def change_display_name_value(step, new_value): def change_value(step, key, new_value): type_in_codemirror(get_index_of(key), new_value) + world.wait(0.5) press_the_notification_button(step, "Save") + world.wait_for_ajax_complete() diff --git a/cms/djangoapps/contentstore/features/checklists.feature b/cms/djangoapps/contentstore/features/checklists.feature index 23995f5aaa..c2b411adf0 100644 --- a/cms/djangoapps/contentstore/features/checklists.feature +++ b/cms/djangoapps/contentstore/features/checklists.feature @@ -9,7 +9,8 @@ Feature: CMS.Course checklists Scenario: A course author can mark tasks as complete Given I have opened Checklists Then I can check and uncheck tasks in a checklist - And They are correctly selected after reloading the page + And I reload the page + Then the tasks are correctly selected # There are issues getting link to be active in browsers other than chrome @skip_firefox diff --git a/cms/djangoapps/contentstore/features/checklists.py b/cms/djangoapps/contentstore/features/checklists.py index 63762b70c1..8a8aad0a12 100644 --- a/cms/djangoapps/contentstore/features/checklists.py +++ b/cms/djangoapps/contentstore/features/checklists.py @@ -45,11 +45,11 @@ def i_can_check_and_uncheck_tasks(step): verifyChecklist2Status(2, 7, 29) -@step('They are correctly selected after reloading the page$') -def tasks_correctly_selected_after_reload(step): - reload_the_page(step) +@step('the tasks are correctly selected$') +def tasks_correctly_selected(step): verifyChecklist2Status(2, 7, 29) # verify that task 7 is still selected by toggling its checkbox state and making sure that it deselects + world.browser.execute_script("window.scrollBy(0,1000)") toggleTask(1, 6) verifyChecklist2Status(1, 7, 14) @@ -109,13 +109,15 @@ def toggleTask(checklist, task): # TODO: figure out a way to do this in phantom and firefox # For now we will mark the scenerios that use this method as skipped def clickActionLink(checklist, task, actionText): - # toggle checklist item to make sure that the link button is showing - toggleTask(checklist, task) - action_link = world.css_find('#course-checklist' + str(checklist) + ' a')[task] - # text will be empty initially, wait for it to populate def verify_action_link_text(driver): - return world.css_text('#course-checklist' + str(checklist) + ' a', index=task) == actionText + actualText = world.css_text('#course-checklist' + str(checklist) + ' a', index=task) + if actualText == actionText: + return True + else: + # toggle checklist item to make sure that the link button is showing + toggleTask(checklist, task) + return False world.wait_for(verify_action_link_text) world.css_click('#course-checklist' + str(checklist) + ' a', index=task) diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 5dd72b3767..1cc35b761d 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -90,6 +90,7 @@ def press_the_notification_button(_step, name): world.browser.execute_script("$('{}').click()".format(btn_css)) else: world.css_click(btn_css) + world.wait_for_ajax_complete() @step('I change the "(.*)" field to "(.*)"$') @@ -244,7 +245,9 @@ def open_new_unit(step): step.given('I have opened a new course section in Studio') step.given('I have added a new subsection') step.given('I expand the first section') + old_url = world.browser.url world.css_click('a.new-unit-item') + world.wait_for(lambda x: world.browser.url != old_url) @step('the save notification button is disabled') @@ -298,6 +301,7 @@ def type_in_codemirror(index, text): g._element.send_keys(text) if world.is_firefox(): world.trigger_event('div.CodeMirror', index=index, event='blur') + world.wait_for_ajax_complete() def upload_file(filename): diff --git a/cms/djangoapps/contentstore/features/component.py b/cms/djangoapps/contentstore/features/component.py index 4fd7b01d9d..bec0c9431b 100644 --- a/cms/djangoapps/contentstore/features/component.py +++ b/cms/djangoapps/contentstore/features/component.py @@ -18,13 +18,22 @@ def add_unit(step): user = create_studio_user(is_staff=False) add_course_author(user, course) log_into_studio() - css_selectors = ['a.course-link', 'div.section-item a.expand-collapse-icon', 'a.new-unit-item'] + world.wait_for_requirejs([ + "jquery", "js/models/course", "coffee/src/models/module", + "coffee/src/views/unit", "jquery.ui", + ]) + world.wait_for_mathjax() + css_selectors = [ + 'a.course-link', 'div.section-item a.expand-collapse-icon', + 'a.new-unit-item', + ] for selector in css_selectors: world.css_click(selector) @step(u'I add this type of single step component:$') def add_a_single_step_component(step): + world.wait_for_xmodule() for step_hash in step.hashes: component = step_hash['Component'] assert_in(component, ['Discussion', 'Video']) @@ -67,6 +76,7 @@ def add_a_multi_step_component(step, is_advanced, category): def click_link(): link.click() + world.wait_for_xmodule() category = category.lower() for step_hash in step.hashes: css_selector = 'a[data-type="{}"]'.format(category) @@ -103,7 +113,7 @@ def see_a_multi_step_component(step, category): @step(u'I add a "([^"]*)" "([^"]*)" component$') -def add_component_catetory(step, component, category): +def add_component_category(step, component, category): assert category in ('single step', 'HTML', 'Problem', 'Advanced Problem') given_string = 'I add this type of {} component:'.format(category) step.given('{}\n{}\n{}'.format(given_string, '|Component|', '|{}|'.format(component))) @@ -111,6 +121,7 @@ def add_component_catetory(step, component, category): @step(u'I delete all components$') def delete_all_components(step): + world.wait_for_xmodule() delete_btn_css = 'a.delete-button' prompt_css = 'div#prompt-warning' btn_css = '{} a.button.action-primary'.format(prompt_css) @@ -118,7 +129,8 @@ def delete_all_components(step): count = len(world.css_find('ol.components li.component')) for _ in range(int(count)): world.css_click(delete_btn_css) - assert_true(world.is_css_present('{}.is-shown'.format(prompt_css)), + assert_true( + world.is_css_present('{}.is-shown'.format(prompt_css)), msg='Waiting for the confirmation prompt to be shown') # Pressing the button via css was not working reliably for the last component diff --git a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py index aa7c0e4b6d..976cb3b21c 100644 --- a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py +++ b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py @@ -20,16 +20,21 @@ def create_component_instance(step, component_button_css, category, if has_multiple_templates: click_component_from_menu(category, boilerplate, expected_css) + if category in ('video',): + world.wait_for_xmodule() + assert_equal( 1, len(world.css_find(expected_css)), "Component instance with css {css} was not created successfully".format(css=expected_css)) - @world.absorb def click_new_component_button(step, component_button_css): step.given('I have clicked the new unit button') + world.wait_for_requirejs( + ["jquery", "js/models/course", "coffee/src/models/module", + "coffee/src/views/unit", "jquery.ui"]) world.css_click(component_button_css) @@ -50,6 +55,7 @@ def click_component_from_menu(category, boilerplate, expected_css): assert_equal(len(elements), 1) world.css_click(elem_css) + @world.absorb def edit_component_and_select_settings(): world.wait_for(lambda _driver: world.css_visible('a.edit-button')) @@ -107,6 +113,7 @@ def verify_all_setting_entries(expected_entries): @world.absorb def save_component_and_reopen(step): world.css_click("a.save-button") + world.wait_for_ajax_complete() # We have a known issue that modifications are still shown within the edit window after cancel (though) # they are not persisted. Refresh the browser to make sure the changes WERE persisted after Save. reload_the_page(step) @@ -136,6 +143,7 @@ def get_setting_entry(label): return None return world.retry_on_exception(get_setting) + @world.absorb def get_setting_entry_index(label): def get_index(): diff --git a/cms/djangoapps/contentstore/features/course-settings.feature b/cms/djangoapps/contentstore/features/course-settings.feature index 230b1a04ad..0aeb95dbd9 100644 --- a/cms/djangoapps/contentstore/features/course-settings.feature +++ b/cms/djangoapps/contentstore/features/course-settings.feature @@ -9,7 +9,8 @@ Feature: CMS.Course Settings When I select Schedule and Details And I set course dates And I press the "Save" notification button - Then I see the set dates on refresh + And I reload the page + Then I see the set dates # IE has trouble with saving information @skip_internetexplorer @@ -17,7 +18,8 @@ Feature: CMS.Course Settings Given I have set course dates And I clear all the dates except start And I press the "Save" notification button - Then I see cleared dates on refresh + And I reload the page + Then I see cleared dates # IE has trouble with saving information @skip_internetexplorer @@ -26,7 +28,8 @@ Feature: CMS.Course Settings And I press the "Save" notification button And I clear the course start date Then I receive a warning about course start date - And The previously set start date is shown on refresh + And I reload the page + And the previously set start date is shown # IE has trouble with saving information # Safari gets CSRF token errors @@ -37,7 +40,8 @@ Feature: CMS.Course Settings And I have entered a new course start date And I press the "Save" notification button Then The warning about course start date goes away - And My new course start date is shown on refresh + And I reload the page + Then my new course start date is shown # Safari does not save + refresh properly through sauce labs @skip_safari @@ -45,7 +49,8 @@ Feature: CMS.Course Settings Given I have set course dates And I press the "Save" notification button When I change fields - Then I do not see the new changes persisted on refresh + And I reload the page + Then I do not see the changes # Safari does not save + refresh properly through sauce labs @skip_safari diff --git a/cms/djangoapps/contentstore/features/course-settings.py b/cms/djangoapps/contentstore/features/course-settings.py index 5f026146b4..7ec6a1071a 100644 --- a/cms/djangoapps/contentstore/features/course-settings.py +++ b/cms/djangoapps/contentstore/features/course-settings.py @@ -31,6 +31,9 @@ def test_i_select_schedule_and_details(step): world.click_course_settings() link_css = 'li.nav-course-settings-schedule a' world.css_click(link_css) + world.wait_for_requirejs( + ["jquery", "js/models/course", + "js/models/settings/course_details", "js/views/settings/main"]) @step('I have set course dates$') @@ -51,12 +54,6 @@ def test_and_i_set_course_dates(step): set_date_or_time(ENROLLMENT_END_TIME_CSS, DUMMY_TIME) -@step('Then I see the set dates on refresh$') -def test_then_i_see_the_set_dates_on_refresh(step): - reload_the_page(step) - i_see_the_set_dates() - - @step('And I clear all the dates except start$') def test_and_i_clear_all_the_dates_except_start(step): set_date_or_time(COURSE_END_DATE_CSS, '') @@ -64,9 +61,8 @@ def test_and_i_clear_all_the_dates_except_start(step): set_date_or_time(ENROLLMENT_END_DATE_CSS, '') -@step('Then I see cleared dates on refresh$') -def test_then_i_see_cleared_dates_on_refresh(step): - reload_the_page(step) +@step('Then I see cleared dates$') +def test_then_i_see_cleared_dates(step): verify_date_or_time(COURSE_END_DATE_CSS, '') verify_date_or_time(ENROLLMENT_START_DATE_CSS, '') verify_date_or_time(ENROLLMENT_END_DATE_CSS, '') @@ -92,9 +88,8 @@ def test_i_receive_a_warning_about_course_start_date(step): assert_true('error' in world.css_find(COURSE_START_TIME_CSS).first._element.get_attribute('class')) -@step('The previously set start date is shown on refresh$') -def test_the_previously_set_start_date_is_shown_on_refresh(step): - reload_the_page(step) +@step('the previously set start date is shown$') +def test_the_previously_set_start_date_is_shown(step): verify_date_or_time(COURSE_START_DATE_CSS, '12/20/2013') verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME) @@ -118,9 +113,8 @@ def test_the_warning_about_course_start_date_goes_away(step): assert_false('error' in world.css_find(COURSE_START_TIME_CSS).first._element.get_attribute('class')) -@step('My new course start date is shown on refresh$') -def test_my_new_course_start_date_is_shown_on_refresh(step): - reload_the_page(step) +@step('my new course start date is shown$') +def new_course_start_date_is_shown(step): verify_date_or_time(COURSE_START_DATE_CSS, '12/22/2013') # Time should have stayed from before attempt to clear date. verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME) @@ -134,16 +128,6 @@ def test_i_change_fields(step): set_date_or_time(ENROLLMENT_END_DATE_CSS, '7/7/7777') -@step('I do not see the new changes persisted on refresh$') -def test_changes_not_shown_on_refresh(step): - step.then('Then I see the set dates on refresh') - - -@step('I do not see the changes') -def test_i_do_not_see_changes(_step): - i_see_the_set_dates() - - @step('I change the course overview') def test_change_course_overview(_step): type_in_codemirror(0, "

Overview

") @@ -168,11 +152,8 @@ def i_see_new_course_image(_step): img = images[0] expected_src = '/c4x/MITx/999/asset/image.jpg' # Don't worry about the domain in the URL - try: - assert img['src'].endswith(expected_src) - except AssertionError as e: - e.args += ('Was looking for {}'.format(expected_src), 'Found {}'.format(img['src'])) - raise + assert img['src'].endswith(expected_src), "Was looking for {expected}, found {actual}".format( + expected=expected_src, actual=img['src']) @step('the image URL should be present in the field') @@ -200,7 +181,9 @@ def verify_date_or_time(css, date_or_time): assert_equal(date_or_time, world.css_value(css)) -def i_see_the_set_dates(): +@step('I do not see the changes') +@step('I see the set dates') +def i_see_the_set_dates(_step): """ Ensure that each field has the value set in `test_and_i_set_course_dates`. """ diff --git a/cms/djangoapps/contentstore/features/grading.py b/cms/djangoapps/contentstore/features/grading.py index 62b6617823..541fc6bbd4 100644 --- a/cms/djangoapps/contentstore/features/grading.py +++ b/cms/djangoapps/contentstore/features/grading.py @@ -4,7 +4,8 @@ from lettuce import world, step from common import * from terrain.steps import reload_the_page -from selenium.common.exceptions import InvalidElementStateException +from selenium.common.exceptions import ( + InvalidElementStateException, WebDriverException) from nose.tools import assert_in, assert_not_in # pylint: disable=E0611 @@ -134,7 +135,7 @@ def change_grade_range(_step, range_name): def i_see_highest_grade_range(_step, range_name): range_css = 'span.letter-grade' grade = world.css_find(range_css).first - assert grade.value == range_name + assert grade.value == range_name, "{0} != {1}".format(grade.value, range_name) @step(u'I cannot edit the "Fail" grade range$') @@ -142,12 +143,18 @@ def cannot_edit_fail(_step): range_css = 'span.letter-grade' ranges = world.css_find(range_css) assert len(ranges) == 2 + assert ranges.last.value != 'Failure' + + # try to change the grade range -- this should throw an exception try: ranges.last.value = 'Failure' - assert False, "Should not be able to edit failing range" - except InvalidElementStateException: + except (InvalidElementStateException): pass # We should get this exception on failing to edit the element + # check to be sure that nothing has changed + ranges = world.css_find(range_css) + assert len(ranges) == 2 + assert ranges.last.value != 'Failure' @step(u'I change the grace period to "(.*)"$') diff --git a/cms/djangoapps/contentstore/features/problem-editor.py b/cms/djangoapps/contentstore/features/problem-editor.py index 89f7b21882..98c8aacf13 100644 --- a/cms/djangoapps/contentstore/features/problem-editor.py +++ b/cms/djangoapps/contentstore/features/problem-editor.py @@ -142,8 +142,9 @@ def set_the_max_attempts(step, max_attempts_set): if world.is_firefox(): world.trigger_event('.wrapper-comp-setting .setting-input', index=index) world.save_component_and_reopen(step) - value = int(world.css_value('input.setting-input', index=index)) - assert value >= 0 + value = world.css_value('input.setting-input', index=index) + assert value != "", "max attempts is blank" + assert int(value) >= 0 @step('Edit High Level Source is not visible') diff --git a/cms/djangoapps/contentstore/features/textbooks.py b/cms/djangoapps/contentstore/features/textbooks.py index b432b84d4f..2cf6683d6d 100644 --- a/cms/djangoapps/contentstore/features/textbooks.py +++ b/cms/djangoapps/contentstore/features/textbooks.py @@ -4,6 +4,7 @@ from lettuce import world, step from django.conf import settings from common import upload_file +from nose.tools import assert_equal TEST_ROOT = settings.COMMON_TEST_DATA_ROOT @@ -82,20 +83,23 @@ def save_textbook(_step): @step(u'I should see a textbook named "([^"]*)" with a chapter path containing "([^"]*)"') def check_textbook(_step, textbook_name, chapter_name): - title = world.css_find(".textbook h3.textbook-title") - chapter = world.css_find(".textbook .wrap-textbook p") - assert title.text == textbook_name, "{} != {}".format(title.text, textbook_name) - assert chapter.text == chapter_name, "{} != {}".format(chapter.text, chapter_name) + title = world.css_text(".textbook h3.textbook-title", index=0) + chapter = world.css_text(".textbook .wrap-textbook p", index=0) + assert_equal(title, textbook_name) + assert_equal(chapter, chapter_name) @step(u'I should see a textbook named "([^"]*)" with (\d+) chapters') def check_textbook_chapters(_step, textbook_name, num_chapters_str): num_chapters = int(num_chapters_str) - title = world.css_find(".textbook .view-textbook h3.textbook-title") - toggle = world.css_find(".textbook .view-textbook .chapter-toggle") - assert title.text == textbook_name, "{} != {}".format(title.text, textbook_name) - assert toggle.text == "{num} PDF Chapters".format(num=num_chapters), \ - "Expected {num} chapters, found {real}".format(num=num_chapters, real=toggle.text) + title = world.css_text(".textbook .view-textbook h3.textbook-title", index=0) + toggle_text = world.css_text(".textbook .view-textbook .chapter-toggle", index=0) + assert_equal(title, textbook_name) + assert_equal( + toggle_text, + "{num} PDF Chapters".format(num=num_chapters), + "Expected {num} chapters, found {real}".format(num=num_chapters, real=toggle_text) + ) @step(u'I click the textbook chapters') diff --git a/cms/djangoapps/contentstore/features/upload.py b/cms/djangoapps/contentstore/features/upload.py index 023b477d76..cf0d92fc94 100644 --- a/cms/djangoapps/contentstore/features/upload.py +++ b/cms/djangoapps/contentstore/features/upload.py @@ -10,7 +10,7 @@ import random import os from django.contrib.auth.models import User from student.models import CourseEnrollment -from nose.tools import assert_equal, assert_not_equal # pylint: disable=E0611 +from nose.tools import assert_equal, assert_not_equal # pylint: disable=E0611 TEST_ROOT = settings.COMMON_TEST_DATA_ROOT ASSET_NAMES_CSS = 'td.name-col > span.title > a.filename' @@ -79,7 +79,7 @@ def check_upload(_step, file_name): @step(u'The url for the file "([^"]*)" is valid$') def check_url(_step, file_name): r = get_file(file_name) - assert_equal(r.status_code , 200) + assert_equal(r.status_code, 200) @step(u'I delete the file "([^"]*)"$') @@ -89,6 +89,8 @@ def delete_file(_step, file_name): delete_css = "a.remove-asset-button" world.css_click(delete_css, index=index) + world.wait_for_present(".wrapper-prompt.is-shown") + world.wait(0.2) # wait for css animation prompt_confirm_css = 'li.nav-item > a.action-primary' world.css_click(prompt_confirm_css) diff --git a/cms/djangoapps/contentstore/features/video-editor.py b/cms/djangoapps/contentstore/features/video-editor.py index 5997b08b86..00f3c2535d 100644 --- a/cms/djangoapps/contentstore/features/video-editor.py +++ b/cms/djangoapps/contentstore/features/video-editor.py @@ -18,6 +18,8 @@ def set_show_captions(step, setting): @step('when I view the video it (.*) show the captions$') def shows_captions(_step, show_captions): + world.wait_for_js_variable_truthy("Video") + world.wait(0.5) if show_captions == 'does not': assert world.is_css_present('div.video.closed') else: @@ -48,6 +50,6 @@ def correct_video_settings(_step): def video_name_persisted(step): world.css_click('a.save-button') reload_the_page(step) + world.wait_for_xmodule() world.edit_component() world.verify_setting_entry(world.get_setting_entry('Display Name'), 'Display Name', '3.4', True) - diff --git a/cms/djangoapps/contentstore/features/video.feature b/cms/djangoapps/contentstore/features/video.feature index ad6fea083b..105a26c868 100644 --- a/cms/djangoapps/contentstore/features/video.feature +++ b/cms/djangoapps/contentstore/features/video.feature @@ -33,4 +33,5 @@ Feature: CMS.Video Component Scenario: Video data is shown correctly Given I have created a video with only XML data + And I reload the page Then the correct Youtube video is shown diff --git a/cms/djangoapps/contentstore/features/video.py b/cms/djangoapps/contentstore/features/video.py index b3ed1a7ed2..20db375184 100644 --- a/cms/djangoapps/contentstore/features/video.py +++ b/cms/djangoapps/contentstore/features/video.py @@ -1,7 +1,6 @@ #pylint: disable=C0111 from lettuce import world, step -from terrain.steps import reload_the_page from xmodule.modulestore import Location from contentstore.utils import get_modulestore @@ -32,6 +31,7 @@ def i_created_a_video_with_subs_with_name(_step, sub_id): # Return to the video world.visit(video_url) + world.wait_for_xmodule() @step('I have uploaded subtitles "([^"]*)"$') @@ -46,6 +46,7 @@ def i_have_uploaded_subtitles(_step, sub_id): @step('when I view the (.*) it does not have autoplay enabled$') def does_not_autoplay(_step, video_type): + world.wait_for_xmodule() assert world.css_find('.%s' % video_type)[0]['data-autoplay'] == 'False' assert world.css_has_class('.video_control', 'play') @@ -66,6 +67,7 @@ def i_edit_the_component(_step): @step('I have (hidden|toggled) captions$') def hide_or_show_captions(step, shown): + world.wait_for_xmodule() button_css = 'a.hide-subtitles' if shown == 'hidden': world.css_click(button_css) @@ -107,12 +109,9 @@ def xml_only_video(step): data='' % youtube_id ) - # Refresh to see the new video - reload_the_page(step) - @step('The correct Youtube video is shown$') def the_youtube_video_is_shown(_step): + world.wait_for_xmodule() ele = world.css_find('.video').first assert ele['data-streams'].split(':')[1] == world.scenario_dict['YOUTUBE_ID'] - diff --git a/cms/djangoapps/contentstore/tests/test_assets.py b/cms/djangoapps/contentstore/tests/test_assets.py index 55613ea362..2e90955220 100644 --- a/cms/djangoapps/contentstore/tests/test_assets.py +++ b/cms/djangoapps/contentstore/tests/test_assets.py @@ -20,6 +20,7 @@ from xmodule.modulestore.django import modulestore from xmodule.modulestore.xml_importer import import_from_xml import json + class AssetsTestCase(CourseTestCase): def setUp(self): super(AssetsTestCase, self).setUp() @@ -50,7 +51,7 @@ class AssetsToyCourseTestCase(CourseTestCase): resp = self.client.get(url) # Test a small portion of the asset data passed to the client. - self.assertContains(resp, "new CMS.Models.AssetCollection([{") + self.assertContains(resp, "new AssetCollection([{") self.assertContains(resp, "/c4x/edX/toy/asset/handouts_sample_handout.txt") diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 28dad65e51..c9edcd60a0 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -90,7 +90,10 @@ def save_item(request): if value is None: field.delete_from(existing_item) else: - value = field.from_json(value) + try: + value = field.from_json(value) + except ValueError: + return JsonResponse({"error": "Invalid data"}, 400) field.write_to(existing_item, value) # Save the data that we've just changed to the underlying # MongoKeyValueStore before we update the mongo datastore. diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index 5c7fff16f7..b5cfc74a57 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -108,6 +108,7 @@ def preview_module_system(request, preview_id, descriptor): wrapper_template = 'xmodule_display.html' return ModuleSystem( + static_url=settings.STATIC_URL, ajax_url=reverse('preview_dispatch', args=[preview_id, descriptor.location.url(), '']).rstrip('/'), # TODO (cpennington): Do we want to track how instructors are using the preview problems? track_function=lambda event_type, event: None, diff --git a/cms/envs/common.py b/cms/envs/common.py index 13faf5520e..08e0f5e586 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -32,6 +32,7 @@ from lms.xblock.mixin import LmsBlockMixin from cms.xmodule_namespace import CmsBlockMixin from xmodule.modulestore.inheritance import InheritanceMixin from xmodule.x_module import XModuleMixin +from dealer.git import git ############################ FEATURE CONFIGURATION ############################# @@ -69,6 +70,7 @@ ENABLE_JASMINE = False PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms REPO_ROOT = PROJECT_ROOT.dirname() COMMON_ROOT = REPO_ROOT / "common" +LMS_ROOT = REPO_ROOT / "lms" ENV_ROOT = REPO_ROOT.dirname() # virtualenv dir /mitx is in GITHUB_REPO_ROOT = ENV_ROOT / "data" @@ -88,7 +90,8 @@ MAKO_TEMPLATES = {} MAKO_TEMPLATES['main'] = [ PROJECT_ROOT / 'templates', COMMON_ROOT / 'templates', - COMMON_ROOT / 'djangoapps' / 'pipeline_mako' / 'templates' + COMMON_ROOT / 'djangoapps' / 'pipeline_mako' / 'templates', + COMMON_ROOT / 'djangoapps' / 'pipeline_js' / 'templates', ] for namespace, template_dirs in lms.envs.common.MAKO_TEMPLATES.iteritems(): @@ -107,7 +110,8 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.static', 'django.contrib.messages.context_processors.messages', 'django.contrib.auth.context_processors.auth', # this is required for admin - 'django.core.context_processors.csrf' + 'django.core.context_processors.csrf', + 'dealer.contrib.django.staff.context_processor', # access git revision ) # use the ratelimit backend to prevent brute force attacks @@ -197,13 +201,14 @@ ADMINS = () MANAGERS = ADMINS # Static content -STATIC_URL = '/static/' +STATIC_URL = '/static/' + git.revision + "/" ADMIN_MEDIA_PREFIX = '/static/admin/' -STATIC_ROOT = ENV_ROOT / "staticfiles" +STATIC_ROOT = ENV_ROOT / "staticfiles" / git.revision STATICFILES_DIRS = [ COMMON_ROOT / "static", PROJECT_ROOT / "static", + LMS_ROOT / "static", # This is how you would use the textbook images locally # ("book", ENV_ROOT / "book_images") @@ -245,42 +250,39 @@ PIPELINE_CSS = { # test_order: Determines the position of this chunk of javascript on # the jasmine test page PIPELINE_JS = { - 'main': { - 'source_filenames': sorted( - rooted_glob(COMMON_ROOT / 'static/', 'coffee/src/**/*.js') + - rooted_glob(PROJECT_ROOT / 'static/', 'coffee/src/**/*.js') - ) + ['js/hesitate.js', 'js/base.js', 'js/views/feedback.js', - 'js/models/course.js', - 'js/models/section.js', 'js/views/section.js', - 'js/models/metadata_model.js', 'js/views/metadata_editor_view.js', - 'js/models/uploads.js', 'js/views/uploads.js', - 'js/models/textbook.js', 'js/views/textbook.js', - 'js/src/utility.js', - 'js/models/settings/course_grading_policy.js', - 'js/models/asset.js', 'js/models/assets.js', - 'js/views/assets.js', - 'js/views/assets_view.js', 'js/views/asset_view.js'], - 'output_filename': 'js/cms-application.js', - 'test_order': 0 - }, 'module-js': { 'source_filenames': ( rooted_glob(COMMON_ROOT / 'static/', 'xmodule/descriptors/js/*.js') + - rooted_glob(COMMON_ROOT / 'static/', 'xmodule/modules/js/*.js') + rooted_glob(COMMON_ROOT / 'static/', 'xmodule/modules/js/*.js') + + rooted_glob(COMMON_ROOT / 'static/', 'coffee/src/discussion/*.js') ), 'output_filename': 'js/cms-modules.js', 'test_order': 1 }, } +PIPELINE_COMPILERS = ( + 'pipeline.compilers.coffee.CoffeeScriptCompiler', +) + PIPELINE_CSS_COMPRESSOR = None PIPELINE_JS_COMPRESSOR = None STATICFILES_IGNORE_PATTERNS = ( - "sass/*", - "coffee/*", "*.py", "*.pyc" + # it would be nice if we could do, for example, "**/*.scss", + # but these strings get passed down to the `fnmatch` module, + # which doesn't support that. :( + # http://docs.python.org/2/library/fnmatch.html + "sass/*.scss", + "sass/*/*.scss", + "sass/*/*/*.scss", + "sass/*/*/*/*.scss", + "coffee/*.coffee", + "coffee/*/*.coffee", + "coffee/*/*/*.coffee", + "coffee/*/*/*/*.coffee", ) PIPELINE_YUI_BINARY = 'yui-compressor' diff --git a/cms/static/coffee/spec/helpers.coffee b/cms/static/coffee/spec/helpers.coffee deleted file mode 100644 index a03e2a0e56..0000000000 --- a/cms/static/coffee/spec/helpers.coffee +++ /dev/null @@ -1,20 +0,0 @@ -jasmine.getFixtures().fixturesPath += 'coffee/fixtures' - -# Stub jQuery.cookie -@stubCookies = - csrftoken: "stubCSRFToken" - -jQuery.cookie = (key, value) => - if value? - @stubCookies[key] = value - else - @stubCookies[key] - -# Path Jasmine's `it` method to raise an error when the test is not defined. -# This is helpful when writing the specs first before writing the test. -@it = (desc, func) -> - if func? - jasmine.getEnv().it(desc, func) - else - jasmine.getEnv().it desc, -> - throw "test is undefined" diff --git a/cms/static/coffee/spec/main.coffee b/cms/static/coffee/spec/main.coffee new file mode 100644 index 0000000000..39214fd6a7 --- /dev/null +++ b/cms/static/coffee/spec/main.coffee @@ -0,0 +1,154 @@ +requirejs.config({ + paths: { + "gettext": "xmodule_js/common_static/js/test/i18n", + "mustache": "xmodule_js/common_static/js/vendor/mustache", + "codemirror": "xmodule_js/common_static/js/vendor/CodeMirror/codemirror", + "jquery": "xmodule_js/common_static/js/vendor/jquery.min", + "jquery.ui": "xmodule_js/common_static/js/vendor/jquery-ui.min", + "jquery.form": "xmodule_js/common_static/js/vendor/jquery.form", + "jquery.markitup": "xmodule_js/common_static/js/vendor/markitup/jquery.markitup", + "jquery.leanModal": "xmodule_js/common_static/js/vendor/jquery.leanModal.min", + "jquery.smoothScroll": "xmodule_js/common_static/js/vendor/jquery.smooth-scroll.min", + "jquery.scrollTo": "xmodule_js/common_static/js/vendor/jquery.scrollTo-1.4.2-min", + "jquery.timepicker": "xmodule_js/common_static/js/vendor/timepicker/jquery.timepicker", + "jquery.cookie": "xmodule_js/common_static/js/vendor/jquery.cookie", + "jquery.qtip": "xmodule_js/common_static/js/vendor/jquery.qtip.min", + "jquery.fileupload": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload", + "jquery.iframe-transport": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport", + "jquery.inputnumber": "xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill", + "datepair": "xmodule_js/common_static/js/vendor/timepicker/datepair", + "date": "xmodule_js/common_static/js/vendor/date", + "underscore": "xmodule_js/common_static/js/vendor/underscore-min", + "underscore.string": "xmodule_js/common_static/js/vendor/underscore.string.min", + "backbone": "xmodule_js/common_static/js/vendor/backbone-min", + "backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min", + "youtube": "xmodule_js/common_static/js/load_youtube", + "tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/tiny_mce", + "jquery.tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/jquery.tinymce", + "mathjax": "https://edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full", + "xmodule": "xmodule_js/src/xmodule", + "utility": "xmodule_js/common_static/js/src/utility", + "sinon": "xmodule_js/common_static/js/vendor/sinon-1.7.1", + "squire": "xmodule_js/common_static/js/vendor/Squire", + "jasmine-stealth": "xmodule_js/common_static/js/vendor/jasmine-stealth", + "jasmine.async": "xmodule_js/common_static/js/vendor/jasmine.async", + + "coffee/src/ajax_prefix": "xmodule_js/common_static/coffee/src/ajax_prefix" + }, + shim: { + "gettext": { + exports: "gettext" + }, + "date": { + exports: "Date" + }, + "jquery.ui": { + deps: ["jquery"], + exports: "jQuery.ui" + }, + "jquery.form": { + deps: ["jquery"], + exports: "jQuery.fn.ajaxForm" + }, + "jquery.markitup": { + deps: ["jquery"], + exports: "jQuery.fn.markitup" + }, + "jquery.leanModal": { + deps: ["jquery"], + exports: "jQuery.fn.leanModal" + }, + "jquery.smoothScroll": { + deps: ["jquery"], + exports: "jQuery.fn.smoothScroll" + }, + "jquery.scrollTo": { + deps: ["jquery"], + exports: "jQuery.fn.scrollTo" + }, + "jquery.cookie": { + deps: ["jquery"], + exports: "jQuery.fn.cookie" + }, + "jquery.qtip": { + deps: ["jquery"], + exports: "jQuery.fn.qtip" + }, + "jquery.fileupload": { + deps: ["jquery.iframe-transport"], + exports: "jQuery.fn.fileupload" + }, + "jquery.inputnumber": { + deps: ["jquery"], + exports: "jQuery.fn.inputNumber" + }, + "jquery.tinymce": { + deps: ["jquery", "tinymce"], + exports: "jQuery.fn.tinymce" + }, + "datepair": { + deps: ["jquery.ui", "jquery.timepicker"] + }, + "underscore": { + exports: "_" + }, + "backbone": { + deps: ["underscore", "jquery"], + exports: "Backbone" + }, + "backbone.associations": { + deps: ["backbone"], + exports: "Backbone.Associations" + }, + "codemirror": { + exports: "CodeMirror" + }, + "tinymce": { + exports: "tinymce" + }, + "mathjax": { + exports: "MathJax" + }, + "xmodule": { + exports: "XModule" + }, + "sinon": { + exports: "sinon" + }, + "jasmine-stealth": { + deps: ["jasmine"] + }, + "jasmine.async": { + deps: ["jasmine"], + exports: "AsyncSpec" + }, + + "coffee/src/main": { + deps: ["coffee/src/ajax_prefix"] + }, + "coffee/src/ajax_prefix": { + deps: ["jquery"] + } + } +}); + +jasmine.getFixtures().fixturesPath += 'coffee/fixtures' + +define([ + "coffee/spec/main_spec", + + "coffee/spec/models/course_spec", "coffee/spec/models/metadata_spec", + "coffee/spec/models/module_spec", "coffee/spec/models/section_spec", + "coffee/spec/models/settings_grading_spec", "coffee/spec/models/textbook_spec", + "coffee/spec/models/upload_spec", + + "coffee/spec/views/section_spec", + "coffee/spec/views/course_info_spec", "coffee/spec/views/feedback_spec", + "coffee/spec/views/metadata_edit_spec", "coffee/spec/views/module_edit_spec", + "coffee/spec/views/textbook_spec", "coffee/spec/views/upload_spec", + + # these tests are run separate in the cms-squire suite, due to process + # isolation issues with Squire.js + # "coffee/spec/views/assets_spec" + ]) + diff --git a/cms/static/coffee/spec/main_spec.coffee b/cms/static/coffee/spec/main_spec.coffee index 9383f2547e..6811b3d96a 100644 --- a/cms/static/coffee/spec/main_spec.coffee +++ b/cms/static/coffee/spec/main_spec.coffee @@ -1,58 +1,55 @@ -describe "CMS", -> - beforeEach -> - CMS.unbind() +require ["jquery", "backbone", "coffee/src/main", "sinon", "jasmine-stealth"], +($, Backbone, main, sinon) -> + describe "CMS", -> + it "should initialize URL", -> + expect(window.CMS.URL).toBeDefined() - it "should initialize Models", -> - expect(CMS.Models).toBeDefined() + describe "main helper", -> + beforeEach -> + @previousAjaxSettings = $.extend(true, {}, $.ajaxSettings) + spyOn($, "cookie") + $.cookie.when("csrftoken").thenReturn("stubCSRFToken") + main() - it "should initialize Views", -> - expect(CMS.Views).toBeDefined() + afterEach -> + $.ajaxSettings = @previousAjaxSettings -describe "main helper", -> - beforeEach -> - @previousAjaxSettings = $.extend(true, {}, $.ajaxSettings) - window.stubCookies["csrftoken"] = "stubCSRFToken" - $(document).ready() + it "turn on Backbone emulateHTTP", -> + expect(Backbone.emulateHTTP).toBeTruthy() - afterEach -> - $.ajaxSettings = @previousAjaxSettings + it "setup AJAX CSRF token", -> + expect($.ajaxSettings.headers["X-CSRFToken"]).toEqual("stubCSRFToken") - it "turn on Backbone emulateHTTP", -> - expect(Backbone.emulateHTTP).toBeTruthy() + describe "AJAX Errors", -> + tpl = readFixtures('system-feedback.underscore') - it "setup AJAX CSRF token", -> - expect($.ajaxSettings.headers["X-CSRFToken"]).toEqual("stubCSRFToken") + beforeEach -> + setFixtures($(" - """ - - appendSetFixtures """ -
- - Will Release: 06/12/2013 at 04:00 UTC - - Edit -
- """ - - appendSetFixtures """ -
-
-

Section Release Date

-
-
- - -
-
- - -
-
-

On the date set above, this section – – will be released to students. Any units marked private will only be visible to admins.

-
-
- SaveCancel -
-
- """ - - appendSetFixtures """ -
- -
- """ - - spyOn(window, 'saveSetSectionScheduleDate').andCallThrough() - # Have to do this here, as it normally gets bound in document.ready() - $('a.save-button').click(saveSetSectionScheduleDate) - $('a.delete-section-button').click(deleteSection) - $(".edit-subsection-publish-settings .start-date").datepicker() - - @notificationSpy = spyOn(CMS.Views.Notification.Mini.prototype, 'show').andCallThrough() - window.analytics = jasmine.createSpyObj('analytics', ['track']) - window.course_location_analytics = jasmine.createSpy() - @xhr = sinon.useFakeXMLHttpRequest() - requests = @requests = [] - @xhr.onCreate = (req) -> requests.push(req) - - afterEach -> - delete window.analytics - delete window.course_location_analytics - @notificationSpy.reset() - - it "should save model when save is clicked", -> - $('a.edit-button').click() - $('a.save-button').click() - expect(saveSetSectionScheduleDate).toHaveBeenCalled() - - it "should show a confirmation on save", -> - $('a.edit-button').click() - $('a.save-button').click() - expect(@notificationSpy).toHaveBeenCalled() - - it "should delete model when delete is clicked", -> - deleteSpy = spyOn(window, '_deleteItem').andCallThrough() - $('a.delete-section-button').click() - $('a.action-primary').click() - expect(deleteSpy).toHaveBeenCalled() - expect(@requests[0].url).toEqual('/delete_item') - - it "should not delete model when cancel is clicked", -> - deleteSpy = spyOn(window, '_deleteItem').andCallThrough() - $('a.delete-section-button').click() - $('a.action-secondary').click() - expect(@requests.length).toEqual(0) - - it "should show a confirmation on delete", -> - $('a.delete-section-button').click() - $('a.action-primary').click() - expect(@notificationSpy).toHaveBeenCalled() diff --git a/cms/static/coffee/spec/views/section_spec.coffee b/cms/static/coffee/spec/views/section_spec.coffee index e64294f249..d83707c8f0 100644 --- a/cms/static/coffee/spec/views/section_spec.coffee +++ b/cms/static/coffee/spec/views/section_spec.coffee @@ -1,85 +1,87 @@ -describe "CMS.Views.SectionShow", -> - describe "Basic", -> - beforeEach -> - spyOn(CMS.Views.SectionShow.prototype, "switchToEditView") - .andCallThrough() - @model = new CMS.Models.Section({ - id: 42 - name: "Life, the Universe, and Everything" - }) - @view = new CMS.Views.SectionShow({model: @model}) - @view.render() +define ["js/models/section", "js/views/section_show", "js/views/section_edit", "sinon"], (Section, SectionShow, SectionEdit, sinon) -> - it "should contain the model name", -> - expect(@view.$el).toHaveText(@model.get('name')) + describe "SectionShow", -> + describe "Basic", -> + beforeEach -> + spyOn(SectionShow.prototype, "switchToEditView") + .andCallThrough() + @model = new Section({ + id: 42 + name: "Life, the Universe, and Everything" + }) + @view = new SectionShow({model: @model}) + @view.render() - it "should call switchToEditView when clicked", -> - @view.$el.click() - expect(@view.switchToEditView).toHaveBeenCalled() + it "should contain the model name", -> + expect(@view.$el).toHaveText(@model.get('name')) - it "should pass the same element to SectionEdit when switching views", -> - spyOn(CMS.Views.SectionEdit.prototype, 'initialize').andCallThrough() - @view.switchToEditView() - expect(CMS.Views.SectionEdit.prototype.initialize).toHaveBeenCalled() - expect(CMS.Views.SectionEdit.prototype.initialize.mostRecentCall.args[0].el).toEqual(@view.el) + it "should call switchToEditView when clicked", -> + @view.$el.click() + expect(@view.switchToEditView).toHaveBeenCalled() -describe "CMS.Views.SectionEdit", -> - describe "Basic", -> - tpl = readFixtures('section-name-edit.underscore') - feedback_tpl = readFixtures('system-feedback.underscore') + it "should pass the same element to SectionEdit when switching views", -> + spyOn(SectionEdit.prototype, 'initialize').andCallThrough() + @view.switchToEditView() + expect(SectionEdit.prototype.initialize).toHaveBeenCalled() + expect(SectionEdit.prototype.initialize.mostRecentCall.args[0].el).toEqual(@view.el) - beforeEach -> - setFixtures($(" - - + @@ -106,8 +206,6 @@ - - diff --git a/cms/templates/base.html b/cms/templates/base.html index b53dc2657d..a27cac7760 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -18,10 +18,10 @@ - <%static:css group='base-style'/> + <%include file="widgets/segment-io.html" /> @@ -29,44 +29,151 @@ - <%include file="courseware_vendor_js.html"/> + + + + - ## js templates - +## js templates + - ## javascript - - - - - - - - - <%static:js group='main'/> - <%static:js group='module-js'/> - - - - - - - - - - - % if context_course: % endif @@ -91,6 +198,7 @@ window.course = new CMS.Models.Course({
<%block name="jsextra"> + <%include file="widgets/qualaroo.html" /> diff --git a/cms/templates/checklists.html b/cms/templates/checklists.html index 0c80a87567..8a9d59512b 100644 --- a/cms/templates/checklists.html +++ b/cms/templates/checklists.html @@ -1,6 +1,8 @@ -<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> -<%! from django.core.urlresolvers import reverse %> +<%! + from django.core.urlresolvers import reverse + from django.utils.translation import ugettext as _ +%> <%block name="title">Course Checklists <%block name="bodyclass">is-signedin course view-checklists @@ -15,22 +17,18 @@ <%block name="jsextra"> - - - - diff --git a/cms/templates/component.html b/cms/templates/component.html index 2412cd74d4..7496e85b18 100644 --- a/cms/templates/component.html +++ b/cms/templates/component.html @@ -1,11 +1,6 @@ <%! from django.utils.translation import ugettext as _ %> <%namespace name='static' file='static_content.html'/> - - - - -
diff --git a/cms/templates/course_info.html b/cms/templates/course_info.html index 132ab6015a..b2abd43e9b 100644 --- a/cms/templates/course_info.html +++ b/cms/templates/course_info.html @@ -7,6 +7,7 @@ <%block name="bodyclass">is-signedin course course-info updates view-updates <%block name="header_extras"> + % for template_name in ["course_info_update", "course_info_handouts"]: - - - - - - - diff --git a/cms/templates/edit-tabs.html b/cms/templates/edit-tabs.html index f884d81dd7..ae597f0377 100644 --- a/cms/templates/edit-tabs.html +++ b/cms/templates/edit-tabs.html @@ -1,18 +1,23 @@ <%inherit file="base.html" /> -<%! from django.utils.translation import ugettext as _ %> -<%! from django.core.urlresolvers import reverse %> +<%namespace name='static' file='static_content.html'/> +<%! + from django.utils.translation import ugettext as _ + from django.core.urlresolvers import reverse +%> <%block name="title">Static Pages <%block name="bodyclass">is-signedin course view-static-pages <%block name="jsextra"> @@ -72,7 +77,7 @@

${_("How Static Pages are Used in Your Course")}

- ${_('Preview of how Static Pages are used in your course')} + ${_('Preview of how Static Pages are used in your course')}
${_("These pages will be presented in your course's main navigation alongside Courseware, Course Info, Discussion, etc.")}
diff --git a/cms/templates/edit_subsection.html b/cms/templates/edit_subsection.html index 189969ecf3..364411b01c 100644 --- a/cms/templates/edit_subsection.html +++ b/cms/templates/edit_subsection.html @@ -97,40 +97,33 @@ <%block name="jsextra"> - - - - - - - - diff --git a/cms/templates/export.html b/cms/templates/export.html index e371a22f49..04b4572528 100644 --- a/cms/templates/export.html +++ b/cms/templates/export.html @@ -1,63 +1,70 @@ -<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> <%namespace name='static' file='static_content.html'/> -<%! from django.core.urlresolvers import reverse %> +<%! + from django.core.urlresolvers import reverse + from django.utils.translation import ugettext as _ + import json +%> <%block name="title">${_("Course Export")} <%block name="bodyclass">is-signedin course tools view-export <%block name="jsextra"> % if in_err: %endif diff --git a/cms/templates/howitworks.html b/cms/templates/howitworks.html index 935148c8bb..f405976040 100644 --- a/cms/templates/howitworks.html +++ b/cms/templates/howitworks.html @@ -1,6 +1,9 @@ -<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> -<%! from django.core.urlresolvers import reverse %> +<%namespace name='static' file='static_content.html'/> +<%! + from django.core.urlresolvers import reverse + from django.utils.translation import ugettext as _ +%> <%block name="title">${_("Welcome")} <%block name="bodyclass">not-signedin index view-howitworks @@ -27,7 +30,7 @@
  • - ${_('Studio Helps You Keep Your Courses Organized')} + ${_('Studio Helps You Keep Your Courses Organized')}
    ${_("Studio Helps You Keep Your Courses Organized")}
    @@ -61,7 +64,7 @@
  • - ${_('Learning is More than Just Lectures')} + ${_('Learning is More than Just Lectures')}
    ${_("Learning is More than Just Lectures")}
    @@ -95,7 +98,7 @@
  • - ${_('Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.')} + ${_('Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.')}
    ${_("Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.")}
    @@ -149,7 +152,7 @@

    ${_("Outlining Your Course")}

    - +
    ${_("Simple two-level outline to organize your couse. Drag and drop, and see your course at a glance.")}
    @@ -162,7 +165,7 @@

    ${_("More than Just Lectures")}

    - +
    ${_("Quickly create videos, text snippets, inline discussions, and a variety of problem types.")}
    @@ -175,7 +178,7 @@

    ${_("Publishing on Date")}

    - +
    ${_("Simply set the date of a section or subsection, and Studio will publish it to your students for you.")}
    diff --git a/cms/templates/import.html b/cms/templates/import.html index d408111641..255276a712 100644 --- a/cms/templates/import.html +++ b/cms/templates/import.html @@ -1,8 +1,9 @@ -<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> <%namespace name='static' file='static_content.html'/> - -<%! from django.core.urlresolvers import reverse %> +<%! + from django.core.urlresolvers import reverse + from django.utils.translation import ugettext as _ +%> <%block name="title">${_("Course Import")} <%block name="bodyclass">is-signedin course tools view-import @@ -46,11 +47,8 @@ <%block name="jsextra"> - - - diff --git a/cms/templates/index.html b/cms/templates/index.html index 717d17f44a..df8459e448 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -5,11 +5,9 @@ <%block name="title">${_("My Courses")} <%block name="bodyclass">is-signedin index view-dashboard - - - <%block name="jsextra"> diff --git a/cms/templates/login.html b/cms/templates/login.html index 31b02b6c34..ac183927c0 100644 --- a/cms/templates/login.html +++ b/cms/templates/login.html @@ -1,6 +1,8 @@ -<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> -<%! from django.core.urlresolvers import reverse %> +<%! +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _ +%> <%block name="title">${_("Sign In")} <%block name="bodyclass">not-signedin view-signin @@ -56,17 +58,14 @@ <%block name="jsextra"> diff --git a/cms/templates/manage_users.html b/cms/templates/manage_users.html index 39c3d4e25d..da4d255ecd 100644 --- a/cms/templates/manage_users.html +++ b/cms/templates/manage_users.html @@ -162,6 +162,9 @@ <%block name="jsextra"> diff --git a/cms/templates/overview.html b/cms/templates/overview.html index d5246c6c8a..b3888d8706 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -13,47 +13,38 @@ <%block name="jsextra"> - - - - - - - - + - - diff --git a/cms/templates/settings.html b/cms/templates/settings.html index ee59189796..961b7d3d92 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -1,55 +1,45 @@ -<%! from django.utils.translation import ugettext as _ %> - <%inherit file="base.html" /> <%block name="title">${_("Schedule & Details Settings")} <%block name="bodyclass">is-signedin course schedule view-settings feature-upload <%namespace name='static' file='static_content.html'/> <%! -from contentstore import utils + from contentstore import utils + from django.utils.translation import ugettext as _ %> <%block name="jsextra"> - - - - - - - - diff --git a/cms/templates/settings_advanced.html b/cms/templates/settings_advanced.html index c5a69a3f06..6be114d714 100644 --- a/cms/templates/settings_advanced.html +++ b/cms/templates/settings_advanced.html @@ -1,8 +1,10 @@ <%inherit file="base.html" /> <%namespace name='static' file='static_content.html'/> -<%! from django.core.urlresolvers import reverse %> -<%! from django.utils.translation import ugettext as _ %> -<%! from contentstore import utils %> +<%! + from django.core.urlresolvers import reverse + from django.utils.translation import ugettext as _ + from contentstore import utils +%> <%block name="title">${_("Advanced Settings")} <%block name="bodyclass">is-signedin course advanced view-settings @@ -13,30 +15,24 @@ % endfor - - - - diff --git a/cms/templates/settings_graders.html b/cms/templates/settings_graders.html index 56ef1c4acc..de22241196 100644 --- a/cms/templates/settings_graders.html +++ b/cms/templates/settings_graders.html @@ -18,30 +18,21 @@ <%block name="jsextra"> - - - - - - - diff --git a/cms/templates/signup.html b/cms/templates/signup.html index fcc03cbd7d..1bc13c856a 100644 --- a/cms/templates/signup.html +++ b/cms/templates/signup.html @@ -98,45 +98,39 @@ <%block name="jsextra"> diff --git a/cms/templates/textbooks.html b/cms/templates/textbooks.html index 73d2b26ae7..ffb96630c6 100644 --- a/cms/templates/textbooks.html +++ b/cms/templates/textbooks.html @@ -16,32 +16,37 @@ <%block name="jsextra"> diff --git a/cms/templates/unit.html b/cms/templates/unit.html index 9a28e1e891..bf4d9363f6 100644 --- a/cms/templates/unit.html +++ b/cms/templates/unit.html @@ -1,34 +1,34 @@ -<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> -<%! from django.core.urlresolvers import reverse %> +<%! +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _ +%> <%namespace name="units" file="widgets/units.html" /> <%block name="title">${_("Individual Unit")} <%block name="bodyclass">is-signedin course unit view-unit <%block name="jsextra"> diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index a0d4d0fd41..f5829e4e1f 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -1,12 +1,15 @@ -<%! from django.core.urlresolvers import reverse %> -<%! from django.utils.translation import ugettext as _ %> +<%namespace name='static' file='../static_content.html'/> +<%! + from django.core.urlresolvers import reverse + from django.utils.translation import ugettext as _ +%>