diff --git a/problem_builder/instructor_tool.py b/problem_builder/instructor_tool.py index 37696deb..6cdd4249 100644 --- a/problem_builder/instructor_tool.py +++ b/problem_builder/instructor_tool.py @@ -26,12 +26,15 @@ import six from django.core.paginator import Paginator +from problem_builder.utils import I18NService +from .mixins import TranslationContentMixin from xblock.core import XBlock from xblock.exceptions import JsonHandlerError from xblock.fields import Dict, List, Scope, String from xblock.fragment import Fragment from xblockutils.resources import ResourceLoader + loader = ResourceLoader(__name__) PAGE_SIZE = 15 @@ -48,7 +51,7 @@ def _(text): @XBlock.needs("i18n") @XBlock.wants('user') -class InstructorToolBlock(XBlock): +class InstructorToolBlock(XBlock, I18NService, TranslationContentMixin): """ InstructorToolBlock: An XBlock for instructors to export student answers from a course. @@ -142,12 +145,13 @@ def student_view(self, context=None): _('Long Answer'): 'AnswerBlock', } - html = loader.render_template('templates/html/instructor_tool.html', { + html = loader.render_django_template('templates/html/instructor_tool.html', { 'block_choices': block_choices, 'course_blocks_api': COURSE_BLOCKS_API, 'root_block_id': six.text_type(getattr(self.runtime, 'course_id', 'course_id')), - }) + }, i18n_service=self.i18n_service) fragment = Fragment(html) + fragment.add_javascript(self.get_translation_content()) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/instructor_tool.css')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/instructor_tool.js')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/vendor/underscore-min.js')) diff --git a/problem_builder/mentoring.py b/problem_builder/mentoring.py index a649e5ff..75bb447f 100644 --- a/problem_builder/mentoring.py +++ b/problem_builder/mentoring.py @@ -53,7 +53,7 @@ from .mixins import (ExpandStaticURLMixin, MessageParentMixin, QuestionMixin, StepParentMixin, StudentViewUserStateMixin, StudentViewUserStateResultsTransformerMixin, - XBlockWithTranslationServiceMixin, _normalize_id) + XBlockWithTranslationServiceMixin, _normalize_id, TranslationContentMixin) from .step_review import ReviewStepBlock from .utils import I18NService @@ -227,7 +227,7 @@ def max_score(self): class MentoringBlock( StudentViewUserStateResultsTransformerMixin, I18NService, - BaseMentoringBlock, StudioContainerWithNestedXBlocksMixin, StepParentMixin, + BaseMentoringBlock, StudioContainerWithNestedXBlocksMixin, StepParentMixin, TranslationContentMixin ): """ An XBlock providing mentoring capabilities @@ -484,12 +484,12 @@ def student_view(self, context): 'child_content': child_content, 'missing_dependency_url': self.has_missing_dependency and self.next_step_url, }, i18n_service=self.i18n_service)) + fragment.add_javascript(self.get_translation_content()) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder.css')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/vendor/underscore-min.js')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/util.js')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring_standard_view.js')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring.js')) - fragment.add_resource(loader.load_unicode('templates/html/mentoring_attempts.underscore'), "text/html") # Workbench doesn't have font awesome, so add it: if WorkbenchRuntime and isinstance(self.runtime, WorkbenchRuntime): @@ -996,7 +996,6 @@ def student_view(self, context): fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/step_util.js')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring_with_steps.js')) - fragment.add_resource(loader.load_unicode('templates/html/mentoring_attempts.underscore'), "text/html") fragment.initialize_js('MentoringWithStepsBlock', { 'show_extended_feedback': self.show_extended_feedback(), }) diff --git a/problem_builder/mixins.py b/problem_builder/mixins.py index ce9b8e9d..ec2cb73b 100644 --- a/problem_builder/mixins.py +++ b/problem_builder/mixins.py @@ -1,7 +1,9 @@ import json +import pkg_resources import six import webob +from django import utils from lazy import lazy from xblock.core import XBlock from xblock.fields import UNIQUE_ID, Boolean, Float, Scope, String @@ -290,3 +292,27 @@ def expand_static_url(self, text): except ImportError: pass return text + + +class TranslationContentMixin(object): + """ + Mixin to provide the translation content + """ + @staticmethod + def resource_string(path): + """Handy helper for getting resources from our kit.""" + data = pkg_resources.resource_string(__name__, path) + return data.decode("utf8") + + def get_translation_content(self): + try: + # here we need to split the lang code and need to change - to _ and post - characters to + # upper case since we have local directories like ja_JP, etc instead of ja-jp, etc + language = utils.translation.get_language().split('-') + if len(language) == 2: + new_lang = language[0] + "_" + language[1].upper() + else: + new_lang = utils.translation.get_language() + return self.resource_string('public/js/translations/{lang}/textjs.js'.format(lang=new_lang)) + except IOError: + return self.resource_string('public/js/translations/en/textjs.js') diff --git a/problem_builder/public/js/instructor_tool.js b/problem_builder/public/js/instructor_tool.js index ccc7ee1a..7aa2dd4e 100644 --- a/problem_builder/public/js/instructor_tool.js +++ b/problem_builder/public/js/instructor_tool.js @@ -1,9 +1,10 @@ function InstructorToolBlock(runtime, element) { 'use strict'; var $element = $(element); + var gettext = window.ProblemBuilderXBlockI18N.gettext; + var ngettext = window.ProblemBuilderXBlockI18N.ngettext; // Pagination - $(document).ajaxSend(function(event, jqxhr, options) { if (options.url.indexOf('get_result_page') !== -1) { options.data = JSON.stringify(options.data); diff --git a/problem_builder/public/js/mentoring.js b/problem_builder/public/js/mentoring.js index 34dd8f53..9486db04 100644 --- a/problem_builder/public/js/mentoring.js +++ b/problem_builder/public/js/mentoring.js @@ -1,11 +1,8 @@ function MentoringBlock(runtime, element) { - // Set up gettext in case it isn't available in the client runtime: - if (typeof gettext == "undefined") { - window.gettext = function gettext_stub(string) { return string; }; - window.ngettext = function ngettext_stub(strA, strB, n) { return n == 1 ? strA : strB; }; - } - var attemptsTemplate = _.template($('#xblock-attempts-template').html()); + var gettext = window.ProblemBuilderXBlockI18N.gettext; + var ngettext = window.ProblemBuilderXBlockI18N.ngettext; + var data = $('.mentoring', element).data(); var children = runtime.children(element); var step = data.step; @@ -69,7 +66,13 @@ function MentoringBlock(runtime, element) { function renderAttempts() { var data = $('.attempts', element).data(); - $('.attempts', element).html(attemptsTemplate(data)); + if (data != undefined && _.isNumber(data.max_attempts) && data.max_attempts > 0) { + var message = _.template( + ngettext("You have used {num_used} of 1 submission.", "You have used {num_used} of {max_attempts} submissions.", data.max_attempts), + {num_used: _.min([data.num_attempts, data.max_attempts]), max_attempts: data.max_attempts}, {interpolate: /\{(.+?)\}/g} + ); + $('.attempts', element).html("" + message + ""); + } } function renderDependency() { diff --git a/problem_builder/public/js/mentoring_with_steps.js b/problem_builder/public/js/mentoring_with_steps.js index faae8dcd..0eb8a519 100644 --- a/problem_builder/public/js/mentoring_with_steps.js +++ b/problem_builder/public/js/mentoring_with_steps.js @@ -1,11 +1,7 @@ function MentoringWithStepsBlock(runtime, element) { - - // Set up gettext in case it isn't available in the client runtime: - if (typeof gettext == "undefined") { - window.gettext = function gettext_stub(string) { return string; }; - window.ngettext = function ngettext_stub(strA, strB, n) { return n == 1 ? strA : strB; }; - } - + // Use problem_builder translations + var gettext = window.ProblemBuilderXBlockI18N.gettext; + var ngettext = window.ProblemBuilderXBlockI18N.ngettext; var children = runtime.children(element); var steps = []; @@ -19,7 +15,6 @@ function MentoringWithStepsBlock(runtime, element) { } var activeStepIndex = $('.mentoring', element).data('active-step'); - var attemptsTemplate = _.template($('#xblock-attempts-template').html()); var message = $('.sb-step-message', element); var checkmark, submitDOM, nextDOM, reviewButtonDOM, tryAgainDOM, gradeDOM, attemptsDOM, reviewLinkDOM, submitXHR; @@ -324,8 +319,12 @@ function MentoringWithStepsBlock(runtime, element) { function showAttempts() { var data = attemptsDOM.data(); - if (data.max_attempts > 0) { - attemptsDOM.html(attemptsTemplate(data)); + if (_.isNumber(data.max_attempts) && data.max_attempts > 0) { + var message = _.template( + ngettext("You have used {num_used} of 1 submission.", "You have used {num_used} of {max_attempts} submissions.", data.max_attempts), + {num_used: _.min([data.num_attempts, data.max_attempts]), max_attempts: data.max_attempts}, {interpolate: /\{(.+?)\}/g} + ); + attemptsDOM.html("" + message + ""); } // Don't show attempts if unlimited attempts available (max_attempts === 0) } diff --git a/problem_builder/public/js/questionnaire.js b/problem_builder/public/js/questionnaire.js index 4b3d96de..e62dcf6e 100644 --- a/problem_builder/public/js/questionnaire.js +++ b/problem_builder/public/js/questionnaire.js @@ -94,6 +94,7 @@ function MessageView(element, mentoring) { } function MCQBlock(runtime, element) { + var gettext = window.ProblemBuilderXBlockI18N.gettext; return { mentoring: null, init: function(options) { @@ -182,6 +183,8 @@ function SwipeBlock(runtime, element) { } function MRQBlock(runtime, element) { + var gettext = window.ProblemBuilderXBlockI18N.gettext; + var ngettext = window.ProblemBuilderXBlockI18N.ngettext; return { mentoring: null, init: function(options) { diff --git a/problem_builder/public/js/step_util.js b/problem_builder/public/js/step_util.js index 06900667..63e210be 100644 --- a/problem_builder/public/js/step_util.js +++ b/problem_builder/public/js/step_util.js @@ -117,20 +117,3 @@ }; })(); - -var gettext; -var ngettext; -if ('ProblemBuilderXBlockI18N' in window) { - // Use problem builder's local translations - gettext = window.ProblemBuilderXBlockI18N.gettext; - ngettext = window.ProblemBuilderXBlockI18N.ngettext; -} else if ('gettext' in window) { - // Use edxapp's global translations - gettext = window.gettext; - ngettext = window.ngettext; -} -if (typeof gettext == "undefined") { - // No translations -- used by test environment - gettext = function(string) { return string; }; - ngettext = function(strA, strB, n) { return n == 1 ? strA : strB; }; -} diff --git a/problem_builder/public/js/util.js b/problem_builder/public/js/util.js index e4ca1951..31569af3 100644 --- a/problem_builder/public/js/util.js +++ b/problem_builder/public/js/util.js @@ -37,20 +37,3 @@ window.ProblemBuilderUtil = { } } }; - -var gettext; -var ngettext; -if ('ProblemBuilderXBlockI18N' in window) { - // Use problem builder's local translations - gettext = window.ProblemBuilderXBlockI18N.gettext; - ngettext = window.ProblemBuilderXBlockI18N.ngettext; -} else if ('gettext' in window) { - // Use edxapp's global translations - gettext = window.gettext; - ngettext = window.ngettext; -} -if (typeof gettext == "undefined") { - // No translations -- used by test environment - gettext = function(string) { return string; }; - ngettext = function(strA, strB, n) { return n == 1 ? strA : strB; }; -} diff --git a/problem_builder/step.py b/problem_builder/step.py index 1e0bfab4..c4ca0894 100644 --- a/problem_builder/step.py +++ b/problem_builder/step.py @@ -20,9 +20,7 @@ import logging -import pkg_resources import six -from django import utils from lazy.lazy import lazy from xblock.core import XBlock from xblock.fields import List, Scope, String @@ -43,6 +41,7 @@ from problem_builder.plot import PlotBlock from problem_builder.slider import SliderBlock from problem_builder.table import MentoringTableBlock +from .mixins import TranslationContentMixin from .utils import I18NService @@ -77,7 +76,7 @@ class Correctness(object): class MentoringStepBlock( StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin, XBlockWithPreviewMixin, EnumerableChildMixin, StepParentMixin, StudentViewUserStateResultsTransformerMixin, - StudentViewUserStateMixin, XBlock, I18NService + StudentViewUserStateMixin, XBlock, I18NService, TranslationContentMixin ): """ An XBlock for a step. @@ -243,27 +242,6 @@ def mentoring_view(self, context=None): """ Mentoring View """ return self._render_view(context, 'mentoring_view') - @staticmethod - def resource_string(path): - """Handy helper for getting resources from our kit.""" - data = pkg_resources.resource_string(__name__, path) - return data.decode("utf8") - - def get_translation_content(self): - try: - # here we need to split the lang code and need to change - to _ and post - characters to - # upper case since we have local directories like ja_JP, etc instead of ja-jp, etc - language = utils.translation.get_language().split('-') - if len(language) == 2: - new_lang = language[0] + "_" + language[1].upper() - else: - new_lang = utils.translation.get_language() - return self.resource_string('public/js/translations/{lang}/textjs.js'.format( - lang=new_lang - )) - except IOError: - return self.resource_string('public/js/translations/en/textjs.js') - def _render_view(self, context, view): """ Actually renders a view """ rendering_for_studio = False diff --git a/problem_builder/templates/html/mentoring_attempts.underscore b/problem_builder/templates/html/mentoring_attempts.underscore deleted file mode 100644 index f9d9f32b..00000000 --- a/problem_builder/templates/html/mentoring_attempts.underscore +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/problem_builder/tests/unit/test_instructor_tool.py b/problem_builder/tests/unit/test_instructor_tool.py index 8d6648b4..cc52f745 100644 --- a/problem_builder/tests/unit/test_instructor_tool.py +++ b/problem_builder/tests/unit/test_instructor_tool.py @@ -36,6 +36,8 @@ def _get_block(self, block_info): def setUp(self): self.course_id = 'course-v1:edX+DemoX+Demo_Course' self.runtime_mock = Mock() + self.service_mock = Mock() + self.runtime_mock.service = Mock(return_value=self.service_mock) self.runtime_mock.get_block = self._get_block self.runtime_mock.course_id = self.course_id scope_ids_mock = Mock() @@ -57,13 +59,14 @@ def test_student_view_template_args(self): } with patch('problem_builder.instructor_tool.loader') as patched_loader: - patched_loader.render_template.return_value = u'' + patched_loader.render_django_template.return_value = u'' self.block.student_view() - patched_loader.render_template.assert_called_once_with('templates/html/instructor_tool.html', { + self.service_mock.i18n_service = Mock(return_value=None) + patched_loader.render_django_template.assert_called_once_with('templates/html/instructor_tool.html', { 'block_choices': block_choices, 'course_blocks_api': COURSE_BLOCKS_API, 'root_block_id': self.course_id, - }) + }, i18n_service=self.service_mock) def test_author_view(self): """ diff --git a/setup.py b/setup.py index 122d5ade..d9a062b0 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ # Constants ######################################################### -VERSION = '3.4.23' +VERSION = '3.5.0' # Functions #########################################################