From 060b8680f445fc1209679c74925655b008c9bf88 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Fri, 13 Jun 2014 13:39:53 -0400 Subject: [PATCH 1/7] update opaque keys dependency to fix install --- requirements/edx/github.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt index 2a5424d6c0..ac64072b5f 100644 --- a/requirements/edx/github.txt +++ b/requirements/edx/github.txt @@ -27,5 +27,5 @@ -e git+https://github.com/edx-solutions/django-splash.git@9965a53c269666a30bb4e2b3f6037c138aef2a55#egg=django-splash -e git+https://github.com/edx/acid-block.git@459aff7b63db8f2c5decd1755706c1a64fb4ebb1#egg=acid-xblock -e git+https://github.com/edx/edx-ora2.git@release-2014-06-13T11.52#egg=edx-ora2 --e git+https://github.com/edx/opaque-keys.git@91b7ec93cfb57c6739332e85805296626b4fb1db#egg=opaque-keys +-e git+https://github.com/edx/opaque-keys.git@668a185b8d27838c655baa6d03d7cd1b58893b42#egg=opaque-keys git+https://github.com/edx/ease.git@a990b25ed4238acb1b15ee6f027465db3a10960e#egg=ease From 3a5ba4ba83a0eb259b34801e59ab004cc24c2dc2 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Fri, 13 Jun 2014 14:51:24 -0400 Subject: [PATCH 2/7] Bump opaque_key dependency to 0.1.1 --- requirements/edx/github.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt index ac64072b5f..aba6a1b234 100644 --- a/requirements/edx/github.txt +++ b/requirements/edx/github.txt @@ -27,5 +27,5 @@ -e git+https://github.com/edx-solutions/django-splash.git@9965a53c269666a30bb4e2b3f6037c138aef2a55#egg=django-splash -e git+https://github.com/edx/acid-block.git@459aff7b63db8f2c5decd1755706c1a64fb4ebb1#egg=acid-xblock -e git+https://github.com/edx/edx-ora2.git@release-2014-06-13T11.52#egg=edx-ora2 --e git+https://github.com/edx/opaque-keys.git@668a185b8d27838c655baa6d03d7cd1b58893b42#egg=opaque-keys +-e git+https://github.com/edx/opaque-keys.git@5929789900b3d0a354ce7274bde74edfd0430f03#egg=opaque-keys git+https://github.com/edx/ease.git@a990b25ed4238acb1b15ee6f027465db3a10960e#egg=ease From 04fcebaf4740586c67fdbebf0c8a31db26d95f83 Mon Sep 17 00:00:00 2001 From: Andy Armstrong Date: Wed, 11 Jun 2014 13:21:46 -0400 Subject: [PATCH 3/7] Allow xblocks to be added as advanced problem types --- CHANGELOG.rst | 2 + .../contentstore/tests/test_contentstore.py | 2 +- .../contentstore/views/component.py | 56 ++++++++----- .../contentstore/views/tests/test_item.py | 68 +++++++++++++++- cms/static/coffee/spec/main.coffee | 1 + cms/static/js/models/component_template.js | 22 ++++-- .../js/spec/models/component_template_spec.js | 79 +++++++++++++++++++ 7 files changed, 205 insertions(+), 25 deletions(-) create mode 100644 cms/static/js/spec/models/component_template_spec.js diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4aebf3f5f2..8ed5a0114a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes, in roughly chronological order, most recent first. Add your entries at or near the top. Include a label indicating the component affected. +Studio: Move Peer Assessment into advanced problems menu. + Blades: Add context-aware video index. BLD-933 Blades: Fix bug with incorrect link format and redirection. BLD-1049 diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index b4326954ba..e19e4776dc 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -140,7 +140,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.check_components_on_page( ADVANCED_COMPONENT_TYPES, ['Word cloud', 'Annotation', 'Text Annotation', 'Video Annotation', 'Image Annotation', - 'Open Response Assessment', 'Peer Grading Interface', 'openassessment', 'split_test'], + 'Open Response Assessment', 'Peer Grading Interface', 'split_test'], ) def test_advanced_components_require_two_clicks(self): diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index 38b45f785a..03de2cbf92 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -45,7 +45,6 @@ COMPONENT_TYPES = ['discussion', 'html', 'problem', 'video'] OPEN_ENDED_COMPONENT_TYPES = ["combinedopenended", "peergrading"] NOTE_COMPONENT_TYPES = ['notes'] - if settings.FEATURES.get('ALLOW_ALL_ADVANCED_COMPONENTS'): ADVANCED_COMPONENT_TYPES = sorted(set(name for name, class_ in XBlock.load_classes()) - set(COMPONENT_TYPES)) else: @@ -65,13 +64,20 @@ else: 'concept', # Concept mapper. See https://github.com/pmitros/ConceptXBlock 'done', # Lets students mark things as done. See https://github.com/pmitros/DoneXBlock 'audio', # Embed an audio file. See https://github.com/pmitros/AudioXBlock - 'openassessment', # edx-ora2 'split_test' ] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES ADVANCED_COMPONENT_CATEGORY = 'advanced' ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules' +# Specify xblocks that should be treated as advanced problems. Each entry is a tuple +# specifying the xblock name and an optional YAML template to be used. +ADVANCED_PROBLEM_TYPES = [ + { + 'component': 'openassessment', + 'boilerplate_name': None + } +] @require_GET @login_required @@ -165,7 +171,7 @@ def unit_handler(request, usage_key_string): except ItemNotFoundError: return HttpResponseBadRequest() - component_templates = _get_component_templates(course) + component_templates = get_component_templates(course) xblocks = item.get_children() @@ -245,7 +251,7 @@ def container_handler(request, usage_key_string): except ItemNotFoundError: return HttpResponseBadRequest() - component_templates = _get_component_templates(course) + component_templates = get_component_templates(course) ancestor_xblocks = [] parent = get_parent_xblock(xblock) while parent and parent.category != 'sequential': @@ -269,7 +275,7 @@ def container_handler(request, usage_key_string): return HttpResponseBadRequest("Only supports html requests") -def _get_component_templates(course): +def get_component_templates(course): """ Returns the applicable component templates that can be used by the specified course. """ @@ -297,9 +303,19 @@ def _get_component_templates(course): 'problem': _("Problem"), 'video': _("Video") } - advanced_component_display_names = {} + + def get_component_display_name(component, default_display_name=None): + """ + Returns the display name for the specified component. + """ + component_class = _load_mixed_class(component) + if hasattr(component_class, 'display_name') and component_class.display_name.default: + return _(component_class.display_name.default) + else: + return default_display_name component_templates = [] + categories = set() # The component_templates array is in the order of "advanced" (if present), followed # by the components in the order listed in COMPONENT_TYPES. for category in COMPONENT_TYPES: @@ -308,11 +324,9 @@ def _get_component_templates(course): # add the default template with localized display name # TODO: Once mixins are defined per-application, rather than per-runtime, # this should use a cms mixed-in class. (cpennington) - if hasattr(component_class, 'display_name'): - display_name = _(component_class.display_name.default) if component_class.display_name.default else _('Blank') - else: - display_name = _('Blank') + display_name = get_component_display_name(category, _('Blank')) templates_for_category.append(create_template_dict(display_name, category)) + categories.add(category) # add boilerplates if hasattr(component_class, 'templates'): @@ -327,6 +341,16 @@ def _get_component_templates(course): template['metadata'].get('markdown') is not None ) ) + + # Add any advanced problem types + if category == 'problem': + for advanced_problem_type in ADVANCED_PROBLEM_TYPES: + component = advanced_problem_type['component'] + boilerplate_name = advanced_problem_type['boilerplate_name'] + component_display_name = get_component_display_name(component) + templates_for_category.append(create_template_dict(component_display_name, component, boilerplate_name)) + categories.add(component) + component_templates.append({ "type": category, "templates": templates_for_category, @@ -342,21 +366,17 @@ def _get_component_templates(course): # Set component types according to course policy file if isinstance(course_advanced_keys, list): for category in course_advanced_keys: - if category in ADVANCED_COMPONENT_TYPES: + if category in ADVANCED_COMPONENT_TYPES and not category in categories: # boilerplates not supported for advanced components try: - component_class = _load_mixed_class(category) - - if component_class.display_name.default: - template_display_name = _(component_class.display_name.default) - else: - template_display_name = advanced_component_display_names.get(category, category) + component_display_name = get_component_display_name(category) advanced_component_templates['templates'].append( create_template_dict( - template_display_name, + component_display_name, category ) ) + categories.add(category) except PluginMissingError: # dhm: I got this once but it can happen any time the # course author configures an advanced component which does diff --git a/cms/djangoapps/contentstore/views/tests/test_item.py b/cms/djangoapps/contentstore/views/tests/test_item.py index 443960359c..ff26ba8176 100644 --- a/cms/djangoapps/contentstore/views/tests/test_item.py +++ b/cms/djangoapps/contentstore/views/tests/test_item.py @@ -14,7 +14,7 @@ from django.test.client import RequestFactory from django.core.urlresolvers import reverse from contentstore.utils import reverse_usage_url -from contentstore.views.component import component_handler +from contentstore.views.component import component_handler, get_component_templates from contentstore.tests.utils import CourseTestCase from contentstore.utils import compute_publish_state, PublishState @@ -947,3 +947,69 @@ class TestComponentHandler(TestCase): self.descriptor.handle = create_response self.assertEquals(component_handler(self.request, self.usage_key_string, 'dummy_handler').status_code, status_code) + + +class TestComponentTemplates(CourseTestCase): + """ + Unit tests for the generation of the component templates for a course. + """ + + def setUp(self): + super(TestComponentTemplates, self).setUp() + self.templates = get_component_templates(self.course) + + def get_templates_of_type(self, template_type): + """ + Returns the templates for the specified type, or None if none is found. + """ + template_dict = next((template for template in self.templates if template.get('type') == template_type), None) + return template_dict.get('templates') if template_dict else None + + def get_template(self, templates, display_name): + """ + Returns the template which has the specified display name. + """ + return next((template for template in templates if template.get('display_name') == display_name), None) + + def test_basic_components(self): + """ + Test the handling of the basic component templates. + """ + self.assertIsNotNone(self.get_templates_of_type('discussion')) + self.assertIsNotNone(self.get_templates_of_type('html')) + self.assertIsNotNone(self.get_templates_of_type('problem')) + self.assertIsNotNone(self.get_templates_of_type('video')) + self.assertIsNone(self.get_templates_of_type('advanced')) + + def test_advanced_components(self): + """ + Test the handling of advanced component templates. + """ + self.course.advanced_modules.append('word_cloud') + self.templates = get_component_templates(self.course) + advanced_templates = self.get_templates_of_type('advanced') + self.assertEqual(len(advanced_templates), 1) + world_cloud_template = advanced_templates[0] + self.assertEqual(world_cloud_template.get('category'), 'word_cloud') + self.assertEqual(world_cloud_template.get('display_name'), u'Word cloud') + self.assertIsNone(world_cloud_template.get('boilerplate_name', None)) + + # Verify that non-advanced components are not added twice + self.course.advanced_modules.append('video') + self.course.advanced_modules.append('openassessment') + self.templates = get_component_templates(self.course) + advanced_templates = self.get_templates_of_type('advanced') + self.assertEqual(len(advanced_templates), 1) + only_template = advanced_templates[0] + self.assertNotEqual(only_template.get('category'), 'video') + self.assertNotEqual(only_template.get('category'), 'openassessment') + + def test_advanced_problems(self): + """ + Test the handling of advanced problem templates. + """ + problem_templates = self.get_templates_of_type('problem') + ora_template = self.get_template(problem_templates, u'Peer Assessment') + self.assertIsNotNone(ora_template) + self.assertEqual(ora_template.get('category'), 'openassessment') + self.assertIsNone(ora_template.get('boilerplate_name', None)) diff --git a/cms/static/coffee/spec/main.coffee b/cms/static/coffee/spec/main.coffee index fa269f71a0..2bae437547 100644 --- a/cms/static/coffee/spec/main.coffee +++ b/cms/static/coffee/spec/main.coffee @@ -212,6 +212,7 @@ define([ "js/spec/video/transcripts/videolist_spec", "js/spec/video/transcripts/message_manager_spec", "js/spec/video/transcripts/file_uploader_spec", + "js/spec/models/component_template_spec", "js/spec/models/explicit_url_spec", "js/spec/utils/drag_and_drop_spec", diff --git a/cms/static/js/models/component_template.js b/cms/static/js/models/component_template.js index 09619aac58..050ef2e0b1 100644 --- a/cms/static/js/models/component_template.js +++ b/cms/static/js/models/component_template.js @@ -13,19 +13,31 @@ define(["backbone"], function (Backbone) { templates: [] }, parse: function (response) { + // Returns true only for templates that both have no boilerplate and are of + // the overall type of the menu. This allows other component types to be added + // and they will get sorted alphabetically rather than just at the top. + // e.g. The ORA openassessment xblock is listed as an advanced problem. + var isPrimaryBlankTemplate = function(template) { + return !template.boilerplate_name && template.category === response.type; + }; + this.type = response.type; this.templates = response.templates; this.display_name = response.display_name; // Sort the templates. this.templates.sort(function (a, b) { - // The entry without a boilerplate always goes first - if (!a.boilerplate_name || (a.display_name < b.display_name)) { + // The blank problem for the current type goes first + if (isPrimaryBlankTemplate(a)) { + return -1; + } else if (isPrimaryBlankTemplate(b)) { + return 1; + } else if (a.display_name > b.display_name) { + return 1; + } else if (a.display_name < b.display_name) { return -1; } - else { - return (a.display_name > b.display_name) ? 1 : 0; - } + return 0; }); } }); diff --git a/cms/static/js/spec/models/component_template_spec.js b/cms/static/js/spec/models/component_template_spec.js new file mode 100644 index 0000000000..1e544e1a97 --- /dev/null +++ b/cms/static/js/spec/models/component_template_spec.js @@ -0,0 +1,79 @@ +define(["js/models/component_template"], + function (ComponentTemplate) { + + describe("ComponentTemplates", function() { + var mockTemplateJSON = { + "templates": [ + { + "category": "problem", + "boilerplate_name": "formularesponse.yaml", + "display_name": "Math Expression Input" + }, { + "category": "problem", + "boilerplate_name": null, + "display_name": "Blank Advanced Problem" + }, { + "category": "problem", + "boilerplate_name": "checkboxes.yaml", + "display_name": "Checkboxes" + }, { + "category": "problem", + "boilerplate_name": "multiple_choice.yaml", + "display_name": "Multiple Choice" + }, { + "category": "problem", + "boilerplate_name": "drag_and_drop.yaml", + "display_name": "Drag and Drop" + }, { + "category": "problem", + "boilerplate_name": "problem_with_hint.yaml", + "display_name": "Problem with Adaptive Hint" + }, { + "category": "problem", + "boilerplate_name": "imageresponse.yaml", + "display_name": "Image Mapped Input" + }, { + "category": "openassessment", + "boilerplate_name": null, + "display_name": "Peer Assessment" + }, { + "category": "problem", + "boilerplate_name": "an_easy_problem.yaml", + "display_name": "An Easy Problem" + }, { + "category": "word_cloud", + "boilerplate_name": null, + "display_name": "Word Cloud" + }, { // duplicate display name to verify sort behavior + "category": "word_cloud", + "boilerplate_name": "alternate_word_cloud.yaml", + "display_name": "Word Cloud" + }], + "type": "problem" + }; + + it('orders templates correctly', function () { + var lastTemplate = null, + firstComparison = true, + componentTemplate = new ComponentTemplate(), + template, templateName, i; + componentTemplate.parse(mockTemplateJSON); + for (i=0; i < componentTemplate.templates.length; i++) { + template = componentTemplate.templates[i]; + templateName = template['display_name']; + if (lastTemplate) { + if (!firstComparison || lastTemplate['boilerplate_name']) { + expect(lastTemplate['display_name'] < templateName).toBeTruthy(); + } + firstComparison = false; + } else { + // If the first template is blank, make sure that it has the correct category + if (!template['boilerplate_name']) { + expect(template['category']).toBe('problem'); + } + lastTemplate = template; + } + } + }); + }); + }); From be1479deb24766849adcb710878524e91a5f8599 Mon Sep 17 00:00:00 2001 From: e0d Date: Fri, 13 Jun 2014 17:42:28 -0400 Subject: [PATCH 4/7] shim to update ruby/node, not python reqs --- pavelib/prereqs.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pavelib/prereqs.py b/pavelib/prereqs.py index 014a23e3e5..9d7cdf4efc 100644 --- a/pavelib/prereqs.py +++ b/pavelib/prereqs.py @@ -151,3 +151,15 @@ def install_prereqs(): install_ruby_prereqs() install_node_prereqs() install_python_prereqs() + +@task +def install_asset_prereqs(): + """ + Installs Ruby and Node + """ + if os.environ.get("NO_PREREQ_INSTALL", False): + return + + install_ruby_prereqs() + install_node_prereqs() + From 6fdb4dc8da7208ad1ade547376496e3ac4068ff9 Mon Sep 17 00:00:00 2001 From: e0d Date: Fri, 13 Jun 2014 17:48:38 -0400 Subject: [PATCH 5/7] missed commit --- pavelib/assets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pavelib/assets.py b/pavelib/assets.py index 3d98189555..4ef79cdcb0 100644 --- a/pavelib/assets.py +++ b/pavelib/assets.py @@ -190,7 +190,7 @@ def watch_assets(options): @task -@needs('pavelib.prereqs.install_prereqs') +@needs('pavelib.prereqs.install_asset_prereqs') @consume_args def update_assets(args): """ From 872d7b6f423492087f050971c78210fe7dc5df64 Mon Sep 17 00:00:00 2001 From: Waheed Ahmed Date: Fri, 6 Jun 2014 18:05:15 +0500 Subject: [PATCH 6/7] Fixed MATLAB Run Code bug with answer boxes. LMS-2807 --- .../lib/capa/capa/templates/matlabinput.html | 83 ++++++++++-- .../acceptance/pages/lms/matlab_problem.py | 43 +++++++ .../acceptance/tests/test_matlab_problem.py | 118 ++++++++++++++++++ 3 files changed, 233 insertions(+), 11 deletions(-) create mode 100644 common/test/acceptance/pages/lms/matlab_problem.py create mode 100644 common/test/acceptance/tests/test_matlab_problem.py diff --git a/common/lib/capa/capa/templates/matlabinput.html b/common/lib/capa/capa/templates/matlabinput.html index 63187f214b..ce61e38808 100644 --- a/common/lib/capa/capa/templates/matlabinput.html +++ b/common/lib/capa/capa/templates/matlabinput.html @@ -39,7 +39,7 @@
${msg|n}
-
+
${queue_msg|n}
@@ -51,6 +51,7 @@ diff --git a/common/test/acceptance/pages/lms/matlab_problem.py b/common/test/acceptance/pages/lms/matlab_problem.py new file mode 100644 index 0000000000..c75b80fc57 --- /dev/null +++ b/common/test/acceptance/pages/lms/matlab_problem.py @@ -0,0 +1,43 @@ +""" +Matlab Problem Page. +""" +from bok_choy.page_object import PageObject + + +class MatlabProblemPage(PageObject): + """ + View of matlab problem page. + """ + + url = None + + def is_browser_on_page(self): + return self.q(css='.ungraded-matlab-result').present + + @property + def problem_name(self): + """ + Return the current problem name. + """ + return self.q(css='.problem-header').text[0] + + def set_response(self, response_str): + """ + Input a response to the prompt. + """ + input_css = "$('.CodeMirror')[0].CodeMirror.setValue('{}');".format(response_str) + self.browser.execute_script(input_css) + + def click_run_code(self): + """ + Click the run code button. + """ + self.q(css='input.save').click() + self.wait_for_ajax() + + def get_grader_msg(self, class_name): + """ + Returns the text value of given class. + """ + self.wait_for_ajax() + return self.q(css=class_name).text diff --git a/common/test/acceptance/tests/test_matlab_problem.py b/common/test/acceptance/tests/test_matlab_problem.py new file mode 100644 index 0000000000..d2e7e9d90a --- /dev/null +++ b/common/test/acceptance/tests/test_matlab_problem.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +""" +E2E tests for the LMS. +""" +import time + +from .helpers import UniqueCourseTest +from ..pages.studio.auto_auth import AutoAuthPage +from ..pages.lms.courseware import CoursewarePage +from ..pages.lms.matlab_problem import MatlabProblemPage +from ..fixtures.course import CourseFixture, XBlockFixtureDesc +from ..fixtures.xqueue import XQueueResponseFixture +from textwrap import dedent + + +class MatlabProblemTest(UniqueCourseTest): + """ + Tests that verify matlab problem "Run Code". + """ + USERNAME = "STAFF_TESTER" + EMAIL = "johndoe@example.com" + + def setUp(self): + super(MatlabProblemTest, self).setUp() + + self.XQUEUE_GRADE_RESPONSE = None + + self.courseware_page = CoursewarePage(self.browser, self.course_id) + + # Install a course with sections/problems, tabs, updates, and handouts + course_fix = CourseFixture( + self.course_info['org'], self.course_info['number'], + self.course_info['run'], self.course_info['display_name'] + ) + + problem_data = dedent(""" + + +

