Merge pull request #11955 from edx/rc/2016-03-29

Release Candidate rc/2016-03-29
This commit is contained in:
sanfordstudent
2016-03-30 09:29:07 -04:00
343 changed files with 4801 additions and 3484 deletions

1
.gitignore vendored
View File

@@ -73,6 +73,7 @@ bin/
lms/static/css/
lms/static/certificates/css/
cms/static/css/
common/static/common/js/vendor/
### Styling generated from templates
lms/static/sass/*.css

View File

@@ -1,12 +0,0 @@
@shard_1
Feature: Course export
I want to export my course to a tar.gz file to share with others or check into source control
# Disabling due to failure on master. 05/21/2014 TODO: fix
# Scenario: User is directed to problem with & in it when export fails
# Given I am in Studio editing a new unit
# When I add a "Blank Advanced Problem" "Advanced Problem" component
# And I edit and enter an ampersand
# And I export the course
# Then I get an error dialog
# And I can click to go to the unit with the error

View File

@@ -1,71 +0,0 @@
# pylint: disable=missing-docstring
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
from lettuce import world, step
from component_settings_editor_helpers import enter_xml_in_advanced_problem
from nose.tools import assert_true, assert_equal
from contentstore.utils import reverse_usage_url
@step('I go to the export page$')
def i_go_to_the_export_page(step):
world.click_tools()
link_css = 'li.nav-course-tools-export a'
world.css_click(link_css)
@step('I export the course$')
def i_export_the_course(step):
step.given('I go to the export page')
world.css_click('a.action-export')
@step('I edit and enter bad XML$')
def i_enter_bad_xml(step):
enter_xml_in_advanced_problem(
step,
"""<problem><h1>Smallest Canvas</h1>
<p>You want to make the smallest canvas you can.</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false"><verbatim><canvas id="myCanvas" width = 10 height = 100> </canvas></verbatim></choice>
<choice correct="true"><code><canvas id="myCanvas" width = 10 height = 10> </canvas></code></choice>
</choicegroup>
</multiplechoiceresponse>
</problem>"""
)
@step('I edit and enter an ampersand$')
def i_enter_an_ampersand(step):
enter_xml_in_advanced_problem(step, "<problem>&</problem>")
@step('I get an error dialog$')
def get_an_error_dialog(step):
assert_true(world.is_css_present("div.prompt.error"))
@step('I can click to go to the unit with the error$')
def i_click_on_error_dialog(step):
world.css_click("button.action-primary")
problem_string = unicode(world.scenario_dict['COURSE'].id.make_usage_key("problem", 'ignore'))
problem_string = u"Problem {}".format(problem_string[:problem_string.rfind('ignore')])
css_selector = "span.inline-error"
world.wait_for_visible(css_selector)
assert_true(
world.css_html(css_selector).startswith(problem_string),
u"{} does not start with {}".format(
world.css_html(css_selector), problem_string
))
# we don't know the actual ID of the vertical. So just check that we did go to a
# vertical page in the course (there should only be one).
vertical_usage_key = world.scenario_dict['COURSE'].id.make_usage_key("vertical", "test")
vertical_url = reverse_usage_url('container_handler', vertical_usage_key)
# Remove the trailing "/None" from the URL - we don't know the course ID, so we just want to
# check that we visited a vertical URL.
if vertical_url.endswith("/test") or vertical_url.endswith("@test"):
vertical_url = vertical_url[:-5]
assert_equal(1, world.browser.url.count(vertical_url))

View File

@@ -101,27 +101,3 @@ Feature: CMS.Problem Editor
Then I can see Reply to Annotation link
And I see that page has scrolled "down" when I click on "annotatable-reply" link
And I see that page has scrolled "up" when I click on "annotation-return" link
# Disabled 11/13/2013 after failing in master
# The screenshot showed that the LaTeX editor had the text "hi",
# but Selenium timed out waiting for the text to appear.
# It also caused later tests to fail with "UnexpectedAlertPresent"
#
# This feature will work in Firefox only when Firefox is the active window
# IE will not interact with the high level source in sauce labs
#@skip_internetexplorer
#Scenario: High Level source is persisted for LaTeX problem (bug STUD-280)
# Given I have created a LaTeX Problem
# When I edit and compile the High Level Source
# Then my change to the High Level Source is persisted
# And when I view the High Level Source I see my changes
# Disabled 10/28/13 due to flakiness observed in master
# Scenario: Exceptions don't cause problem to be uneditable (bug STUD-786)
#Given I have an empty course
#And I go to the import page
#And I import the file "get_html_exception_test.tar.gz"
#When I go to the unit "Probability and BMI"
#And I click on "edit a draft"
#Then I see a message that says "We're having trouble rendering your component"
#And I can edit the problem

View File

@@ -1,57 +0,0 @@
@shard_2 @requires_stub_youtube
Feature: CMS Video Component
As a course author, I want to be able to view my created videos in Studio
# 2
# Disabled 2/19/14 after intermittent failures in master
#Scenario: Check that position is stored on page refresh, position within start-end range
# Given I have created a Video component with subtitles
# And Make sure captions are closed
# And I edit the component
# And I open tab "Advanced"
# And I set value "00:00:12" to the field "Video Start Time"
# And I set value "00:00:24" to the field "Video Stop Time"
# And I save changes
# And I click video button "play"
# Then I see a range on slider
# Then I seek video to "16" seconds
# And I click video button "pause"
# And I reload the page
# And I click video button "play"
# Then I see video starts playing from "0:16" position
# 3
# Disabled 2/18/14 after intermittent failures in master
# Scenario: Check that position is stored on page refresh, position before start-end range
# Given I have created a Video component with subtitles
# And Make sure captions are closed
# And I edit the component
# And I open tab "Advanced"
# And I set value "00:00:12" to the field "Video Start Time"
# And I set value "00:00:24" to the field "Video Stop Time"
# And I save changes
# And I click video button "play"
# Then I see a range on slider
# Then I seek video to "5" seconds
# And I click video button "pause"
# And I reload the page
# And I click video button "play"
# Then I see video starts playing from "0:12" position
# 4
# Disabled 2/18/14 after intermittent failures in master
# Scenario: Check that position is stored on page refresh, position after start-end range
# Given I have created a Video component with subtitles
# And Make sure captions are closed
# And I edit the component
# And I open tab "Advanced"
# And I set value "00:00:12" to the field "Video Start Time"
# And I set value "00:00:24" to the field "Video Stop Time"
# And I save changes
# And I click video button "play"
# Then I see a range on slider
# Then I seek video to "30" seconds
# And I click video button "pause"
# And I reload the page
# And I click video button "play"
# Then I see video starts playing from "0:12" position

View File

@@ -1,321 +0,0 @@
# -*- coding: utf-8 -*-
# disable missing docstring
# pylint: disable=missing-docstring
import requests
from lettuce import world, step
from nose.tools import assert_true, assert_equal, assert_in, assert_not_equal
from terrain.steps import reload_the_page
from django.conf import settings
from common import upload_file, attach_file
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
DISPLAY_NAME = "Component Display Name"
NATIVE_LANGUAGES = {lang: label for lang, label in settings.LANGUAGES if len(lang) == 2}
LANGUAGES = {
lang: NATIVE_LANGUAGES.get(lang, display)
for lang, display in settings.ALL_LANGUAGES
}
LANGUAGES.update({
'table': 'Table of Contents'
})
TRANSLATION_BUTTONS = {
'add': '.metadata-video-translations .create-action',
'upload': '.metadata-video-translations .upload-action',
'download': '.metadata-video-translations .download-action',
'remove': '.metadata-video-translations .remove-action',
'clear': '.metadata-video-translations .setting-clear',
}
VIDEO_MENUS = {
'language': '.lang .menu',
}
class RequestHandlerWithSessionId(object):
def get(self, url):
"""
Sends a request.
"""
kwargs = dict()
session_id = [{i['name']:i['value']} for i in world.browser.cookies.all() if i['name'] == u'sessionid']
if session_id:
kwargs.update({
'cookies': session_id[0]
})
response = requests.get(url, **kwargs)
self.response = response
self.status_code = response.status_code
self.headers = response.headers
self.content = response.content
return self
def is_success(self):
"""
Returns `True` if the response was succeed, otherwise, returns `False`.
"""
if self.status_code < 400:
return True
return False
def check_header(self, name, value):
"""
Returns `True` if the response header exist and has appropriate value,
otherwise, returns `False`.
"""
if value in self.headers.get(name, ''):
return True
return False
def success_upload_file(filename):
upload_file(filename, sub_path="uploads/")
world.css_has_text('#upload_confirm', 'Success!')
world.is_css_not_present('.wrapper-modal-window-assetupload', wait_time=30)
def get_translations_container():
return world.browser.find_by_xpath('//label[text()="Transcript Languages"]/following-sibling::div')
def get_setting_container(lang_code):
try:
get_xpath = lambda value: './/descendant::a[@data-lang="{}" and contains(@class,"remove-setting")]/parent::*'.format(value)
return get_translations_container().find_by_xpath(get_xpath(lang_code)).first
except Exception:
return None
def get_last_dropdown():
return get_translations_container().find_by_xpath('.//descendant::select[last()]').last
def choose_option(dropdown, value):
dropdown.find_by_value(value)[0].click()
def choose_new_lang(lang_code):
world.css_click(TRANSLATION_BUTTONS['add'])
choose_option(get_last_dropdown(), lang_code)
assert_equal(get_last_dropdown().value, lang_code, "Option with provided value is not available or was not selected")
def open_menu(menu):
world.browser.execute_script("$('{selector}').parent().addClass('is-opened')".format(
selector=VIDEO_MENUS[menu]
))
@step('I have set "transcript display" to (.*)$')
def set_show_captions(step, setting):
# Prevent cookies from overriding course settings
world.browser.cookies.delete('hide_captions')
world.edit_component()
world.select_editor_tab('Advanced')
world.browser.select('Show Transcript', setting)
world.save_component()
@step('when I view the video it (.*) show the captions$')
def shows_captions(_step, show_captions):
world.wait_for_js_variable_truthy("Video")
world.wait(0.5)
if show_captions == 'does not':
assert_true(world.is_css_present('div.video.closed'))
else:
assert_true(world.is_css_not_present('div.video.closed'))
# Prevent cookies from overriding course settings
world.browser.cookies.delete('hide_captions')
world.browser.cookies.delete('current_player_mode')
@step('I see the correct video settings and default values$')
def correct_video_settings(_step):
expected_entries = [
# basic
[DISPLAY_NAME, 'Video', False],
['Default Video URL', 'http://youtu.be/3_yD_cEKoCk, , ', False],
# advanced
[DISPLAY_NAME, 'Video', False],
['Default Timed Transcript', '', False],
['Download Transcript Allowed', 'False', False],
['Downloadable Transcript URL', '', False],
['Show Transcript', 'True', False],
['Transcript Languages', '', False],
['Upload Handout', '', False],
['Video Available on Web Only', 'False', False],
['Video Download Allowed', 'False', False],
['Video File URLs', '', False],
['Video Start Time', '00:00:00', False],
['Video Stop Time', '00:00:00', False],
['YouTube ID', '3_yD_cEKoCk', False],
['YouTube ID for .75x speed', '', False],
['YouTube ID for 1.25x speed', '', False],
['YouTube ID for 1.5x speed', '', False]
]
world.verify_all_setting_entries(expected_entries)
@step('my video display name change is persisted on save$')
def video_name_persisted(step):
world.save_component()
reload_the_page(step)
world.wait_for_xmodule()
world.edit_component()
world.verify_setting_entry(
world.get_setting_entry(DISPLAY_NAME),
DISPLAY_NAME, '3.4', True
)
@step('I can modify video display name')
def i_can_modify_video_display_name(_step):
index = world.get_setting_entry_index(DISPLAY_NAME)
world.set_field_value(index, '3.4')
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, '3.4', True)
@step('I upload transcript file(?:s)?:$')
def upload_transcript(step):
input_hidden = '.metadata-video-translations .input'
# Number of previously added translations
initial_index = len(world.css_find(TRANSLATION_BUTTONS['download']))
if step.hashes:
for i, item in enumerate(step.hashes):
lang_code = item['lang_code']
filename = item['filename']
index = initial_index + i
choose_new_lang(lang_code)
expected_text = world.css_text(TRANSLATION_BUTTONS['upload'], index=index)
assert_equal(expected_text, "Upload")
assert_equal(world.css_find(input_hidden).last.value, "")
world.css_click(TRANSLATION_BUTTONS['upload'], index=index)
success_upload_file(filename)
world.wait_for_visible(TRANSLATION_BUTTONS['download'], index=index)
assert_equal(world.css_find(TRANSLATION_BUTTONS['upload']).last.text, "Replace")
assert_equal(world.css_find(input_hidden).last.value, filename)
@step('I try to upload transcript file "([^"]*)"$')
def try_to_upload_transcript(step, filename):
world.css_click(TRANSLATION_BUTTONS['upload'])
attach_file(filename, 'uploads/')
@step('I upload transcript file "([^"]*)" for "([^"]*)" language code$')
def upload_transcript_for_lang(step, filename, lang_code):
get_xpath = lambda value: './/div/a[contains(@class, "upload-action")]'.format(value)
container = get_setting_container(lang_code)
# If translation isn't uploaded, prepare drop-down and try to find container again
choose_new_lang(lang_code)
container = get_setting_container(lang_code)
button = container.find_by_xpath(get_xpath(lang_code)).first
button.click()
success_upload_file(filename)
@step('I replace transcript file for "([^"]*)" language code by "([^"]*)"$')
def replace_transcript_for_lang(step, lang_code, filename):
get_xpath = lambda value: './/div/a[contains(@class, "upload-action")]'.format(value)
container = get_setting_container(lang_code)
button = container.find_by_xpath(get_xpath(lang_code)).first
button.click()
success_upload_file(filename)
@step('I see validation error "([^"]*)"$')
def verify_validation_error_message(step, error_message):
assert_equal(world.css_text('#upload_error'), error_message)
@step('I can download transcript for "([^"]*)" language code, that contains text "([^"]*)"$')
def i_can_download_transcript(_step, lang_code, text):
MIME_TYPE = 'application/x-subrip'
get_xpath = lambda value: './/div/a[contains(text(), "Download")]'.format(value)
container = get_setting_container(lang_code)
assert container
button = container.find_by_xpath(get_xpath(lang_code)).first
url = button['href']
request = RequestHandlerWithSessionId()
assert_true(request.get(url).is_success())
assert_true(request.check_header('content-type', MIME_TYPE))
assert_in(text.encode('utf-8'), request.content)
@step('I remove translation for "([^"]*)" language code$')
def i_can_remove_transcript(_step, lang_code):
get_xpath = lambda value: './/descendant::a[@data-lang="{}" and contains(@class,"remove-setting")]'.format(value)
container = get_setting_container(lang_code)
assert container
button = container.find_by_xpath(get_xpath(lang_code)).first
button.click()
@step('I see translations for "([^"]*)"$')
def verify_translations(_step, lang_codes_string):
expected = [l.strip() for l in lang_codes_string.split(',')]
actual = [l['data-lang'] for l in world.css_find('.metadata-video-translations .remove-setting')]
assert_equal(set(expected), set(actual))
@step('I do not see translations$')
def no_translations(_step):
assert_true(world.is_css_not_present('.metadata-video-translations .remove-setting'))
@step('I confirm prompt$')
def confirm_prompt(_step):
world.confirm_studio_prompt()
@step('I (cannot )?choose "([^"]*)" language code$')
def i_choose_lang_code(_step, cannot, lang_code):
choose_option(get_last_dropdown(), lang_code)
if cannot:
assert_not_equal(get_last_dropdown().value, lang_code, "Option with provided value was selected, but shouldn't")
else:
assert_equal(get_last_dropdown().value, lang_code, "Option with provided value is not available or was not selected")
@step('I click button "([^"]*)"$')
def click_button(_step, button):
world.css_click(TRANSLATION_BUTTONS[button.lower()])
@step('video language menu has "([^"]*)" translations$')
def i_see_correct_langs(_step, langs):
menu_name = 'language'
open_menu(menu_name)
items = world.css_find(VIDEO_MENUS[menu_name] + ' li')
translations = {t.strip(): LANGUAGES[t.strip()] for t in langs.split(',')}
assert_equal(len(translations), len(items))
for lang_code, label in translations.items():
assert_true(any([i.text == label for i in items]))
assert_true(any([i['data-lang-code'] == lang_code for i in items]))
@step('video language with code "([^"]*)" at position "(\d+)"$')
def i_see_lang_at_position(_step, code, position):
menu_name = 'language'
open_menu(menu_name)
item = world.css_find(VIDEO_MENUS[menu_name] + ' li')[int(position)]
assert_equal(item['data-lang-code'], code)

View File

@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
# disable missing docstring
# pylint: disable=missing-docstring
from lettuce import world, step
from nose.tools import assert_true
from video_editor import RequestHandlerWithSessionId, success_upload_file
@step('I (?:upload|replace) handout file(?: by)? "([^"]*)"$')
def upload_handout(step, filename):
world.css_click('.wrapper-comp-setting.file-uploader .upload-action')
success_upload_file(filename)
@step('I can download handout file( in editor)? with mime type "([^"]*)"$')
def i_can_download_handout_with_mime_type(_step, is_editor, mime_type):
if is_editor:
selector = '.wrapper-comp-setting.file-uploader .download-action'
else:
selector = '.video-handout.video-download-button a'
button = world.css_find(selector).first
url = button['href']
request = RequestHandlerWithSessionId()
assert_true(request.get(url).is_success())
assert_true(request.check_header('content-type', mime_type))
@step('I clear handout$')
def clear_handout(_step):
world.css_click('.wrapper-comp-setting.file-uploader .setting-clear')
@step('I have created a Video component with handout file "([^"]*)"')
def create_video_with_handout(_step, filename):
_step.given('I have created a Video component')
_step.given('I edit the component')
_step.given('I open tab "Advanced"')
_step.given('I upload handout file "{0}"'.format(filename))

View File

@@ -90,6 +90,7 @@ from util.organizations_helpers import (
organizations_enabled,
)
from util.string_utils import _has_non_ascii_characters
from util.course_key_utils import from_string_or_404
from xmodule.contentstore.content import StaticContent
from xmodule.course_module import CourseFields
from xmodule.course_module import DEFAULT_START_DATE
@@ -868,10 +869,7 @@ def course_info_handler(request, course_key_string):
GET
html: return html for editing the course info handouts and updates.
"""
try:
course_key = CourseKey.from_string(course_key_string)
except InvalidKeyError:
raise Http404
course_key = from_string_or_404(course_key_string)
with modulestore().bulk_operations(course_key):
course_module = get_course_and_check_access(course_key, request.user)

View File

@@ -283,6 +283,17 @@ else:
DATABASES = AUTH_TOKENS['DATABASES']
MODULESTORE = convert_module_store_setting_if_needed(AUTH_TOKENS.get('MODULESTORE', MODULESTORE))
MODULESTORE_FIELD_OVERRIDE_PROVIDERS = ENV_TOKENS.get(
'MODULESTORE_FIELD_OVERRIDE_PROVIDERS',
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
)
XBLOCK_FIELD_DATA_WRAPPERS = ENV_TOKENS.get(
'XBLOCK_FIELD_DATA_WRAPPERS',
XBLOCK_FIELD_DATA_WRAPPERS
)
CONTENTSTORE = AUTH_TOKENS['CONTENTSTORE']
DOC_STORE_CONFIG = AUTH_TOKENS['DOC_STORE_CONFIG']
# Datadog for events!

View File

@@ -383,6 +383,9 @@ XBLOCK_MIXINS = (
XBLOCK_SELECT_FUNCTION = prefer_xmodules
# Paths to wrapper methods which should be applied to every XBlock's FieldData.
XBLOCK_FIELD_DATA_WRAPPERS = ()
############################ Modulestore Configuration ################################
MODULESTORE_BRANCH = 'draft-preferred'
@@ -417,6 +420,10 @@ MODULESTORE = {
}
}
# Modulestore-level field override providers. These field override providers don't
# require student context.
MODULESTORE_FIELD_OVERRIDE_PROVIDERS = ()
#################### Python sandbox ############################################
CODE_JAIL = {

View File

@@ -50,7 +50,7 @@
"moment": "js/vendor/moment.min",
"moment-with-locales": "js/vendor/moment-with-locales.min",
"text": 'js/vendor/requirejs/text',
"underscore": "js/vendor/underscore-min",
"underscore": "common/js/vendor/underscore",
"underscore.string": "js/vendor/underscore.string.min",
"backbone": "js/vendor/backbone-min",
"backbone-relational" : "js/vendor/backbone-relational.min",

View File

@@ -26,7 +26,7 @@ requirejs.config({
"moment": "xmodule_js/common_static/js/vendor/moment.min",
"moment-with-locales": "xmodule_js/common_static/js/vendor/moment-with-locales.min",
"text": "xmodule_js/common_static/js/vendor/requirejs/text",
"underscore": "xmodule_js/common_static/js/vendor/underscore-min",
"underscore": "xmodule_js/common_static/common/js/vendor/underscore",
"underscore.string": "xmodule_js/common_static/js/vendor/underscore.string.min",
"backbone": "xmodule_js/common_static/js/vendor/backbone-min",
"backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min",

View File

@@ -22,7 +22,7 @@ requirejs.config({
"datepair": "xmodule_js/common_static/js/vendor/timepicker/datepair",
"date": "xmodule_js/common_static/js/vendor/date",
"text": "xmodule_js/common_static/js/vendor/requirejs/text",
"underscore": "xmodule_js/common_static/js/vendor/underscore-min",
"underscore": "xmodule_js/common_static/common/js/vendor/underscore",
"underscore.string": "xmodule_js/common_static/js/vendor/underscore.string.min",
"backbone": "xmodule_js/common_static/js/vendor/backbone-min",
"backbone.associations": "xmodule_js/common_static/js/vendor/backbone-associations-min",

View File

@@ -0,0 +1 @@
../../common/static/edx-pattern-library

1
cms/static/edx-ui-toolkit Symbolic link
View File

@@ -0,0 +1 @@
../../common/static/edx-ui-toolkit

View File

@@ -129,9 +129,9 @@ function ($, _, Backbone, gettext,
if (event && event.preventDefault) { event.preventDefault(); }
var model = this.model;
var self = this;
var titleText = gettext('Delete "<%= signatoryName %>" from the list of signatories?');
var titleTextTemplate = _.template(gettext('Delete "<%= signatoryName %>" from the list of signatories?'));
var confirm = new PromptView.Warning({
title: _.template(titleText, {signatoryName: model.get('name')}),
title: titleTextTemplate({signatoryName: model.get('name')}),
message: gettext('This action cannot be undone.'),
actions: {
primary: {

View File

@@ -1605,6 +1605,7 @@
"Update post": "\u062a\u062d\u062f\u064a\u062b \u0627\u0644\u0645\u0646\u0634\u0648\u0631 ",
"Update response": "\u062a\u062d\u062f\u064a\u062b \u0627\u0644\u0631\u062f ",
"Update team.": "\u0639\u062f\u0651\u0644 \u0641\u0631\u064a\u0642.",
"Updating Tags": "\u062a\u062d\u062f\u064a\u062b \u0627\u0644\u0634\u064e\u0627\u0631\u0627\u062a",
"Updating with latest library content": "\u062c\u0627\u0631\u064a \u0627\u0644\u062a\u062d\u062f\u064a\u062b \u0645\u0639 \u0645\u0633\u062a\u062c\u062f\u0651\u0627\u062a \u0645\u062d\u062a\u0648\u0649 \u0627\u0644\u0645\u0643\u062a\u0628\u0629",
"Upgrade Deadline": "\u0627\u0644\u0645\u0648\u0639\u062f \u0627\u0644\u0646\u0647\u0627\u0626\u064a \u0644\u0644\u062a\u062d\u062f\u064a\u062b ",
"Upgrade to a Verified Certificate for %(courseName)s": "\u0642\u0645 \u0628\u0627\u0644\u062a\u0631\u0642\u064a\u0629 \u0644\u062a\u062d\u0635\u0644 \u0639\u0644\u0649 \u0634\u0647\u0627\u062f\u0629 \u0645\u0648\u062b\u0651\u0642\u0629 \u0644\u0644\u0645\u0633\u0627\u0642 %(courseName)s",

View File

@@ -301,7 +301,7 @@
"Cancel team updating.": "\u00c7\u00e4n\u00e7\u00e9l t\u00e9\u00e4m \u00fcpd\u00e4t\u00efng. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #",
"Cannot delete when in use by a unit": "\u00c7\u00e4nn\u00f6t d\u00e9l\u00e9t\u00e9 wh\u00e9n \u00efn \u00fcs\u00e9 \u00df\u00fd \u00e4 \u00fcn\u00eft \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442#",
"Cannot delete when in use by an experiment": "\u00c7\u00e4nn\u00f6t d\u00e9l\u00e9t\u00e9 wh\u00e9n \u00efn \u00fcs\u00e9 \u00df\u00fd \u00e4n \u00e9xp\u00e9r\u00efm\u00e9nt \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f #",
"Cannot drop more <% attrs.types %> than will assigned.": "\u00c7\u00e4nn\u00f6t dr\u00f6p m\u00f6r\u00e9 <% attrs.types %> th\u00e4n w\u00efll \u00e4ss\u00efgn\u00e9d. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f#",
"Cannot drop more <%= types %> assignments than are assigned.": "\u00c7\u00e4nn\u00f6t dr\u00f6p m\u00f6r\u00e9 <%= types %> \u00e4ss\u00efgnm\u00e9nts th\u00e4n \u00e4r\u00e9 \u00e4ss\u00efgn\u00e9d. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#",
"Caption": "\u00c7\u00e4pt\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c #",
"Caution: The last published version of this unit is live. By publishing changes you will change the student experience.": "\u00c7\u00e4\u00fct\u00ef\u00f6n: Th\u00e9 l\u00e4st p\u00fc\u00dfl\u00efsh\u00e9d v\u00e9rs\u00ef\u00f6n \u00f6f th\u00efs \u00fcn\u00eft \u00efs l\u00efv\u00e9. B\u00fd p\u00fc\u00dfl\u00efsh\u00efng \u00e7h\u00e4ng\u00e9s \u00fd\u00f6\u00fc w\u00efll \u00e7h\u00e4ng\u00e9 th\u00e9 st\u00fcd\u00e9nt \u00e9xp\u00e9r\u00ef\u00e9n\u00e7\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c#",
"Cell": "\u00c7\u00e9ll \u2c60'\u03c3\u044f\u0454\u043c \u03b9#",
@@ -385,7 +385,6 @@
"Cohorts Disabled": "\u00c7\u00f6h\u00f6rts D\u00efs\u00e4\u00dfl\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c#",
"Cohorts Enabled": "\u00c7\u00f6h\u00f6rts \u00c9n\u00e4\u00dfl\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#",
"Collapse All": "\u00c7\u00f6ll\u00e4ps\u00e9 \u00c0ll \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455#",
"Collapse All Sections": "\u00c7\u00f6ll\u00e4ps\u00e9 \u00c0ll S\u00e9\u00e7t\u00ef\u00f6ns \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #",
"Collapse Instructions": "\u00c7\u00f6ll\u00e4ps\u00e9 \u00ccnstr\u00fc\u00e7t\u00ef\u00f6ns \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #",
"Collapse discussion": "\u00c7\u00f6ll\u00e4ps\u00e9 d\u00efs\u00e7\u00fcss\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#",
"Collapse/Expand this %(xblock_type)s": "\u00c7\u00f6ll\u00e4ps\u00e9/\u00c9xp\u00e4nd th\u00efs %(xblock_type)s \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7#",
@@ -550,7 +549,6 @@
"Edit Team": "\u00c9d\u00eft T\u00e9\u00e4m \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142#",
"Edit Your Name": "\u00c9d\u00eft \u00dd\u00f6\u00fcr N\u00e4m\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442#",
"Edit post title": "\u00c9d\u00eft p\u00f6st t\u00eftl\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#",
"Edit section release date": "\u00c9d\u00eft s\u00e9\u00e7t\u00ef\u00f6n r\u00e9l\u00e9\u00e4s\u00e9 d\u00e4t\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#",
"Edit the name": "\u00c9d\u00eft th\u00e9 n\u00e4m\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9#",
"Edit this certificate?": "\u00c9d\u00eft th\u00efs \u00e7\u00e9rt\u00eff\u00ef\u00e7\u00e4t\u00e9? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2#",
"Editable": "\u00c9d\u00eft\u00e4\u00dfl\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#",
@@ -639,7 +637,6 @@
"Exception Granted": "\u00c9x\u00e7\u00e9pt\u00ef\u00f6n Gr\u00e4nt\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#",
"Exit full browser": "\u00c9x\u00eft f\u00fcll \u00dfr\u00f6ws\u00e9r \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#",
"Expand All": "\u00c9xp\u00e4nd \u00c0ll \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3#",
"Expand All Sections": "\u00c9xp\u00e4nd \u00c0ll S\u00e9\u00e7t\u00ef\u00f6ns \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#",
"Expand Instructions": "\u00c9xp\u00e4nd \u00ccnstr\u00fc\u00e7t\u00ef\u00f6ns \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#",
"Expand discussion": "\u00c9xp\u00e4nd d\u00efs\u00e7\u00fcss\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#",
"Explain if other.": "\u00c9xpl\u00e4\u00efn \u00eff \u00f6th\u00e9r. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#",
@@ -1085,7 +1082,6 @@
"Release Date:": "R\u00e9l\u00e9\u00e4s\u00e9 D\u00e4t\u00e9: \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9#",
"Release Status:": "R\u00e9l\u00e9\u00e4s\u00e9 St\u00e4t\u00fcs: \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#",
"Release Time in UTC:": "R\u00e9l\u00e9\u00e4s\u00e9 T\u00efm\u00e9 \u00efn \u00dbT\u00c7: \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #",
"Release date:": "R\u00e9l\u00e9\u00e4s\u00e9 d\u00e4t\u00e9: \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9#",
"Release:": "R\u00e9l\u00e9\u00e4s\u00e9: \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#",
"Released:": "R\u00e9l\u00e9\u00e4s\u00e9d: \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142#",
"Removal is in progress. To avoid errors, stay on this page until the process is complete.": "R\u00e9m\u00f6v\u00e4l \u00efs \u00efn pr\u00f6gr\u00e9ss. T\u00f6 \u00e4v\u00f6\u00efd \u00e9rr\u00f6rs, st\u00e4\u00fd \u00f6n th\u00efs p\u00e4g\u00e9 \u00fcnt\u00efl th\u00e9 pr\u00f6\u00e7\u00e9ss \u00efs \u00e7\u00f6mpl\u00e9t\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#",
@@ -1291,7 +1287,7 @@
"Task Type": "T\u00e4sk T\u00fdp\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142#",
"Task inputs": "T\u00e4sk \u00efnp\u00fcts \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f #",
"Teaching Assistant": "T\u00e9\u00e4\u00e7h\u00efng \u00c0ss\u00efst\u00e4nt \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442#",
"Team \"%(team)s\" successfully deleted.": "T\u00e9\u00e4m \"%(team)s\" s\u00fc\u00e7\u00e7\u00e9ssf\u00fcll\u00fd d\u00e9l\u00e9t\u00e9d. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454#",
"Team \"{team}\" successfully deleted.": "T\u00e9\u00e4m \"{team}\" s\u00fc\u00e7\u00e7\u00e9ssf\u00fcll\u00fd d\u00e9l\u00e9t\u00e9d. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454#",
"Team Description (Required) *": "T\u00e9\u00e4m D\u00e9s\u00e7r\u00efpt\u00ef\u00f6n (R\u00e9q\u00fc\u00efr\u00e9d) * \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2#",
"Team Details": "T\u00e9\u00e4m D\u00e9t\u00e4\u00efls \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455#",
"Team Name (Required) *": "T\u00e9\u00e4m N\u00e4m\u00e9 (R\u00e9q\u00fc\u00efr\u00e9d) * \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2#",
@@ -1818,7 +1814,6 @@
"with %(section_or_subsection)s": "w\u00efth %(section_or_subsection)s \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#",
"{browse_span_start}Browse teams in other topics{span_end} or {search_span_start}search teams{span_end} in this topic. If you still can't find a team to join, {create_span_start}create a new team in this topic{span_end}.": "{browse_span_start}Br\u00f6ws\u00e9 t\u00e9\u00e4ms \u00efn \u00f6th\u00e9r t\u00f6p\u00ef\u00e7s{span_end} \u00f6r {search_span_start}s\u00e9\u00e4r\u00e7h t\u00e9\u00e4ms{span_end} \u00efn th\u00efs t\u00f6p\u00ef\u00e7. \u00ccf \u00fd\u00f6\u00fc st\u00efll \u00e7\u00e4n't f\u00efnd \u00e4 t\u00e9\u00e4m t\u00f6 j\u00f6\u00efn, {create_span_start}\u00e7r\u00e9\u00e4t\u00e9 \u00e4 n\u00e9w t\u00e9\u00e4m \u00efn th\u00efs t\u00f6p\u00ef\u00e7{span_end}. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f\u03c3\u03b9\u2202\u0454\u03b7\u0442, \u0455\u03c5\u03b7\u0442 \u03b9\u03b7 \u00a2\u03c5\u0142\u03c1\u03b1 q\u03c5\u03b9 \u03c3\u0192\u0192\u03b9\u00a2\u03b9\u03b1 \u2202\u0454\u0455\u0454\u044f\u03c5\u03b7\u0442 \u043c\u03c3\u0142\u0142\u03b9\u0442 \u03b1\u03b7#",
"{email} is already on the {container} team. Recheck the email address if you want to add a new member.": "{email} \u00efs \u00e4lr\u00e9\u00e4d\u00fd \u00f6n th\u00e9 {container} t\u00e9\u00e4m. R\u00e9\u00e7h\u00e9\u00e7k th\u00e9 \u00e9m\u00e4\u00efl \u00e4ddr\u00e9ss \u00eff \u00fd\u00f6\u00fc w\u00e4nt t\u00f6 \u00e4dd \u00e4 n\u00e9w m\u00e9m\u00df\u00e9r. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#",
"{month}/{day}/{year} at {hour}:{minute} UTC": "{month}/{day}/{year} \u00e4t {hour}:{minute} \u00dbT\u00c7 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#",
"{numMoved} student was removed from {oldCohort}": [
"{numMoved} st\u00fcd\u00e9nt w\u00e4s r\u00e9m\u00f6v\u00e9d fr\u00f6m {oldCohort} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454#",
"{numMoved} st\u00fcd\u00e9nts w\u00e9r\u00e9 r\u00e9m\u00f6v\u00e9d fr\u00f6m {oldCohort} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442#"

View File

@@ -301,7 +301,7 @@
"Cancel team updating.": "\u023b\u0250n\u0254\u01ddl \u0287\u01dd\u0250\u026f ndd\u0250\u0287\u1d09n\u0183.",
"Cannot delete when in use by a unit": "\u023b\u0250nn\u00f8\u0287 d\u01ddl\u01dd\u0287\u01dd \u028d\u0265\u01ddn \u1d09n ns\u01dd b\u028e \u0250 nn\u1d09\u0287",
"Cannot delete when in use by an experiment": "\u023b\u0250nn\u00f8\u0287 d\u01ddl\u01dd\u0287\u01dd \u028d\u0265\u01ddn \u1d09n ns\u01dd b\u028e \u0250n \u01ddxd\u01dd\u0279\u1d09\u026f\u01ddn\u0287",
"Cannot drop more <% attrs.types %> than will assigned.": "\u023b\u0250nn\u00f8\u0287 d\u0279\u00f8d \u026f\u00f8\u0279\u01dd <% attrs.types %> \u0287\u0265\u0250n \u028d\u1d09ll \u0250ss\u1d09\u0183n\u01ddd.",
"Cannot drop more <%= types %> assignments than are assigned.": "\u023b\u0250nn\u00f8\u0287 d\u0279\u00f8d \u026f\u00f8\u0279\u01dd <%= types %> \u0250ss\u1d09\u0183n\u026f\u01ddn\u0287s \u0287\u0265\u0250n \u0250\u0279\u01dd \u0250ss\u1d09\u0183n\u01ddd.",
"Caption": "\u023b\u0250d\u0287\u1d09\u00f8n",
"Caution: The last published version of this unit is live. By publishing changes you will change the student experience.": "\u023b\u0250n\u0287\u1d09\u00f8n: \u0166\u0265\u01dd l\u0250s\u0287 dnbl\u1d09s\u0265\u01ddd \u028c\u01dd\u0279s\u1d09\u00f8n \u00f8\u025f \u0287\u0265\u1d09s nn\u1d09\u0287 \u1d09s l\u1d09\u028c\u01dd. \u0243\u028e dnbl\u1d09s\u0265\u1d09n\u0183 \u0254\u0265\u0250n\u0183\u01dds \u028e\u00f8n \u028d\u1d09ll \u0254\u0265\u0250n\u0183\u01dd \u0287\u0265\u01dd s\u0287nd\u01ddn\u0287 \u01ddxd\u01dd\u0279\u1d09\u01ddn\u0254\u01dd.",
"Cell": "\u023b\u01ddll",
@@ -385,7 +385,6 @@
"Cohorts Disabled": "\u023b\u00f8\u0265\u00f8\u0279\u0287s \u0110\u1d09s\u0250bl\u01ddd",
"Cohorts Enabled": "\u023b\u00f8\u0265\u00f8\u0279\u0287s \u0246n\u0250bl\u01ddd",
"Collapse All": "\u023b\u00f8ll\u0250ds\u01dd \u023all",
"Collapse All Sections": "\u023b\u00f8ll\u0250ds\u01dd \u023all S\u01dd\u0254\u0287\u1d09\u00f8ns",
"Collapse Instructions": "\u023b\u00f8ll\u0250ds\u01dd \u0197ns\u0287\u0279n\u0254\u0287\u1d09\u00f8ns",
"Collapse discussion": "\u023b\u00f8ll\u0250ds\u01dd d\u1d09s\u0254nss\u1d09\u00f8n",
"Collapse/Expand this %(xblock_type)s": "\u023b\u00f8ll\u0250ds\u01dd/\u0246xd\u0250nd \u0287\u0265\u1d09s %(xblock_type)s",
@@ -550,7 +549,6 @@
"Edit Team": "\u0246d\u1d09\u0287 \u0166\u01dd\u0250\u026f",
"Edit Your Name": "\u0246d\u1d09\u0287 \u024e\u00f8n\u0279 N\u0250\u026f\u01dd",
"Edit post title": "\u0246d\u1d09\u0287 d\u00f8s\u0287 \u0287\u1d09\u0287l\u01dd",
"Edit section release date": "\u0246d\u1d09\u0287 s\u01dd\u0254\u0287\u1d09\u00f8n \u0279\u01ddl\u01dd\u0250s\u01dd d\u0250\u0287\u01dd",
"Edit the name": "\u0246d\u1d09\u0287 \u0287\u0265\u01dd n\u0250\u026f\u01dd",
"Edit this certificate?": "\u0246d\u1d09\u0287 \u0287\u0265\u1d09s \u0254\u01dd\u0279\u0287\u1d09\u025f\u1d09\u0254\u0250\u0287\u01dd?",
"Editable": "\u0246d\u1d09\u0287\u0250bl\u01dd",
@@ -639,7 +637,6 @@
"Exception Granted": "\u0246x\u0254\u01ddd\u0287\u1d09\u00f8n \u01e4\u0279\u0250n\u0287\u01ddd",
"Exit full browser": "\u0246x\u1d09\u0287 \u025fnll b\u0279\u00f8\u028ds\u01dd\u0279",
"Expand All": "\u0246xd\u0250nd \u023all",
"Expand All Sections": "\u0246xd\u0250nd \u023all S\u01dd\u0254\u0287\u1d09\u00f8ns",
"Expand Instructions": "\u0246xd\u0250nd \u0197ns\u0287\u0279n\u0254\u0287\u1d09\u00f8ns",
"Expand discussion": "\u0246xd\u0250nd d\u1d09s\u0254nss\u1d09\u00f8n",
"Explain if other.": "\u0246xdl\u0250\u1d09n \u1d09\u025f \u00f8\u0287\u0265\u01dd\u0279.",
@@ -1085,7 +1082,6 @@
"Release Date:": "\u024c\u01ddl\u01dd\u0250s\u01dd \u0110\u0250\u0287\u01dd:",
"Release Status:": "\u024c\u01ddl\u01dd\u0250s\u01dd S\u0287\u0250\u0287ns:",
"Release Time in UTC:": "\u024c\u01ddl\u01dd\u0250s\u01dd \u0166\u1d09\u026f\u01dd \u1d09n \u0244\u0166\u023b:",
"Release date:": "\u024c\u01ddl\u01dd\u0250s\u01dd d\u0250\u0287\u01dd:",
"Release:": "\u024c\u01ddl\u01dd\u0250s\u01dd:",
"Released:": "\u024c\u01ddl\u01dd\u0250s\u01ddd:",
"Removal is in progress. To avoid errors, stay on this page until the process is complete.": "\u024c\u01dd\u026f\u00f8\u028c\u0250l \u1d09s \u1d09n d\u0279\u00f8\u0183\u0279\u01ddss. \u0166\u00f8 \u0250\u028c\u00f8\u1d09d \u01dd\u0279\u0279\u00f8\u0279s, s\u0287\u0250\u028e \u00f8n \u0287\u0265\u1d09s d\u0250\u0183\u01dd nn\u0287\u1d09l \u0287\u0265\u01dd d\u0279\u00f8\u0254\u01ddss \u1d09s \u0254\u00f8\u026fdl\u01dd\u0287\u01dd.",
@@ -1291,7 +1287,7 @@
"Task Type": "\u0166\u0250s\u029e \u0166\u028ed\u01dd",
"Task inputs": "\u0166\u0250s\u029e \u1d09ndn\u0287s",
"Teaching Assistant": "\u0166\u01dd\u0250\u0254\u0265\u1d09n\u0183 \u023ass\u1d09s\u0287\u0250n\u0287",
"Team \"%(team)s\" successfully deleted.": "\u0166\u01dd\u0250\u026f \"%(team)s\" sn\u0254\u0254\u01ddss\u025fnll\u028e d\u01ddl\u01dd\u0287\u01ddd.",
"Team \"{team}\" successfully deleted.": "\u0166\u01dd\u0250\u026f \"{team}\" sn\u0254\u0254\u01ddss\u025fnll\u028e d\u01ddl\u01dd\u0287\u01ddd.",
"Team Description (Required) *": "\u0166\u01dd\u0250\u026f \u0110\u01dds\u0254\u0279\u1d09d\u0287\u1d09\u00f8n (\u024c\u01ddbn\u1d09\u0279\u01ddd) *",
"Team Details": "\u0166\u01dd\u0250\u026f \u0110\u01dd\u0287\u0250\u1d09ls",
"Team Name (Required) *": "\u0166\u01dd\u0250\u026f N\u0250\u026f\u01dd (\u024c\u01ddbn\u1d09\u0279\u01ddd) *",
@@ -1818,7 +1814,6 @@
"with %(section_or_subsection)s": "\u028d\u1d09\u0287\u0265 %(section_or_subsection)s",
"{browse_span_start}Browse teams in other topics{span_end} or {search_span_start}search teams{span_end} in this topic. If you still can't find a team to join, {create_span_start}create a new team in this topic{span_end}.": "{browse_span_start}\u0243\u0279\u00f8\u028ds\u01dd \u0287\u01dd\u0250\u026fs \u1d09n \u00f8\u0287\u0265\u01dd\u0279 \u0287\u00f8d\u1d09\u0254s{span_end} \u00f8\u0279 {search_span_start}s\u01dd\u0250\u0279\u0254\u0265 \u0287\u01dd\u0250\u026fs{span_end} \u1d09n \u0287\u0265\u1d09s \u0287\u00f8d\u1d09\u0254. \u0197\u025f \u028e\u00f8n s\u0287\u1d09ll \u0254\u0250n'\u0287 \u025f\u1d09nd \u0250 \u0287\u01dd\u0250\u026f \u0287\u00f8 \u027e\u00f8\u1d09n, {create_span_start}\u0254\u0279\u01dd\u0250\u0287\u01dd \u0250 n\u01dd\u028d \u0287\u01dd\u0250\u026f \u1d09n \u0287\u0265\u1d09s \u0287\u00f8d\u1d09\u0254{span_end}.",
"{email} is already on the {container} team. Recheck the email address if you want to add a new member.": "{email} \u1d09s \u0250l\u0279\u01dd\u0250d\u028e \u00f8n \u0287\u0265\u01dd {container} \u0287\u01dd\u0250\u026f. \u024c\u01dd\u0254\u0265\u01dd\u0254\u029e \u0287\u0265\u01dd \u01dd\u026f\u0250\u1d09l \u0250dd\u0279\u01ddss \u1d09\u025f \u028e\u00f8n \u028d\u0250n\u0287 \u0287\u00f8 \u0250dd \u0250 n\u01dd\u028d \u026f\u01dd\u026fb\u01dd\u0279.",
"{month}/{day}/{year} at {hour}:{minute} UTC": "{month}/{day}/{year} \u0250\u0287 {hour}:{minute} \u0244\u0166\u023b",
"{numMoved} student was removed from {oldCohort}": [
"{numMoved} s\u0287nd\u01ddn\u0287 \u028d\u0250s \u0279\u01dd\u026f\u00f8\u028c\u01ddd \u025f\u0279\u00f8\u026f {oldCohort}",
"{numMoved} s\u0287nd\u01ddn\u0287s \u028d\u01dd\u0279\u01dd \u0279\u01dd\u026f\u00f8\u028c\u01ddd \u025f\u0279\u00f8\u026f {oldCohort}"

View File

@@ -301,7 +301,7 @@
"Cancel team updating.": "\u0630\u0634\u0631\u0630\u062b\u0645 \u0641\u062b\u0634\u0648 \u0639\u062d\u064a\u0634\u0641\u0647\u0631\u0644.",
"Cannot delete when in use by a unit": "\u0630\u0634\u0631\u0631\u062e\u0641 \u064a\u062b\u0645\u062b\u0641\u062b \u0635\u0627\u062b\u0631 \u0647\u0631 \u0639\u0633\u062b \u0632\u063a \u0634 \u0639\u0631\u0647\u0641",
"Cannot delete when in use by an experiment": "\u0630\u0634\u0631\u0631\u062e\u0641 \u064a\u062b\u0645\u062b\u0641\u062b \u0635\u0627\u062b\u0631 \u0647\u0631 \u0639\u0633\u062b \u0632\u063a \u0634\u0631 \u062b\u0637\u062d\u062b\u0642\u0647\u0648\u062b\u0631\u0641",
"Cannot drop more <% attrs.types %> than will assigned.": "\u0630\u0634\u0631\u0631\u062e\u0641 \u064a\u0642\u062e\u062d \u0648\u062e\u0642\u062b <% attrs.types %> \u0641\u0627\u0634\u0631 \u0635\u0647\u0645\u0645 \u0634\u0633\u0633\u0647\u0644\u0631\u062b\u064a.",
"Cannot drop more <%= types %> assignments than are assigned.": "\u0630\u0634\u0631\u0631\u062e\u0641 \u064a\u0642\u062e\u062d \u0648\u062e\u0642\u062b <%= types %> \u0634\u0633\u0633\u0647\u0644\u0631\u0648\u062b\u0631\u0641\u0633 \u0641\u0627\u0634\u0631 \u0634\u0642\u062b \u0634\u0633\u0633\u0647\u0644\u0631\u062b\u064a.",
"Caption": "\u0630\u0634\u062d\u0641\u0647\u062e\u0631",
"Caution: The last published version of this unit is live. By publishing changes you will change the student experience.": "\u0630\u0634\u0639\u0641\u0647\u062e\u0631: \u0641\u0627\u062b \u0645\u0634\u0633\u0641 \u062d\u0639\u0632\u0645\u0647\u0633\u0627\u062b\u064a \u062f\u062b\u0642\u0633\u0647\u062e\u0631 \u062e\u0628 \u0641\u0627\u0647\u0633 \u0639\u0631\u0647\u0641 \u0647\u0633 \u0645\u0647\u062f\u062b. \u0632\u063a \u062d\u0639\u0632\u0645\u0647\u0633\u0627\u0647\u0631\u0644 \u0630\u0627\u0634\u0631\u0644\u062b\u0633 \u063a\u062e\u0639 \u0635\u0647\u0645\u0645 \u0630\u0627\u0634\u0631\u0644\u062b \u0641\u0627\u062b \u0633\u0641\u0639\u064a\u062b\u0631\u0641 \u062b\u0637\u062d\u062b\u0642\u0647\u062b\u0631\u0630\u062b.",
"Cell": "\u0630\u062b\u0645\u0645",
@@ -385,7 +385,6 @@
"Cohorts Disabled": "\u0630\u062e\u0627\u062e\u0642\u0641\u0633 \u064a\u0647\u0633\u0634\u0632\u0645\u062b\u064a",
"Cohorts Enabled": "\u0630\u062e\u0627\u062e\u0642\u0641\u0633 \u062b\u0631\u0634\u0632\u0645\u062b\u064a",
"Collapse All": "\u0630\u062e\u0645\u0645\u0634\u062d\u0633\u062b \u0634\u0645\u0645",
"Collapse All Sections": "\u0630\u062e\u0645\u0645\u0634\u062d\u0633\u062b \u0634\u0645\u0645 \u0633\u062b\u0630\u0641\u0647\u062e\u0631\u0633",
"Collapse Instructions": "\u0630\u062e\u0645\u0645\u0634\u062d\u0633\u062b \u0647\u0631\u0633\u0641\u0642\u0639\u0630\u0641\u0647\u062e\u0631\u0633",
"Collapse discussion": "\u0630\u062e\u0645\u0645\u0634\u062d\u0633\u062b \u064a\u0647\u0633\u0630\u0639\u0633\u0633\u0647\u062e\u0631",
"Collapse/Expand this %(xblock_type)s": "\u0630\u062e\u0645\u0645\u0634\u062d\u0633\u062b/\u062b\u0637\u062d\u0634\u0631\u064a \u0641\u0627\u0647\u0633 %(xblock_type)s",
@@ -550,7 +549,6 @@
"Edit Team": "\u062b\u064a\u0647\u0641 \u0641\u062b\u0634\u0648",
"Edit Your Name": "\u062b\u064a\u0647\u0641 \u063a\u062e\u0639\u0642 \u0631\u0634\u0648\u062b",
"Edit post title": "\u062b\u064a\u0647\u0641 \u062d\u062e\u0633\u0641 \u0641\u0647\u0641\u0645\u062b",
"Edit section release date": "\u062b\u064a\u0647\u0641 \u0633\u062b\u0630\u0641\u0647\u062e\u0631 \u0642\u062b\u0645\u062b\u0634\u0633\u062b \u064a\u0634\u0641\u062b",
"Edit the name": "\u062b\u064a\u0647\u0641 \u0641\u0627\u062b \u0631\u0634\u0648\u062b",
"Edit this certificate?": "\u062b\u064a\u0647\u0641 \u0641\u0627\u0647\u0633 \u0630\u062b\u0642\u0641\u0647\u0628\u0647\u0630\u0634\u0641\u062b?",
"Editable": "\u062b\u064a\u0647\u0641\u0634\u0632\u0645\u062b",
@@ -639,7 +637,6 @@
"Exception Granted": "\u062b\u0637\u0630\u062b\u062d\u0641\u0647\u062e\u0631 \u0644\u0642\u0634\u0631\u0641\u062b\u064a",
"Exit full browser": "\u062b\u0637\u0647\u0641 \u0628\u0639\u0645\u0645 \u0632\u0642\u062e\u0635\u0633\u062b\u0642",
"Expand All": "\u062b\u0637\u062d\u0634\u0631\u064a \u0634\u0645\u0645",
"Expand All Sections": "\u062b\u0637\u062d\u0634\u0631\u064a \u0634\u0645\u0645 \u0633\u062b\u0630\u0641\u0647\u062e\u0631\u0633",
"Expand Instructions": "\u062b\u0637\u062d\u0634\u0631\u064a \u0647\u0631\u0633\u0641\u0642\u0639\u0630\u0641\u0647\u062e\u0631\u0633",
"Expand discussion": "\u062b\u0637\u062d\u0634\u0631\u064a \u064a\u0647\u0633\u0630\u0639\u0633\u0633\u0647\u062e\u0631",
"Explain if other.": "\u062b\u0637\u062d\u0645\u0634\u0647\u0631 \u0647\u0628 \u062e\u0641\u0627\u062b\u0642.",
@@ -1085,7 +1082,6 @@
"Release Date:": "\u0642\u062b\u0645\u062b\u0634\u0633\u062b \u064a\u0634\u0641\u062b:",
"Release Status:": "\u0642\u062b\u0645\u062b\u0634\u0633\u062b \u0633\u0641\u0634\u0641\u0639\u0633:",
"Release Time in UTC:": "\u0642\u062b\u0645\u062b\u0634\u0633\u062b \u0641\u0647\u0648\u062b \u0647\u0631 \u0639\u0641\u0630:",
"Release date:": "\u0642\u062b\u0645\u062b\u0634\u0633\u062b \u064a\u0634\u0641\u062b:",
"Release:": "\u0642\u062b\u0645\u062b\u0634\u0633\u062b:",
"Released:": "\u0642\u062b\u0645\u062b\u0634\u0633\u062b\u064a:",
"Removal is in progress. To avoid errors, stay on this page until the process is complete.": "\u0642\u062b\u0648\u062e\u062f\u0634\u0645 \u0647\u0633 \u0647\u0631 \u062d\u0642\u062e\u0644\u0642\u062b\u0633\u0633. \u0641\u062e \u0634\u062f\u062e\u0647\u064a \u062b\u0642\u0642\u062e\u0642\u0633, \u0633\u0641\u0634\u063a \u062e\u0631 \u0641\u0627\u0647\u0633 \u062d\u0634\u0644\u062b \u0639\u0631\u0641\u0647\u0645 \u0641\u0627\u062b \u062d\u0642\u062e\u0630\u062b\u0633\u0633 \u0647\u0633 \u0630\u062e\u0648\u062d\u0645\u062b\u0641\u062b.",
@@ -1291,7 +1287,7 @@
"Task Type": "\u0641\u0634\u0633\u0646 \u0641\u063a\u062d\u062b",
"Task inputs": "\u0641\u0634\u0633\u0646 \u0647\u0631\u062d\u0639\u0641\u0633",
"Teaching Assistant": "\u0641\u062b\u0634\u0630\u0627\u0647\u0631\u0644 \u0634\u0633\u0633\u0647\u0633\u0641\u0634\u0631\u0641",
"Team \"%(team)s\" successfully deleted.": "\u0641\u062b\u0634\u0648 \"%(team)s\" \u0633\u0639\u0630\u0630\u062b\u0633\u0633\u0628\u0639\u0645\u0645\u063a \u064a\u062b\u0645\u062b\u0641\u062b\u064a.",
"Team \"{team}\" successfully deleted.": "\u0641\u062b\u0634\u0648 \"{team}\" \u0633\u0639\u0630\u0630\u062b\u0633\u0633\u0628\u0639\u0645\u0645\u063a \u064a\u062b\u0645\u062b\u0641\u062b\u064a.",
"Team Description (Required) *": "\u0641\u062b\u0634\u0648 \u064a\u062b\u0633\u0630\u0642\u0647\u062d\u0641\u0647\u062e\u0631 (\u0642\u062b\u0636\u0639\u0647\u0642\u062b\u064a) *",
"Team Details": "\u0641\u062b\u0634\u0648 \u064a\u062b\u0641\u0634\u0647\u0645\u0633",
"Team Name (Required) *": "\u0641\u062b\u0634\u0648 \u0631\u0634\u0648\u062b (\u0642\u062b\u0636\u0639\u0647\u0642\u062b\u064a) *",
@@ -1818,7 +1814,6 @@
"with %(section_or_subsection)s": "\u0635\u0647\u0641\u0627 %(section_or_subsection)s",
"{browse_span_start}Browse teams in other topics{span_end} or {search_span_start}search teams{span_end} in this topic. If you still can't find a team to join, {create_span_start}create a new team in this topic{span_end}.": "{browse_span_start}\u0632\u0642\u062e\u0635\u0633\u062b \u0641\u062b\u0634\u0648\u0633 \u0647\u0631 \u062e\u0641\u0627\u062b\u0642 \u0641\u062e\u062d\u0647\u0630\u0633{span_end} \u062e\u0642 {search_span_start}\u0633\u062b\u0634\u0642\u0630\u0627 \u0641\u062b\u0634\u0648\u0633{span_end} \u0647\u0631 \u0641\u0627\u0647\u0633 \u0641\u062e\u062d\u0647\u0630. \u0647\u0628 \u063a\u062e\u0639 \u0633\u0641\u0647\u0645\u0645 \u0630\u0634\u0631'\u0641 \u0628\u0647\u0631\u064a \u0634 \u0641\u062b\u0634\u0648 \u0641\u062e \u062a\u062e\u0647\u0631, {create_span_start}\u0630\u0642\u062b\u0634\u0641\u062b \u0634 \u0631\u062b\u0635 \u0641\u062b\u0634\u0648 \u0647\u0631 \u0641\u0627\u0647\u0633 \u0641\u062e\u062d\u0647\u0630{span_end}.",
"{email} is already on the {container} team. Recheck the email address if you want to add a new member.": "{email} \u0647\u0633 \u0634\u0645\u0642\u062b\u0634\u064a\u063a \u062e\u0631 \u0641\u0627\u062b {container} \u0641\u062b\u0634\u0648. \u0642\u062b\u0630\u0627\u062b\u0630\u0646 \u0641\u0627\u062b \u062b\u0648\u0634\u0647\u0645 \u0634\u064a\u064a\u0642\u062b\u0633\u0633 \u0647\u0628 \u063a\u062e\u0639 \u0635\u0634\u0631\u0641 \u0641\u062e \u0634\u064a\u064a \u0634 \u0631\u062b\u0635 \u0648\u062b\u0648\u0632\u062b\u0642.",
"{month}/{day}/{year} at {hour}:{minute} UTC": "{month}/{day}/{year} \u0634\u0641 {hour}:{minute} \u0639\u0641\u0630",
"{numMoved} student was removed from {oldCohort}": [
"{numMoved} \u0633\u0641\u0639\u064a\u062b\u0631\u0641 \u0635\u0634\u0633 \u0642\u062b\u0648\u062e\u062f\u062b\u064a \u0628\u0642\u062e\u0648 {oldCohort}",
"{numMoved} \u0633\u0641\u0639\u064a\u062b\u0631\u0641\u0633 \u0635\u062b\u0642\u062b \u0642\u062b\u0648\u062e\u062f\u062b\u064a \u0628\u0642\u062e\u0648 {oldCohort}"

View File

@@ -649,6 +649,7 @@
"Error deleting entrance exam state for student '{student_id}'. Make sure student identifier is correct.": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u0432\u0441\u0442\u0443\u043f\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u044f \u0434\u043b\u044f '{student_id}'. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.",
"Error deleting student '<%= student_id %>'s state on problem '<%= problem_id %>'. Make sure that the problem and student identifiers are complete and correct.": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u043d\u0438\u044f '<%= problem_id %>' \u0434\u043b\u044f '<%= student_id %>'. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u044b \u0437\u0430\u0434\u0430\u043d\u0438\u044f \u0438 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f \u0432\u0432\u0435\u0434\u0435\u043d\u044b \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e.",
"Error enrolling/unenrolling users.": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438/\u043e\u0442\u043c\u0435\u043d\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439:",
"Error generating ORA data report. Please try again.": "\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043e\u0442\u0447\u0435\u0442\u0430 \u043f\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u044e \u0441 \u043e\u0442\u0432\u0435\u0442\u0430\u043c\u0438 \u0432 \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u0435 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.",
"Error generating grades. Please try again.": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0440\u0430\u0441\u0447\u0451\u0442\u0430 \u043e\u0446\u0435\u043d\u043e\u043a. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437.",
"Error generating list of students who may enroll. Please try again.": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0441\u043f\u0438\u0441\u043a\u0430 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u043a\u0443\u0440\u0441. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437.",
"Error generating problem grade report. Please try again.": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0433\u043e \u043e\u0446\u0435\u043d\u043e\u0447\u043d\u043e\u0433\u043e \u043b\u0438\u0441\u0442\u0430. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437.",

View File

@@ -66,9 +66,10 @@ var CourseGrader = Backbone.Model.extend({
else attrs.drop_count = intDropCount;
}
if (_.has(attrs, 'min_count') && _.has(attrs, 'drop_count') && !_.has(errors, 'min_count') && !_.has(errors, 'drop_count') && attrs.drop_count > attrs.min_count) {
errors.drop_count = _.template(
gettext("Cannot drop more <% attrs.types %> than will assigned."),
attrs, {variable: 'attrs'});
var template = _.template(
gettext("Cannot drop more <%= types %> assignments than are assigned.")
);
errors.drop_count = template({types: attrs.type});
}
if (!_.isEmpty(errors)) return errors;
}

View File

@@ -15,8 +15,7 @@ var FileUpload = Backbone.Model.extend({
validate: function(attrs, options) {
if(attrs.selectedFile && !this.checkTypeValidity(attrs.selectedFile)) {
return {
message: _.template(
gettext("Only <%= fileTypes %> files can be uploaded. Please select a file ending in <%= fileExtensions %> to upload."),
message: _.template(gettext("Only <%= fileTypes %> files can be uploaded. Please select a file ending in <%= fileExtensions %> to upload."))( // jshint ignore:line
this.formatValidTypes()
),
attributes: {selectedFile: true}
@@ -64,7 +63,7 @@ var FileUpload = Backbone.Model.extend({
}
var or = gettext('or');
var formatTypes = function(types) {
return _.template('<%= initial %> <%= or %> <%= last %>', {
return _.template('<%= initial %> <%= or %> <%= last %>')({
initial: _.initial(types).join(', '),
or: or,
last: _.last(types)

View File

@@ -359,12 +359,12 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "draggabilly",
makeDraggable: function (element, options) {
var draggable;
options = _.defaults({
type: null,
handleClass: null,
droppableClass: null,
parentLocationSelector: null,
refresh: null,
ensureChildrenRendered: null
type: undefined,
handleClass: undefined,
droppableClass: undefined,
parentLocationSelector: undefined,
refresh: undefined,
ensureChildrenRendered: undefined
}, options);
if ($(element).data('droppable-class') !== options.droppableClass) {

View File

@@ -67,7 +67,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/models/asset
ViewUtils.hideLoadingIndicator();
// Create the table
this.$el.html(_.template(asset_library_template, {typeData: this.typeData}));
this.$el.html(_.template(asset_library_template)({typeData: this.typeData}));
tableBody = this.$('#asset-table-body');
this.tableBody = tableBody;
this.pagingHeader = new PagingHeader({view: this, el: $('#asset-paging-header')});

View File

@@ -75,18 +75,18 @@ define([
},
getOutlineAnchorMessage: function () {
var message = gettext(
var message = _.escape(gettext(
/*
Translators: 'outlineAnchor' is an anchor pointing to
the course outline page.
*/
'This content group is not in use. Add a content group to any unit from the %(outlineAnchor)s.'
),
)),
anchor = str.sprintf(
'<a href="%(url)s" title="%(text)s">%(text)s</a>',
{
url: this.model.collection.parents[0].outlineUrl,
text: gettext('Course Outline')
text: _.escape(gettext('Course Outline'))
}
);

