Merge remote-tracking branch 'origin/master' into release, conflicts resolved
Conflicts: cms/envs/common.py common/lib/xmodule/xmodule/seq_module.py lms/envs/common.py requirements/edx/edx-private.txt
This commit is contained in:
@@ -3,10 +3,9 @@
|
||||
|
||||
from lettuce import world, step
|
||||
from nose.tools import assert_false, assert_equal, assert_regexp_matches # pylint: disable=E0611
|
||||
from common import type_in_codemirror, press_the_notification_button
|
||||
from common import type_in_codemirror, press_the_notification_button, get_codemirror_value
|
||||
|
||||
KEY_CSS = '.key input.policy-key'
|
||||
VALUE_CSS = 'textarea.json'
|
||||
DISPLAY_NAME_KEY = "display_name"
|
||||
DISPLAY_NAME_VALUE = '"Robot Super Course"'
|
||||
|
||||
@@ -101,7 +100,7 @@ def assert_policy_entries(expected_keys, expected_values):
|
||||
for key, value in zip(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
|
||||
found_value = get_codemirror_value(index)
|
||||
assert_equal(
|
||||
value, found_value,
|
||||
"Expected {} to have value {} but found {}".format(key, value, found_value)
|
||||
@@ -120,15 +119,13 @@ def get_index_of(expected_key):
|
||||
|
||||
def get_display_name_value():
|
||||
index = get_index_of(DISPLAY_NAME_KEY)
|
||||
return world.css_value(VALUE_CSS, index=index)
|
||||
|
||||
return get_codemirror_value(index)
|
||||
|
||||
def change_display_name_value(step, new_value):
|
||||
change_value(step, DISPLAY_NAME_KEY, new_value)
|
||||
|
||||
|
||||
def change_value(step, key, new_value):
|
||||
type_in_codemirror(get_index_of(key), new_value)
|
||||
world.wait(0.5)
|
||||
index = get_index_of(key)
|
||||
type_in_codemirror(index, new_value)
|
||||
press_the_notification_button(step, "Save")
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
@@ -319,20 +319,18 @@ def i_am_shown_a_notification(step):
|
||||
|
||||
|
||||
def type_in_codemirror(index, text):
|
||||
world.wait(1) # For now, slow this down so that it works. TODO: fix it.
|
||||
world.css_click("div.CodeMirror-lines", index=index)
|
||||
world.browser.execute_script("$('div.CodeMirror.CodeMirror-focused > div').css('overflow', '')")
|
||||
g = world.css_find("div.CodeMirror.CodeMirror-focused > div > textarea")
|
||||
if world.is_mac():
|
||||
g._element.send_keys(Keys.COMMAND + 'a')
|
||||
else:
|
||||
g._element.send_keys(Keys.CONTROL + 'a')
|
||||
g._element.send_keys(Keys.DELETE)
|
||||
g._element.send_keys(text)
|
||||
if world.is_firefox():
|
||||
world.trigger_event('div.CodeMirror', index=index, event='blur')
|
||||
script = """
|
||||
var cm = $('div.CodeMirror:eq({})').get(0).CodeMirror;
|
||||
cm.getInputField().focus();
|
||||
cm.setValue(arguments[0]);
|
||||
cm.getInputField().blur();""".format(index)
|
||||
world.browser.driver.execute_script(script, str(text))
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
def get_codemirror_value(index=0):
|
||||
return world.browser.driver.execute_script("""
|
||||
return $('div.CodeMirror:eq({})').get(0).CodeMirror.getValue();
|
||||
""".format(index))
|
||||
|
||||
def upload_file(filename):
|
||||
path = os.path.join(TEST_ROOT, filename)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from lettuce import world, step
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from common import type_in_codemirror
|
||||
from common import type_in_codemirror, get_codemirror_value
|
||||
from nose.tools import assert_in # pylint: disable=E0611
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ def change_date(_step, new_date):
|
||||
@step(u'I should see the date "([^"]*)"$')
|
||||
def check_date(_step, date):
|
||||
date_css = 'span.date-display'
|
||||
assert date == world.css_html(date_css)
|
||||
assert_in(date, world.css_html(date_css))
|
||||
|
||||
|
||||
@step(u'I modify the handout to "([^"]*)"$')
|
||||
@@ -87,7 +87,7 @@ def edit_handouts(_step, text):
|
||||
@step(u'I see the handout "([^"]*)"$')
|
||||
def check_handout(_step, handout):
|
||||
handout_css = 'div.handouts-content'
|
||||
assert handout in world.css_html(handout_css)
|
||||
assert_in(handout, world.css_html(handout_css))
|
||||
|
||||
|
||||
@step(u'I see the handout error text')
|
||||
@@ -127,6 +127,6 @@ def change_text(text):
|
||||
|
||||
def verify_text_in_editor_and_update(button_css, before, after):
|
||||
world.css_click(button_css)
|
||||
text = world.css_find(".cm-string").html
|
||||
assert before in text
|
||||
text = get_codemirror_value()
|
||||
assert_in(before, text)
|
||||
change_text(after)
|
||||
|
||||
@@ -19,3 +19,9 @@ Feature: CMS.HTML Editor
|
||||
Given I have created an E-text Written in LaTeX
|
||||
When I edit and select Settings
|
||||
Then Edit High Level Source is visible
|
||||
|
||||
Scenario: TinyMCE image plugin sets urls correctly
|
||||
Given I have created a Blank HTML Page
|
||||
When I edit the page and select the Visual Editor
|
||||
And I add an image with a static link via the Image Plugin Icon
|
||||
Then the image static link is rewritten to translate the path
|
||||
@@ -2,6 +2,7 @@
|
||||
#pylint: disable=C0111
|
||||
|
||||
from lettuce import world, step
|
||||
from nose.tools import assert_in # pylint: disable=no-name-in-module
|
||||
|
||||
|
||||
@step('I have created a Blank HTML Page$')
|
||||
@@ -28,3 +29,43 @@ def i_created_etext_in_latex(step):
|
||||
category='html',
|
||||
component_type='E-text Written in LaTeX'
|
||||
)
|
||||
|
||||
|
||||
@step('I edit the page and select the Visual Editor')
|
||||
def i_click_on_edit_icon(step):
|
||||
world.edit_component()
|
||||
world.wait_for(lambda _driver: world.css_visible('a.visual-tab'))
|
||||
world.css_click('a.visual-tab')
|
||||
|
||||
|
||||
@step('I add an image with a static link via the Image Plugin Icon')
|
||||
def i_click_on_image_plugin_icon(step):
|
||||
# Click on image plugin button
|
||||
world.wait_for(lambda _driver: world.css_visible('a.mce_image'))
|
||||
world.css_click('a.mce_image')
|
||||
|
||||
# Change to the non-modal TinyMCE Image window
|
||||
# keeping parent window so we can go back to it.
|
||||
parent_window = world.browser.current_window
|
||||
for window in world.browser.windows:
|
||||
|
||||
world.browser.switch_to_window(window) # Switch to a different window
|
||||
if world.browser.title == 'Insert/Edit Image':
|
||||
|
||||
# This is the Image window so find the url text box,
|
||||
# enter text in it then hit Insert button.
|
||||
url_elem = world.browser.find_by_id("src")
|
||||
url_elem.fill('/static/image.jpg')
|
||||
world.browser.find_by_id('insert').click()
|
||||
|
||||
world.browser.switch_to_window(parent_window) # Switch back to the main window
|
||||
|
||||
|
||||
@step('the image static link is rewritten to translate the path')
|
||||
def image_static_link_is_rewritten(step):
|
||||
# Find the TinyMCE iframe within the main window
|
||||
with world.browser.get_iframe('mce_0_ifr') as tinymce:
|
||||
image = tinymce.find_by_tag('img').first
|
||||
|
||||
# Test onExecCommandHandler set the url to absolute.
|
||||
assert_in('c4x/MITx/999/asset/image.jpg', image['src'])
|
||||
|
||||
@@ -173,7 +173,7 @@ def cancel_does_not_save_changes(step):
|
||||
def enable_latex_compiler(step):
|
||||
url = world.browser.url
|
||||
step.given("I select the Advanced Settings")
|
||||
change_value(step, 'use_latex_compiler', True)
|
||||
change_value(step, 'use_latex_compiler', 'true')
|
||||
world.visit(url)
|
||||
world.wait_for_xmodule()
|
||||
|
||||
|
||||
@@ -1003,7 +1003,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
# We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case.
|
||||
vertical.location = mongo.draft.as_draft(vertical.location.replace(name='no_references'))
|
||||
|
||||
draft_store.save_xmodule(vertical)
|
||||
draft_store.update_item(vertical, allow_not_found=True)
|
||||
orphan_vertical = draft_store.get_item(vertical.location)
|
||||
self.assertEqual(orphan_vertical.location.name, 'no_references')
|
||||
|
||||
@@ -1020,13 +1020,14 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
|
||||
# now create a new/different private (draft only) vertical
|
||||
vertical.location = mongo.draft.as_draft(Location(['i4x', 'edX', 'toy', 'vertical', 'a_private_vertical', None]))
|
||||
draft_store.save_xmodule(vertical)
|
||||
draft_store.update_item(vertical, allow_not_found=True)
|
||||
private_vertical = draft_store.get_item(vertical.location)
|
||||
vertical = None # blank out b/c i destructively manipulated its location 2 lines above
|
||||
|
||||
# add the new private to list of children
|
||||
sequential = module_store.get_item(Location(['i4x', 'edX', 'toy',
|
||||
'sequential', 'vertical_sequential', None]))
|
||||
sequential = module_store.get_item(
|
||||
Location('i4x', 'edX', 'toy', 'sequential', 'vertical_sequential', None)
|
||||
)
|
||||
private_location_no_draft = private_vertical.location.replace(revision=None)
|
||||
sequential.children.append(private_location_no_draft.url())
|
||||
module_store.update_item(sequential, self.user.id)
|
||||
|
||||
@@ -17,6 +17,7 @@ from .utils import CourseTestCase
|
||||
import contentstore.git_export_utils as git_export_utils
|
||||
from xmodule.contentstore.django import _CONTENTSTORE
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from contentstore.utils import get_modulestore
|
||||
|
||||
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
|
||||
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
|
||||
@@ -70,7 +71,7 @@ class TestExportGit(CourseTestCase):
|
||||
Test failed course export response.
|
||||
"""
|
||||
self.course_module.giturl = 'foobar'
|
||||
modulestore().save_xmodule(self.course_module)
|
||||
get_modulestore(self.course_module.location).update_item(self.course_module)
|
||||
|
||||
response = self.client.get('{}?action=push'.format(self.test_url))
|
||||
self.assertIn('Export Failed:', response.content)
|
||||
@@ -93,7 +94,7 @@ class TestExportGit(CourseTestCase):
|
||||
|
||||
self.populateCourse()
|
||||
self.course_module.giturl = 'file://{}'.format(bare_repo_dir)
|
||||
modulestore().save_xmodule(self.course_module)
|
||||
get_modulestore(self.course_module.location).update_item(self.course_module)
|
||||
|
||||
response = self.client.get('{}?action=push'.format(self.test_url))
|
||||
self.assertIn('Export Succeeded', response.content)
|
||||
|
||||
@@ -23,11 +23,10 @@ from xblock.exceptions import NoSuchHandlerError
|
||||
from xblock.fields import Scope
|
||||
from xblock.plugin import PluginMissingError
|
||||
from xblock.runtime import Mixologist
|
||||
from xmodule.x_module import prefer_xmodules
|
||||
|
||||
from lms.lib.xblock.runtime import unquote_slashes
|
||||
|
||||
from contentstore.utils import get_lms_link_for_item, compute_unit_state, UnitState
|
||||
from contentstore.utils import get_lms_link_for_item, compute_unit_state, UnitState, get_modulestore
|
||||
from contentstore.views.helpers import get_parent_xblock
|
||||
|
||||
from models.settings.course_grading import CourseGradingModel
|
||||
@@ -310,13 +309,20 @@ def container_handler(request, tag=None, package_id=None, branch=None, version_g
|
||||
old_location, course, xblock, __ = _get_item_in_course(request, locator)
|
||||
except ItemNotFoundError:
|
||||
return HttpResponseBadRequest()
|
||||
parent_xblock = get_parent_xblock(xblock)
|
||||
|
||||
ancestor_xblocks = []
|
||||
parent = get_parent_xblock(xblock)
|
||||
while parent and parent.category != 'sequential':
|
||||
ancestor_xblocks.append(parent)
|
||||
parent = get_parent_xblock(parent)
|
||||
|
||||
ancestor_xblocks.reverse()
|
||||
|
||||
return render_to_response('container.html', {
|
||||
'context_course': course,
|
||||
'xblock': xblock,
|
||||
'xblock_locator': locator,
|
||||
'parent_xblock': parent_xblock,
|
||||
'ancestor_xblocks': ancestor_xblocks,
|
||||
})
|
||||
else:
|
||||
return HttpResponseBadRequest("Only supports html requests")
|
||||
@@ -359,7 +365,7 @@ def component_handler(request, usage_id, handler, suffix=''):
|
||||
|
||||
location = unquote_slashes(usage_id)
|
||||
|
||||
descriptor = modulestore().get_item(location)
|
||||
descriptor = get_modulestore(location).get_item(location)
|
||||
# Let the module handle the AJAX
|
||||
req = django_to_webob_request(request)
|
||||
|
||||
@@ -370,6 +376,8 @@ def component_handler(request, usage_id, handler, suffix=''):
|
||||
log.info("XBlock %s attempted to access missing handler %r", descriptor, handler, exc_info=True)
|
||||
raise Http404
|
||||
|
||||
modulestore().save_xmodule(descriptor)
|
||||
# unintentional update to handle any side effects of handle call; so, request user didn't author
|
||||
# the change
|
||||
get_modulestore(location).update_item(descriptor, None)
|
||||
|
||||
return webob_to_django_response(resp)
|
||||
|
||||
@@ -127,6 +127,12 @@ def xblock_handler(request, tag=None, package_id=None, branch=None, version_guid
|
||||
|
||||
return _delete_item_at_location(old_location, delete_children, delete_all_versions, request.user)
|
||||
else: # Since we have a package_id, we are updating an existing xblock.
|
||||
if block == 'handouts' and old_location is None:
|
||||
# update handouts location in loc_mapper
|
||||
course_location = loc_mapper().translate_locator_to_location(locator, get_course=True)
|
||||
old_location = course_location.replace(category='course_info', name=block)
|
||||
locator = loc_mapper().translate_location(course_location.course_id, old_location)
|
||||
|
||||
return _save_item(
|
||||
request,
|
||||
locator,
|
||||
@@ -202,16 +208,16 @@ def xblock_view_handler(request, package_id, view_name, tag=None, branch=None, v
|
||||
log.debug("unable to render studio_view for %r", component, exc_info=True)
|
||||
fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)}))
|
||||
|
||||
store.save_xmodule(component)
|
||||
# change not authored by requestor but by xblocks.
|
||||
store.update_item(component, None)
|
||||
|
||||
elif view_name == 'student_view' and component.has_children:
|
||||
# For non-leaf xblocks on the unit page, show the special rendering
|
||||
# which links to the new container page.
|
||||
course_location = loc_mapper().translate_locator_to_location(locator, True)
|
||||
course = store.get_item(course_location)
|
||||
html = render_to_string('unit_container_xblock_component.html', {
|
||||
'course': course,
|
||||
html = render_to_string('container_xblock_component.html', {
|
||||
'xblock': component,
|
||||
'locator': locator
|
||||
'locator': locator,
|
||||
'reordering_enabled': True,
|
||||
})
|
||||
return JsonResponse({
|
||||
'html': html,
|
||||
@@ -521,8 +527,8 @@ def orphan_handler(request, tag=None, package_id=None, branch=None, version_guid
|
||||
if request.method == 'DELETE':
|
||||
if request.user.is_staff:
|
||||
items = modulestore().get_orphans(old_location, 'draft')
|
||||
for item in items:
|
||||
modulestore('draft').delete_item(item, delete_all_versions=True)
|
||||
for itemloc in items:
|
||||
modulestore('draft').delete_item(itemloc, delete_all_versions=True)
|
||||
return JsonResponse({'deleted': items})
|
||||
else:
|
||||
raise PermissionDenied()
|
||||
|
||||
@@ -179,6 +179,8 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
|
||||
}
|
||||
if xblock.category == 'vertical':
|
||||
template = 'studio_vertical_wrapper.html'
|
||||
elif xblock.location != context.get('root_xblock').location and xblock.has_children:
|
||||
template = 'container_xblock_component.html'
|
||||
else:
|
||||
template = 'studio_xblock_wrapper.html'
|
||||
html = render_to_string(template, template_context)
|
||||
|
||||
@@ -26,8 +26,44 @@ class ContainerViewTestCase(CourseTestCase):
|
||||
category="video", display_name="My Video")
|
||||
|
||||
def test_container_html(self):
|
||||
url = xblock_studio_url(self.child_vertical)
|
||||
self._test_html_content(
|
||||
self.child_vertical,
|
||||
expected_section_tag='<section class="wrapper-xblock level-page" data-locator="MITx.999.Robot_Super_Course/branch/published/block/Child_Vertical"/>',
|
||||
expected_breadcrumbs=(
|
||||
r'<a href="/unit/MITx.999.Robot_Super_Course/branch/published/block/Unit"\s*'
|
||||
r'class="navigation-link navigation-parent">Unit</a>\s*'
|
||||
r'<a href="#" class="navigation-link navigation-current">Child Vertical</a>'),
|
||||
)
|
||||
|
||||
def test_container_on_container_html(self):
|
||||
"""
|
||||
Create the scenario of an xblock with children (non-vertical) on the container page.
|
||||
This should create a container page that is a child of another container page.
|
||||
"""
|
||||
xblock_with_child = ItemFactory.create(parent_location=self.child_vertical.location,
|
||||
category="wrapper", display_name="Wrapper")
|
||||
ItemFactory.create(parent_location=xblock_with_child.location,
|
||||
category="html", display_name="Child HTML")
|
||||
self._test_html_content(
|
||||
xblock_with_child,
|
||||
expected_section_tag='<section class="wrapper-xblock level-page" data-locator="MITx.999.Robot_Super_Course/branch/published/block/Wrapper"/>',
|
||||
expected_breadcrumbs=(
|
||||
r'<a href="/unit/MITx.999.Robot_Super_Course/branch/published/block/Unit"\s*'
|
||||
r'class="navigation-link navigation-parent">Unit</a>\s*'
|
||||
r'<a href="/container/MITx.999.Robot_Super_Course/branch/published/block/Child_Vertical"\s*'
|
||||
r'class="navigation-link navigation-parent">Child Vertical</a>\s*'
|
||||
r'<a href="#" class="navigation-link navigation-current">Wrapper</a>'),
|
||||
)
|
||||
|
||||
def _test_html_content(self, xblock, expected_section_tag, expected_breadcrumbs):
|
||||
"""
|
||||
Get the HTML for a container page and verify the section tag is correct
|
||||
and the breadcrumbs trail is correct.
|
||||
"""
|
||||
url = xblock_studio_url(xblock, self.course)
|
||||
resp = self.client.get_html(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
html = resp.content
|
||||
self.assertIn('<section class="wrapper-xblock level-page" data-locator="MITx.999.Robot_Super_Course/branch/published/block/Child_Vertical"/>', html)
|
||||
self.assertIn(expected_section_tag, html)
|
||||
# Verify the navigation link at the top of the page is correct.
|
||||
self.assertRegexpMatches(html, expected_breadcrumbs)
|
||||
|
||||
@@ -230,7 +230,8 @@ class CourseUpdateTest(CourseTestCase):
|
||||
|
||||
def test_post_course_update(self):
|
||||
"""
|
||||
Test that a user can successfully post on course updates of a course whose location in not in loc_mapper
|
||||
Test that a user can successfully post on course updates and handouts of a course
|
||||
whose location in not in loc_mapper
|
||||
"""
|
||||
# create a course via the view handler
|
||||
course_location = Location(['i4x', 'Org_1', 'Course_1', 'course', 'Run_1'])
|
||||
@@ -270,3 +271,19 @@ class CourseUpdateTest(CourseTestCase):
|
||||
updates_locator = loc_mapper().translate_location(course_location.course_id, updates_location)
|
||||
self.assertTrue(isinstance(updates_locator, BlockUsageLocator))
|
||||
self.assertEqual(updates_locator.block_id, block)
|
||||
|
||||
# check posting on handouts
|
||||
block = u'handouts'
|
||||
handouts_locator = BlockUsageLocator(
|
||||
package_id=updates_locator.package_id, branch=updates_locator.branch, version_guid=version, block_id=block
|
||||
)
|
||||
course_handouts_url = handouts_locator.url_reverse('xblock')
|
||||
content = u"Sample handout"
|
||||
payload = {"data": content}
|
||||
resp = self.client.ajax_post(course_handouts_url, payload)
|
||||
|
||||
# check that response status is 200 not 500
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
payload = json.loads(resp.content)
|
||||
self.assertHTMLEqual(payload['data'], content)
|
||||
|
||||
@@ -132,6 +132,31 @@ class GetItem(ItemTest):
|
||||
# Verify that the Studio element wrapper has been added
|
||||
self.assertIn('level-element', html)
|
||||
|
||||
def test_get_container_nested_container_fragment(self):
|
||||
"""
|
||||
Test the case of the container page containing a link to another container page.
|
||||
"""
|
||||
# Add a wrapper with child beneath a child vertical
|
||||
root_locator = self._create_vertical()
|
||||
|
||||
resp = self.create_xblock(parent_locator=root_locator, category="wrapper")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
wrapper_locator = self.response_locator(resp)
|
||||
|
||||
resp = self.create_xblock(parent_locator=wrapper_locator, category='problem', boilerplate='multiplechoice.yaml')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# Get the preview HTML and verify the View -> link is present.
|
||||
html, __ = self._get_container_preview(root_locator)
|
||||
self.assertIn('wrapper-xblock', html)
|
||||
self.assertRegexpMatches(
|
||||
html,
|
||||
# The instance of the wrapper class will have an auto-generated ID (wrapperxxx). Allow anything
|
||||
# for the 3 characters after wrapper.
|
||||
(r'"/container/MITx.999.Robot_Super_Course/branch/published/block/wrapper.{3}" class="action-button">\s*'
|
||||
'<span class="action-button-text">View</span>')
|
||||
)
|
||||
|
||||
|
||||
class DeleteItem(ItemTest):
|
||||
"""Tests for '/xblock' DELETE url."""
|
||||
@@ -636,11 +661,11 @@ class TestComponentHandler(TestCase):
|
||||
def setUp(self):
|
||||
self.request_factory = RequestFactory()
|
||||
|
||||
patcher = patch('contentstore.views.component.modulestore')
|
||||
self.modulestore = patcher.start()
|
||||
patcher = patch('contentstore.views.component.get_modulestore')
|
||||
self.get_modulestore = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
self.descriptor = self.modulestore.return_value.get_item.return_value
|
||||
self.descriptor = self.get_modulestore.return_value.get_item.return_value
|
||||
|
||||
self.usage_id = 'dummy_usage_id'
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ class CourseDetails(object):
|
||||
result = None
|
||||
if video_key:
|
||||
result = '<iframe width="560" height="315" src="//www.youtube.com/embed/' + \
|
||||
video_key + '?autoplay=1&rel=0" frameborder="0" allowfullscreen=""></iframe>'
|
||||
video_key + '?rel=0" frameborder="0" allowfullscreen=""></iframe>'
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ os.environ['SERVICE_VARIANT'] = 'bok_choy'
|
||||
os.environ['CONFIG_ROOT'] = path(__file__).abspath().dirname() #pylint: disable=E1120
|
||||
|
||||
from .aws import * # pylint: disable=W0401, W0614
|
||||
from xmodule.x_module import prefer_xmodules
|
||||
from xmodule.modulestore import prefer_xmodules
|
||||
|
||||
|
||||
######################### Testing overrides ####################################
|
||||
|
||||
@@ -296,7 +296,7 @@ PIPELINE_CSS = {
|
||||
'css/vendor/normalize.css',
|
||||
'css/vendor/font-awesome.css',
|
||||
'css/vendor/html5-input-polyfills/number-polyfill.css',
|
||||
'js/vendor/CodeMirror/codemirror.css',
|
||||
'js/vendor/CodeMirror/codemirror-3.21.0.css',
|
||||
'css/vendor/ui-lightness/jquery-ui-1.8.22.custom.css',
|
||||
'css/vendor/jquery.qtip.min.css',
|
||||
'js/vendor/markitup/skins/simple/style.css',
|
||||
|
||||
@@ -16,6 +16,7 @@ from .common import *
|
||||
import os
|
||||
from path import path
|
||||
from warnings import filterwarnings
|
||||
from xmodule.modulestore import prefer_xmodules
|
||||
|
||||
# Nose Test Runner
|
||||
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
|
||||
@@ -158,7 +159,6 @@ filterwarnings('ignore', message='No request passed to the backend, unable to ra
|
||||
|
||||
|
||||
################################# XBLOCK ######################################
|
||||
from xmodule.x_module import prefer_xmodules
|
||||
XBLOCK_SELECT_FUNCTION = prefer_xmodules
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@ define [
|
||||
super()
|
||||
@savingNotification = new NotificationView.Mini
|
||||
title: gettext('Saving…')
|
||||
@alert = new NotificationView.Error
|
||||
title: "OpenAssessment Save Error",
|
||||
closeIcon: false,
|
||||
shown: false
|
||||
|
||||
handlerUrl: (element, handlerName, suffix, query, thirdparty) ->
|
||||
uri = URI("/xblock").segment($(element).data('usage-id'))
|
||||
@@ -41,11 +45,15 @@ define [
|
||||
|
||||
# Starting to save, so show the "Saving..." notification
|
||||
if data.state == 'start'
|
||||
@_hideEditor()
|
||||
@savingNotification.show()
|
||||
|
||||
# Finished saving, so hide the "Saving..." notification
|
||||
else if data.state == 'end'
|
||||
|
||||
# Hide the editor *after* we finish saving in case there are validation
|
||||
# errors that the user needs to correct.
|
||||
@_hideEditor()
|
||||
|
||||
$('.component.editing').removeClass('editing')
|
||||
@savingNotification.hide()
|
||||
|
||||
@@ -54,7 +62,8 @@ define [
|
||||
|
||||
else if name == 'error'
|
||||
if 'msg' of data
|
||||
@_showAlert(data.msg)
|
||||
@alert.options.message = data.msg
|
||||
@alert.show()
|
||||
|
||||
_hideEditor: () ->
|
||||
# This will close all open component editors, which works
|
||||
@@ -64,9 +73,6 @@ define [
|
||||
el.find('.component-editor').slideUp(150)
|
||||
ModalUtils.hideModalCover()
|
||||
|
||||
_showAlert: (msg) ->
|
||||
new NotificationView.Error({
|
||||
title: "OpenAssessment Save Error",
|
||||
message: msg,
|
||||
closeIcon: false
|
||||
}).show()
|
||||
# Hide any alerts that are being shown
|
||||
if @alert.options.shown
|
||||
@alert.hide()
|
||||
|
||||
@@ -7,9 +7,10 @@ define(["codemirror", 'js/utils/handle_iframe_binding', "utility"],
|
||||
mode: "text/html",
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
onChange: function () {
|
||||
autoCloseTags: true
|
||||
});
|
||||
$codeMirror.on('change', function () {
|
||||
$('.save-button').removeClass('is-disabled');
|
||||
}
|
||||
});
|
||||
$codeMirror.setValue(content);
|
||||
$codeMirror.clearHistory();
|
||||
|
||||
@@ -47,9 +47,11 @@ var AdvancedView = ValidatingView.extend({
|
||||
|
||||
var self = this;
|
||||
var oldValue = $(textarea).val();
|
||||
CodeMirror.fromTextArea(textarea, {
|
||||
mode: "application/json", lineNumbers: false, lineWrapping: false,
|
||||
onChange: function(instance, changeobj) {
|
||||
var cm = CodeMirror.fromTextArea(textarea, {
|
||||
mode: "application/json",
|
||||
lineNumbers: false,
|
||||
lineWrapping: false});
|
||||
cm.on('change', function(instance, changeobj) {
|
||||
instance.save();
|
||||
// this event's being called even when there's no change :-(
|
||||
if (instance.getValue() !== oldValue) {
|
||||
@@ -58,11 +60,11 @@ var AdvancedView = ValidatingView.extend({
|
||||
_.bind(self.saveView, self),
|
||||
_.bind(self.revertView, self));
|
||||
}
|
||||
},
|
||||
onFocus : function(mirror) {
|
||||
});
|
||||
cm.on('focus', function(mirror) {
|
||||
$(textarea).parent().children('label').addClass("is-focused");
|
||||
},
|
||||
onBlur: function (mirror) {
|
||||
});
|
||||
cm.on('blur', function (mirror) {
|
||||
$(textarea).parent().children('label').removeClass("is-focused");
|
||||
var key = $(mirror.getWrapperElement()).closest('.field-group').children('.key').attr('id');
|
||||
var stringValue = $.trim(mirror.getValue());
|
||||
@@ -91,8 +93,7 @@ var AdvancedView = ValidatingView.extend({
|
||||
if (JSONValue !== undefined) {
|
||||
self.model.set(key, JSONValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
saveView : function() {
|
||||
// TODO one last verification scan:
|
||||
|
||||
@@ -206,15 +206,14 @@ var DetailsView = ValidatingView.extend({
|
||||
var cachethis = this;
|
||||
var field = this.selectorToField[thisTarget.id];
|
||||
this.codeMirrors[thisTarget.id] = CodeMirror.fromTextArea(thisTarget, {
|
||||
mode: "text/html", lineNumbers: true, lineWrapping: true,
|
||||
onChange: function (mirror) {
|
||||
mode: "text/html", lineNumbers: true, lineWrapping: true});
|
||||
this.codeMirrors[thisTarget.id].on('change', function (mirror) {
|
||||
mirror.save();
|
||||
cachethis.clearValidationErrors();
|
||||
var newVal = mirror.getValue();
|
||||
if (cachethis.model.get(field) != newVal) {
|
||||
cachethis.setAndValidate(field, newVal);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -334,11 +334,12 @@ p, ul, ol, dl {
|
||||
.navigation-link {
|
||||
@extend %cont-truncated;
|
||||
display: inline-block;
|
||||
max-width: 150px;
|
||||
max-width: 250px;
|
||||
|
||||
&.navigation-current {
|
||||
@extend %ui-disabled;
|
||||
color: $gray;
|
||||
max-width: 250px;
|
||||
|
||||
&:before {
|
||||
color: $gray;
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
}
|
||||
|
||||
// UI: xblock is collapsible
|
||||
.wrapper-xblock.is-collapsible {
|
||||
.wrapper-xblock.is-collapsible, .wrapper-xblock.xblock-type-container {
|
||||
|
||||
[class^="icon-"] {
|
||||
font-style: normal;
|
||||
|
||||
@@ -831,19 +831,15 @@
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
color: $baseFontColor;
|
||||
outline: 0;
|
||||
height: auto;
|
||||
min-height: ($baseline*2.25);
|
||||
max-height: ($baseline*10);
|
||||
|
||||
&.CodeMirror-focused {
|
||||
@include linear-gradient($paleYellow, tint($paleYellow, 90%));
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
min-height: ($baseline*1.5);
|
||||
max-height: ($baseline*10);
|
||||
}
|
||||
|
||||
// editor color changes just for JSON
|
||||
.CodeMirror-lines {
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from django.utils.translation import ugettext as _
|
||||
<%
|
||||
xblock_info = {
|
||||
'id': str(xblock_locator),
|
||||
'display-name': xblock.display_name,
|
||||
'display-name': xblock.display_name_with_default,
|
||||
'category': xblock.category,
|
||||
};
|
||||
%>
|
||||
@@ -47,14 +47,16 @@ xblock_info = {
|
||||
<header class="mast has-actions has-navigation">
|
||||
<h1 class="page-header">
|
||||
<small class="navigation navigation-parents">
|
||||
<%
|
||||
parent_url = xblock_studio_url(parent_xblock, context_course)
|
||||
%>
|
||||
% if parent_url:
|
||||
<a href="${parent_url}"
|
||||
class="navigation-link navigation-parent">${parent_xblock.display_name | h}</a>
|
||||
% endif
|
||||
<a href="#" class="navigation-link navigation-current">${xblock.display_name | h}</a>
|
||||
% for ancestor in ancestor_xblocks:
|
||||
<%
|
||||
ancestor_url = xblock_studio_url(ancestor, context_course)
|
||||
%>
|
||||
% if ancestor_url:
|
||||
<a href="${ancestor_url}"
|
||||
class="navigation-link navigation-parent">${ancestor.display_name_with_default | h}</a>
|
||||
% endif
|
||||
% endfor
|
||||
<a href="#" class="navigation-link navigation-current">${xblock.display_name_with_default | h}</a>
|
||||
</small>
|
||||
</h1>
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ from contentstore.views.helpers import xblock_studio_url
|
||||
<section class="wrapper-xblock xblock-type-container level-element" data-locator="${locator}">
|
||||
<header class="xblock-header">
|
||||
<div class="header-details">
|
||||
${xblock.display_name}
|
||||
${xblock.display_name_with_default}
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<ul class="actions-list">
|
||||
<li class="action-item action-view">
|
||||
<a href="${xblock_studio_url(xblock, course)}" class="action-button">
|
||||
<a href="${xblock_studio_url(xblock)}" class="action-button">
|
||||
## Translators: this is a verb describing the action of viewing more details
|
||||
<span class="action-button-text">${_('View')}</span>
|
||||
<i class="icon-arrow-right"></i>
|
||||
@@ -21,5 +21,8 @@ from contentstore.views.helpers import xblock_studio_url
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
<span data-tooltip="${_("Drag to reorder")}" class="drag-handle"></span>
|
||||
## We currently support reordering only on the unit page.
|
||||
% if reordering_enabled:
|
||||
<span data-tooltip="${_("Drag to reorder")}" class="drag-handle"></span>
|
||||
% endif
|
||||
</section>
|
||||
@@ -8,7 +8,7 @@
|
||||
<i class="icon-caret-down ui-toggle-expansion"></i>
|
||||
<span class="sr">${_('Expand or Collapse')}</span>
|
||||
</a>
|
||||
<span>${xblock.display_name | h}</span>
|
||||
<span>${xblock.display_name_with_default | h}</span>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<ul class="actions-list">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
% endif
|
||||
<header class="xblock-header">
|
||||
<div class="header-details">
|
||||
${xblock.display_name | h}
|
||||
${xblock.display_name_with_default | h}
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<ul class="actions-list">
|
||||
|
||||
@@ -97,7 +97,7 @@ require(["jquery", "jquery.leanModal", "codemirror/stex"], function($) {
|
||||
});
|
||||
// resize the codemirror box
|
||||
var h = el.height();
|
||||
el.find('.CodeMirror-scroll').height(h - 100);
|
||||
el.find('.CodeMirror').height(h - 160);
|
||||
}
|
||||
|
||||
// compile & save button
|
||||
|
||||
Reference in New Issue
Block a user