+ Write MATLAB code to create the following row vector and store it in a variable named V. +

+ + + + +
[1 1 2 3 5 8 13]
+

+ + + + + + + + + + + + + +

+
+
+ """) + + course_fix.add_children( + XBlockFixtureDesc('chapter', 'Test Section').add_children( + XBlockFixtureDesc('sequential', 'Test Subsection').add_children( + XBlockFixtureDesc('problem', 'Test Matlab Problem', data=problem_data) + ) + ) + ).install() + + # Auto-auth register for the course. + AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, + course_id=self.course_id, staff=False).visit() + + def _goto_matlab_problem_page(self): + """ + Open matlab problem page with assertion. + """ + self.courseware_page.visit() + matlab_problem_page = MatlabProblemPage(self.browser) + self.assertEqual(matlab_problem_page.problem_name, 'TEST MATLAB PROBLEM') + return matlab_problem_page + + def test_run_code(self): + """ + Test "Run Code" button functionality. + """ + + # Enter a submission, which will trigger a pre-defined response from the XQueue stub. + self.submission = "a=1" + self.unique_id[0:5] + + self.XQUEUE_GRADE_RESPONSE = {'msg': self.submission} + + matlab_problem_page = self._goto_matlab_problem_page() + + # Configure the XQueue stub's response for the text we will submit + if self.XQUEUE_GRADE_RESPONSE is not None: + XQueueResponseFixture(self.submission, self.XQUEUE_GRADE_RESPONSE).install() + + matlab_problem_page.set_response(self.submission) + matlab_problem_page.click_run_code() + + self.assertEqual( + u'Submitted. As soon as a response is returned, this message will be replaced by that feedback.', + matlab_problem_page.get_grader_msg(".external-grader-message")[0] + ) + + # Wait 5 seconds for xqueue stub server grader response sent back to lms. + time.sleep(5) + + self.assertEqual(u'', matlab_problem_page.get_grader_msg(".external-grader-message")[0]) + self.assertEqual( + self.XQUEUE_GRADE_RESPONSE.get("msg"), + matlab_problem_page.get_grader_msg(".ungraded-matlab-result")[0] + ) From 0dbcf06280d8c79ca40c48f6d5f6a1ed521b0b8a Mon Sep 17 00:00:00 2001 From: polesye Date: Mon, 16 Jun 2014 18:39:25 +0300 Subject: [PATCH 7/7] Fix i18n in ChoiceGroup and ChoiceTextGroup. --- common/lib/capa/capa/inputtypes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index bbae259403..3b17cc14bc 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -230,8 +230,8 @@ class InputTypeBase(object): self.setup() except Exception as err: # Something went wrong: add xml to message, but keep the traceback - msg = "Error in xml '{x}': {err} ".format( - x=etree.tostring(xml), err=str(err)) + msg = u"Error in xml '{x}': {err} ".format( + x=etree.tostring(xml), err=err.message) raise Exception, msg, sys.exc_info()[2] @classmethod @@ -1744,7 +1744,7 @@ class ChoiceTextGroup(InputTypeBase): for choice in element: if choice.tag != 'choice': - msg = "[capa.inputtypes.extract_choices] {0}".format( + msg = u"[capa.inputtypes.extract_choices] {0}".format( # Translators: a "tag" is an XML element, such as "" in HTML _("Expected a {expected_tag} tag; got {given_tag} instead").format( expected_tag=u"",