diff --git a/cms/djangoapps/contentstore/features/component.py b/cms/djangoapps/contentstore/features/component.py index 8adbb0fdae..38fb905bcf 100644 --- a/cms/djangoapps/contentstore/features/component.py +++ b/cms/djangoapps/contentstore/features/component.py @@ -55,9 +55,9 @@ def see_a_multi_step_component(step, category): if category == 'HTML': html_matcher = { 'Text': '\n \n', - 'Announcement': '

Announcement Date

', - 'Zooming Image Tool': '

Zooming Image Tool

', - 'E-text Written in LaTeX': '

Example: E-text page

', + 'Announcement': '

Announcement Date

', + 'Zooming Image Tool': '

Zooming Image Tool

', + 'E-text Written in LaTeX': '

Example: E-text page

', 'Raw HTML': '

This template is similar to the Text template. The only difference is', } actual_html = world.css_html(selector, index=idx) diff --git a/cms/djangoapps/contentstore/features/problem-editor.py b/cms/djangoapps/contentstore/features/problem-editor.py index c8fc76ed87..b1d7c97159 100644 --- a/cms/djangoapps/contentstore/features/problem-editor.py +++ b/cms/djangoapps/contentstore/features/problem-editor.py @@ -125,6 +125,9 @@ def my_display_name_change_is_persisted_on_save(step): @step('the problem display name is "(.*)"$') def verify_problem_display_name(step, name): + """ + name is uppercased because the heading styles are uppercase in css + """ assert_equal(name, world.browser.find_by_css('.problem-header').text) diff --git a/cms/djangoapps/contentstore/management/commands/export_convert_format.py b/cms/djangoapps/contentstore/management/commands/export_convert_format.py deleted file mode 100644 index eb0cc575d9..0000000000 --- a/cms/djangoapps/contentstore/management/commands/export_convert_format.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -Script for converting a tar.gz file representing an exported course -to the archive format used by a different version of export. - -Sample invocation: ./manage.py export_convert_format mycourse.tar.gz ~/newformat/ -""" -import os -from path import Path as path -from django.core.management.base import BaseCommand, CommandError -from django.conf import settings - -from tempfile import mkdtemp -import tarfile -import shutil -from openedx.core.lib.extract_tar import safetar_extractall - -from xmodule.modulestore.xml_exporter import convert_between_versions - - -class Command(BaseCommand): - """ - Convert between export formats. - """ - help = 'Convert between versions 0 and 1 of the course export format' - args = ' ' - - def handle(self, *args, **options): - "Execute the command" - if len(args) != 2: - raise CommandError("export requires two arguments: ") - - source_archive = args[0] - output_path = args[1] - - # Create temp directories to extract the source and create the target archive. - temp_source_dir = mkdtemp(dir=settings.DATA_DIR) - temp_target_dir = mkdtemp(dir=settings.DATA_DIR) - try: - extract_source(source_archive, temp_source_dir) - - desired_version = convert_between_versions(temp_source_dir, temp_target_dir) - - # New zip up the target directory. - parts = os.path.basename(source_archive).split('.') - archive_name = path(output_path) / "{source_name}_version_{desired_version}.tar.gz".format( - source_name=parts[0], desired_version=desired_version - ) - with open(archive_name, "w"): - tar_file = tarfile.open(archive_name, mode='w:gz') - try: - for item in os.listdir(temp_target_dir): - tar_file.add(path(temp_target_dir) / item, arcname=item) - - finally: - tar_file.close() - - print "Created archive {0}".format(archive_name) - - except ValueError as err: - raise CommandError(err) - - finally: - shutil.rmtree(temp_source_dir) - shutil.rmtree(temp_target_dir) - - -def extract_source(source_archive, target): - """ - Extract the archive into the given target directory. - """ - with tarfile.open(source_archive) as tar_file: - safetar_extractall(tar_file, target) diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_export_convert_format.py b/cms/djangoapps/contentstore/management/commands/tests/test_export_convert_format.py deleted file mode 100644 index 0160a3feab..0000000000 --- a/cms/djangoapps/contentstore/management/commands/tests/test_export_convert_format.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -Test for export_convert_format. -""" -from unittest import TestCase -from django.core.management import call_command, CommandError -from django.conf import settings -from tempfile import mkdtemp -import shutil -from path import Path as path -from contentstore.management.commands.export_convert_format import Command, extract_source -from xmodule.tests.helpers import directories_equal - - -class ConvertExportFormat(TestCase): - """ - Tests converting between export formats. - """ - def setUp(self): - """ Common setup. """ - super(ConvertExportFormat, self).setUp() - - self.temp_dir = mkdtemp(dir=settings.DATA_DIR) - self.addCleanup(shutil.rmtree, self.temp_dir) - self.data_dir = path(__file__).realpath().parent / 'data' - self.version0 = self.data_dir / "Version0_drafts.tar.gz" - self.version1 = self.data_dir / "Version1_drafts.tar.gz" - - self.command = Command() - - def test_no_args(self): - """ Test error condition of no arguments. """ - errstring = "export requires two arguments" - with self.assertRaisesRegexp(CommandError, errstring): - self.command.handle() - - def test_version1_archive(self): - """ - Smoke test for creating a version 1 archive from a version 0. - """ - call_command('export_convert_format', self.version0, self.temp_dir) - output = path(self.temp_dir) / 'Version0_drafts_version_1.tar.gz' - self.assertTrue(self._verify_archive_equality(output, self.version1)) - - def test_version0_archive(self): - """ - Smoke test for creating a version 0 archive from a version 1. - """ - call_command('export_convert_format', self.version1, self.temp_dir) - output = path(self.temp_dir) / 'Version1_drafts_version_0.tar.gz' - self.assertTrue(self._verify_archive_equality(output, self.version0)) - - def _verify_archive_equality(self, file1, file2): - """ - Helper function for determining if 2 archives are equal. - """ - temp_dir_1 = mkdtemp(dir=settings.DATA_DIR) - temp_dir_2 = mkdtemp(dir=settings.DATA_DIR) - try: - extract_source(file1, temp_dir_1) - extract_source(file2, temp_dir_2) - return directories_equal(temp_dir_1, temp_dir_2) - - finally: - shutil.rmtree(temp_dir_1) - shutil.rmtree(temp_dir_2) diff --git a/cms/djangoapps/contentstore/tests/test_course_listing.py b/cms/djangoapps/contentstore/tests/test_course_listing.py index d0ea4f3422..5800be50b1 100644 --- a/cms/djangoapps/contentstore/tests/test_course_listing.py +++ b/cms/djangoapps/contentstore/tests/test_course_listing.py @@ -8,6 +8,7 @@ from chrono import Timer from mock import patch, Mock import ddt +from django.conf import settings from django.test import RequestFactory from django.test.client import Client @@ -28,8 +29,9 @@ from opaque_keys.edx.locations import CourseLocator from xmodule.error_module import ErrorDescriptor from course_action_state.models import CourseRerunState -TOTAL_COURSES_COUNT = 500 -USER_COURSES_COUNT = 50 + +TOTAL_COURSES_COUNT = 10 +USER_COURSES_COUNT = 1 @ddt.ddt @@ -99,6 +101,15 @@ class TestCourseListing(ModuleStoreTestCase, XssTestMixin): self.assertEqual(response.status_code, 200) self.assert_no_xss(response, escaping_content) + def test_empty_course_listing(self): + """ + Test on empty course listing, studio name is properly displayed + """ + message = "Are you staff on an existing {studio_name} course?".format(studio_name=settings.STUDIO_SHORT_NAME) + response = self.client.get('/home') + self.assertEqual(response.status_code, 200) + self.assertIn(message, response.content) + def test_get_course_list(self): """ Test getting courses with new access group format e.g. 'instructor_edx.course.run' @@ -147,8 +158,8 @@ class TestCourseListing(ModuleStoreTestCase, XssTestMixin): self.assertEqual(courses_list_by_groups, []) @ddt.data( - (ModuleStoreEnum.Type.split, 5), - (ModuleStoreEnum.Type.mongo, 3) + (ModuleStoreEnum.Type.split, 3), + (ModuleStoreEnum.Type.mongo, 2) ) @ddt.unpack def test_staff_course_listing(self, default_store, mongo_calls): @@ -255,8 +266,8 @@ class TestCourseListing(ModuleStoreTestCase, XssTestMixin): ) @ddt.data( - (ModuleStoreEnum.Type.split, 150, 505), - (ModuleStoreEnum.Type.mongo, USER_COURSES_COUNT, 3) + (ModuleStoreEnum.Type.split, 3, 13), + (ModuleStoreEnum.Type.mongo, USER_COURSES_COUNT, 2) ) @ddt.unpack def test_course_listing_performance(self, store, courses_list_from_group_calls, courses_list_calls): diff --git a/cms/djangoapps/contentstore/views/tests/test_import_export.py b/cms/djangoapps/contentstore/views/tests/test_import_export.py index 7568c9ca5b..f934efba62 100644 --- a/cms/djangoapps/contentstore/views/tests/test_import_export.py +++ b/cms/djangoapps/contentstore/views/tests/test_import_export.py @@ -2,6 +2,7 @@ Unit tests for course import and export """ import copy +import ddt import json import logging import lxml @@ -15,20 +16,24 @@ from uuid import uuid4 from django.test.utils import override_settings from django.conf import settings from xmodule.contentstore.django import contentstore +from xmodule.modulestore.django import modulestore from xmodule.modulestore.xml_exporter import export_library_to_xml from xmodule.modulestore.xml_importer import import_library_from_xml -from xmodule.modulestore import LIBRARY_ROOT +from xmodule.modulestore import LIBRARY_ROOT, ModuleStoreEnum from contentstore.utils import reverse_course_url +from contentstore.tests.utils import CourseTestCase from xmodule.modulestore.tests.factories import ItemFactory, LibraryFactory +from xmodule.modulestore.tests.utils import ( + MongoContentstoreBuilder, SPLIT_MODULESTORE_SETUP, TEST_DATA_DIR +) +from opaque_keys.edx.locator import LibraryLocator -from contentstore.tests.utils import CourseTestCase from openedx.core.lib.extract_tar import safetar_extractall from student import auth from student.roles import CourseInstructorRole, CourseStaffRole from models.settings.course_metadata import CourseMetadata from util import milestones_helpers -from xmodule.modulestore.django import modulestore from milestones.tests.utils import MilestonesTestCaseMixin TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE) @@ -123,6 +128,7 @@ class ImportEntranceExamTestCase(CourseTestCase, MilestonesTestCaseMixin): self.assertEquals(course.entrance_exam_minimum_score_pct, 0.7) +@ddt.ddt @override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE) class ImportTestCase(CourseTestCase): """ @@ -435,6 +441,73 @@ class ImportTestCase(CourseTestCase): self.assertIn(test_block3.url_name, children) self.assertIn(test_block4.url_name, children) + @ddt.data( + ModuleStoreEnum.Branch.draft_preferred, + ModuleStoreEnum.Branch.published_only, + ) + def test_library_import_branch_settings(self, branch_setting): + """ + Try importing a known good library archive under either branch setting. + The branch setting should have no effect on library import. + """ + with self.store.branch_setting(branch_setting): + library = LibraryFactory.create(modulestore=self.store) + lib_key = library.location.library_key + extract_dir = path(tempfile.mkdtemp(dir=settings.DATA_DIR)) + # the extract_dir needs to be passed as a relative dir to + # import_library_from_xml + extract_dir_relative = path.relpath(extract_dir, settings.DATA_DIR) + + try: + with tarfile.open(path(TEST_DATA_DIR) / 'imports' / 'library.HhJfPD.tar.gz') as tar: + safetar_extractall(tar, extract_dir) + import_library_from_xml( + self.store, + self.user.id, + settings.GITHUB_REPO_ROOT, + [extract_dir_relative / 'library'], + load_error_modules=False, + static_content_store=contentstore(), + target_id=lib_key + ) + finally: + shutil.rmtree(extract_dir) + + @ddt.data( + ModuleStoreEnum.Branch.draft_preferred, + ModuleStoreEnum.Branch.published_only, + ) + def test_library_import_branch_settings_again(self, branch_setting): + # Construct the contentstore for storing the import + with MongoContentstoreBuilder().build() as source_content: + # Construct the modulestore for storing the import (using the previously created contentstore) + with SPLIT_MODULESTORE_SETUP.build(contentstore=source_content) as source_store: + # Use the test branch setting. + with source_store.branch_setting(branch_setting): + source_library_key = LibraryLocator(org='TestOrg', library='TestProbs') + + extract_dir = path(tempfile.mkdtemp(dir=settings.DATA_DIR)) + # the extract_dir needs to be passed as a relative dir to + # import_library_from_xml + extract_dir_relative = path.relpath(extract_dir, settings.DATA_DIR) + + try: + with tarfile.open(path(TEST_DATA_DIR) / 'imports' / 'library.HhJfPD.tar.gz') as tar: + safetar_extractall(tar, extract_dir) + import_library_from_xml( + source_store, + self.user.id, + settings.GITHUB_REPO_ROOT, + [extract_dir_relative / 'library'], + static_content_store=source_content, + target_id=source_library_key, + load_error_modules=False, + raise_on_failure=True, + create_if_not_present=True, + ) + finally: + shutil.rmtree(extract_dir) + @override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE) class ExportTestCase(CourseTestCase): @@ -556,3 +629,59 @@ class ExportTestCase(CourseTestCase): ) self.test_export_targz_urlparam() + + +@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE) +class TestLibraryImportExport(CourseTestCase): + """ + Tests for importing content libraries from XML and exporting them to XML. + """ + def setUp(self): + super(TestLibraryImportExport, self).setUp() + self.export_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.export_dir, ignore_errors=True) + + def test_content_library_export_import(self): + library1 = LibraryFactory.create(modulestore=self.store) + source_library1_key = library1.location.library_key + library2 = LibraryFactory.create(modulestore=self.store) + source_library2_key = library2.location.library_key + + import_library_from_xml( + self.store, + 'test_user', + TEST_DATA_DIR, + ['library_empty_problem'], + static_content_store=contentstore(), + target_id=source_library1_key, + load_error_modules=False, + raise_on_failure=True, + create_if_not_present=True, + ) + + export_library_to_xml( + self.store, + contentstore(), + source_library1_key, + self.export_dir, + 'exported_source_library', + ) + + source_library = self.store.get_library(source_library1_key) + self.assertEqual(source_library.url_name, 'library') + + # Import the exported library into a different content library. + import_library_from_xml( + self.store, + 'test_user', + self.export_dir, + ['exported_source_library'], + static_content_store=contentstore(), + target_id=source_library2_key, + load_error_modules=False, + raise_on_failure=True, + create_if_not_present=True, + ) + + # Compare the two content libraries for equality. + self.assertCoursesEqual(source_library1_key, source_library2_key) diff --git a/cms/djangoapps/contentstore/views/tests/test_programs.py b/cms/djangoapps/contentstore/views/tests/test_programs.py index 751f39f598..fc5f2df2d2 100644 --- a/cms/djangoapps/contentstore/views/tests/test_programs.py +++ b/cms/djangoapps/contentstore/views/tests/test_programs.py @@ -10,7 +10,7 @@ from provider.constants import CONFIDENTIAL from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin -from openedx.core.djangolib.markup import escape +from openedx.core.djangolib.markup import Text from student.tests.factories import UserFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -64,7 +64,7 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule self.mock_programs_api(data={'results': []}) response = self.client.get(self.studio_home) - self.assertIn(escape("You haven't created any programs yet."), response.content) + self.assertIn(Text("You haven't created any programs yet."), response.content) # When data is provided, expect a program listing. self.mock_programs_api() diff --git a/cms/envs/acceptance.py b/cms/envs/acceptance.py index 20bdeb0270..3bb18399f3 100644 --- a/cms/envs/acceptance.py +++ b/cms/envs/acceptance.py @@ -80,6 +80,14 @@ DATABASES = { 'timeout': 30, }, 'ATOMIC_REQUESTS': True, + }, + 'student_module_history': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': TEST_ROOT / "db" / "test_student_module_history.db", + 'TEST_NAME': TEST_ROOT / "db" / "test_student_module_history.db", + 'OPTIONS': { + 'timeout': 30, + }, } } diff --git a/cms/envs/aws.py b/cms/envs/aws.py index 0139c70062..9aadf61eab 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -274,12 +274,6 @@ else: DATABASES = AUTH_TOKENS['DATABASES'] -# Enable automatic transaction management on all databases -# https://docs.djangoproject.com/en/1.8/topics/db/transactions/#tying-transactions-to-http-requests -# This needs to be true for all databases -for database_name in DATABASES: - DATABASES[database_name]['ATOMIC_REQUESTS'] = True - MODULESTORE = convert_module_store_setting_if_needed(AUTH_TOKENS.get('MODULESTORE', MODULESTORE)) CONTENTSTORE = AUTH_TOKENS['CONTENTSTORE'] DOC_STORE_CONFIG = AUTH_TOKENS['DOC_STORE_CONFIG'] diff --git a/cms/envs/aws_migrate.py b/cms/envs/aws_migrate.py index e14834ec2d..e6f5b83d61 100644 --- a/cms/envs/aws_migrate.py +++ b/cms/envs/aws_migrate.py @@ -13,18 +13,27 @@ from .aws import * import os from django.core.exceptions import ImproperlyConfigured -DB_OVERRIDES = dict( - PASSWORD=os.environ.get('DB_MIGRATION_PASS', None), - ENGINE=os.environ.get('DB_MIGRATION_ENGINE', DATABASES['default']['ENGINE']), - USER=os.environ.get('DB_MIGRATION_USER', DATABASES['default']['USER']), - NAME=os.environ.get('DB_MIGRATION_NAME', DATABASES['default']['NAME']), - HOST=os.environ.get('DB_MIGRATION_HOST', DATABASES['default']['HOST']), - PORT=os.environ.get('DB_MIGRATION_PORT', DATABASES['default']['PORT']), -) -if DB_OVERRIDES['PASSWORD'] is None: - raise ImproperlyConfigured("No database password was provided for running " - "migrations. This is fatal.") +def get_db_overrides(db_name): + """ + Now that we have multiple databases, we want to look up from the environment + for both databases. + """ + db_overrides = dict( + PASSWORD=os.environ.get('DB_MIGRATION_PASS', None), + ENGINE=os.environ.get('DB_MIGRATION_ENGINE', DATABASES[db_name]['ENGINE']), + USER=os.environ.get('DB_MIGRATION_USER', DATABASES[db_name]['USER']), + NAME=os.environ.get('DB_MIGRATION_NAME', DATABASES[db_name]['NAME']), + HOST=os.environ.get('DB_MIGRATION_HOST', DATABASES[db_name]['HOST']), + PORT=os.environ.get('DB_MIGRATION_PORT', DATABASES[db_name]['PORT']), + ) -for override, value in DB_OVERRIDES.iteritems(): - DATABASES['default'][override] = value + if db_overrides['PASSWORD'] is None: + raise ImproperlyConfigured("No database password was provided for running " + "migrations. This is fatal.") + return db_overrides + +for db in DATABASES: + # You never migrate a read_replica + if db != 'read_replica': + DATABASES[db].update(get_db_overrides(db)) diff --git a/cms/envs/bok_choy.auth.json b/cms/envs/bok_choy.auth.json index 79dbf904c1..44eac070f6 100644 --- a/cms/envs/bok_choy.auth.json +++ b/cms/envs/bok_choy.auth.json @@ -30,6 +30,14 @@ "PASSWORD": "", "PORT": "3306", "USER": "root" + }, + "student_module_history": { + "ENGINE": "django.db.backends.mysql", + "HOST": "localhost", + "NAME": "student_module_history_test", + "PASSWORD": "", + "PORT": "3306", + "USER": "root" } }, "DOC_STORE_CONFIG": { diff --git a/cms/envs/common.py b/cms/envs/common.py index bb99b01a98..40dd6c5ac0 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -1114,6 +1114,11 @@ PROCTORING_BACKEND_PROVIDER = { } PROCTORING_SETTINGS = {} +############################ Global Database Configuration ##################### + +DATABASE_ROUTERS = [ + 'openedx.core.lib.django_courseware_routers.StudentModuleHistoryExtendedRouter', +] ############################ OAUTH2 Provider ################################### diff --git a/cms/envs/test.py b/cms/envs/test.py index 8577e82a3b..ce63bc4d90 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -23,6 +23,7 @@ import os from path import Path as path from warnings import filterwarnings, simplefilter from uuid import uuid4 +from util.db import NoOpMigrationModules # import settings from LMS for consistent behavior with CMS # pylint: disable=unused-import @@ -42,7 +43,7 @@ MONGO_HOST = os.environ.get('EDXAPP_TEST_MONGO_HOST', 'localhost') THIS_UUID = uuid4().hex[:5] # Nose Test Runner -TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' +TEST_RUNNER = 'openedx.core.djangolib.nose.NoseTestSuiteRunner' _SYSTEM = 'cms' @@ -129,9 +130,10 @@ DATABASES = { }, } -# This hack disables migrations during tests. We want to create tables directly from the models for speed. -# See https://groups.google.com/d/msg/django-developers/PWPj3etj3-U/kCl6pMsQYYoJ. -MIGRATION_MODULES = {app: "app.migrations_not_used_in_tests" for app in INSTALLED_APPS} +if os.environ.get('DISABLE_MIGRATIONS'): + # Create tables directly from apps' models. This can be removed once we upgrade + # to Django 1.9, which allows setting MIGRATION_MODULES to None in order to skip migrations. + MIGRATION_MODULES = NoOpMigrationModules() LMS_BASE = "localhost:8000" FEATURES['PREVIEW_LMS_BASE'] = "preview" diff --git a/cms/startup.py b/cms/startup.py index 55244c9045..398fdbdc41 100644 --- a/cms/startup.py +++ b/cms/startup.py @@ -9,7 +9,11 @@ settings.INSTALLED_APPS # pylint: disable=pointless-statement from openedx.core.lib.django_startup import autostartup import django -from monkey_patch import third_party_auth +from monkey_patch import ( + third_party_auth, + django_db_models_options, + django_utils_http_is_safe_url +) import xmodule.x_module import cms.lib.xblock.runtime @@ -22,6 +26,8 @@ def run(): Executed during django startup """ third_party_auth.patch() + django_db_models_options.patch() + django_utils_http_is_safe_url.patch() # Comprehensive theming needs to be set up before django startup, # because modifying django template paths after startup has no effect. diff --git a/cms/static/js/i18n/ru/djangojs.js b/cms/static/js/i18n/ru/djangojs.js index 2d73a5d2f1..2f4f9960eb 100644 --- a/cms/static/js/i18n/ru/djangojs.js +++ b/cms/static/js/i18n/ru/djangojs.js @@ -230,7 +230,7 @@ "Adding the selected course to your cart": "\u041f\u043e\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u0432\u0430\u043c\u0438 \u043a\u0443\u0440\u0441\u0430 \u0432 \u043a\u043e\u0440\u0437\u0438\u043d\u0443", "Additional Information (optional)": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f)", "Admin": "\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440", - "Advanced": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0442\u0438\u043f\u044b \u0437\u0430\u0434\u0430\u043d\u0438\u0439", + "Advanced": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e", "Align center": "\u041f\u043e \u0446\u0435\u043d\u0442\u0440\u0443", "Align left": "\u041f\u043e \u043b\u0435\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", "Align right": "\u041f\u043e \u043f\u0440\u0430\u0432\u043e\u043c\u0443 \u043a\u0440\u0430\u044e", @@ -479,7 +479,7 @@ "Course": "\u041a\u0443\u0440\u0441", "Course Credit Requirements": "\u0422\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0437\u0430\u0447\u0451\u0442\u0430 \u043d\u0430 \u043a\u0443\u0440\u0441\u0435", "Course End": "\u041a\u0443\u0440\u0441 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f", - "Course Handouts": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u044b \u043f\u043e \u043a\u0443\u0440\u0441\u0443", + "Course Handouts": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u044b", "Course ID": "\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043a\u0443\u0440\u0441\u0430", "Course Index": "\u041f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441 \u043a\u0443\u0440\u0441\u0430", "Course Key": "\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043a\u0443\u0440\u0441\u0430", @@ -610,8 +610,8 @@ "Editor": "\u0420\u0435\u0434\u0430\u043a\u0442\u043e\u0440", "Education Completed": "\u0417\u0430\u043a\u043e\u043d\u0447\u0435\u043d\u043d\u043e\u0435 \u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435", "Email": "\u042d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0430\u044f \u043f\u043e\u0447\u0442\u0430", - "Email Address": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", - "Email address": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", + "Email Address": "E-mail \u0430\u0434\u0440\u0435\u0441", + "Email address": "E-mail \u0430\u0434\u0440\u0435\u0441", "Emails successfully sent. The following users are no longer enrolled in the course:": "\u042d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u044b. \u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u0431\u043e\u043b\u0435\u0435 \u043d\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u043d\u0430 \u043a\u0443\u0440\u0441\u0435:", "Embed": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c", "Emoticons": "\u0421\u043c\u0430\u0439\u043b\u044b", @@ -891,8 +891,8 @@ "Load more": "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0435\u0449\u0451", "Load next %(numResponses)s responses": "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 %(numResponses)s \u043e\u0442\u0432\u0435\u0442\u043e\u0432", "Load next %(num_items)s result": [ - "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 %(num_items)s \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442", - "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 %(num_items)s \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b", + "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c %(num_items)s \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442", + "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 %(num_items)s \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430", "", "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 %(num_items)s \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b" ], @@ -1291,12 +1291,12 @@ "Start regenerating certificates for students in this course?": "\u041d\u0430\u0447\u0430\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0432 \u0434\u043b\u044f \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 \u043a\u0443\u0440\u0441\u0430?", "Start search": "\u041d\u0430\u0447\u0430\u0442\u044c \u043f\u043e\u0438\u0441\u043a", "Started entrance exam rescore task for student '{student_id}'. Click the 'Show Background Task History for Student' button to see the status of the task.": "\u0417\u0430\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0435\u0440\u0435\u043f\u0440\u043e\u0432\u0435\u0440\u043a\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}'. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u00ab\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0444\u043e\u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447\u00bb, \u0447\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 \u0437\u0430\u0434\u0430\u0447\u0438.", - "Started rescore problem task for problem '<%= problem_id %>' and student '<%= student_id %>'. Click the 'Show Background Task History for Student' button to see the status of the task.": "\u0417\u0430\u043f\u0443\u0449\u0435\u043d\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u043e\u0446\u0435\u043d\u043a\u0438 \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u043d\u0438\u044f '<%= problem_id %>' \u0434\u043b\u044f '<%= student_id %>'. \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438, \u0449\u0451\u043b\u043a\u043d\u0438\u0442\u0435 \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0435 '\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0444\u043e\u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447'.", + "Started rescore problem task for problem '<%= problem_id %>' and student '<%= student_id %>'. Click the 'Show Background Task History for Student' button to see the status of the task.": "\u0417\u0430\u043f\u0443\u0449\u0435\u043d\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u043e\u0446\u0435\u043d\u043a\u0438 \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u043d\u0438\u044f '<%= problem_id %>' \u0434\u043b\u044f '<%= student_id %>'. \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438, \u0449\u0451\u043b\u043a\u043d\u0438\u0442\u0435 \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0435 \u00ab\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0444\u043e\u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447\u00bb.", "Starts": "\u041d\u0430\u0447\u0430\u043b\u043e", "Starts: %(start)s": "\u041d\u0430\u0447\u0430\u043b\u043e: %(start)s", "Starts: %(start_date)s": "\u041d\u0430\u0447\u0430\u043b\u043e: %(start_date)s", "State": "\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435", - "Status": "\u0421\u0442\u0430\u0442\u0443\u0441", + "Status": "\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435", "Status: unsubmitted": "\u0421\u0442\u0430\u0442\u0443\u0441: \u043d\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e", "Strikethrough": "\u0417\u0430\u0447\u0451\u0440\u043a\u043d\u0443\u0442\u044b\u0439", "Student": "\u041e\u0431\u0443\u0447\u0430\u044e\u0449\u0438\u0439\u0441\u044f", @@ -1323,8 +1323,8 @@ "Successfully reset the attempts for user {user}": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0431\u0440\u043e\u0448\u0435\u043d\u044b \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f: {user}", "Successfully sent enrollment emails to the following users. They will be allowed to enroll once they register:": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u044b \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c. \u041e\u043d\u0438 \u0441\u043c\u043e\u0433\u0443\u0442 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0443\u0442 \u0441\u0432\u043e\u0438 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438:", "Successfully sent enrollment emails to the following users. They will be enrolled once they register:": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u044b \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c: \u041e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b, \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0443\u0442 \u0441\u0432\u043e\u0438 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438.", - "Successfully started task to rescore problem '<%= problem_id %>' for all students. Click the 'Show Background Task History for Problem' button to see the status of the task.": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u0435\u0440\u0435\u043e\u0446\u0435\u043d\u043a\u0438 \u0437\u0430\u0434\u0430\u043d\u0438\u044f '<%= problem_id %>' \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 '\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0444\u043e\u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447' \u0447\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 \u0437\u0430\u0434\u0430\u0447\u0438.", - "Successfully started task to reset attempts for problem '<%= problem_id %>'. Click the 'Show Background Task History for Problem' button to see the status of the task.": "\u0417\u0430\u0434\u0430\u0447\u0430 \u043f\u043e \u0441\u0431\u0440\u043e\u0441\u0443 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u043f\u043e\u043f\u044b\u0442\u043e\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u043d\u0438\u044f '<%= problem_id %>' \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e. \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u043d\u0438\u044f, \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u00ab\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0444\u043e\u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447 \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u043d\u0438\u044f\u00bb.", + "Successfully started task to rescore problem '<%= problem_id %>' for all students. Click the 'Show Background Task History for Problem' button to see the status of the task.": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u0435\u0440\u0435\u043e\u0446\u0435\u043d\u043a\u0438 \u0437\u0430\u0434\u0430\u043d\u0438\u044f '<%= problem_id %>' \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u00ab\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0444\u043e\u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447\u00bb \u0447\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 \u0437\u0430\u0434\u0430\u0447\u0438.", + "Successfully started task to reset attempts for problem '<%= problem_id %>'. Click the 'Show Background Task History for Problem' button to see the status of the task.": "\u0417\u0430\u0434\u0430\u0447\u0430 \u043f\u043e \u0441\u0431\u0440\u043e\u0441\u0443 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u043f\u043e\u043f\u044b\u0442\u043e\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u043d\u0438\u044f '<%= problem_id %>' \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e. \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u043d\u0438\u044f, \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u00ab\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0444\u043e\u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447\u00bb.", "Successfully unlinked.": "\u0423\u0434\u0430\u043b\u0435\u043d\u043e.", "Superscript": "\u0432\u0435\u0440\u0445\u043d\u0438\u0439 \u0438\u043d\u0434\u0435\u043a\u0441", "Table": "\u0422\u0430\u0431\u043b\u0438\u0446\u0430", @@ -1399,7 +1399,7 @@ "The minimum score percentage must be a whole number between 0 and 100.": "\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043e\u0441\u0432\u043e\u0435\u043d\u0438\u044f \u0432\u044b\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0432 \u043f\u0440\u043e\u0446\u0435\u043d\u0442\u0430\u0445 \u0438 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0446\u0435\u043b\u044b\u043c \u0447\u0438\u0441\u043b\u043e\u043c \u043c\u0435\u0436\u0434\u0443 0 \u0438 100.", "The name of this signatory as it should appear on certificates.": "\u0418\u043c\u044f \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u0435\u043b\u044f \u0432 \u0444\u043e\u0440\u043c\u0435, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u043e\u043b\u0436\u043d\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0435", "The name that identifies you throughout {platform_name}. You cannot change your username.": "\u0418\u043c\u044f, \u043f\u043e\u0434 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0432\u0430\u0441 \u0437\u043d\u0430\u044e\u0442 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 {platform_name}. \u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u043c\u0435\u043d\u0438\u0442\u044c \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.", - "The name that is used for ID verification and appears on your certificates. Other learners never see your full name. Make sure to enter your name exactly as it appears on your government-issued photo ID, including any non-Roman characters.": "\u0418\u043c\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u043d\u043e \u043d\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430\u0445. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c \u0432 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0432\u0432\u043e\u0434\u0430 \u0432\u0430\u0448\u0435\u0433\u043e \u0438\u043c\u0435\u043d\u0438. \u041e\u043d\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a, \u043a\u0430\u043a \u0443\u043a\u0430\u0437\u0430\u043d\u043e \u0432 \u0432\u0430\u0448\u0435\u043c \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u0435\u043d\u0438\u0438 \u043b\u0438\u0447\u043d\u043e\u0441\u0442\u0438.", + "The name that is used for ID verification and appears on your certificates. Other learners never see your full name. Make sure to enter your name exactly as it appears on your government-issued photo ID, including any non-Roman characters.": "\u0418\u043c\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u043d\u043e \u043d\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430\u0445. \u041e\u043d\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a, \u043a\u0430\u043a \u0443\u043a\u0430\u0437\u0430\u043d\u043e \u0432 \u0432\u0430\u0448\u0435\u043c \u043f\u0430\u0441\u043f\u043e\u0440\u0442\u0435 \u0438\u043b\u0438 \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u0435\u043d\u0438\u0438 \u043b\u0438\u0447\u043d\u043e\u0441\u0442\u0438.", "The organization that this signatory belongs to, as it should appear on certificates.": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043b\u0438\u0446\u043e, \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0432\u0448\u0435\u0435 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442.", "The page \"%(route)s\" could not be found.": "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u00ab%(route)s\u00bb \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430.", "The photo of your face matches the photo on your ID.": "\u0424\u043e\u0442\u043e \u0432\u0430\u0448\u0435\u0433\u043e \u043b\u0438\u0446\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0444\u043e\u0442\u043e \u0432 \u0432\u0430\u0448\u0435\u043c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0435.", @@ -1488,7 +1488,7 @@ "Timed Transcript from %(filename)s": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u044b \u0438\u0437 %(filename)s", "Tips on taking a successful photo": "\u0421\u043e\u0432\u0435\u0442\u044b: \u043a\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0443\u0434\u0430\u0447\u043d\u044b\u0439 \u0441\u043d\u0438\u043c\u043e\u043a", "Title": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", - "Title ": "\u041e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0435", + "Title ": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a", "Title of the signatory": "\u041e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043a \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u0435\u043b\u044e, \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0449\u0435\u043c\u0443 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442", "Title:": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a:", "Titles more than 100 characters may prevent students from printing their certificate on a single page.": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0434\u043b\u0438\u043d\u043e\u0439 \u0431\u043e\u043b\u0435\u0435 100 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0440\u0430\u0441\u043f\u0435\u0447\u0430\u0442\u044b\u0432\u0430\u043d\u0438\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430 \u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435.", @@ -1860,7 +1860,7 @@ "section": "\u0440\u0430\u0437\u0434\u0435\u043b", "section.title": "section.title", "send an email message to {email}": "\u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0435 \u043f\u0438\u0441\u044c\u043c\u043e \u043d\u0430 {email}", - "status": "\u0441\u0442\u0430\u0442\u0443\u0441", + "status": "c\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435", "strong text": "\u0442\u0435\u043a\u0441\u0442 \u0436\u0438\u0440\u043d\u044b\u043c \u0448\u0440\u0438\u0444\u0442\u043e\u043c", "subsection": "\u043f\u043e\u0434\u0440\u0430\u0437\u0434\u0435\u043b", "team count": "\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u043e\u043c\u0430\u043d\u0434", diff --git a/cms/static/sass/_build.scss b/cms/static/sass/_build.scss index dcf8f2bc81..57a133fa83 100644 --- a/cms/static/sass/_build.scss +++ b/cms/static/sass/_build.scss @@ -75,6 +75,7 @@ // ==================== @import 'xmodule/modules/css/module-styles.scss'; @import 'xmodule/descriptors/css/module-styles.scss'; +@import 'xmodule/headings'; @import 'elements/xmodules'; // styling for Studio-specific contexts @import 'developer'; // used for any developer-created scss that needs further polish/refactoring diff --git a/cms/static/sass/xmodule/_headings.scss b/cms/static/sass/xmodule/_headings.scss new file mode 100644 index 0000000000..e44d31ec2d --- /dev/null +++ b/cms/static/sass/xmodule/_headings.scss @@ -0,0 +1,121 @@ +/* + * This comes from the UXPL, and is modified for use. + * The UXPL isn't available retroactively, so this shims + * the headings from the UXPL with what we're using in + * the platform to better sync things up in the meantime. + * It is scoped to #seq_content, specifically for xblock. + * + * Once the UXPl is fitted retroactively, this can be removed. + */ + +$headings-count: 8; + +$headings-font-weight-light: 200; +$headings-font-weight-normal: 400; +$headings-font-weight-bold: 600; +$headings-base-font-family: inherit; +$headings-base-color: $gray-d2; + +%reset-headings { + margin: 0; + font-weight: $headings-font-weight-normal; + font-size: inherit; + line-height: inherit; + color: $headings-base-color; +} + +%hd-1 { + margin-bottom: 1.41575em; + font-size: 2em; + line-height: 1.4em; +} + + +%hd-2 { + margin-bottom: 1em; + font-size: 1.5em; + font-weight: $headings-font-weight-normal; + line-height: 1.4em; +} + + +%hd-3 { + margin-bottom: ($baseline / 2); + font-size: 1.35em; + font-weight: $headings-font-weight-normal; + line-height: 1.4em; +} + + +%hd-4 { + margin-bottom: ($baseline / 2); + font-size: 1.25em; + font-weight: $headings-font-weight-bold; + line-height: 1.4em; +} + + +%hd-5 { + margin-bottom: ($baseline / 2); + font-size: 1.1em; + font-weight: $headings-font-weight-bold; + line-height: 1.4em; +} + + +%hd-6 { + margin-bottom: ($baseline / 2); + font-size: 1em; + font-weight: $headings-font-weight-bold; + line-height: 1.4em; +} + +%hd-7 { + margin-bottom: ($baseline / 4); + font-size: 14px; + font-weight: $headings-font-weight-bold; + text-transform: uppercase; + line-height: 1.6em; + letter-spacing: 1px; +} + +%hd-8 { + margin-bottom: ($baseline / 8); + font-size: 12px; + font-weight: $headings-font-weight-bold; + text-transform: uppercase; + line-height: 1.5em; + letter-spacing: 1px; +} + +.wrapper-xblock .xblock-render .xblock .xblock-render .xblock { + + .hd-1, + .hd-2, + .hd-3, + .hd-4, + .hd-5, + .hd-6, + .hd-7, + .hd-8 { + @extend %reset-headings; + } + + + // ---------------------------- + // #CANNED + // ---------------------------- + // canned heading classes + @for $i from 1 through $headings-count { + .hd-#{$i} { + @extend %hd-#{$i}; + } + } + + h3 { + @extend %hd-2; + font-weight: $headings-font-weight-normal; + // override external modules and xblocks that use inline CSS + text-transform: initial; + } +} diff --git a/cms/templates/base.html b/cms/templates/base.html index 9691be94d0..5bba36a828 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -1,7 +1,8 @@ ## coding=utf-8 <%namespace name='static' file='static_content.html'/> <%! -from openedx.core.djangolib.markup import ugettext as _ +from django.utils.translation import ugettext as _ + from openedx.core.djangolib.js_utils import ( dump_js_escaped_json, js_escaped_string ) diff --git a/cms/templates/container.html b/cms/templates/container.html index 60b593560a..872031c4c4 100644 --- a/cms/templates/container.html +++ b/cms/templates/container.html @@ -1,3 +1,4 @@ +<%page expression_filter="h"/> <%inherit file="base.html" /> <%def name="online_help_token()"> <% @@ -8,27 +9,30 @@ else: %> <%! +from django.utils.translation import ugettext as _ + from contentstore.views.helpers import xblock_studio_url, xblock_type_display_name from openedx.core.djangolib.js_utils import ( dump_js_escaped_json, js_escaped_string ) -from openedx.core.djangolib.markup import HTML, ugettext as _ +from openedx.core.djangolib.markup import Text, HTML %> -<%block name="title">${xblock.display_name_with_default_escaped} ${xblock_type_display_name(xblock) | h} + +<%block name="title">${xblock.display_name_with_default} ${xblock_type_display_name(xblock)} <%block name="bodyclass">is-signedin course container view-container <%namespace name='static' file='static_content.html'/> <%block name="header_extras"> % for template_name in templates: - % endfor - + <%block name="requirejs"> @@ -57,15 +61,15 @@ from openedx.core.djangolib.markup import HTML, ugettext as _ ancestor_url = xblock_studio_url(ancestor) %> % if ancestor_url: - ${ancestor.display_name_with_default_escaped | h} + ${ancestor.display_name_with_default} % else: - ${ancestor.display_name_with_default_escaped | h} + ${ancestor.display_name_with_default} % endif % endfor

-

${xblock.display_name_with_default_escaped | h}

+

${xblock.display_name_with_default}

@@ -74,12 +78,12 @@ from openedx.core.djangolib.markup import HTML, ugettext as _