diff --git a/AUTHORS b/AUTHORS index 091e054a45..154b0c9b98 100644 --- a/AUTHORS +++ b/AUTHORS @@ -74,3 +74,4 @@ Jason Bau Frances Botsford Jonah Stanley Slater Victoroff +Peter Fogg diff --git a/cms/djangoapps/contentstore/features/advanced-settings.feature b/cms/djangoapps/contentstore/features/advanced-settings.feature index 6f6cc50702..558294e890 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.feature +++ b/cms/djangoapps/contentstore/features/advanced-settings.feature @@ -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 diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py index 3acebecac8..eb00c06ba9 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.py +++ b/cms/djangoapps/contentstore/features/advanced-settings.py @@ -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") diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 96b840ae96..494192ad06 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -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') diff --git a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py index 4c674dc34c..43164f62be 100644 --- a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py +++ b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py @@ -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') diff --git a/cms/djangoapps/contentstore/features/discussion-editor.feature b/cms/djangoapps/contentstore/features/discussion-editor.feature index 24683c3297..8fb14c3205 100644 --- a/cms/djangoapps/contentstore/features/discussion-editor.feature +++ b/cms/djangoapps/contentstore/features/discussion-editor.feature @@ -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 diff --git a/cms/djangoapps/contentstore/features/discussion-editor.py b/cms/djangoapps/contentstore/features/discussion-editor.py index aced4c2c88..ae3da3c458 100644 --- a/cms/djangoapps/contentstore/features/discussion-editor.py +++ b/cms/djangoapps/contentstore/features/discussion-editor.py @@ -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')) diff --git a/cms/djangoapps/contentstore/features/video.feature b/cms/djangoapps/contentstore/features/video.feature index a4cf84d978..07771c9d61 100644 --- a/cms/djangoapps/contentstore/features/video.feature +++ b/cms/djangoapps/contentstore/features/video.feature @@ -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 diff --git a/cms/djangoapps/contentstore/features/video.py b/cms/djangoapps/contentstore/features/video.py index f25b8d6d7e..7cbe8a2258 100644 --- a/cms/djangoapps/contentstore/features/video.py +++ b/cms/djangoapps/contentstore/features/video.py @@ -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')) diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index 0b4535bb70..aebfb91126 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -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]) diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py index 0757992f2f..c4b0f4bb51 100644 --- a/cms/djangoapps/contentstore/tests/test_utils.py +++ b/cms/djangoapps/contentstore/tests/test_utils.py @@ -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. """ diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 35451cf7cc..703e9a266a 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -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: diff --git a/cms/envs/aws.py b/cms/envs/aws.py index f6064229e6..63c7d533cd 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -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) diff --git a/cms/static/coffee/src/views/unit.coffee b/cms/static/coffee/src/views/unit.coffee index e23477ccfa..058bcf0ce1 100644 --- a/cms/static/coffee/src/views/unit.coffee +++ b/cms/static/coffee/src/views/unit.coffee @@ -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' diff --git a/cms/static/img/large-advanced-icon.png b/cms/static/img/large-advanced-icon.png index c6a19ea5a9..97442f5aa0 100644 Binary files a/cms/static/img/large-advanced-icon.png and b/cms/static/img/large-advanced-icon.png differ diff --git a/cms/templates/unit.html b/cms/templates/unit.html index 851e3da260..b9a83265d9 100644 --- a/cms/templates/unit.html +++ b/cms/templates/unit.html @@ -53,9 +53,15 @@
Add New Component
% for type, templates in sorted(component_templates.items()): + % if len(templates) > 1 or type == 'advanced':
% if type == "problem": -
- - % endif -
-
    - % for name, location, has_markdown in templates: - % if has_markdown or type != "problem": -
  • - - ${name} - -
  • - % endif - - %endfor -
-
- % if type == "problem": -
-
- % endif - Cancel -
+ % endif +
+
    + % for name, location, has_markdown in templates: + % if has_markdown or type != "problem": +
  • + + ${name} + +
  • + % endif + + %endfor +
+
+ % if type == "problem": +
+
    + % for name, location, has_markdown in templates: + % if not has_markdown: +
  • + + ${name} + +
  • + % endif + % endfor +
