diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..5ee0fa3661 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +### Please consider the following when opening a pull request: + +- Link to the relevant JIRA ticket(s) and tag any relevant team(s). +- Squash your changes down into one or more discrete commits. + In each commit, include description that could help a developer + several months from now. +- If running `make upgrade`, run _as close to the time of merging as possible_ + to avoid accidentally downgrading someone else's package. + Put the output of `make upgrade` in its own separate commit, + decoupled from other code changes. +- Aim for comprehensive test coverage, but remember that + automated testing isn't a substitute for manual verification. +- Carefully consider naming, code organization, dependencies when adding new code. + Code that is amenable to refactoring and improvement benefits all platform developers, + especially given the size and scope of edx-platform. + Consult existing Architectural Decision Records (ADRs), + including those concerning the app(s) you are changing and + [those concerning edx-platform as a whole](https://github.com/edx/edx-platform/tree/master/docs/decisions). diff --git a/.gitignore b/.gitignore index eb0bc8d037..3a564cda8c 100644 --- a/.gitignore +++ b/.gitignore @@ -85,6 +85,7 @@ test_root/uploads/ django-pyfs .tox/ common/test/db_cache/bok_choy_*.yaml +common/test/data/badges/*.png ### Installation artifacts *.egg-info diff --git a/Makefile b/Makefile index 92d761a8da..2300b278e3 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ # Do things in edx-platform -.PHONY: clean docs extract_translations help pull pull_translations push_translations requirements shell upgrade +.PHONY: clean extract_translations help pull pull_translations push_translations requirements shell upgrade +.PHONY: api-docs docs guides swagger # Careful with mktemp syntax: it has to work on Mac and Ubuntu, which have differences. PRIVATE_FILES := $(shell mktemp -u /tmp/private_files.XXXXXX) @@ -18,7 +19,19 @@ clean: ## archive and delete most git-ignored files tar xf $(PRIVATE_FILES) rm $(PRIVATE_FILES) -docs: ## build the developer documentation for this repository +SWAGGER = docs/swagger.yaml + +docs: api-docs guides ## build all the developer documentation for this repository + +swagger: ## generate the swagger.yaml file + DJANGO_SETTINGS_MODULE=docs.docs_settings python manage.py lms generate_swagger --generator-class=openedx.core.openapi.ApiSchemaGenerator -o $(SWAGGER) + +api-docs: swagger ## build the REST api docs + rm -f docs/api/gen/* + python docs/sw2md.py $(SWAGGER) docs/api/gen + cd docs/api; make html + +guides: ## build the developer guide docs cd docs/guides; make clean html extract_translations: ## extract localizable strings from sources diff --git a/cms/djangoapps/contentstore/api/views/course_import.py b/cms/djangoapps/contentstore/api/views/course_import.py index 239bc27694..18d9a2fb21 100644 --- a/cms/djangoapps/contentstore/api/views/course_import.py +++ b/cms/djangoapps/contentstore/api/views/course_import.py @@ -128,7 +128,9 @@ class CourseImportView(CourseImportExportViewMixin, GenericAPIView): developer_message='Parameter in the wrong format', error_code='internal_error', ) - course_dir = path(settings.GITHUB_REPO_ROOT) / base64.urlsafe_b64encode(repr(course_key)) + course_dir = path(settings.GITHUB_REPO_ROOT) / base64.urlsafe_b64encode( + repr(course_key).encode('utf-8') + ).decode('utf-8') temp_filepath = course_dir / filename if not course_dir.isdir(): os.mkdir(course_dir) diff --git a/cms/djangoapps/contentstore/git_export_utils.py b/cms/djangoapps/contentstore/git_export_utils.py index ed3922aca8..31739364fd 100644 --- a/cms/djangoapps/contentstore/git_export_utils.py +++ b/cms/djangoapps/contentstore/git_export_utils.py @@ -108,7 +108,7 @@ def export_to_git(course_id, repo, user='', rdir=None): # Get current branch cmd = ['git', 'symbolic-ref', '--short', 'HEAD'] try: - branch = cmd_log(cmd, cwd).strip('\n') + branch = cmd_log(cmd, cwd).decode('utf-8').strip('\n') except subprocess.CalledProcessError as ex: log.exception(u'Failed to get branch: %r', ex.output) raise GitExportError(GitExportError.DETACHED_HEAD) @@ -146,7 +146,7 @@ def export_to_git(course_id, repo, user='', rdir=None): if not branch: cmd = ['git', 'symbolic-ref', '--short', 'HEAD'] try: - branch = cmd_log(cmd, os.path.abspath(rdirp)).strip('\n') + branch = cmd_log(cmd, os.path.abspath(rdirp)).decode('utf-8').strip('\n') except subprocess.CalledProcessError as ex: log.exception(u'Failed to get branch from freshly cloned repo: %r', ex.output) diff --git a/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py b/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py index f59dd7bdba..9cb787be5f 100644 --- a/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py +++ b/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py @@ -12,7 +12,7 @@ from django.core.management.base import BaseCommand, CommandError from opaque_keys.edx.keys import CourseKey from contentstore.views import tabs -from courseware.courses import get_course_by_id +from lms.djangoapps.courseware.courses import get_course_by_id from .prompt import query_yes_no diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py b/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py index 8d0388aa14..d5b915a7c8 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py @@ -40,7 +40,7 @@ class DeleteCourseTests(ModuleStoreTestCase): store = contentstore() asset_key = course_run.id.make_asset_key('asset', 'test.txt') - content = StaticContent(asset_key, 'test.txt', 'plain/text', 'test data') + content = StaticContent(asset_key, 'test.txt', 'plain/text', b'test data') store.save(content) __, asset_count = store.get_all_content_for_course(course_run.id) assert asset_count == 1 @@ -69,7 +69,7 @@ class DeleteCourseTests(ModuleStoreTestCase): store = contentstore() asset_key = course_run.id.make_asset_key('asset', 'test.txt') - content = StaticContent(asset_key, 'test.txt', 'plain/text', 'test data') + content = StaticContent(asset_key, 'test.txt', 'plain/text', b'test data') store.save(content) __, asset_count = store.get_all_content_for_course(course_run.id) assert asset_count == 1 diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_export_olx.py b/cms/djangoapps/contentstore/management/commands/tests/test_export_olx.py index a508505fa3..021f9fcb9a 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_export_olx.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_export_olx.py @@ -29,7 +29,10 @@ class TestArgParsingCourseExportOlx(unittest.TestCase): """ Test export command with no arguments """ - errstring = "Error: too few arguments" + if six.PY2: + errstring = "Error: too few arguments" + else: + errstring = "Error: the following arguments are required: course_id" with self.assertRaisesRegexp(CommandError, errstring): call_command('export_olx') diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py b/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py index ae21e494c5..3b4fe60ae2 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py @@ -162,7 +162,7 @@ class TestGitExport(CourseTestCase): ) cwd = os.path.abspath(git_export_utils.GIT_REPO_EXPORT_DIR / 'test_bare') git_log = subprocess.check_output(['git', 'log', '-1', - '--format=%an|%ae'], cwd=cwd) + '--format=%an|%ae'], cwd=cwd).decode('utf-8') self.assertEqual(expect_string, git_log) # Make changes to course so there is something to commit @@ -177,7 +177,7 @@ class TestGitExport(CourseTestCase): self.user.email, ) git_log = subprocess.check_output( - ['git', 'log', '-1', '--format=%an|%ae'], cwd=cwd) + ['git', 'log', '-1', '--format=%an|%ae'], cwd=cwd).decode('utf-8') self.assertEqual(expect_string, git_log) def test_no_change(self): diff --git a/cms/djangoapps/contentstore/tasks.py b/cms/djangoapps/contentstore/tasks.py index 2e247e1042..0725ca88c7 100644 --- a/cms/djangoapps/contentstore/tasks.py +++ b/cms/djangoapps/contentstore/tasks.py @@ -811,7 +811,7 @@ def import_olx(self, user_id, course_key_string, archive_path, archive_name, lan try: tar_file = tarfile.open(temp_filepath) try: - safetar_extractall(tar_file, (course_dir + u'/').encode(u'utf-8')) + safetar_extractall(tar_file, (course_dir + u'/')) except SuspiciousOperation as exc: LOGGER.info(u'Course import %s: Unsafe tar file - %s', courselike_key, exc.args[0]) with respect_language(language): diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index fb60589b53..51539efae9 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -149,7 +149,7 @@ class ImportRequiredTestCases(ContentStoreTestCase): # asset in the course. (i.e. _invalid_displayname_subs-esLhHcdKGWvKs.srt) asset_key = course.id.make_asset_key('asset', 'sample_asset.srt') content = StaticContent( - asset_key, expected_displayname, 'application/text', 'test', + asset_key, expected_displayname, 'application/text', b'test', ) content_store.save(content) @@ -159,7 +159,7 @@ class ImportRequiredTestCases(ContentStoreTestCase): # Verify both assets have similar `displayname` after saving. for asset in assets: - self.assertEquals(asset['displayname'], expected_displayname) + self.assertEqual(asset['displayname'], expected_displayname) # Test course export does not fail root_dir = path(mkdtemp_clean()) @@ -500,7 +500,7 @@ class ImportRequiredTestCases(ContentStoreTestCase): # Create a module, and ensure that its `data` field is empty word_cloud = ItemFactory.create(parent_location=parent.location, category="word_cloud", display_name="untitled") del word_cloud.data - self.assertEquals(word_cloud.data, '') + self.assertEqual(word_cloud.data, '') # Export the course root_dir = path(mkdtemp_clean()) @@ -511,7 +511,7 @@ class ImportRequiredTestCases(ContentStoreTestCase): imported_word_cloud = self.store.get_item(course_id.make_usage_key('word_cloud', 'untitled')) # It should now contain empty data - self.assertEquals(imported_word_cloud.data, '') + self.assertEqual(imported_word_cloud.data, '') def test_html_export_roundtrip(self): """ @@ -689,7 +689,7 @@ class MiscCourseTests(ContentStoreTestCase): resp = self.client.get_html(get_url('container_handler', self.vert_loc) + '?action=' + malicious_code) self.assertEqual(resp.status_code, 200) # Test that malicious code does not appear in html - self.assertNotIn(malicious_code, resp.content) + self.assertNotIn(malicious_code, resp.content.decode('utf-8')) def test_advanced_components_in_edit_unit(self): # This could be made better, but for now let's just assert that we see the advanced modules mentioned in the @@ -708,7 +708,7 @@ class MiscCourseTests(ContentStoreTestCase): # Create an asset with slash `invalid_displayname` ' asset_key = self.course.id.make_asset_key('asset', "fake_asset.txt") content = StaticContent( - asset_key, invalid_displayname, 'application/text', 'test', + asset_key, invalid_displayname, 'application/text', b'test', ) content_store.save(content) @@ -766,7 +766,7 @@ class MiscCourseTests(ContentStoreTestCase): asset_path = 'sample_asset_{}.txt'.format(i) asset_key = self.course.id.make_asset_key('asset', asset_path) content = StaticContent( - asset_key, asset_displayname, 'application/text', 'test', + asset_key, asset_displayname, 'application/text', b'test', ) content_store.save(content) @@ -776,7 +776,7 @@ class MiscCourseTests(ContentStoreTestCase): # Verify both assets have similar 'displayname' after saving. for asset in assets: - self.assertEquals(asset['displayname'], asset_displayname) + self.assertEqual(asset['displayname'], asset_displayname) # Now export the course to a tempdir and test that it contains assets. root_dir = path(mkdtemp_clean()) @@ -1000,7 +1000,7 @@ class MiscCourseTests(ContentStoreTestCase): """ asset_key = self.course.id.make_asset_key('asset', 'sample_static.html') content = StaticContent( - asset_key, "Fake asset", "application/text", "test", + asset_key, "Fake asset", "application/text", b"test", ) contentstore().save(content) @@ -1082,7 +1082,7 @@ class MiscCourseTests(ContentStoreTestCase): # add an asset asset_key = self.course.id.make_asset_key('asset', 'sample_static.html') content = StaticContent( - asset_key, "Fake asset", "application/text", "test", + asset_key, "Fake asset", "application/text", b"test", ) contentstore().save(content) assets, count = contentstore().get_all_content_for_course(self.course.id) @@ -1207,7 +1207,7 @@ class ContentStoreTest(ContentStoreTestCase): test_course_data = self.assert_created_course() course_id = _get_course_id(self.store, test_course_data) course_module = self.store.get_course(course_id) - self.assertEquals(course_module.language, 'hr') + self.assertEqual(course_module.language, 'hr') def test_create_course_with_dots(self): """Test new course creation with dots in the name""" @@ -1640,7 +1640,7 @@ class ContentStoreTest(ContentStoreTestCase): # Import a course with wiki_slug == location.course import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], target_id=target_id) course_module = self.store.get_course(target_id) - self.assertEquals(course_module.wiki_slug, 'toy') + self.assertEqual(course_module.wiki_slug, 'toy') # But change the wiki_slug if it is a different course. target_id = self.store.make_course_key('MITx', '111', '2013_Spring') @@ -1655,12 +1655,12 @@ class ContentStoreTest(ContentStoreTestCase): # Import a course with wiki_slug == location.course import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], target_id=target_id) course_module = self.store.get_course(target_id) - self.assertEquals(course_module.wiki_slug, 'MITx.111.2013_Spring') + self.assertEqual(course_module.wiki_slug, 'MITx.111.2013_Spring') # Now try importing a course with wiki_slug == '{0}.{1}.{2}'.format(location.org, location.course, location.run) import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['two_toys'], target_id=target_id) course_module = self.store.get_course(target_id) - self.assertEquals(course_module.wiki_slug, 'MITx.111.2013_Spring') + self.assertEqual(course_module.wiki_slug, 'MITx.111.2013_Spring') def test_import_metadata_with_attempts_empty_string(self): import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['simple'], create_if_not_present=True) @@ -1797,13 +1797,13 @@ class ContentStoreTest(ContentStoreTestCase): course_key = _get_course_id(self.store, self.course_data) _create_course(self, course_key, self.course_data) course_module = self.store.get_course(course_key) - self.assertEquals(course_module.wiki_slug, 'MITx.111.2013_Spring') + self.assertEqual(course_module.wiki_slug, 'MITx.111.2013_Spring') def test_course_handler_with_invalid_course_key_string(self): """Test viewing the course overview page with invalid course id""" response = self.client.get_html('/course/edX/test') - self.assertEquals(response.status_code, 404) + self.assertEqual(response.status_code, 404) class MetadataSaveTestCase(ContentStoreTestCase): @@ -1933,7 +1933,7 @@ class RerunCourseTest(ContentStoreTestCase): 'should_display': True, } for field_name, expected_value in six.iteritems(expected_states): - self.assertEquals(getattr(rerun_state, field_name), expected_value) + self.assertEqual(getattr(rerun_state, field_name), expected_value) # Verify that the creator is now enrolled in the course. self.assertTrue(CourseEnrollment.is_enrolled(self.user, destination_course_key)) @@ -2023,7 +2023,7 @@ class RerunCourseTest(ContentStoreTestCase): # Verify that the course rerun action is marked failed rerun_state = CourseRerunState.objects.find_first(course_key=destination_course_key) - self.assertEquals(rerun_state.state, CourseRerunUIStateManager.State.FAILED) + self.assertEqual(rerun_state.state, CourseRerunUIStateManager.State.FAILED) self.assertIn("Cannot find a course at", rerun_state.message) # Verify that the creator is not enrolled in the course. @@ -2071,7 +2071,7 @@ class RerunCourseTest(ContentStoreTestCase): source_course = CourseFactory.create() destination_course_key = self.post_rerun_request(source_course.id) rerun_state = CourseRerunState.objects.find_first(course_key=destination_course_key) - self.assertEquals(rerun_state.state, CourseRerunUIStateManager.State.FAILED) + self.assertEqual(rerun_state.state, CourseRerunUIStateManager.State.FAILED) self.assertIn(error_message, rerun_state.message) def test_rerun_error_trunc_message(self): @@ -2090,7 +2090,7 @@ class RerunCourseTest(ContentStoreTestCase): with mock.patch('traceback.format_exc', return_value=message_too_long): destination_course_key = self.post_rerun_request(source_course.id) rerun_state = CourseRerunState.objects.find_first(course_key=destination_course_key) - self.assertEquals(rerun_state.state, CourseRerunUIStateManager.State.FAILED) + self.assertEqual(rerun_state.state, CourseRerunUIStateManager.State.FAILED) self.assertTrue(rerun_state.message.endswith("traceback")) self.assertEqual(len(rerun_state.message), CourseRerunState.MAX_MESSAGE_LENGTH) @@ -2112,7 +2112,7 @@ class RerunCourseTest(ContentStoreTestCase): source_course = self.store.get_course(source_course_key) # Verify created course's wiki_slug. - self.assertEquals(source_course.wiki_slug, source_wiki_slug) + self.assertEqual(source_course.wiki_slug, source_wiki_slug) destination_course_data = course_data destination_course_data['run'] = '2013_Rerun' @@ -2128,7 +2128,7 @@ class RerunCourseTest(ContentStoreTestCase): ) # Verify rerun course's wiki_slug. - self.assertEquals(destination_course.wiki_slug, destination_wiki_slug) + self.assertEqual(destination_course.wiki_slug, destination_wiki_slug) class ContentLicenseTest(ContentStoreTestCase): @@ -2144,8 +2144,9 @@ class ContentLicenseTest(ContentStoreTestCase): export_course_to_xml(self.store, content_store, self.course.id, root_dir, u'test_license') fname = "{block}.xml".format(block=self.course.scope_ids.usage_id.block_id) run_file_path = root_dir / "test_license" / "course" / fname - run_xml = etree.parse(run_file_path.open()) - self.assertEqual(run_xml.getroot().get("license"), "creative-commons: BY SA") + with run_file_path.open() as f: + run_xml = etree.parse(f) + self.assertEqual(run_xml.getroot().get("license"), "creative-commons: BY SA") def test_video_license_export(self): content_store = contentstore() @@ -2157,8 +2158,9 @@ class ContentLicenseTest(ContentStoreTestCase): export_course_to_xml(self.store, content_store, self.course.id, root_dir, u'test_license') fname = "{block}.xml".format(block=video_descriptor.scope_ids.usage_id.block_id) video_file_path = root_dir / "test_license" / "video" / fname - video_xml = etree.parse(video_file_path.open()) - self.assertEqual(video_xml.getroot().get("license"), "all-rights-reserved") + with video_file_path.open() as f: + video_xml = etree.parse(f) + self.assertEqual(video_xml.getroot().get("license"), "all-rights-reserved") def test_license_import(self): course_items = import_course_from_xml( diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index e0a7bc5df6..7170adff52 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -22,6 +22,7 @@ from pytz import UTC from contentstore.config.waffle import ENABLE_PROCTORING_PROVIDER_OVERRIDES from contentstore.utils import reverse_course_url, reverse_usage_url +from course_modes.models import CourseMode from models.settings.course_grading import GRADING_POLICY_CHANGED_EVENT_TYPE, CourseGradingModel, hash_grading_policy from models.settings.course_metadata import CourseMetadata from models.settings.encoder import CourseSettingsEncoder @@ -179,6 +180,29 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin): elif field in encoded and encoded[field] is not None: self.fail(field + " included in encoding but missing from details at " + context) + @ddt.data( + (False, False), + (True, False), + (True, True), + ) + @ddt.unpack + def test_upgrade_deadline(self, has_verified_mode, has_expiration_date): + if has_verified_mode: + deadline = None + if has_expiration_date: + deadline = self.course.start + datetime.timedelta(days=2) + CourseMode.objects.get_or_create( + course_id=self.course.id, + mode_display_name="Verified", + mode_slug="verified", + min_price=1, + _expiration_datetime=deadline, + ) + + settings_details_url = get_url(self.course.id) + response = self.client.get_html(settings_details_url) + self.assertEqual("Upgrade Deadline Date" in response.content, has_expiration_date and has_verified_mode) + @mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True}) def test_pre_requisite_course_list_present(self): settings_details_url = get_url(self.course.id) @@ -240,15 +264,14 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin): (False, True, False), (True, True, True), ) - @override_settings(MKTG_URLS={'ROOT': 'dummy-root'}) def test_visibility_of_entrance_exam_section(self, feature_flags): """ Tests entrance exam section is available if ENTRANCE_EXAMS feature is enabled no matter any other - feature is enabled or disabled i.e ENABLE_MKTG_SITE. + feature is enabled or disabled i.e ENABLE_PUBLISHER. """ with patch.dict("django.conf.settings.FEATURES", { 'ENTRANCE_EXAMS': feature_flags[0], - 'ENABLE_MKTG_SITE': feature_flags[1] + 'ENABLE_PUBLISHER': feature_flags[1] }): course_details_url = get_url(self.course.id) resp = self.client.get_html(course_details_url) @@ -257,11 +280,11 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin): b'
- + Learn more about Creative Commons diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index aef8ba49e3..156b490e76 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -221,7 +221,7 @@
${description_text}
% endfor % for choice_id, choice_label in choices: + <% + label_class = 'response-label field-label label-inline' + input_class = 'field-input input-' + input_type + input_checked = '' + + if is_radio_input(choice_id) or (input_type != 'radio' and choice_id in value): + input_class += ' submitted' + if status.classname and not show_correctness == 'never': + label_class += ' choicegroup_' + status.classname + %>' + p1 + '';
});
diff --git a/common/lib/xmodule/xmodule/library_root_xblock.py b/common/lib/xmodule/xmodule/library_root_xblock.py
index 6494bc4440..17b2c856df 100644
--- a/common/lib/xmodule/xmodule/library_root_xblock.py
+++ b/common/lib/xmodule/xmodule/library_root_xblock.py
@@ -4,8 +4,9 @@
from __future__ import absolute_import
import logging
-
import six
+
+from django.utils.encoding import python_2_unicode_compatible
from web_fragments.fragment import Fragment
from xblock.core import XBlock
from xblock.fields import Boolean, List, Scope, String
@@ -18,6 +19,7 @@ log = logging.getLogger(__name__)
_ = lambda text: text
+@python_2_unicode_compatible
class LibraryRoot(XBlock):
"""
The LibraryRoot is the root XBlock of a content library. All other blocks in
@@ -47,11 +49,8 @@ class LibraryRoot(XBlock):
has_children = True
has_author_view = True
- def __unicode__(self):
- return u"Library: {}".format(self.display_name)
-
def __str__(self):
- return six.text_type(self).encode('utf-8')
+ return u"Library: {}".format(self.display_name)
def author_view(self, context):
"""
diff --git a/common/lib/xmodule/xmodule/lti_2_util.py b/common/lib/xmodule/xmodule/lti_2_util.py
index adf5708fdd..0e15fd1f8b 100644
--- a/common/lib/xmodule/xmodule/lti_2_util.py
+++ b/common/lib/xmodule/xmodule/lti_2_util.py
@@ -174,12 +174,12 @@ class LTI20ModuleMixin(object):
}
self.system.rebind_noauth_module_to_user(self, real_user)
if self.module_score is None: # In this case, no score has been ever set
- return Response(json.dumps(base_json_obj), content_type=LTI_2_0_JSON_CONTENT_TYPE)
+ return Response(json.dumps(base_json_obj).encode('utf-8'), content_type=LTI_2_0_JSON_CONTENT_TYPE)
# Fall through to returning grade and comment
base_json_obj['resultScore'] = round(self.module_score, 2)
base_json_obj['comment'] = self.score_comment
- return Response(json.dumps(base_json_obj), content_type=LTI_2_0_JSON_CONTENT_TYPE)
+ return Response(json.dumps(base_json_obj).encode('utf-8'), content_type=LTI_2_0_JSON_CONTENT_TYPE)
def _lti_2_0_result_del_handler(self, request, real_user): # pylint: disable=unused-argument
"""
@@ -211,7 +211,7 @@ class LTI20ModuleMixin(object):
webob.response: response to this request. status 200 if success. 404 if body of PUT request is malformed
"""
try:
- (score, comment) = self.parse_lti_2_0_result_json(request.body)
+ (score, comment) = self.parse_lti_2_0_result_json(request.body.decode('utf-8'))
except LTIError:
return Response(status=404) # have to do 404 due to spec, but 400 is better, with error msg in body
diff --git a/common/lib/xmodule/xmodule/lti_module.py b/common/lib/xmodule/xmodule/lti_module.py
index b439f335ae..73f7199069 100644
--- a/common/lib/xmodule/xmodule/lti_module.py
+++ b/common/lib/xmodule/xmodule/lti_module.py
@@ -85,7 +85,7 @@ from openedx.core.djangolib.markup import HTML, Text
log = logging.getLogger(__name__)
DOCS_ANCHOR_TAG_OPEN = (
- ""
)
BREAK_TAG = '
|