View File

@@ -1,3 +1,5 @@
/*global course */
define(["js/views/baseview", "underscore", "underscore.string", "jquery", "gettext", "js/models/uploads", "js/views/uploads"],
function(BaseView, _, str, $, gettext, FileUploadModel, UploadDialogView) {
_.str = str; // used in template
@@ -52,10 +54,8 @@ define(["js/views/baseview", "underscore", "underscore.string", "jquery", "gette
asset_path: this.$("input.chapter-asset-path").val()
});
var msg = new FileUploadModel({
title: _.template(
gettext("Upload a new PDF to “<%= name %>”"),
{name: window.course.escape('name')}
),
title: _.template(gettext("Upload a new PDF to “<%= name %>”"))(
{name: course.escape('name')}),
message: gettext("Please select a PDF file to upload."),
mimeTypes: ['application/pdf']
});

View File

@@ -54,8 +54,8 @@ define(['jquery', 'underscore', 'gettext', "js/views/baseview",
title: messages.alreadyMember.title,
message: _.template(
messages.alreadyMember.messageTpl,
{email: email, container: containerName},
{interpolate: /\{(.+?)}/g}
{interpolate: /\{(.+?)}/g})(
{email: email, container: containerName}
),
actions: {
primary: {
@@ -140,7 +140,9 @@ define(['jquery', 'underscore', 'gettext', "js/views/baseview",
roles = _.object(_.pluck(this.roles, 'key'), _.pluck(this.roles, "name")),
adminRoleCount = this.getAdminRoleCount(),
viewHelpers = {
format: function (template, data) { return _.template(template, data, {interpolate: /\{(.+?)}/g}); }
format: function (template, data) {
return _.template(template, {interpolate: /\{(.+?)}/g})(data);
}
};
for (var i = 0; i < this.users.length; i++) {
var user = this.users[i],
@@ -284,8 +286,8 @@ define(['jquery', 'underscore', 'gettext', "js/views/baseview",
title: self.messages.deleteUser.title,
message: _.template(
self.messages.deleteUser.messageTpl,
{email: email, container: self.containerName},
{interpolate: /\{(.+?)}/g}
{interpolate: /\{(.+?)}/g})(
{email: email, container: self.containerName}
),
actions: {
primary: {

View File

@@ -1,242 +0,0 @@
define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "common/js/components/views/feedback_notification",
"js/utils/cancel_on_escape", "js/utils/date_utils", "js/utils/module", "common/js/components/utils/view_utils"],
function (domReady, $, ui, _, gettext, NotificationView, CancelOnEscape,
DateUtils, ModuleUtils, ViewUtils) {
var modalSelector = '.edit-section-publish-settings';
var toggleSections = function(e) {
e.preventDefault();
var $section = $('.courseware-section');
var $button = $(this);
var $labelCollapsed = $('<i class="icon fa fa-arrow-up"></i> <span class="label">' +
gettext('Collapse All Sections') + '</span>');
var $labelExpanded = $('<i class="icon fa fa-arrow-down"></i> <span class="label">' +
gettext('Expand All Sections') + '</span>');
var buttonLabel = $button.hasClass('is-activated') ? $labelCollapsed : $labelExpanded;
$button.toggleClass('is-activated').html(buttonLabel);
if ($button.hasClass('is-activated')) {
$section.addClass('collapsed');
// first child in order to avoid the icons on the subsection lists which are not in the first child
$section.find('header .expand-collapse').removeClass('collapse').addClass('expand');
} else {
$section.removeClass('collapsed');
// first child in order to avoid the icons on the subsection lists which are not in the first child
$section.find('header .expand-collapse').removeClass('expand').addClass('collapse');
}
};
var toggleSubmodules = function(e) {
e.preventDefault();
$(this).toggleClass('expand collapse');
$(this).closest('.is-collapsible, .window').toggleClass('collapsed');
};
var closeModalNew = function (e) {
if (e) {
e.preventDefault();
}
$('body').removeClass('modal-window-is-shown');
$('.edit-section-publish-settings').removeClass('is-shown');
};
var editSectionPublishDate = function (e) {
e.preventDefault();
var $modal = $(modalSelector);
$modal.attr('data-locator', $(this).attr('data-locator'));
$modal.find('.start-date').val($(this).attr('data-date'));
$modal.find('.start-time').val($(this).attr('data-time'));
if ($modal.find('.start-date').val() == '' && $modal.find('.start-time').val() == '') {
$modal.find('.save-button').hide();
}
$modal.find('.section-name').html('"' + $(this).closest('.courseware-section').find('.section-name-span').text() + '"');
$('body').addClass('modal-window-is-shown');
$('.edit-section-publish-settings').addClass('is-shown');
};
var saveSetSectionScheduleDate = function (e) {
e.preventDefault();
var datetime = DateUtils.getDate(
$('.edit-section-publish-settings .start-date'),
$('.edit-section-publish-settings .start-time')
);
var locator = $(modalSelector).attr('data-locator');
analytics.track('Edited Section Release Date', {
'course': course_location_analytics,
'id': locator,
'start': datetime
});
var saving = new NotificationView.Mini({
title: gettext("Saving")
});
saving.show();
// call into server to commit the new order
$.ajax({
url: ModuleUtils.getUpdateUrl(locator),
type: "PUT",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({
'metadata': {
'start': datetime
}
})
}).success(function() {
var pad2 = function(number) {
// pad a number to two places: useful for formatting months, days, hours, etc
// when displaying a date/time
return (number < 10 ? '0' : '') + number;
};
var $thisSection = $('.courseware-section[data-locator="' + locator + '"]');
var html = _.template(
'<span class="published-status">' +
'<strong>' + gettext("Release date:") + '&nbsp;</strong>' +
gettext("{month}/{day}/{year} at {hour}:{minute} UTC") +
'</span>' +
'<a href="#" class="edit-release-date action" data-date="{month}/{day}/{year}" data-time="{hour}:{minute}" data-locator="{locator}"><i class="icon fa fa-time"></i> <span class="sr">' +
gettext("Edit section release date") +
'</span></a>',
{year: datetime.getUTCFullYear(), month: pad2(datetime.getUTCMonth() + 1), day: pad2(datetime.getUTCDate()),
hour: pad2(datetime.getUTCHours()), minute: pad2(datetime.getUTCMinutes()),
locator: locator},
{interpolate: /\{(.+?)\}/g});
$thisSection.find('.section-published-date').html(html);
saving.hide();
closeModalNew();
});
};
var addNewSection = function (e) {
e.preventDefault();
$(e.target).addClass('disabled');
var $newSection = $($('#new-section-template').html());
var $cancelButton = $newSection.find('.new-section-name-cancel');
$('.courseware-overview').prepend($newSection);
$newSection.find('.new-section-name').focus().select();
$newSection.find('.section-name-form').bind('submit', saveNewSection);
$cancelButton.bind('click', cancelNewSection);
CancelOnEscape($cancelButton);
};
var saveNewSection = function (e) {
e.preventDefault();
var $saveButton = $(this).find('.new-section-name-save');
var parent = $saveButton.data('parent');
var category = $saveButton.data('category');
var display_name = $(this).find('.new-section-name').val();
analytics.track('Created a Section', {
'course': course_location_analytics,
'display_name': display_name
});
$.postJSON(ModuleUtils.getUpdateUrl(), {
'parent_locator': parent,
'category': category,
'display_name': display_name
},
function(data) {
if (data.locator != undefined) location.reload();
});
};
var cancelNewSection = function (e) {
e.preventDefault();
$('.new-courseware-section-button').removeClass('disabled');
$(this).parents('section.new-section').remove();
};
var addNewSubsection = function (e) {
e.preventDefault();
var $section = $(this).closest('.courseware-section');
var $newSubsection = $($('#new-subsection-template').html());
$section.find('.subsection-list > ol').append($newSubsection);
$section.find('.new-subsection-name-input').focus().select();
var $saveButton = $newSubsection.find('.new-subsection-name-save');
var $cancelButton = $newSubsection.find('.new-subsection-name-cancel');
var parent = $(this).parents("section.courseware-section").data("locator");
$saveButton.data('parent', parent);
$saveButton.data('category', $(this).data('category'));
$newSubsection.find('.new-subsection-form').bind('submit', saveNewSubsection);
$cancelButton.bind('click', cancelNewSubsection);
CancelOnEscape($cancelButton);
};
var saveNewSubsection = function (e) {
e.preventDefault();
var parent = $(this).find('.new-subsection-name-save').data('parent');
var category = $(this).find('.new-subsection-name-save').data('category');
var display_name = $(this).find('.new-subsection-name-input').val();
analytics.track('Created a Subsection', {
'course': course_location_analytics,
'display_name': display_name
});
$.postJSON(ModuleUtils.getUpdateUrl(), {
'parent_locator': parent,
'category': category,
'display_name': display_name
},
function(data) {
if (data.locator != undefined) {
location.reload();
}
});
};
var cancelNewSubsection = function (e) {
e.preventDefault();
$(this).parents('li.courseware-subsection').remove();
};
domReady(function() {
// toggling overview section details
$(function() {
if ($('.courseware-section').length > 0) {
$('.toggle-button-sections').addClass('is-shown');
}
});
$('.toggle-button-sections').bind('click', toggleSections);
$('.expand-collapse').bind('click', toggleSubmodules);
$('.dismiss-button').bind('click', ViewUtils.deleteNotificationHandler(function () {
$('.wrapper-alert-announcement').remove();
}));
var $body = $('body');
$body.on('click', '.section-published-date .edit-release-date', editSectionPublishDate);
$body.on('click', '.edit-section-publish-settings .action-save', saveSetSectionScheduleDate);
$body.on('click', '.edit-section-publish-settings .action-cancel', closeModalNew);
$('.new-courseware-section-button').bind('click', addNewSection);
$('.new-subsection-item').bind('click', addNewSubsection);
});
return {
saveSetSectionScheduleDate: saveSetSectionScheduleDate
};
});

View File

@@ -22,9 +22,7 @@ define(["underscore", "backbone", "gettext", "text!templates/paging-header.under
currentPage = collection.currentPage,
lastPage = collection.totalPages - 1,
messageHtml = this.messageHtml();
this.$el.html(_.template(paging_header_template, {
messageHtml: messageHtml
}));
this.$el.html(_.template(paging_header_template)({ messageHtml: messageHtml}));
this.$(".previous-page-link").toggleClass("is-disabled", currentPage === 0).attr('aria-disabled', currentPage === 0);
this.$(".next-page-link").toggleClass("is-disabled", currentPage === lastPage).attr('aria-disabled', currentPage === lastPage);
return this;

View File

@@ -27,10 +27,9 @@ define(["js/views/baseview", "underscore", "gettext", "common/js/components/view
},
confirmDelete: function(e) {
if(e && e.preventDefault) { e.preventDefault(); }
var textbook = this.model, collection = this.model.collection;
var msg = new PromptView.Warning({
title: _.template(
gettext("Delete “<%= name %>”?"),
var textbook = this.model;
new PromptView.Warning({
title: _.template(gettext("Delete “<%= name %>”?"))(
{name: textbook.get('name')}
),
message: gettext("Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed."),

View File

@@ -18,7 +18,9 @@ function($, Backbone, _, Utils) {
uploadTpl: '#file-upload',
initialize: function () {
_.bindAll(this);
_.bindAll(this,
'changeHandler', 'clickHandler', 'xhrResetProgressBar', 'xhrProgressHandler', 'xhrCompleteHandler'
);
this.file = false;
this.render();

View File

@@ -29,7 +29,9 @@ function($, Backbone, _, Utils, FileUploader, gettext) {
},
initialize: function () {
_.bindAll(this);
_.bindAll(this,
'importHandler', 'replaceHandler', 'chooseHandler', 'useExistingHandler', 'showError', 'hideError'
);
this.component_locator = this.$el.closest('[data-locator]').data('locator');

View File

@@ -35,7 +35,7 @@ lib_paths:
- xmodule_js/common_static/js/vendor/jquery-ui.min.js
- xmodule_js/common_static/js/vendor/jquery.cookie.js
- xmodule_js/common_static/js/vendor/jquery.simulate.js
- xmodule_js/common_static/js/vendor/underscore-min.js
- xmodule_js/common_static/common/js/vendor/underscore.js
- xmodule_js/common_static/js/vendor/underscore.string.min.js
- xmodule_js/common_static/js/vendor/backbone-min.js
- xmodule_js/common_static/js/vendor/backbone-associations-min.js
@@ -78,6 +78,8 @@ src_paths:
- js/certificates
- js/factories
- common/js
- edx-pattern-library/js
- edx-ui-toolkit/js
# Paths to spec (test) JavaScript files
# We should define the custom path mapping in /coffee/spec/main.coffee as well e.g. certificates etc.

View File

@@ -34,7 +34,7 @@ lib_paths:
- xmodule_js/common_static/js/vendor/jquery.min.js
- xmodule_js/common_static/js/vendor/jquery-ui.min.js
- xmodule_js/common_static/js/vendor/jquery.cookie.js
- xmodule_js/common_static/js/vendor/underscore-min.js
- xmodule_js/common_static/common/js/vendor/underscore.js
- xmodule_js/common_static/js/vendor/underscore.string.min.js
- xmodule_js/common_static/js/vendor/backbone-min.js
- xmodule_js/common_static/js/vendor/backbone-associations-min.js
@@ -73,6 +73,8 @@ src_paths:
- js/utils
- js/views
- common/js
- edx-pattern-library/js
- edx-ui-toolkit/js
# Paths to spec (test) JavaScript files
spec_paths:

View File

@@ -1,4 +1,8 @@
<%! from django.utils.translation import ugettext as _ %>
<%page expression_filter="h"/>
<%!
from django.utils.translation import ugettext as _
from openedx.core.djangolib.markup import Text, HTML
%>
<%inherit file="base.html" />
<%block name="title">${_("Page Not Found")}</%block>
<%block name="bodyclass">view-util util-404</%block>
@@ -11,12 +15,14 @@
<h1 class="title title-1">${_("Page not found")}</h1>
</header>
<article class="content-primary" role="main">
<p>${_('The page that you were looking for was not found.')}
${_('Go back to the {homepage} or let us know about any pages that may have been moved at {email}.').format(
homepage='<a href="/">homepage</a>',
email=u'<a href="mailto:{address}">{address}</a>'.format(
address=settings.TECH_SUPPORT_EMAIL,
))}
<p>
${_('The page that you were looking for was not found.')}
${Text(_('Go back to the {homepage} or let us know about any pages that may have been moved at {email}.')).format(
homepage=HTML('<a href="/">homepage</a>'),
email=HTML('<a href="mailto:{address}">{address}</a>'.format(
address=Text(settings.TECH_SUPPORT_EMAIL)
))
)}
</p>
</article>
</section>

View File

@@ -1,6 +1,14 @@
<%! from django.utils.translation import ugettext as _ %>
<%page expression_filter="h"/>
<%!
from openedx.core.djangolib.markup import Text, HTML
from django.utils.translation import ugettext as _
%>
<%inherit file="base.html" />
<%block name="title">${_("{studio_name} Server Error").format(studio_name=settings.STUDIO_SHORT_NAME)}</%block>
<%block name="title">
${Text(_("{studio_name} Server Error")).format(
studio_name=Text(settings.STUDIO_SHORT_NAME)
)}
</%block>
<%block name="bodyclass">view-util util-500</%block>
<%block name="content">
@@ -8,20 +16,24 @@
<section class="content">
<header>
<h1 class="title title-1">
${_("The {studio_name} servers encountered an error").format(
studio_name=u"<em>{studio_name}</em>".format(studio_name=settings.STUDIO_SHORT_NAME)
${Text(_(u"The {em_start}{studio_name}{em_end} servers encountered an error")).format(
em_start=HTML('<em>'),
em_end=HTML('</em>'),
studio_name=Text(settings.STUDIO_SHORT_NAME),
)}
</h1>
</header>
<article class="content-primary" role="main">
<p>
${_("An error occurred in {studio_name} and the page could not be loaded. Please try again in a few moments.").format(studio_name=settings.STUDIO_SHORT_NAME)}
${Text(_("An error occurred in {studio_name} and the page could not be loaded. Please try again in a few moments.")).format(
studio_name=Text(settings.STUDIO_SHORT_NAME),
)}
${_("We've logged the error and our staff is currently working to resolve this error as soon as possible.")}
${_('If the problem persists, please email us at {email_link}.').format(
email_link=u'<a href="mailto:{email_address}">{email_address}</a>'.format(
email_address=settings.TECH_SUPPORT_EMAIL,
)
)}
${Text(_(u'If the problem persists, please email us at {email_link}.')).format(
email_link=HTML(u'<a href="mailto:{email_address}">{email_address}</a>'.format(
email_address=Text(settings.TECH_SUPPORT_EMAIL),
))
)}
</p>
</article>
</section>

View File

@@ -1,10 +1,18 @@
<%! from django.utils.translation import ugettext as _ %>
<%!
from openedx.core.djangolib.markup import Text
from django.utils.translation import ugettext as _
%>
<%page expression_filter="h"/>
<%inherit file="base.html" />
<%block name="content">
<div class="wrapper-mast wrapper sr">
<header class="mast">
<h1 class="page-header">${_("{studio_name} Account Activation").format(studio_name=settings.STUDIO_SHORT_NAME)}</h1>
<h1 class="page-header">
${Text(_("{studio_name} Account Activation")).format(
studio_name=Text(settings.STUDIO_SHORT_NAME),
)}
</h1>
</header>
</div>
@@ -17,14 +25,20 @@
<div class="msg">
<h1 class="title">${_("Your account activation is complete!")}</h1>
<div class="copy">
<p>${_("Thank you for activating your account. You may now sign in and start using {studio_name} to author courses.").format(studio_name=settings.STUDIO_NAME)}</p>
<p>
${Text(_("Thank you for activating your account. You may now sign in and start using {studio_name} to author courses.")).format(
studio_name=Text(settings.STUDIO_NAME)
)}
</p>
</div>
</div>
<ul class="list-actions">
<li class="action-item">
<a href="/signin" class="action-primary action-signin">
${_("Sign into {studio_name}").format(studio_name=settings.STUDIO_SHORT_NAME)}
${Text(_("Sign into {studio_name}")).format(
studio_name=Text(settings.STUDIO_SHORT_NAME)
)}
</a>
</li>
</ul>

View File

@@ -1,10 +1,18 @@
<%! from django.utils.translation import ugettext as _ %>
<%!
from openedx.core.djangolib.markup import HTML, Text
from django.utils.translation import ugettext as _
%>
<%page expression_filter="h"/>
<%inherit file="base.html" />
<%block name="content">
<div class="wrapper-mast wrapper sr">
<header class="mast">
<h1 class="page-header">${_("{studio_name} Account Activation").format(studio_name=settings.STUDIO_SHORT_NAME)}</h1>
<h1 class="page-header">
${Text(_("{studio_name} Account Activation")).format(
studio_name=Text(settings.STUDIO_SHORT_NAME)
)}
</h1>
</header>
</div>
@@ -18,10 +26,14 @@
<h1 class="title">${_("Your account activation is invalid")}</h1>
<div class="copy">
<p>${_("We're sorry. Something went wrong with your activation. Check to make sure the URL you went to was correct, as e-mail programs will sometimes split it into two lines.")}</p>
<p>${_("If you still have issues, contact {platform_name} Support. In the meantime, you can also return to {link_start}the {studio_name} homepage.{link_end}").format(
platform_name=settings.PLATFORM_NAME, studio_name=settings.STUDIO_NAME,
link_start='<a href="/">', link_end="</a>"
)}</p>
<p>
${Text(_("If you still have issues, contact {platform_name} Support. In the meantime, you can also return to {link_start}the {studio_name} homepage.{link_end}")).format(
platform_name=Text(settings.PLATFORM_NAME),
studio_name=Text(settings.STUDIO_NAME),
link_start=HTML('<a href="/">'),
link_end=HTML('</a>')
)}
</p>
</div>
</div>
</div>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<%inherit file="../base.html" />
<%block name="content">
You're in dev mode!

View File

@@ -3,7 +3,7 @@
<h3 class="title">
<a href="#" class="toggle group-toggle <% if (showContentGroupUsages){ print('hide'); } else { print('show'); } %>-groups">
<i class="ui-toggle-expansion icon fa fa-caret-<% if (showContentGroupUsages){ print('down'); } else { print('right'); } %>"></i>
<%= name %>
<%- name %>
</a>
</h3>
</header>
@@ -11,28 +11,28 @@
<ol class="collection-info group-configuration-info group-configuration-info-<% if(showContentGroupUsages){ print('block'); } else { print('inline'); } %>">
<% if (!_.isUndefined(id)) { %>
<li class="group-configuration-id"
><span class="group-configuration-label"><%= gettext('ID') %>: </span
><span class="group-configuration-value"><%= id %></span
><span class="group-configuration-label"><%- gettext('ID') %>: </span
><span class="group-configuration-value"><%- id %></span
></li>
<% } %>
<% if (!showContentGroupUsages) { %>
<li class="group-configuration-usage-count">
<%= usageCountMessage %>
<%- usageCountMessage %>
</li>
<% } %>
</ol>
<ul class="actions group-configuration-actions">
<li class="action action-edit">
<button class="edit"><i class="icon fa fa-pencil"></i> <%= gettext("Edit") %></button>
<button class="edit"><i class="icon fa fa-pencil"></i> <%- gettext("Edit") %></button>
</li>
<% if (_.isEmpty(usage)) { %>
<li class="action action-delete wrapper-delete-button" data-tooltip="<%= gettext('Delete') %>">
<button class="delete action-icon"><i class="icon fa fa-trash-o"></i><span><%= gettext("Delete") %></span></button>
<li class="action action-delete wrapper-delete-button" data-tooltip="<%- gettext('Delete') %>">
<button class="delete action-icon"><i class="icon fa fa-trash-o"></i><span><%- gettext("Delete") %></span></button>
</li>
<% } else { %>
<li class="action action-delete wrapper-delete-button" data-tooltip="<%= gettext('Cannot delete when in use by a unit') %>">
<button class="delete action-icon is-disabled" aria-disabled="true" disabled="disabled"><i class="icon fa fa-trash-o"></i><span><%= gettext("Delete") %></span></button>
<li class="action action-delete wrapper-delete-button" data-tooltip="<%- gettext('Cannot delete when in use by a unit') %>">
<button class="delete action-icon is-disabled" aria-disabled="true" disabled="disabled"><i class="icon fa fa-trash-o"></i><span><%- gettext("Delete") %></span></button>
</li>
<% } %>
</ul>
@@ -41,17 +41,18 @@
<% if (showContentGroupUsages) { %>
<div class="collection-references wrapper-group-configuration-usages">
<% if (!_.isEmpty(usage)) { %>
<h4 class="intro group-configuration-usage-text"><%= gettext('This content group is used in:') %></h4>
<h4 class="intro group-configuration-usage-text"><%- gettext('This content group is used in:') %></h4>
<ol class="usage group-configuration-usage">
<% _.each(usage, function(unit) { %>
<li class="usage-unit group-configuration-usage-unit">
<p><a href=<%= unit.url %> ><%= unit.label %></a></p>
<p><a href=<%- unit.url %> ><%- unit.label %></a></p>
</li>
<% }) %>
</ol>
<% } else { %>
<p class="group-configuration-usage-text">
<%= outlineAnchorMessage %>
<!-- This contains an anchor link and therefore can't be escaped. -->
<%= outlineAnchorMessage %>
</p>
<% } %>
</div>

View File

@@ -3,7 +3,7 @@
<h3 class="title group-configuration-title">
<a href="#" class="toggle group-toggle <% if(showGroups){ print('hide'); } else { print('show'); } %>-groups">
<i class="ui-toggle-expansion icon fa fa-caret-<% if(showGroups){ print('down'); } else { print('right'); } %>"></i>
<%= name %>
<%- name %>
</a>
</h3>
</header>
@@ -11,20 +11,20 @@
<ol class="collection-info group-configuration-info group-configuration-info-<% if(showGroups){ print('block'); } else { print('inline'); } %>">
<% if (!_.isUndefined(id)) { %>
<li class="group-configuration-id"
><span class="group-configuration-label"><%= gettext('ID') %>: </span
><span class="group-configuration-value"><%= id %></span
><span class="group-configuration-label"><%- gettext('ID') %>: </span
><span class="group-configuration-value"><%- id %></span
></li>
<% } %>
<% if (showGroups) { %>
<li class="collection-description group-configuration-description">
<%= description %>
<%- description %>
</li>
<% } else { %>
<li class="group-configuration-groups-count">
<%= groupsCountMessage %>
<%- groupsCountMessage %>
</li>
<li class="group-configuration-usage-count">
<%= usageCountMessage %>
<%- usageCountMessage %>
</li>
<% } %>
</ol>
@@ -34,23 +34,23 @@
<ol class="collection-items groups groups-<%= index %>">
<% groups.each(function(group, groupIndex) { %>
<li class="item group group-<%= groupIndex %>">
<span class="name group-name"><%= group.get('name') %></span>
<span class="meta group-allocation"><%= allocation %>%</span>
<span class="name group-name"><%- group.get('name') %></span>
<span class="meta group-allocation"><%- allocation %>%</span>
</li>
<% }) %>
</ol>
<% } %>
<ul class="actions group-configuration-actions">
<li class="action action-edit">
<button class="edit"><i class="icon fa fa-pencil"></i> <%= gettext("Edit") %></button>
<button class="edit"><i class="icon fa fa-pencil"></i> <%- gettext("Edit") %></button>
</li>
<% if (_.isEmpty(usage)) { %>
<li class="action action-delete wrapper-delete-button">
<button class="delete action-icon"><i class="icon fa fa-trash-o"></i><span><%= gettext("Delete") %></span></button>
<button class="delete action-icon"><i class="icon fa fa-trash-o"></i><span><%- gettext("Delete") %></span></button>
</li>
<% } else { %>
<li class="action action-delete wrapper-delete-button" data-tooltip="<%= gettext('Cannot delete when in use by an experiment') %>">
<button class="delete action-icon is-disabled" aria-disabled="true"><i class="icon fa fa-trash-o"></i><span><%= gettext("Delete") %></span></button>
<li class="action action-delete wrapper-delete-button" data-tooltip="<%- gettext('Cannot delete when in use by an experiment') %>">
<button class="delete action-icon is-disabled" aria-disabled="true"><i class="icon fa fa-trash-o"></i><span><%- gettext("Delete") %></span></button>
</li>
<% } %>
</ul>
@@ -58,11 +58,11 @@
<% if(showGroups) { %>
<div class="collection-references wrapper-group-configuration-usages">
<% if (!_.isEmpty(usage)) { %>
<h4 class="intro group-configuration-usage-text"><%= gettext('This Group Configuration is used in:') %></h4>
<h4 class="intro group-configuration-usage-text"><%- gettext('This Group Configuration is used in:') %></h4>
<ol class="usage group-configuration-usage">
<% _.each(usage, function(unit) { %>
<li class="usage-unit group-configuration-usage-unit">
<p><a href=<%= unit.url %> ><%= unit.label %></a></p>
<p><a href=<%- unit.url %> ><%- unit.label %></a></p>
<% if (unit.validation) { %>
<p>
<% if (unit.validation.type === 'warning') { %>
@@ -71,7 +71,7 @@
<i class="icon fa fa-exclamation-circle"></i>
<% } %>
<span class="usage-validation-message group-configuration-validation-message">
<%= unit.validation.text %>
<%- unit.validation.text %>
</span>
</p>
<% } %>
@@ -80,6 +80,7 @@
</ol>
<% } else { %>
<p class="group-configuration-usage-text">
<!-- This contains an anchor link and therefore can't be escaped. -->
<%= outlineAnchorMessage %>
</p>
<% } %>

View File

@@ -2,17 +2,17 @@
<div class "error-header">
<p>
<%= _.template(
ngettext(
"There was {strong_start}{num_errors} validation error{strong_end} while trying to save the course settings in the database.",
"There were {strong_start}{num_errors} validation errors{strong_end} while trying to save the course settings in the database.",
num_errors
),
{
strong_start:'<strong>',
num_errors: num_errors,
strong_end: '</strong>'
},
{interpolate: /\{(.+?)\}/g})%>
ngettext(
"There was {strong_start}{num_errors} validation error{strong_end} while trying to save the course settings in the database.",
"There were {strong_start}{num_errors} validation errors{strong_end} while trying to save the course settings in the database.",
num_errors
),
{interpolate: /\{(.+?)\}/g})(
{
strong_start:'<strong>',
num_errors: num_errors,
strong_end: '</strong>'
})%>
<%= gettext("Please check the following validation feedbacks and reflect them in your course settings:")%></p>
</div>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<%inherit file="../../base.html" />
<%block name="view_notes">

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="wrapper wrapper-modal-window wrapper-modal-window-edit-xblock" aria-labelledby="modal-window-title" role="dialog">
<div class="modal-window-overlay"></div>
<div class="modal-window confirm modal-med modal-type-html modal-editor" style="top: 50px; left: 400px;" tabindex="-1" aria-labelledby="modal-window-title">

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-section" aria-labelledby="modal-window-title" role="dialog">

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-subsection" aria-labelledby="modal-window-title" role="dialog">

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-unit" aria-labelledby="modal-window-title" role="dialog">

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="add-section add-item">
<a href="#" class="button button-new" data-category="" data-parent="" data-default-name="New Section">
<i class="icon fa fa-plus"></i> New Section

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="add-subsection add-item">
<a href="#" class="button button-new" data-category="" data-parent="" data-default-name="New Subsection">
<i class="icon fa fa-plus"></i> New Subsection

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="add-unit add-item">
<a href="#" class="button button-new" data-category="" data-parent="" data-default-name="New Unit">
<i class="icon fa fa-plus"></i> New Unit

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="section-header">
<h3 class="section-header-details ui-toggle-expansion" title="Collapse/Expand this Section">
<i class="icon fa fa-caret-down icon"></i>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="section-header">
<h3 class="section-header-details ui-toggle-expansion" title="Collapse/Expand this Section">
<i class="icon fa fa-caret-down icon"></i>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="status-grading">
<p>
<span class="sr status-grading-label">Graded as:</span>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="status-message">
<i class="icon fa fa-warning"></i>
<p class="status-message-copy">Critical error</p>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="status-message">
<i class="icon fa fa-lock"></i>
<p class="status-message-copy">Contains Staff only content</p>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="status-message">
<i class="icon fa fa-file-o"></i>
<p class="status-message-copy">Unpublished change(s) to live content</p>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="status-message">
<i class="icon fa fa-file-o"></i>
<p class="status-message-copy">Unpublished unit(s) will not be released</p>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="status-release">
<p>
<span class="sr status-release-label">Release Status:</span>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="status-release">
<p>
<span class="sr status-release-label">Release Status:</span>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="status-release">
<p>
<span class="sr status-release-label">Release Status:</span>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="status-release">
<p>
<span class="sr status-release-label">Release Status:</span>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="status-release">
<p>
<span class="sr status-release-label">Release Status:</span>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="status-release">
<p>
<span class="sr status-release-label">Release Status:</span>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="subsection-header">
<h3 class="subsection-header-details ui-toggle-expansion" title="Collapse/Expand this Subsection">
<i class="icon fa fa-caret-down icon"></i>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="subsection-header">
<h3 class="subsection-header-details ui-toggle-expansion" title="Collapse/Expand this Subsection">
<i class="icon fa fa-caret-down icon"></i>

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<div class="unit-header">
<h3 class="unit-header-details">
<span class="unit-title item-title"><a class="unit-url" href="">Unit Name</a></span>

View File

@@ -1 +1,2 @@
<%page expression_filter="h"/>
<span class="draggable-drop-indicator draggable-drop-indicator-after"><i class="icon fa fa-caret-right"></i></span>

View File

@@ -1 +1,2 @@
<%page expression_filter="h"/>
<span class="draggable-drop-indicator draggable-drop-indicator-before"><i class="icon fa fa-caret-right"></i></span>

View File

@@ -1 +1,2 @@
<%page expression_filter="h"/>
<span class="draggable-drop-indicator draggable-drop-indicator-initial"><i class="icon fa fa-caret-right"></i></span>

View File

@@ -1 +1,2 @@
<%page expression_filter="h"/>
<%include file="metadata-edit.html" />

View File

@@ -1,3 +1,6 @@
<%page expression_filter="h"/>
<%! from openedx.core.djangolib.js_utils import js_escaped_string %>
% if settings.CMS_SEGMENT_KEY:
<!-- begin segment footer -->
<script type="text/javascript">
@@ -6,10 +9,10 @@
// screws up RequireJS' JQuery initialization.
var onLoadCallback = function() {
analytics.identify(
"${user.id}",
"${ user.id | n, js_escaped_string }",
{
email: "${user.email}",
username: "${user.username}"
email: "${ user.email | n, js_escaped_string }",
username: "${ user.username | n, js_escaped_string }"
},
{
integrations: {

View File

@@ -1,4 +1,5 @@
<%! from django.template.defaultfilters import escapejs %>
<%page expression_filter="h"/>
<%! from openedx.core.djangolib.js_utils import js_escaped_string %>
% if context_course:
<%
@@ -11,12 +12,12 @@
<script type="text/javascript">
// if inside course, inject the course location into the JS namespace
%if context_course:
var course_location_analytics = "${locator | escapejs}";
var course_location_analytics = "${ locator | n, js_escaped_string }";
%endif
// Asynchronously load Segment's analytics.js library
!function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.1.0";
analytics.load("${ settings.CMS_SEGMENT_KEY }");
analytics.load("${ settings.CMS_SEGMENT_KEY | n, js_escaped_string }");
analytics.page();
}}();
// Note: user tracking moved to segment-io-footer.html
@@ -26,7 +27,7 @@
<!-- dummy Segment -->
<script type="text/javascript">
%if context_course:
var course_location_analytics = "${locator | escapejs}";
var course_location_analytics = "${ locator | n, js_escaped_string }";
%endif
var analytics = {
"track": function() {}

View File

@@ -1,3 +1,4 @@
<%page expression_filter="h"/>
<section class="sequence-edit">
<%include file="metadata-edit.html" />
</section>

View File

@@ -9,6 +9,7 @@ from datetime import datetime, timedelta
from urlparse import urljoin
import pytz
from markupsafe import escape
from mock import Mock, patch
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from pyquery import PyQuery as pq
@@ -404,7 +405,7 @@ class DashboardTest(ModuleStoreTestCase):
self.assertNotIn('Add Certificate to LinkedIn', response.content)
response_url = 'http://www.linkedin.com/profile/add?_ed='
self.assertNotContains(response, response_url)
self.assertNotContains(response, escape(response_url))
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@patch.dict('django.conf.settings.FEATURES', {'CERTIFICATES_HTML_VIEW': False})
@@ -452,7 +453,7 @@ class DashboardTest(ModuleStoreTestCase):
'pfCertificationUrl=www.edx.org&'
'source=o'
)
self.assertContains(response, expected_url)
self.assertContains(response, escape(expected_url))
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)

View File

@@ -0,0 +1,30 @@
"""
Convenience methods for working with course objects
"""
from django.http import Http404
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
def from_string_or_404(course_key_string):
"""
Gets CourseKey from the string passed as parameter.
Parses course key from string(containing course key) or raises 404 if the string's format is invalid.
Arguments:
course_key_string(str): It is string containing the course key
Returns:
CourseKey: A key that uniquely identifies a course
Raises:
HTTP404: A 404 not found exception will be thrown if course_key_string's format is invalid
"""
try:
course_key = CourseKey.from_string(course_key_string)
except InvalidKeyError:
raise Http404
return course_key

View File

@@ -0,0 +1,32 @@
"""
Tests for util.course_key_utils
"""
from nose.tools import assert_equals, assert_raises # pylint: disable=no-name-in-module
from util.course_key_utils import from_string_or_404
from opaque_keys.edx.keys import CourseKey
from django.http import Http404
def test_from_string_or_404():
#testing with split style course keys
assert_raises(
Http404,
from_string_or_404,
"/some.invalid.key/course-v1:TTT+CS01+2015_T0"
)
assert_equals(
CourseKey.from_string("course-v1:TTT+CS01+2015_T0"),
from_string_or_404("course-v1:TTT+CS01+2015_T0")
)
#testing with mongo style course keys
assert_raises(
Http404,
from_string_or_404,
"/some.invalid.key/TTT/CS01/2015_T0"
)
assert_equals(
CourseKey.from_string("TTT/CS01/2015_T0"),
from_string_or_404("TTT/CS01/2015_T0")
)

View File

@@ -45,7 +45,7 @@ lib_paths:
- common_static/js/vendor/jquery.ui.draggable.js
- common_static/js/vendor/jquery.cookie.js
- common_static/js/vendor/json2.js
- common_static/js/vendor/underscore-min.js
- common_static/common/js/vendor/underscore.js
- common_static/js/vendor/backbone-min.js
- common_static/js/vendor/jquery.leanModal.js
- common_static/js/vendor/CodeMirror/codemirror.js

View File

@@ -361,9 +361,14 @@ function (VideoPlayer) {
describe('onSeek', function () {
beforeEach(function () {
// jasmine.Clock can't be used to fake out debounce with newer versions of underscore
spyOn(_, 'debounce').andCallFake(function (func) {
return function () {
func.apply(this, arguments);
};
});
state = jasmine.initializePlayer();
state.videoEl = $('video, iframe');
jasmine.Clock.useMock();
spyOn(state.videoPlayer, 'duration').andReturn(120);
});
@@ -384,9 +389,6 @@ function (VideoPlayer) {
spyOn(state.videoPlayer, 'stopTimer');
spyOn(state.videoPlayer, 'runTimer');
state.videoPlayer.seekTo(10);
// Video player uses _.debounce (with a wait time in 300 ms) for seeking.
// That's why we have to do this tick(300).
jasmine.Clock.tick(300);
expect(state.videoPlayer.currentTime).toBe(10);
expect(state.videoPlayer.stopTimer).toHaveBeenCalled();
expect(state.videoPlayer.runTimer).toHaveBeenCalled();
@@ -399,9 +401,6 @@ function (VideoPlayer) {
state.videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 30 }
);
// Video player uses _.debounce (with a wait time in 300 ms) for seeking.
// That's why we have to do this tick(300).
jasmine.Clock.tick(300);
expect(state.videoPlayer.currentTime).toBe(30);
expect(state.videoPlayer.player.seekTo).toHaveBeenCalledWith(30, true);
});
@@ -413,9 +412,6 @@ function (VideoPlayer) {
state.videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 30 }
);
// Video player uses _.debounce (with a wait time in 300 ms) for seeking.
// That's why we have to do this tick(300).
jasmine.Clock.tick(300);
expect(state.videoPlayer.currentTime).toBe(30);
expect(state.videoPlayer.updatePlayTime).toHaveBeenCalledWith(30, true);
});
@@ -426,17 +422,11 @@ function (VideoPlayer) {
state.videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 20 }
);
// Video player uses _.debounce (with a wait time in 300 ms) for seeking.
// That's why we have to do this tick(300).
jasmine.Clock.tick(300);
state.videoPlayer.pause();
expect(state.videoPlayer.currentTime).toBe(20);
state.videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 10 }
);
// Video player uses _.debounce (with a wait time in 300 ms) for seeking.
// That's why we have to do this tick(300).
jasmine.Clock.tick(300);
expect(state.videoPlayer.currentTime).toBe(10);
});

View File

@@ -13,7 +13,7 @@ var setupFullScreenModal = function() {
"largeALT": smallImageObject.attr('alt'),
"largeSRC": largeImageSRC
};
var html = _.template($("#image-modal-tpl").text(), data);
var html = _.template($("#image-modal-tpl").text())(data);
$(this).replaceWith(html);
}
});

View File

@@ -1160,7 +1160,7 @@ class ModuleStoreReadBase(BulkOperationsMixin, ModuleStoreRead):
contentstore=None,
doc_store_config=None, # ignore if passed up
metadata_inheritance_cache_subsystem=None, request_cache=None,
xblock_mixins=(), xblock_select=None, disabled_xblock_types=(), # pylint: disable=bad-continuation
xblock_mixins=(), xblock_select=None, xblock_field_data_wrappers=(), disabled_xblock_types=(), # pylint: disable=bad-continuation
# temporary parms to enable backward compatibility. remove once all envs migrated
db=None, collection=None, host=None, port=None, tz_aware=True, user=None, password=None,
# allow lower level init args to pass harmlessly
@@ -1177,6 +1177,7 @@ class ModuleStoreReadBase(BulkOperationsMixin, ModuleStoreRead):
self.request_cache = request_cache
self.xblock_mixins = xblock_mixins
self.xblock_select = xblock_select
self.xblock_field_data_wrappers = xblock_field_data_wrappers
self.disabled_xblock_types = disabled_xblock_types
self.contentstore = contentstore

View File

@@ -120,11 +120,25 @@ def load_function(path):
"""
Load a function by name.
path is a string of the form "path.to.module.function"
returns the imported python object `function` from `path.to.module`
Arguments:
path: String of the form 'path.to.module.function'. Strings of the form
'path.to.module:Class.function' are also valid.
Returns:
The imported object 'function'.
"""
module_path, _, name = path.rpartition('.')
return getattr(import_module(module_path), name)
if ':' in path:
module_path, _, method_path = path.rpartition(':')
module = import_module(module_path)
class_name, method_name = method_path.split('.')
_class = getattr(module, class_name)
function = getattr(_class, method_name)
else:
module_path, _, name = path.rpartition('.')
function = getattr(import_module(module_path), name)
return function
def create_modulestore_instance(
@@ -179,12 +193,15 @@ def create_modulestore_instance(
else:
disabled_xblock_types = ()
xblock_field_data_wrappers = [load_function(path) for path in settings.XBLOCK_FIELD_DATA_WRAPPERS]
return class_(
contentstore=content_store,
metadata_inheritance_cache_subsystem=metadata_inheritance_cache,
request_cache=request_cache,
xblock_mixins=getattr(settings, 'XBLOCK_MIXINS', ()),
xblock_select=getattr(settings, 'XBLOCK_SELECT_FUNCTION', None),
xblock_field_data_wrappers=xblock_field_data_wrappers,
disabled_xblock_types=disabled_xblock_types,
doc_store_config=doc_store_config,
i18n_service=i18n_service or ModuleI18nService(),

View File

@@ -218,6 +218,17 @@ class InheritanceMixin(XBlockMixin):
default=False
)
self_paced = Boolean(
display_name=_('Self Paced'),
help=_(
'Set this to "true" to mark this course as self-paced. Self-paced courses do not have '
'due dates for assignments, and students can progress through the course at any rate before '
'the course ends.'
),
default=False,
scope=Scope.settings
)
def compute_inherited_metadata(descriptor):
"""Given a descriptor, traverse all of its descendants and do metadata

View File

@@ -12,27 +12,24 @@ structure:
}
"""
import pymongo
import sys
import logging
import copy
from datetime import datetime
from importlib import import_module
import logging
import pymongo
import re
import sys
from uuid import uuid4
from bson.son import SON
from datetime import datetime
from contracts import contract, new_contract
from fs.osfs import OSFS
from mongodb_proxy import autoretry_read
from opaque_keys.edx.keys import UsageKey, CourseKey, AssetKey
from opaque_keys.edx.locations import Location, BlockUsageLocator, SlashSeparatedCourseKey
from opaque_keys.edx.locator import CourseLocator, LibraryLocator
from path import Path as path
from pytz import UTC
from contracts import contract, new_contract
from importlib import import_module
from opaque_keys.edx.keys import UsageKey, CourseKey, AssetKey
from opaque_keys.edx.locations import Location, BlockUsageLocator
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.locator import CourseLocator, LibraryLocator
from xblock.core import XBlock
from xblock.exceptions import InvalidScopeError
from xblock.fields import Scope, ScopeIds, Reference, ReferenceList, ReferenceValueDict
@@ -54,6 +51,7 @@ from xmodule.modulestore.xml import CourseLocationManager
from xmodule.modulestore.store_utilities import DETACHED_XBLOCK_TYPES
from xmodule.services import SettingsService
log = logging.getLogger(__name__)
new_contract('CourseKey', CourseKey)
@@ -318,6 +316,9 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
).replace(tzinfo=UTC)
module._edit_info['published_by'] = raw_metadata.get('published_by')
for wrapper in self.modulestore.xblock_field_data_wrappers:
module._field_data = wrapper(module, module._field_data) # pylint: disable=protected-access
# decache any computed pending field settings
module.save()
return module

View File

@@ -1,5 +1,6 @@
import sys
import logging
from contracts import contract, new_contract
from fs.osfs import OSFS
from lazy import lazy
@@ -7,6 +8,7 @@ from xblock.runtime import KvsFieldData, KeyValueStore
from xblock.fields import ScopeIds
from xblock.core import XBlock
from opaque_keys.edx.locator import BlockUsageLocator, LocalId, CourseLocator, LibraryLocator, DefinitionLocator
from xmodule.library_tools import LibraryToolsService
from xmodule.mako_module import MakoDescriptorSystem
from xmodule.error_module import ErrorDescriptor
@@ -263,6 +265,10 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
module.update_version = edit_info.update_version
module.source_version = edit_info.source_version
module.definition_locator = DefinitionLocator(block_key.type, definition_id)
for wrapper in self.modulestore.xblock_field_data_wrappers:
module._field_data = wrapper(module, module._field_data) # pylint: disable=protected-access
# decache any pending field settings
module.save()

View File

@@ -108,6 +108,8 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
# To make sure that js files are called in proper order we use numerical
# index. We do that to avoid issues that occurs in tests.
module = __name__.replace('.video_module', '', 2)
#TODO: For each of the following, ensure that any generated html is properly escaped.
js = {
'js': [
resource_string(module, 'js/src/video/00_component.js'),

View File

@@ -23,6 +23,14 @@ class @DiscussionViewSpecHelper
}
$.extend(thread, props)
@checkVoteClasses = (view) ->
view.render()
display_button = view.$el.find(".display-vote")
expect(display_button.hasClass("is-hidden")).toBe(true)
action_button = view.$el.find(".action-vote")
# Check that inline css is not applied to the ".action-vote"
expect(action_button).not.toHaveAttr('style','display: inline; ');
@expectVoteRendered = (view, model, user) ->
button = view.$el.find(".action-vote")
expect(button.hasClass("is-checked")).toBe(user.voted(model))

View File

@@ -30,6 +30,9 @@ describe "ThreadResponseShowView", ->
it "renders the vote state correctly", ->
DiscussionViewSpecHelper.checkRenderVote(@view, @comment)
it "check the vote classes after renders", ->
DiscussionViewSpecHelper.checkVoteClasses(@view)
it "votes correctly via click", ->
DiscussionViewSpecHelper.checkUpvote(@view, @comment, @user, $.Event("click"))
@@ -115,7 +118,7 @@ describe "ThreadResponseShowView", ->
expect(@view.$(".posted-details").text()).not.toMatch("marked as answer")
it "allows a moderator to mark an answer in a question thread", ->
DiscussionUtil.loadRoles({"Moderator": parseInt(window.user.id)})
DiscussionUtil.loadRoles({"Moderator": [parseInt(window.user.id)]})
@thread.set({
"thread_type": "question",
"user_id": (parseInt(window.user.id) + 1).toString()

View File

@@ -1,28 +0,0 @@
class @DiscussionFilter
# TODO: this helper class duplicates functionality in DiscussionThreadListView.filterTopics
# for use with a very similar category dropdown in the New Post form. The two menus' implementations
# should be merged into a single reusable view.
@filterDrop: (e) ->
$drop = $(e.target).parents('.topic-menu-wrapper')
query = $(e.target).val()
$items = $drop.find('.topic-menu-item')
if(query.length == 0)
$items.removeClass('hidden')
return;
$items.addClass('hidden')
$items.each (i) ->
path = $(this).parents(".topic-menu-item").andSelf()
pathTitles = path.children(".topic-title").map((i, elem) -> $(elem).text()).get()
pathText = pathTitles.join(" / ").toLowerCase()
if query.split(" ").every((term) -> pathText.search(term.toLowerCase()) != -1)
$(this).removeClass('hidden')
# show children
$(this).find('.topic-menu-item').removeClass('hidden');
# show parents
$(this).parents('.topic-menu-item').removeClass('hidden');

View File

@@ -138,7 +138,6 @@ if Backbone?
closed: (closed) ->
@updateButtonState(".action-close", closed)
@$(".post-label-closed").toggleClass("is-hidden", not closed)
@$(".action-vote").toggle(not closed)
@$(".display-vote").toggle(closed)
})

View File

@@ -19,7 +19,7 @@
this.threadType = this.model.get('thread_type');
this.topicId = this.model.get('commentable_id');
this.context = options.context || 'course';
_.bindAll(this);
_.bindAll(this, 'updateHandler', 'cancelHandler');
return this;
},

View File

@@ -39,9 +39,9 @@ if Backbone?
@searchAlertCollection.on "add", (searchAlert) =>
content = _.template(
$("#search-alert-template").html(),
$("#search-alert-template").html())(
{'message': searchAlert.attributes.message, 'cid': searchAlert.cid}
)
)
@$(".search-alerts").append(content)
@$("#search-alert-" + searchAlert.cid + " a.dismiss").bind "click", searchAlert, (event) =>
@removeSearchAlert(event.data.cid)
@@ -491,7 +491,7 @@ if Backbone?
message = interpolate(
_.escape(gettext('Show posts by %(username)s.')),
{"username":
_.template('<a class="link-jump" href="<%= url %>"><%- username %></a>', {
_.template('<a class="link-jump" href="<%= url %>"><%- username %></a>')({
url: DiscussionUtil.urlFor("user_profile", response.users[0].id),
username: response.users[0].username
})

View File

@@ -6,7 +6,7 @@
'click .post-topic-button': 'toggleTopicDropdown',
'click .topic-menu-wrapper': 'handleTopicEvent',
'click .topic-filter-label': 'ignoreClick',
'keyup .topic-filter-input': this.DiscussionFilter.filterDrop
'keyup .topic-filter-input': 'filterDrop'
},
attributes: {
@@ -17,7 +17,9 @@
this.course_settings = options.course_settings;
this.currentTopicId = options.topicId;
this.maxNameWidth = 100;
_.bindAll(this);
_.bindAll(this,
'toggleTopicDropdown', 'handleTopicEvent', 'hideTopicDropdown', 'ignoreClick'
);
return this;
},
@@ -34,7 +36,7 @@
render: function() {
var context = _.clone(this.course_settings.attributes);
context.topics_html = this.renderCategoryMap(this.course_settings.get('category_map'));
this.$el.html(_.template($('#topic-template').html(), context));
this.$el.html(_.template($('#topic-template').html())(context));
this.dropdownButton = this.$('.post-topic-button');
this.topicMenu = this.$('.topic-menu-wrapper');
this.selectedTopic = this.$('.js-selected-topic');
@@ -187,6 +189,38 @@
}
}
return name;
},
// TODO: this helper class duplicates functionality in DiscussionThreadListView.filterTopics
// for use with a very similar category dropdown in the New Post form. The two menus' implementations
// should be merged into a single reusable view.
filterDrop: function (e) {
var $drop, $items, query;
$drop = $(e.target).parents('.topic-menu-wrapper');
query = $(e.target).val();
$items = $drop.find('.topic-menu-item');
if (query.length === 0) {
$items.removeClass('hidden');
return;
}
$items.addClass('hidden');
$items.each(function (_index, item) {
var path, pathText, pathTitles;
path = $(item).parents(".topic-menu-item").andSelf();
pathTitles = path.children(".topic-title").map(function (_, elem) {
return $(elem).text();
}).get();
pathText = pathTitles.join(" / ").toLowerCase();
if (query.split(" ").every(function (term) {
return pathText.search(term.toLowerCase()) !== -1;
})) {
$(item).removeClass('hidden');
$(item).find('.topic-menu-item').removeClass('hidden');
$(item).parents('.topic-menu-item').removeClass('hidden');
}
});
}
});
}

View File

@@ -17,7 +17,7 @@ if Backbone?
mode: @mode,
form_id: @mode + (if @topicId then "-" + @topicId else "")
})
@$el.html(_.template($("#new-post-template").html(), context))
@$el.html(_.template($("#new-post-template").html())(context))
threadTypeTemplate = _.template($("#thread-type-template").html());
if $('.js-group-select').is(':disabled')
$('.group-selector-wrapper').addClass('disabled')

View File

@@ -79,6 +79,9 @@
* underlying server API.
*/
getPage: function () {
// TODO: this.currentPage is currently returning a function sometimes when it is called.
// It is possible it always did this, but we either need to investigate more, or just wait until
// we replace this code with the pattern library.
return this.currentPage + (this.isZeroIndexed ? 1 : 0);
},

View File

@@ -244,7 +244,7 @@
if (!validateTotalKeyLength(key_field_selectors)) {
$(selectors.errorWrapper).addClass(classes.shown).removeClass(classes.hiding);
$(selectors.errorMessage).html(
'<p>' + _.template(message_tpl, {limit: MAX_SUM_KEY_LENGTH}) + '</p>'
'<p>' + _.template(message_tpl)({limit: MAX_SUM_KEY_LENGTH}) + '</p>'
);
$(selectors.save).addClass(classes.disabled);
} else {

View File

@@ -2,7 +2,7 @@
'use strict';
define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"],
function($, _, str, SystemFeedbackView) {
str = str || _.str;
var Alert = SystemFeedbackView.extend({
options: $.extend({}, SystemFeedbackView.prototype.options, {
type: "alert"

View File

@@ -2,7 +2,7 @@
'use strict';
define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"],
function($, _, str, SystemFeedbackView) {
str = str || _.str;
var Notification = SystemFeedbackView.extend({
options: $.extend({}, SystemFeedbackView.prototype.options, {
type: "notification",

View File

@@ -2,7 +2,7 @@
'use strict';
define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"],
function($, _, str, SystemFeedbackView) {
str = str || _.str;
var Prompt = SystemFeedbackView.extend({
options: $.extend({}, SystemFeedbackView.prototype.options, {
type: "prompt",

View File

@@ -50,7 +50,7 @@
},
render: function () {
this.$el.html(_.template(paginatedViewTemplate, {type: this.type}));
this.$el.html(_.template(paginatedViewTemplate)({type: this.type}));
this.assign(this.listView, '.' + this.type + '-list');
if (this.headerView) {
this.assign(this.headerView, '.' + this.type + '-paging-header');

Some files were not shown because too many files have changed in this diff Show More