+
+
+ % endif + Cancel + + % endif % endfor diff --git a/common/djangoapps/mitxmako/makoloader.py b/common/djangoapps/mitxmako/makoloader.py index d623e8bcff..6b6b31d464 100644 --- a/common/djangoapps/mitxmako/makoloader.py +++ b/common/djangoapps/mitxmako/makoloader.py @@ -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 diff --git a/common/djangoapps/mitxmako/management/commands/preprocess_assets.py b/common/djangoapps/mitxmako/management/commands/preprocess_assets.py index 36a2da9ad3..6afcbe896e 100644 --- a/common/djangoapps/mitxmako/management/commands/preprocess_assets.py +++ b/common/djangoapps/mitxmako/management/commands/preprocess_assets.py @@ -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())) diff --git a/common/djangoapps/terrain/browser.py b/common/djangoapps/terrain/browser.py index a932863322..d2a9480b35 100644 --- a/common/djangoapps/terrain/browser.py +++ b/common/djangoapps/terrain/browser.py @@ -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. diff --git a/common/lib/xmodule/xmodule/error_module.py b/common/lib/xmodule/xmodule/error_module.py index 8014234f69..7f15370176 100644 --- a/common/lib/xmodule/xmodule/error_module.py +++ b/common/lib/xmodule/xmodule/error_module.py @@ -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, ) diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index bbf24a6320..8af3693b3f 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -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 diff --git a/common/lib/xmodule/xmodule/tests/test_error_module.py b/common/lib/xmodule/xmodule/tests/test_error_module.py index d6b6f77ae6..78e5b46a96 100644 --- a/common/lib/xmodule/xmodule/tests/test_error_module.py +++ b/common/lib/xmodule/xmodule/tests/test_error_module.py @@ -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 = "" + self.location = Location(['i4x', self.org, self.course, None, None]) + self.valid_xml = "" self.broken_xml = "" 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) diff --git a/common/lib/xmodule/xmodule/tests/test_html_module.py b/common/lib/xmodule/xmodule/tests/test_html_module.py new file mode 100644 index 0000000000..e56e9babe7 --- /dev/null +++ b/common/lib/xmodule/xmodule/tests/test_html_module.py @@ -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 = ''' + +

Hi USER_ID!11!

+ + ''' + 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) + diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 284b746249..2ae7bcdc1f 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -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()) ) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index a3d5cb653f..588d9034db 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -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'] diff --git a/lms/static/images/bg-banner-login.png b/lms/static/images/bg-banner-login.png index 4bbba21628..85b3ad3c48 100644 Binary files a/lms/static/images/bg-banner-login.png and b/lms/static/images/bg-banner-login.png differ diff --git a/lms/static/images/bg-banner-register.png b/lms/static/images/bg-banner-register.png index f1fe626f05..807be395fa 100644 Binary files a/lms/static/images/bg-banner-register.png and b/lms/static/images/bg-banner-register.png differ diff --git a/lms/static/sass/base/_variables.scss b/lms/static/sass/base/_variables.scss index 6bd593c28c..2d0021c642 100644 --- a/lms/static/sass/base/_variables.scss +++ b/lms/static/sass/base/_variables.scss @@ -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'; \ No newline at end of file +$video-thumb-url: '../images/courses/video-thumb.jpg'; diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index 4d8f000668..9650dabc95 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -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; } -} \ No newline at end of file +} diff --git a/lms/static/sass/multicourse/_account.scss b/lms/static/sass/multicourse/_account.scss index eab8cbe66b..5147b6a09b 100644 --- a/lms/static/sass/multicourse/_account.scss +++ b/lms/static/sass/multicourse/_account.scss @@ -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; } } diff --git a/lms/static/sass/shared/_header.scss b/lms/static/sass/shared/_header.scss index 0608a8faf4..e49accf904 100644 --- a/lms/static/sass/shared/_header.scss +++ b/lms/static/sass/shared/_header.scss @@ -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; - } + } } diff --git a/lms/templates/login.html b/lms/templates/login.html index 3e33c84b7a..91210e7bb7 100644 --- a/lms/templates/login.html +++ b/lms/templates/login.html @@ -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">Log into your edX Account <%block name="js_extra"> @@ -77,14 +80,14 @@
-

Log Into Your Account

+

${_("Log Into Your Account")}