Merge branch 'master' of https://github.com/edx/edx-platform into slater/search
This commit is contained in:
1
AUTHORS
1
AUTHORS
@@ -74,3 +74,4 @@ Jason Bau <jbau@stanford.edu>
|
||||
Frances Botsford <frances@edx.org>
|
||||
Jonah Stanley <Jonah_Stanley@brown.edu>
|
||||
Slater Victoroff <slater.r.victoroff@gmail.com>
|
||||
Peter Fogg <peter.p.fogg@gmail.com>
|
||||
|
||||
@@ -11,8 +11,6 @@ Feature: Advanced (manual) course policy
|
||||
Given I am on the Advanced Course Settings page in Studio
|
||||
Then the settings are alphabetized
|
||||
|
||||
# Skipped because Ubuntu ChromeDriver cannot click notification "Cancel"
|
||||
@skip
|
||||
Scenario: Test cancel editing key value
|
||||
Given I am on the Advanced Course Settings page in Studio
|
||||
When I edit the value of a policy key
|
||||
@@ -21,8 +19,6 @@ Feature: Advanced (manual) course policy
|
||||
And I reload the page
|
||||
Then the policy key value is unchanged
|
||||
|
||||
# Skipped because Ubuntu ChromeDriver cannot click notification "Save"
|
||||
@skip
|
||||
Scenario: Test editing key value
|
||||
Given I am on the Advanced Course Settings page in Studio
|
||||
When I edit the value of a policy key and save
|
||||
@@ -30,8 +26,6 @@ Feature: Advanced (manual) course policy
|
||||
And I reload the page
|
||||
Then the policy key value is changed
|
||||
|
||||
# Skipped because Ubuntu ChromeDriver cannot edit CodeMirror input
|
||||
@skip
|
||||
Scenario: Test how multi-line input appears
|
||||
Given I am on the Advanced Course Settings page in Studio
|
||||
When I create a JSON object as a value
|
||||
@@ -39,8 +33,6 @@ Feature: Advanced (manual) course policy
|
||||
And I reload the page
|
||||
Then it is displayed as formatted
|
||||
|
||||
# Skipped because Ubuntu ChromeDriver cannot edit CodeMirror input
|
||||
@skip
|
||||
Scenario: Test automatic quoting of non-JSON values
|
||||
Given I am on the Advanced Course Settings page in Studio
|
||||
When I create a non-JSON value not in quotes
|
||||
|
||||
@@ -42,8 +42,9 @@ def edit_the_value_of_a_policy_key(step):
|
||||
It is hard to figure out how to get into the CodeMirror
|
||||
area, so cheat and do it from the policy key field :)
|
||||
"""
|
||||
e = world.css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)]
|
||||
e._element.send_keys(Keys.TAB, Keys.END, Keys.ARROW_LEFT, ' ', 'X')
|
||||
world.css_find(".CodeMirror")[get_index_of(DISPLAY_NAME_KEY)].click()
|
||||
g = world.css_find("div.CodeMirror.CodeMirror-focused > div > textarea")
|
||||
g._element.send_keys(Keys.ARROW_LEFT, ' ', 'X')
|
||||
|
||||
|
||||
@step(u'I edit the value of a policy key and save$')
|
||||
@@ -123,10 +124,12 @@ def get_display_name_value():
|
||||
|
||||
|
||||
def change_display_name_value(step, new_value):
|
||||
e = world.css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)]
|
||||
|
||||
world.css_find(".CodeMirror")[get_index_of(DISPLAY_NAME_KEY)].click()
|
||||
g = world.css_find("div.CodeMirror.CodeMirror-focused > div > textarea")
|
||||
display_name = get_display_name_value()
|
||||
for count in range(len(display_name)):
|
||||
e._element.send_keys(Keys.TAB, Keys.END, Keys.BACK_SPACE)
|
||||
g._element.send_keys(Keys.END, Keys.BACK_SPACE)
|
||||
# Must delete "" before typing the JSON value
|
||||
e._element.send_keys(Keys.TAB, Keys.END, Keys.BACK_SPACE, Keys.BACK_SPACE, new_value)
|
||||
g._element.send_keys(Keys.END, Keys.BACK_SPACE, Keys.BACK_SPACE, new_value)
|
||||
press_the_notification_button(step, "Save")
|
||||
|
||||
@@ -161,3 +161,11 @@ def i_created_a_video_component(step):
|
||||
'i4x://edx/templates/video/default',
|
||||
'.xmodule_VideoModule'
|
||||
)
|
||||
|
||||
|
||||
@step('I have clicked the new unit button')
|
||||
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')
|
||||
world.css_click('a.new-unit-item')
|
||||
|
||||
@@ -14,20 +14,27 @@ def create_component_instance(step, component_button_css, instance_id, expected_
|
||||
|
||||
@world.absorb
|
||||
def click_new_component_button(step, component_button_css):
|
||||
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')
|
||||
world.css_click('a.new-unit-item')
|
||||
step.given('I have clicked the new unit button')
|
||||
world.css_click(component_button_css)
|
||||
|
||||
|
||||
@world.absorb
|
||||
def click_component_from_menu(instance_id, expected_css):
|
||||
"""
|
||||
Creates a component from `instance_id`. For components with more
|
||||
than one template, clicks on `elem_css` to create the new
|
||||
component. Components with only one template are created as soon
|
||||
as the user clicks the appropriate button, so we assert that the
|
||||
expected component is present.
|
||||
"""
|
||||
elem_css = "a[data-location='%s']" % instance_id
|
||||
assert_equal(1, len(world.css_find(elem_css)))
|
||||
world.css_click(elem_css)
|
||||
elements = world.css_find(elem_css)
|
||||
assert(len(elements) == 1)
|
||||
if elements[0]['id'] == instance_id: # If this is a component with multiple templates
|
||||
world.css_click(elem_css)
|
||||
assert_equal(1, len(world.css_find(expected_css)))
|
||||
|
||||
|
||||
@world.absorb
|
||||
def edit_component_and_select_settings():
|
||||
world.css_click('a.edit-button')
|
||||
|
||||
@@ -11,3 +11,7 @@ Feature: Discussion Component Editor
|
||||
And I edit and select Settings
|
||||
Then I can modify the display name
|
||||
And my display name change is persisted on save
|
||||
|
||||
Scenario: Creating a discussion takes a single click
|
||||
Given I have clicked the new unit button
|
||||
Then creating a discussion takes a single click
|
||||
|
||||
@@ -21,3 +21,10 @@ def i_see_only_the_settings_and_values(step):
|
||||
['Display Name', "Discussion Tag", True],
|
||||
['Subcategory', "Topic-Level Student-Visible Label", True]
|
||||
])
|
||||
|
||||
|
||||
@step('creating a discussion takes a single click')
|
||||
def discussion_takes_a_single_click(step):
|
||||
assert(not world.is_css_present('.xmodule_DiscussionModule'))
|
||||
world.css_click("a[data-location='i4x://edx/templates/discussion/Discussion_Tag']")
|
||||
assert(world.is_css_present('.xmodule_DiscussionModule'))
|
||||
|
||||
@@ -4,3 +4,7 @@ Feature: Video Component
|
||||
Scenario: Autoplay is disabled in Studio
|
||||
Given I have created a Video component
|
||||
Then when I view the video it does not have autoplay enabled
|
||||
|
||||
Scenario: Creating a video takes a single click
|
||||
Given I have clicked the new unit button
|
||||
Then creating a video takes a single click
|
||||
|
||||
@@ -9,3 +9,10 @@ from lettuce import world, step
|
||||
def does_not_autoplay(step):
|
||||
assert world.css_find('.video')[0]['data-autoplay'] == 'False'
|
||||
assert world.css_find('.video_control')[0].has_class('play')
|
||||
|
||||
|
||||
@step('creating a video takes a single click')
|
||||
def video_takes_a_single_click(step):
|
||||
assert(not world.is_css_present('.xmodule_VideoModule'))
|
||||
world.css_click("a[data-location='i4x://edx/templates/video/default']")
|
||||
assert(world.is_css_present('.xmodule_VideoModule'))
|
||||
|
||||
@@ -77,14 +77,25 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
self.client = Client()
|
||||
self.client.login(username=uname, password=password)
|
||||
|
||||
def test_advanced_components_in_edit_unit(self):
|
||||
def check_components_on_page(self, component_types, expected_types):
|
||||
"""
|
||||
Ensure that the right types end up on the page.
|
||||
|
||||
component_types is the list of advanced components.
|
||||
|
||||
expected_types is the list of elements that should appear on the page.
|
||||
|
||||
expected_types and component_types should be similar, but not
|
||||
exactly the same -- for example, 'videoalpha' in
|
||||
component_types should cause 'Video Alpha' to be present.
|
||||
"""
|
||||
store = modulestore('direct')
|
||||
import_from_xml(store, 'common/test/data/', ['simple'])
|
||||
|
||||
course = store.get_item(Location(['i4x', 'edX', 'simple',
|
||||
'course', '2012_Fall', None]), depth=None)
|
||||
|
||||
course.advanced_modules = ADVANCED_COMPONENT_TYPES
|
||||
course.advanced_modules = component_types
|
||||
|
||||
store.update_metadata(course.location, own_metadata(course))
|
||||
|
||||
@@ -94,13 +105,20 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()}))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
for expected in expected_types:
|
||||
self.assertIn(expected, resp.content)
|
||||
|
||||
def test_advanced_components_in_edit_unit(self):
|
||||
# This could be made better, but for now let's just assert that we see the advanced modules mentioned in the page
|
||||
# response HTML
|
||||
self.assertIn('Video Alpha', resp.content)
|
||||
self.assertIn('Word cloud', resp.content)
|
||||
self.assertIn('Annotation', resp.content)
|
||||
self.assertIn('Open Ended Response', resp.content)
|
||||
self.assertIn('Peer Grading Interface', resp.content)
|
||||
self.check_components_on_page(ADVANCED_COMPONENT_TYPES, ['Video Alpha',
|
||||
'Word cloud',
|
||||
'Annotation',
|
||||
'Open Ended Response',
|
||||
'Peer Grading Interface'])
|
||||
|
||||
def test_advanced_components_require_two_clicks(self):
|
||||
self.check_components_on_page(['videoalpha'], ['Video Alpha'])
|
||||
|
||||
def check_edit_unit(self, test_course_name):
|
||||
import_from_xml(modulestore('direct'), 'common/test/data/', [test_course_name])
|
||||
|
||||
@@ -4,6 +4,7 @@ import mock
|
||||
import collections
|
||||
import copy
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
@@ -11,11 +12,28 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
class LMSLinksTestCase(TestCase):
|
||||
""" Tests for LMS links. """
|
||||
def about_page_test(self):
|
||||
""" Get URL for about page. """
|
||||
""" Get URL for about page, no marketing site """
|
||||
# default for ENABLE_MKTG_SITE is False.
|
||||
self.assertEquals(self.get_about_page_link(), "//localhost:8000/courses/mitX/101/test/about")
|
||||
|
||||
@override_settings(MKTG_URLS={'ROOT': 'dummy-root'})
|
||||
def about_page_marketing_site_test(self):
|
||||
""" Get URL for about page, marketing root present. """
|
||||
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': True}):
|
||||
self.assertEquals(self.get_about_page_link(), "//dummy-root/courses/mitX/101/test/about")
|
||||
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': False}):
|
||||
self.assertEquals(self.get_about_page_link(), "//localhost:8000/courses/mitX/101/test/about")
|
||||
|
||||
@override_settings(LMS_BASE=None)
|
||||
def about_page_no_lms_base_test(self):
|
||||
""" No LMS_BASE, nor is ENABLE_MKTG_SITE True """
|
||||
self.assertEquals(self.get_about_page_link(), None)
|
||||
|
||||
def get_about_page_link(self):
|
||||
""" create mock course and return the about page link """
|
||||
location = 'i4x', 'mitX', '101', 'course', 'test'
|
||||
utils.get_course_id = mock.Mock(return_value="mitX/101/test")
|
||||
link = utils.get_lms_link_for_about_page(location)
|
||||
self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/about")
|
||||
return utils.get_lms_link_for_about_page(location)
|
||||
|
||||
def lms_link_test(self):
|
||||
""" Tests get_lms_link_for_item. """
|
||||
|
||||
@@ -107,9 +107,18 @@ def get_lms_link_for_about_page(location):
|
||||
"""
|
||||
Returns the url to the course about page from the location tuple.
|
||||
"""
|
||||
if settings.LMS_BASE is not None:
|
||||
lms_link = "//{lms_base}/courses/{course_id}/about".format(
|
||||
lms_base=settings.LMS_BASE,
|
||||
if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False):
|
||||
# Root will be "www.edx.org". The complete URL will still not be exactly correct,
|
||||
# but redirects exist from www.edx.org to get to the drupal course about page URL.
|
||||
about_base = settings.MKTG_URLS.get('ROOT')
|
||||
elif settings.LMS_BASE is not None:
|
||||
about_base = settings.LMS_BASE
|
||||
else:
|
||||
about_base = None
|
||||
|
||||
if about_base is not None:
|
||||
lms_link = "//{about_base_url}/courses/{course_id}/about".format(
|
||||
about_base_url=about_base,
|
||||
course_id=get_course_id(location)
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -91,6 +91,12 @@ CACHES = ENV_TOKENS['CACHES']
|
||||
|
||||
SESSION_COOKIE_DOMAIN = ENV_TOKENS.get('SESSION_COOKIE_DOMAIN')
|
||||
|
||||
# allow for environments to specify what cookie name our login subsystem should use
|
||||
# this is to fix a bug regarding simultaneous logins between edx.org and edge.edx.org which can
|
||||
# happen with some browsers (e.g. Firefox)
|
||||
if ENV_TOKENS.get('SESSION_COOKIE_NAME', None):
|
||||
SESSION_COOKIE_NAME = ENV_TOKENS.get('SESSION_COOKIE_NAME')
|
||||
|
||||
#Email overrides
|
||||
DEFAULT_FROM_EMAIL = ENV_TOKENS.get('DEFAULT_FROM_EMAIL', DEFAULT_FROM_EMAIL)
|
||||
DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS.get('DEFAULT_FEEDBACK_EMAIL', DEFAULT_FEEDBACK_EMAIL)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class CMS.Views.UnitEdit extends Backbone.View
|
||||
events:
|
||||
'click .new-component .new-component-type a': 'showComponentTemplates'
|
||||
'click .new-component .new-component-type a.multiple-templates': 'showComponentTemplates'
|
||||
'click .new-component .new-component-type a.single-template': 'saveNewComponent'
|
||||
'click .new-component .cancel-button': 'closeNewComponent'
|
||||
'click .new-component-templates .new-component-template a': 'saveNewComponent'
|
||||
'click .new-component-templates .cancel-button': 'closeNewComponent'
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 633 B |
@@ -53,9 +53,15 @@
|
||||
<div class="new-component">
|
||||
<h5>Add New Component</h5>
|
||||
<ul class="new-component-type">
|
||||
% for type in sorted(component_templates.keys()):
|
||||
% for type, templates in sorted(component_templates.items()):
|
||||
<li>
|
||||
<a href="#" data-type="${type}">
|
||||
% if type == 'advanced' or len(templates) > 1:
|
||||
<a href="#" class="multiple-templates" data-type="${type}">
|
||||
% else:
|
||||
% for _, location, _ in templates:
|
||||
<a href="#" class="single-template" data-type="${type}" data-location="${location}">
|
||||
% endfor
|
||||
% endif
|
||||
<span class="large-template-icon large-${type}-icon"></span>
|
||||
<span class="name">${type}</span>
|
||||
</a>
|
||||
@@ -64,50 +70,52 @@
|
||||
</ul>
|
||||
</div>
|
||||
% for type, templates in sorted(component_templates.items()):
|
||||
% if len(templates) > 1 or type == 'advanced':
|
||||
<div class="new-component-templates new-component-${type}">
|
||||
% if type == "problem":
|
||||
<div class="tab-group tabs">
|
||||
<ul class="problem-type-tabs nav-tabs">
|
||||
<li class="current">
|
||||
<a class="link-tab" href="#tab1">Common Problem Types</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link-tab" href="#tab2">Advanced</a>
|
||||
</li>
|
||||
</ul>
|
||||
% endif
|
||||
<div class="tab current" id="tab1">
|
||||
<ul class="new-component-template">
|
||||
% for name, location, has_markdown in templates:
|
||||
% if has_markdown or type != "problem":
|
||||
<li class="editor-md">
|
||||
<a href="#" id="${location}" data-location="${location}">
|
||||
<span class="name"> ${name}</span>
|
||||
</a>
|
||||
</li>
|
||||
% endif
|
||||
|
||||
%endfor
|
||||
</ul>
|
||||
</div>
|
||||
% if type == "problem":
|
||||
<div class="tab" id="tab2">
|
||||
<ul class="new-component-template">
|
||||
% for name, location, has_markdown in templates:
|
||||
% if not has_markdown:
|
||||
<li class="editor-manual">
|
||||
<a href="#" id="${location}" data-location="${location}">
|
||||
<span class="name"> ${name}</span>
|
||||
</a>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
<div class="tab-group tabs">
|
||||
<ul class="problem-type-tabs nav-tabs">
|
||||
<li class="current">
|
||||
<a class="link-tab" href="#tab1">Common Problem Types</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link-tab" href="#tab2">Advanced</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<a href="#" class="cancel-button">Cancel</a>
|
||||
</div>
|
||||
% endif
|
||||
<div class="tab current" id="tab1">
|
||||
<ul class="new-component-template">
|
||||
% for name, location, has_markdown in templates:
|
||||
% if has_markdown or type != "problem":
|
||||
<li class="editor-md">
|
||||
<a href="#" id="${location}" data-location="${location}">
|
||||
<span class="name"> ${name}</span>
|
||||
</a>
|
||||
</li>
|
||||
% endif
|
||||
|
||||
%endfor
|
||||
</ul>
|
||||
</div>
|
||||
% if type == "problem":
|
||||
<div class="tab" id="tab2">
|
||||
<ul class="new-component-template">
|
||||
% for name, location, has_markdown in templates:
|
||||
% if not has_markdown:
|
||||
<li class="editor-manual">
|
||||
<a href="#" id="${location}" data-location="${location}">
|
||||
<span class="name"> ${name}</span>
|
||||
</a>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<a href="#" class="cancel-button">Cancel</a>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
@@ -44,7 +44,11 @@ class MakoLoader(object):
|
||||
|
||||
if source.startswith("## mako\n"):
|
||||
# This is a mako template
|
||||
template = Template(filename=file_path, module_directory=self.module_directory, uri=template_name)
|
||||
template = Template(filename=file_path,
|
||||
module_directory=self.module_directory,
|
||||
input_encoding='utf-8',
|
||||
output_encoding='utf-8',
|
||||
uri=template_name)
|
||||
return template, None
|
||||
else:
|
||||
# This is a regular template
|
||||
|
||||
@@ -14,6 +14,7 @@ from django.core.management.base import NoArgsCommand
|
||||
from django.conf import settings
|
||||
|
||||
from mako.template import Template
|
||||
import textwrap
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
"""
|
||||
@@ -61,5 +62,12 @@ class Command(NoArgsCommand):
|
||||
result in `outfile`.
|
||||
"""
|
||||
with open(outfile, "w") as _outfile:
|
||||
_outfile.write(textwrap.dedent("""\
|
||||
/*
|
||||
* This file is dynamically generated and ignored by Git.
|
||||
* DO NOT MAKE CHANGES HERE. Instead, go edit its template:
|
||||
* %s
|
||||
*/
|
||||
""" % infile))
|
||||
_outfile.write(Template(filename=str(infile)).render(env=self.__context()))
|
||||
|
||||
|
||||
@@ -87,8 +87,8 @@ def reset_data(scenario):
|
||||
LOGGER.debug("Flushing the test database...")
|
||||
call_command('flush', interactive=False)
|
||||
|
||||
|
||||
@after.each_scenario
|
||||
# Uncomment below to trigger a screenshot on error
|
||||
# @after.each_scenario
|
||||
def screenshot_on_error(scenario):
|
||||
"""
|
||||
Save a screenshot to help with debugging.
|
||||
|
||||
@@ -121,7 +121,7 @@ class ErrorDescriptor(ErrorFields, JSONEditingDescriptor):
|
||||
def from_descriptor(cls, descriptor, error_msg='Error not available'):
|
||||
return cls._construct(
|
||||
descriptor.system,
|
||||
descriptor._model_data,
|
||||
str(descriptor),
|
||||
error_msg,
|
||||
location=descriptor.location,
|
||||
)
|
||||
|
||||
@@ -32,6 +32,8 @@ class HtmlModule(HtmlFields, XModule):
|
||||
css = {'scss': [resource_string(__name__, 'css/html/display.scss')]}
|
||||
|
||||
def get_html(self):
|
||||
if self.system.anonymous_student_id:
|
||||
return self.data.replace("%%USER_ID%%", self.system.anonymous_student_id)
|
||||
return self.data
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ Tests for ErrorModule and NonStaffErrorModule
|
||||
import unittest
|
||||
from xmodule.tests import test_system
|
||||
import xmodule.error_module as error_module
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.x_module import XModuleDescriptor
|
||||
from mock import MagicMock
|
||||
|
||||
|
||||
class TestErrorModule(unittest.TestCase):
|
||||
@@ -14,22 +17,33 @@ class TestErrorModule(unittest.TestCase):
|
||||
self.system = test_system()
|
||||
self.org = "org"
|
||||
self.course = "course"
|
||||
self.fake_xml = "<problem />"
|
||||
self.location = Location(['i4x', self.org, self.course, None, None])
|
||||
self.valid_xml = "<problem />"
|
||||
self.broken_xml = "<problem>"
|
||||
self.error_msg = "Error"
|
||||
|
||||
def test_error_module_create(self):
|
||||
def test_error_module_xml_rendering(self):
|
||||
descriptor = error_module.ErrorDescriptor.from_xml(
|
||||
self.fake_xml, self.system, self.org, self.course)
|
||||
self.valid_xml, self.system, self.org, self.course, self.error_msg)
|
||||
self.assertTrue(isinstance(descriptor, error_module.ErrorDescriptor))
|
||||
|
||||
def test_error_module_rendering(self):
|
||||
descriptor = error_module.ErrorDescriptor.from_xml(
|
||||
self.fake_xml, self.system, self.org, self.course, self.error_msg)
|
||||
module = descriptor.xmodule(self.system)
|
||||
rendered_html = module.get_html()
|
||||
self.assertIn(self.error_msg, rendered_html)
|
||||
self.assertIn(self.fake_xml, rendered_html)
|
||||
self.assertIn(self.valid_xml, rendered_html)
|
||||
|
||||
def test_error_module_from_descriptor(self):
|
||||
descriptor = MagicMock([XModuleDescriptor],
|
||||
system=self.system,
|
||||
location=self.location,
|
||||
_model_data=self.valid_xml)
|
||||
|
||||
error_descriptor = error_module.ErrorDescriptor.from_descriptor(
|
||||
descriptor, self.error_msg)
|
||||
self.assertTrue(isinstance(error_descriptor, error_module.ErrorDescriptor))
|
||||
module = error_descriptor.xmodule(self.system)
|
||||
rendered_html = module.get_html()
|
||||
self.assertIn(self.error_msg, rendered_html)
|
||||
self.assertIn(str(descriptor), rendered_html)
|
||||
|
||||
|
||||
class TestNonStaffErrorModule(TestErrorModule):
|
||||
@@ -39,13 +53,27 @@ class TestNonStaffErrorModule(TestErrorModule):
|
||||
|
||||
def test_non_staff_error_module_create(self):
|
||||
descriptor = error_module.NonStaffErrorDescriptor.from_xml(
|
||||
self.fake_xml, self.system, self.org, self.course)
|
||||
self.valid_xml, self.system, self.org, self.course)
|
||||
self.assertTrue(isinstance(descriptor, error_module.NonStaffErrorDescriptor))
|
||||
|
||||
def test_non_staff_error_module_rendering(self):
|
||||
def test_from_xml_render(self):
|
||||
descriptor = error_module.NonStaffErrorDescriptor.from_xml(
|
||||
self.fake_xml, self.system, self.org, self.course)
|
||||
self.valid_xml, self.system, self.org, self.course)
|
||||
module = descriptor.xmodule(self.system)
|
||||
rendered_html = module.get_html()
|
||||
self.assertNotIn(self.error_msg, rendered_html)
|
||||
self.assertNotIn(self.fake_xml, rendered_html)
|
||||
self.assertNotIn(self.valid_xml, rendered_html)
|
||||
|
||||
def test_error_module_from_descriptor(self):
|
||||
descriptor = MagicMock([XModuleDescriptor],
|
||||
system=self.system,
|
||||
location=self.location,
|
||||
_model_data=self.valid_xml)
|
||||
|
||||
error_descriptor = error_module.NonStaffErrorDescriptor.from_descriptor(
|
||||
descriptor, self.error_msg)
|
||||
self.assertTrue(isinstance(error_descriptor, error_module.ErrorDescriptor))
|
||||
module = error_descriptor.xmodule(self.system)
|
||||
rendered_html = module.get_html()
|
||||
self.assertNotIn(self.error_msg, rendered_html)
|
||||
self.assertNotIn(str(descriptor), rendered_html)
|
||||
|
||||
40
common/lib/xmodule/xmodule/tests/test_html_module.py
Normal file
40
common/lib/xmodule/xmodule/tests/test_html_module.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import unittest
|
||||
|
||||
from mock import Mock
|
||||
|
||||
from xmodule.html_module import HtmlModule
|
||||
from xmodule.modulestore import Location
|
||||
|
||||
from . import test_system
|
||||
|
||||
class HtmlModuleSubstitutionTestCase(unittest.TestCase):
|
||||
location = Location(["i4x", "edX", "toy", "html", "simple_html"])
|
||||
descriptor = Mock()
|
||||
|
||||
def test_substitution_works(self):
|
||||
sample_xml = '''%%USER_ID%%'''
|
||||
module_data = {'data': sample_xml}
|
||||
module_system = test_system()
|
||||
module = HtmlModule(module_system, self.location, self.descriptor, module_data)
|
||||
self.assertEqual(module.get_html(), str(module_system.anonymous_student_id))
|
||||
|
||||
|
||||
def test_substitution_without_magic_string(self):
|
||||
sample_xml = '''
|
||||
<html>
|
||||
<p>Hi USER_ID!11!</p>
|
||||
</html>
|
||||
'''
|
||||
module_data = {'data': sample_xml}
|
||||
module = HtmlModule(test_system(), self.location, self.descriptor, module_data)
|
||||
self.assertEqual(module.get_html(), sample_xml)
|
||||
|
||||
|
||||
def test_substitution_without_anonymous_student_id(self):
|
||||
sample_xml = '''%%USER_ID%%'''
|
||||
module_data = {'data': sample_xml}
|
||||
module_system = test_system()
|
||||
module_system.anonymous_student_id = None
|
||||
module = HtmlModule(module_system, self.location, self.descriptor, module_data)
|
||||
self.assertEqual(module.get_html(), sample_xml)
|
||||
|
||||
@@ -335,9 +335,8 @@ def get_module_for_descriptor(user, request, descriptor, model_data_cache, cours
|
||||
else:
|
||||
err_descriptor_class = NonStaffErrorDescriptor
|
||||
|
||||
err_descriptor = err_descriptor_class.from_xml(
|
||||
str(descriptor), descriptor.system,
|
||||
org=descriptor.location.org, course=descriptor.location.course,
|
||||
err_descriptor = err_descriptor_class.from_descriptor(
|
||||
descriptor,
|
||||
error_msg=exc_info_to_str(sys.exc_info())
|
||||
)
|
||||
|
||||
|
||||
@@ -102,6 +102,12 @@ with open(ENV_ROOT / CONFIG_PREFIX + "env.json") as env_file:
|
||||
SITE_NAME = ENV_TOKENS['SITE_NAME']
|
||||
SESSION_COOKIE_DOMAIN = ENV_TOKENS.get('SESSION_COOKIE_DOMAIN')
|
||||
|
||||
# allow for environments to specify what cookie name our login subsystem should use
|
||||
# this is to fix a bug regarding simultaneous logins between edx.org and edge.edx.org which can
|
||||
# happen with some browsers (e.g. Firefox)
|
||||
if ENV_TOKENS.get('SESSION_COOKIE_NAME', None):
|
||||
SESSION_COOKIE_NAME = ENV_TOKENS.get('SESSION_COOKIE_NAME')
|
||||
|
||||
BOOK_URL = ENV_TOKENS['BOOK_URL']
|
||||
MEDIA_URL = ENV_TOKENS['MEDIA_URL']
|
||||
LOG_DIR = ENV_TOKENS['LOG_DIR']
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
@@ -46,8 +46,9 @@ $m-gray: rgb(153,153,153);
|
||||
$m-gray-d1: rgb(102,102,102);
|
||||
$m-gray-d2: rgb(51,51,51);
|
||||
$m-gray-a1: rgb(80,80,80);
|
||||
$m-blue: rgb(85, 151, 221);
|
||||
$m-blue-l1: rgb(230,245,252);
|
||||
$m-blue: rgb(65, 116, 170);
|
||||
// $m-blue: rgb(85, 151, 221); (used in marketing redesign)
|
||||
$m-blue-l1: rgb(85, 151, 221);
|
||||
$m-blue-d1: shade($m-blue,15%);
|
||||
$m-blue-s1: saturate($m-blue,15%);
|
||||
$m-pink: rgb(204,51,102);
|
||||
@@ -122,4 +123,4 @@ $modal-bg-color: rgb(245,245,245);
|
||||
//-----------------
|
||||
$homepage-bg-image: '../images/homepage-bg.jpg';
|
||||
|
||||
$video-thumb-url: '../images/courses/video-thumb.jpg';
|
||||
$video-thumb-url: '../images/courses/video-thumb.jpg';
|
||||
|
||||
@@ -62,7 +62,8 @@ nav.course-material {
|
||||
|
||||
header.global.slim {
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1));
|
||||
height: 50px;
|
||||
height: auto;
|
||||
padding: 5px 0 10px 0;
|
||||
border-bottom: 1px solid $outer-border-color;
|
||||
background: $white;
|
||||
|
||||
@@ -106,16 +107,15 @@ header.global.slim {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
h1.logo {
|
||||
margin-left: 13px;
|
||||
margin-right: 20px;
|
||||
h1.logo {
|
||||
margin: 0 10px 0 13px;
|
||||
padding-right: 20px;
|
||||
|
||||
&:before {
|
||||
@extend .faded-vertical-divider;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 40px;
|
||||
height: 35px;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 0;
|
||||
@@ -126,12 +126,16 @@ header.global.slim {
|
||||
@extend .faded-vertical-divider-light;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 40px;
|
||||
height: 35px;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-global {
|
||||
@@ -147,6 +151,7 @@ header.global.slim {
|
||||
color: #777;
|
||||
letter-spacing: 0;
|
||||
margin-top: 9px;
|
||||
margin-bottom: 0;
|
||||
text-transform: none;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
white-space: nowrap;
|
||||
@@ -168,4 +173,4 @@ header.global.slim {
|
||||
font-weight: bold;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,8 +148,8 @@
|
||||
}
|
||||
|
||||
&:hover, &:active {
|
||||
border-bottom: 1px dotted $m-blue-s1;
|
||||
color: $m-blue-s1;
|
||||
border-bottom: 1px dotted $m-blue-l1;
|
||||
color: $m-blue-l1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,11 +339,11 @@
|
||||
&.is-focused {
|
||||
|
||||
label {
|
||||
color: $m-blue-s1;
|
||||
color: $m-blue-l1;
|
||||
}
|
||||
|
||||
.tip {
|
||||
color: $m-blue-s1;
|
||||
color: $m-blue-l1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -317,13 +317,13 @@ header.global {
|
||||
}
|
||||
|
||||
// page-based nav states
|
||||
.view-howitworks .nav-global-01,
|
||||
.view-courses .nav-global-02,
|
||||
.view-schools .nav-global-03,
|
||||
.view-howitworks .nav-global-01,
|
||||
.view-courses .nav-global-02,
|
||||
.view-schools .nav-global-03,
|
||||
.view-register .nav-global-04 {
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $m-blue-s1 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<%inherit file="main.html" />
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
<%block name="title"><title>Log into your edX Account</title></%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
@@ -77,14 +80,14 @@
|
||||
|
||||
<section class="introduction">
|
||||
<header>
|
||||
<h1 class="sr">Log Into Your Account</h1>
|
||||
<h1 class="sr">${_("Log Into Your Account")}</h1>
|
||||
</header>
|
||||
</section>
|
||||
|
||||
<section class="login container">
|
||||
<section role="main" class="content">
|
||||
<header>
|
||||
<h2 class="sr">Login Form</h2>
|
||||
<h2 class="sr">${_("Login Form")}</h2>
|
||||
</header>
|
||||
|
||||
<form role="form" id="login-form" method="post" data-remote="true" action="/login_ajax">
|
||||
|
||||
@@ -39,7 +39,7 @@ site_status_msg = get_site_status_msg(course_id)
|
||||
|
||||
<h1 class="logo">
|
||||
<a href="${marketing_link('ROOT')}">
|
||||
<img src="${static.url(branding.get_logo_url(request.META.get('HTTP_HOST')))}"/>
|
||||
<img src="${static.url(branding.get_logo_url(request.META.get('HTTP_HOST')))}" alt="edX home" />
|
||||
</a></h1>
|
||||
|
||||
% if course:
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%namespace file='main.html' import="login_query"/>
|
||||
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%! from django.utils import html %>
|
||||
<%! from django_countries.countries import COUNTRIES %>
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from student.models import UserProfile %>
|
||||
<%! from datetime import date %>
|
||||
<%! import calendar %>
|
||||
@@ -81,14 +83,14 @@
|
||||
|
||||
<section class="introduction">
|
||||
<header>
|
||||
<h1 class="sr">Register for edX</h1>
|
||||
<h1 class="sr">${_("Register for edX")}</h1>
|
||||
</header>
|
||||
</section>
|
||||
|
||||
<section class="register container">
|
||||
<section role="main" class="content">
|
||||
<header>
|
||||
<h2 class="sr">Registration Form</h2>
|
||||
<h2 class="sr">${_("Welcome! Register below to create your edX account")}</h2>
|
||||
</header>
|
||||
|
||||
<form role="form" id="register-form" method="post" data-remote="true" action="/create_account">
|
||||
@@ -262,10 +264,10 @@
|
||||
|
||||
<div class="cta cta-help">
|
||||
<h3>Need Help?</h3>
|
||||
<p>Need help in registering with edX?
|
||||
<p>Need help in registering with edX?
|
||||
<a href="${marketing_link('FAQ')}">
|
||||
View our FAQs for answers to commonly asked questions.
|
||||
</a>
|
||||
</a>
|
||||
Once registered, most questions can be answered in the course specific discussion forums or through the FAQs.</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -27,10 +27,11 @@ def coffee_cmd(watch=false, debug=false)
|
||||
#
|
||||
# Ref: https://github.com/joyent/node/issues/2479
|
||||
#
|
||||
# Instead, watch 50 files per process in parallel
|
||||
# Rather than watching all of the directories in one command
|
||||
# watch each static files subdirectory separately
|
||||
cmds = []
|
||||
Dir['*/static/**/*.coffee'].each_slice(50) do |coffee_files|
|
||||
cmds << "node_modules/.bin/coffee --watch --compile #{coffee_files.join(' ')}"
|
||||
['lms/static/coffee', 'cms/static/coffee', 'common/static/coffee', 'common/static/xmodule'].each do |coffee_folder|
|
||||
cmds << "node_modules/.bin/coffee --watch --compile #{coffee_folder}"
|
||||
end
|
||||
cmds
|
||||
else
|
||||
@@ -119,12 +120,15 @@ namespace :assets do
|
||||
namespace :sass do
|
||||
# In watch mode, sass doesn't immediately compile out of date files,
|
||||
# so force a recompile first
|
||||
task :_watch => 'assets:sass:debug'
|
||||
# Also force xmodule files to be generated before we start watching anything
|
||||
task :_watch => ['assets:sass:debug', 'assets:xmodule']
|
||||
multitask :debug => 'assets:xmodule:debug'
|
||||
end
|
||||
|
||||
multitask :coffee => 'assets:xmodule'
|
||||
namespace :coffee do
|
||||
# Force xmodule files to be generated before we start watching anything
|
||||
task :_watch => 'assets:xmodule'
|
||||
multitask :debug => 'assets:xmodule:debug'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
numpy==1.6.2
|
||||
networkx==1.7
|
||||
sympy==0.7.1
|
||||
Reference in New Issue
Block a user