From 9117c9979ede49b6f116df0dc7bffb040c3df005 Mon Sep 17 00:00:00 2001
From: Julian Arni
Date: Thu, 29 Aug 2013 16:12:56 -0400
Subject: [PATCH 1/9] Fix import errors with unicode filenames
---
cms/djangoapps/contentstore/views/import_export.py | 2 +-
common/lib/xmodule/xmodule/modulestore/xml_importer.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/cms/djangoapps/contentstore/views/import_export.py b/cms/djangoapps/contentstore/views/import_export.py
index 8d7f1aac76..df6d960bba 100644
--- a/cms/djangoapps/contentstore/views/import_export.py
+++ b/cms/djangoapps/contentstore/views/import_export.py
@@ -154,7 +154,7 @@ def import_course(request, org, course, name):
sf.write("Extracting")
tar_file = tarfile.open(temp_filepath)
- tar_file.extractall(course_dir + '/')
+ tar_file.extractall((course_dir + '/').encode('utf-8'))
with open(status_file, 'w+') as sf:
sf.write("Verifying")
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
index 9ee6fe66a3..815793a8c7 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
@@ -31,7 +31,7 @@ def import_static_content(modules, course_loc, course_data_path, static_content_
try:
content_path = os.path.join(dirname, filename)
if verbose:
- log.debug('importing static content {0}...'.format(content_path))
+ log.debug('importing static content {0}...'.format(content_path.encode("utf-8")))
fullname_with_subpath = content_path.replace(static_dir, '') # strip away leading path from the name
if fullname_with_subpath.startswith('/'):
From b62adb562c07816911e5bef6a825ffe67e325cd3 Mon Sep 17 00:00:00 2001
From: Julian Arni
Date: Wed, 4 Sep 2013 13:33:08 -0400
Subject: [PATCH 2/9] Add tests for imports with unicode filenames
---
.../xmodule/modulestore/tests/test_mongo.py | 30 +++++++++++---
.../lib/xmodule/xmodule/tests/test_import.py | 39 +++++++++++++++++++
.../data/test_unicode/about/end_date.html | 1 +
.../chapter/handout_container.xml | 3 ++
.../data/test_unicode/chapter/poll_test.xml | 7 ++++
.../test_unicode/chapter/secret/magic.xml | 3 ++
common/test/data/test_unicode/course.xml | 1 +
.../data/test_unicode/course/2012_Fall.xml | 20 ++++++++++
.../test/data/test_unicode/info/handouts.html | 1 +
.../data/test_unicode/policies/2012_Fall.json | 33 ++++++++++++++++
.../sequential/vertical_sequential.xml | 4 ++
.../static/handouts/sample_handout.txt | 0
.../test_unicode/static/sample_static.txt | 0
.../test/data/test_unicode/static/unicø∂é.txt | 13 +++++++
.../static_import/∫hould_be_imπorted.html | 1 +
.../test/data/test_unicode/tabs/syllabus.html | 1 +
.../data/test_unicode/tabs/®esources.html | 1 +
.../test_unicode/vertical/vertical_test.xml | 3 ++
.../test/data/test_unicode/video/a_video.xml | 1 +
19 files changed, 157 insertions(+), 5 deletions(-)
create mode 100644 common/test/data/test_unicode/about/end_date.html
create mode 100644 common/test/data/test_unicode/chapter/handout_container.xml
create mode 100644 common/test/data/test_unicode/chapter/poll_test.xml
create mode 100644 common/test/data/test_unicode/chapter/secret/magic.xml
create mode 100644 common/test/data/test_unicode/course.xml
create mode 100644 common/test/data/test_unicode/course/2012_Fall.xml
create mode 100644 common/test/data/test_unicode/info/handouts.html
create mode 100644 common/test/data/test_unicode/policies/2012_Fall.json
create mode 100644 common/test/data/test_unicode/sequential/vertical_sequential.xml
create mode 100644 common/test/data/test_unicode/static/handouts/sample_handout.txt
create mode 100644 common/test/data/test_unicode/static/sample_static.txt
create mode 100644 common/test/data/test_unicode/static/unicø∂é.txt
create mode 100644 common/test/data/test_unicode/static_import/∫hould_be_imπorted.html
create mode 100644 common/test/data/test_unicode/tabs/syllabus.html
create mode 100644 common/test/data/test_unicode/tabs/®esources.html
create mode 100644 common/test/data/test_unicode/vertical/vertical_test.xml
create mode 100644 common/test/data/test_unicode/video/a_video.xml
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
index 6f3276bf44..74ba7aa2e1 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
@@ -4,6 +4,7 @@ from nose.tools import assert_equals, assert_raises, \
assert_not_equals, assert_false
# pylint: enable=E0611
import pymongo
+import logging
from uuid import uuid4
from xblock.fields import Scope
@@ -19,6 +20,7 @@ from xmodule.contentstore.mongo import MongoContentStore
from xmodule.modulestore.tests.test_modulestore import check_path_to_location
+log = logging.getLogger(__name__)
HOST = 'localhost'
PORT = 27017
@@ -59,7 +61,7 @@ class TestMongoModuleStore(object):
#
draft_store = DraftModuleStore(HOST, DB, COLLECTION, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS)
# Explicitly list the courses to load (don't want the big one)
- courses = ['toy', 'simple', 'simple_with_draft']
+ courses = ['toy', 'simple', 'simple_with_draft', 'test_unicode']
import_from_xml(store, DATA_DIR, courses, draft_store=draft_store, static_content_store=content_store)
# also test a course with no importing of static content
@@ -100,12 +102,14 @@ class TestMongoModuleStore(object):
def test_get_courses(self):
'''Make sure the course objects loaded properly'''
courses = self.store.get_courses()
- assert_equals(len(courses), 4)
+ assert_equals(len(courses), 5)
courses.sort(key=lambda c: c.id)
assert_equals(courses[0].id, 'edX/simple/2012_Fall')
assert_equals(courses[1].id, 'edX/simple_with_draft/2012_Fall')
assert_equals(courses[2].id, 'edX/test_import_course/2012_Fall')
- assert_equals(courses[3].id, 'edX/toy/2012_Fall')
+ assert_equals(courses[3].id, 'edX/test_unicode/2012_Fall')
+ assert_equals(courses[4].id, 'edX/toy/2012_Fall')
+ log.debug(str(courses))
def test_loads(self):
assert_not_equals(
@@ -120,6 +124,22 @@ class TestMongoModuleStore(object):
self.store.get_item("i4x://edX/toy/video/Welcome"),
None)
+ def test_unicode_loads(self):
+ assert_not_equals(
+ self.store.get_item("i4x://edX/test_unicode/course/2012_Fall"),
+ None)
+ # All items with ascii-only filenames should load properly.
+ assert_not_equals(
+ self.store.get_item("i4x://edX/test_unicode/video/Welcome"),
+ None)
+ assert_not_equals(
+ self.store.get_item("i4x://edX/test_unicode/video/Welcome"),
+ None)
+ assert_not_equals(
+ self.store.get_item("i4x://edX/test_unicode/chapter/Overview"),
+ None)
+
+
def test_find_one(self):
assert_not_equals(
self.store._find_one(Location("i4x://edX/toy/course/2012_Fall")),
@@ -159,9 +179,9 @@ class TestMongoModuleStore(object):
"""
Helper function for pulling out the name of a given static tab.
- Assumes the information is desired for courses[1] ('toy' course).
+ Assumes the information is desired for courses[4] ('toy' course).
"""
- return courses[2].tabs[index]['name']
+ return courses[4].tabs[index]['name']
# There was a bug where model.save was not getting called after the static tab name
# was set set for tabs that have a URL slug. 'Syllabus' and 'Resources' fall into that
diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py
index 639cb51149..f40e00e8d0 100644
--- a/common/lib/xmodule/xmodule/tests/test_import.py
+++ b/common/lib/xmodule/xmodule/tests/test_import.py
@@ -369,6 +369,45 @@ class ImportTestCase(BaseCourseTestCase):
html = modulestore.get_instance(course_id, loc)
self.assertEquals(html.display_name, "Toy lab")
+ def test_unicode(self):
+ """Check that courses with unicode characters in filenames and in
+ org/course/name import properly. Currently, this means: (a) Having
+ files with unicode names does not prevent import; (b) if files are not
+ loaded because of unicode filenames, there are appropriate
+ exceptions/errors to that effect."""
+
+ print("Starting import")
+ modulestore = XMLModuleStore(DATA_DIR, course_dirs=['test_unicode'])
+ courses = modulestore.get_courses()
+ self.assertEquals(len(courses), 1)
+ course = courses[0]
+ course_id = course.id
+
+ print("course errors:")
+
+ # Expect to find an error/exception about characters in "®esources"
+ found = False
+ expect = "Invalid characters in '®esources'"
+ for (msg, err) in modulestore.get_item_errors(course.location):
+ msg_u = msg.encode("utf-8")
+ err_u = err.encode("utf-8")
+ print(msg_u)
+ print(err_u)
+ if max(msg_u.find(expect), err_u.find(expect)) >= 1:
+ found = True
+
+ self.assertTrue(found)
+ chapters = course.get_children()
+ self.assertEquals(len(chapters), 5)
+
+ ch2 = chapters[1]
+ self.assertEquals(ch2.url_name, "secret:magic")
+
+ print("Ch2 location: ", ch2.location)
+
+ also_ch2 = modulestore.get_instance(course_id, ch2.location)
+ self.assertEquals(ch2, also_ch2)
+
def test_url_name_mangling(self):
"""
Make sure that url_names are only mangled once.
diff --git a/common/test/data/test_unicode/about/end_date.html b/common/test/data/test_unicode/about/end_date.html
new file mode 100644
index 0000000000..a0990367ef
--- /dev/null
+++ b/common/test/data/test_unicode/about/end_date.html
@@ -0,0 +1 @@
+TBD
diff --git a/common/test/data/test_unicode/chapter/handout_container.xml b/common/test/data/test_unicode/chapter/handout_container.xml
new file mode 100644
index 0000000000..0be4e30bbb
--- /dev/null
+++ b/common/test/data/test_unicode/chapter/handout_container.xml
@@ -0,0 +1,3 @@
+
+ handouts
+
\ No newline at end of file
diff --git a/common/test/data/test_unicode/chapter/poll_test.xml b/common/test/data/test_unicode/chapter/poll_test.xml
new file mode 100644
index 0000000000..bbb340e936
--- /dev/null
+++ b/common/test/data/test_unicode/chapter/poll_test.xml
@@ -0,0 +1,7 @@
+
+
+ Have you changed your mind?
+ Yes
+ No
+
+
\ No newline at end of file
diff --git a/common/test/data/test_unicode/chapter/secret/magic.xml b/common/test/data/test_unicode/chapter/secret/magic.xml
new file mode 100644
index 0000000000..fe7adb7967
--- /dev/null
+++ b/common/test/data/test_unicode/chapter/secret/magic.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/common/test/data/test_unicode/course.xml b/common/test/data/test_unicode/course.xml
new file mode 100644
index 0000000000..0375e11d3a
--- /dev/null
+++ b/common/test/data/test_unicode/course.xml
@@ -0,0 +1 @@
+
diff --git a/common/test/data/test_unicode/course/2012_Fall.xml b/common/test/data/test_unicode/course/2012_Fall.xml
new file mode 100644
index 0000000000..4eb9d66171
--- /dev/null
+++ b/common/test/data/test_unicode/course/2012_Fall.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/common/test/data/test_unicode/info/handouts.html b/common/test/data/test_unicode/info/handouts.html
new file mode 100644
index 0000000000..85fa34d71d
--- /dev/null
+++ b/common/test/data/test_unicode/info/handouts.html
@@ -0,0 +1 @@
+Sample
\ No newline at end of file
diff --git a/common/test/data/test_unicode/policies/2012_Fall.json b/common/test/data/test_unicode/policies/2012_Fall.json
new file mode 100644
index 0000000000..c35877b8f5
--- /dev/null
+++ b/common/test/data/test_unicode/policies/2012_Fall.json
@@ -0,0 +1,33 @@
+{
+ "course/2012_Fall": {
+ "graceperiod": "2 days 5 hours 59 minutes 59 seconds",
+ "start": "2015-07-17T12:00",
+ "display_name": "Toy Course",
+ "graded": "true",
+ "tabs": [
+ {"type": "courseware"},
+ {"type": "course_info", "name": "Course Info"},
+ {"type": "static_tab", "url_slug": "syllabus", "name": "ßyllabus"},
+ {"type": "static_tab", "url_slug": "resources", "name": "Resources"},
+ {"type": "discussion", "name": "∂iscussion"},
+ {"type": "wiki", "name": "∑iki"},
+ {"type": "progress", "name": "πrogress"}
+ ]
+ },
+ "chapter/Overview": {
+ "display_name": "O√erview"
+ },
+ "videosequence/Toy_Videos": {
+ "display_name": "†oy Videos",
+ "format": "Lecture Sequence"
+ },
+ "html/secret:toylab": {
+ "display_name": "Toy lab"
+ },
+ "video/Video_Resources": {
+ "display_name": "Video Resources"
+ },
+ "video/Welcome": {
+ "display_name": "Welcome"
+ }
+}
diff --git a/common/test/data/test_unicode/sequential/vertical_sequential.xml b/common/test/data/test_unicode/sequential/vertical_sequential.xml
new file mode 100644
index 0000000000..695e640243
--- /dev/null
+++ b/common/test/data/test_unicode/sequential/vertical_sequential.xml
@@ -0,0 +1,4 @@
+
+
+ …
+
\ No newline at end of file
diff --git a/common/test/data/test_unicode/static/handouts/sample_handout.txt b/common/test/data/test_unicode/static/handouts/sample_handout.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/test/data/test_unicode/static/sample_static.txt b/common/test/data/test_unicode/static/sample_static.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/test/data/test_unicode/static/unicø∂é.txt b/common/test/data/test_unicode/static/unicø∂é.txt
new file mode 100644
index 0000000000..248ebeafb7
--- /dev/null
+++ b/common/test/data/test_unicode/static/unicø∂é.txt
@@ -0,0 +1,13 @@
+μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος
+οὐλομένην, ἣ μυρί᾽ Ἀχαιοῖς ἄλγε᾽ ἔθηκε,
+πολλὰς δ᾽ ἰφθίμους ψυχὰς Ἄϊδι προΐαψεν
+ἡρώων, αὐτοὺς δὲ ἑλώρια τεῦχε κύνεσσιν
+5οἰωνοῖσί τε πᾶσι, Διὸς δ᾽ ἐτελείετο βουλή,
+ἐξ οὗ δὴ τὰ πρῶτα διαστήτην ἐρίσαντε
+Ἀτρεΐδης τε ἄναξ ἀνδρῶν καὶ δῖος Ἀχιλλεύς.
+τίς τ᾽ ἄρ σφωε θεῶν ἔριδι ξυνέηκε μάχεσθαι;
+Λητοῦς καὶ Διὸς υἱός: ὃ γὰρ βασιλῆϊ χολωθεὶς
+10νοῦσον ἀνὰ στρατὸν ὄρσε κακήν, ὀλέκοντο δὲ λαοί,
+οὕνεκα τὸν Χρύσην ἠτίμασεν ἀρητῆρα
+Ἀτρεΐδης: ὃ γὰρ ἦλθε θοὰς ἐπὶ νῆας Ἀχαιῶν
+λυσόμενός τε θύγατρα φέρων τ᾽ ἀπερείσι᾽ ἄποινα,
diff --git a/common/test/data/test_unicode/static_import/∫hould_be_imπorted.html b/common/test/data/test_unicode/static_import/∫hould_be_imπorted.html
new file mode 100644
index 0000000000..2ab33a3a03
--- /dev/null
+++ b/common/test/data/test_unicode/static_import/∫hould_be_imπorted.html
@@ -0,0 +1 @@
+
this ƒîlë should be in the contentstore
diff --git a/common/test/data/test_unicode/tabs/syllabus.html b/common/test/data/test_unicode/tabs/syllabus.html
new file mode 100644
index 0000000000..45c2a7fc8f
--- /dev/null
+++ b/common/test/data/test_unicode/tabs/syllabus.html
@@ -0,0 +1 @@
+This is a syllabus
diff --git a/common/test/data/test_unicode/tabs/®esources.html b/common/test/data/test_unicode/tabs/®esources.html
new file mode 100644
index 0000000000..0f6bdf0984
--- /dev/null
+++ b/common/test/data/test_unicode/tabs/®esources.html
@@ -0,0 +1 @@
+resources!
diff --git a/common/test/data/test_unicode/vertical/vertical_test.xml b/common/test/data/test_unicode/vertical/vertical_test.xml
new file mode 100644
index 0000000000..82df156dfe
--- /dev/null
+++ b/common/test/data/test_unicode/vertical/vertical_test.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/common/test/data/test_unicode/video/a_video.xml b/common/test/data/test_unicode/video/a_video.xml
new file mode 100644
index 0000000000..b90ea9d8c4
--- /dev/null
+++ b/common/test/data/test_unicode/video/a_video.xml
@@ -0,0 +1 @@
+
From beb5f2f846aba47b2d1b822b5c523858df06f1d7 Mon Sep 17 00:00:00 2001
From: Julian Arni
Date: Wed, 4 Sep 2013 13:33:43 -0400
Subject: [PATCH 3/9] Fix unicode errors on exceptions and logging
---
common/lib/xmodule/xmodule/modulestore/xml.py | 10 ++++++----
common/lib/xmodule/xmodule/modulestore/xml_importer.py | 2 +-
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py
index 2d8ab54452..432abd7cb3 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml.py
@@ -173,7 +173,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
# Didn't load properly. Fall back on loading as an error
# descriptor. This should never error due to formatting.
- msg = "Error loading from xml. " + str(err)[:200]
+ msg = "Error loading from xml. " + unicode(err)[:200]
log.warning(msg)
# Normally, we don't want lots of exception traces in our logs from common
# content problems. But if you're debugging the xml loading code itself,
@@ -317,7 +317,8 @@ class XMLModuleStore(ModuleStoreBase):
try:
course_descriptor = self.load_course(course_dir, errorlog.tracker)
except Exception as e:
- msg = "ERROR: Failed to load course '{0}': {1}".format(course_dir, str(e))
+ msg = "ERROR: Failed to load course '{0}': {1}".format(course_dir.encode("utf-8"),
+ unicode(e))
log.exception(msg)
errorlog.tracker(msg)
@@ -493,8 +494,9 @@ class XMLModuleStore(ModuleStoreBase):
module.save()
self.modules[course_descriptor.id][module.location] = module
except Exception, e:
- logging.exception("Failed to load {0}. Skipping... Exception: {1}".format(filepath, str(e)))
- system.error_tracker("ERROR: " + str(e))
+ logging.exception("Failed to load %s. Skipping... \
+ Exception: %s", filepath, unicode(e))
+ system.error_tracker("ERROR: " + unicode(e))
def get_instance(self, course_id, location, depth=0):
"""
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
index 815793a8c7..709a431462 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
@@ -31,7 +31,7 @@ def import_static_content(modules, course_loc, course_data_path, static_content_
try:
content_path = os.path.join(dirname, filename)
if verbose:
- log.debug('importing static content {0}...'.format(content_path.encode("utf-8")))
+ log.debug('importing static content %s...', content_path.encode("utf-8"))
fullname_with_subpath = content_path.replace(static_dir, '') # strip away leading path from the name
if fullname_with_subpath.startswith('/'):
From 767c408e0709b93fbac3bf01e6f71a8f550e7ab1 Mon Sep 17 00:00:00 2001
From: Julian Arni
Date: Wed, 4 Sep 2013 15:54:49 -0400
Subject: [PATCH 4/9] Use upside-down english for unicode text
---
.../test/data/test_unicode/static/unicø∂é.txt | 23 ++++++++-----------
1 file changed, 10 insertions(+), 13 deletions(-)
diff --git a/common/test/data/test_unicode/static/unicø∂é.txt b/common/test/data/test_unicode/static/unicø∂é.txt
index 248ebeafb7..44d66a7400 100644
--- a/common/test/data/test_unicode/static/unicø∂é.txt
+++ b/common/test/data/test_unicode/static/unicø∂é.txt
@@ -1,13 +1,10 @@
-μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος
-οὐλομένην, ἣ μυρί᾽ Ἀχαιοῖς ἄλγε᾽ ἔθηκε,
-πολλὰς δ᾽ ἰφθίμους ψυχὰς Ἄϊδι προΐαψεν
-ἡρώων, αὐτοὺς δὲ ἑλώρια τεῦχε κύνεσσιν
-5οἰωνοῖσί τε πᾶσι, Διὸς δ᾽ ἐτελείετο βουλή,
-ἐξ οὗ δὴ τὰ πρῶτα διαστήτην ἐρίσαντε
-Ἀτρεΐδης τε ἄναξ ἀνδρῶν καὶ δῖος Ἀχιλλεύς.
-τίς τ᾽ ἄρ σφωε θεῶν ἔριδι ξυνέηκε μάχεσθαι;
-Λητοῦς καὶ Διὸς υἱός: ὃ γὰρ βασιλῆϊ χολωθεὶς
-10νοῦσον ἀνὰ στρατὸν ὄρσε κακήν, ὀλέκοντο δὲ λαοί,
-οὕνεκα τὸν Χρύσην ἠτίμασεν ἀρητῆρα
-Ἀτρεΐδης: ὃ γὰρ ἦλθε θοὰς ἐπὶ νῆας Ἀχαιῶν
-λυσόμενός τε θύγατρα φέρων τ᾽ ἀπερείσι᾽ ἄποινα,
+
+Upside down unicode text (http://www.sunnyneo.com/upsidedowntext.php?)
+
+˙ʇʇɐld uoɯəl plos əɥs ˙pəʌıl əuɹʎq ʎʇʇəq əɹəɥʍ pɐoɹ əɥʇ uʍop əɯɐɔ ʍoɔ-ooɯ əɥʇ
+˙ooʞɔnʇ ʎqɐq sɐʍ əɥ ˙əɔɐɟ ʎɹıɐɥ ɐ pɐɥ əɥ :ssɐlƃ ɐ ɥƃnoɹɥʇ ɯıɥ ʇɐ pəʞool ɹəɥʇɐɟ
+sıɥ :ʎɹoʇs ʇɐɥʇ ɯıɥ ploʇ ɹəɥʇɐɟ sıɥ
+
+˙˙˙ooʞɔnʇ ʎqɐq pəɯɐu ʎoq əlʇʇıl suəɔıu ɐ ʇəɯ pɐoɹ əɥʇ ƃuolɐ uʍop ƃuıɯoɔ sɐʍ ʇɐɥʇ
+ʍoɔ-ooɯ sıɥʇ puɐ pɐoɹ əɥʇ ƃuolɐ uʍop ƃuıɯoɔ ʍoɔ-ooɯ ɐ sɐʍ əɹəɥʇ sɐʍ ʇı əɯıʇ pooƃ
+ʎɹəʌ ɐ puɐ əɯıʇ ɐ uodn əɔuo
From 71dd0c36d6ca9830abfac799dacb8eaf2ea9479b Mon Sep 17 00:00:00 2001
From: Julian Arni
Date: Wed, 4 Sep 2013 16:03:49 -0400
Subject: [PATCH 5/9] Remove unnecessary encode
---
common/lib/xmodule/xmodule/modulestore/xml_importer.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
index 709a431462..f9f1806b2d 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
@@ -31,7 +31,7 @@ def import_static_content(modules, course_loc, course_data_path, static_content_
try:
content_path = os.path.join(dirname, filename)
if verbose:
- log.debug('importing static content %s...', content_path.encode("utf-8"))
+ log.debug('importing static content %s...', content_path)
fullname_with_subpath = content_path.replace(static_dir, '') # strip away leading path from the name
if fullname_with_subpath.startswith('/'):
From aa4147c546615cddd0590267d28f3cae6a18e4b6 Mon Sep 17 00:00:00 2001
From: Julian Arni
Date: Wed, 4 Sep 2013 16:54:53 -0400
Subject: [PATCH 6/9] Bump diff-cover version
---
requirements/edx/github.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt
index 372aa91f84..5c8c7d79e1 100644
--- a/requirements/edx/github.txt
+++ b/requirements/edx/github.txt
@@ -16,5 +16,5 @@
# Our libraries:
-e git+https://github.com/edx/XBlock.git@aa0d60627#egg=XBlock
-e git+https://github.com/edx/codejail.git@0a1b468#egg=codejail
--e git+https://github.com/edx/diff-cover.git@v0.2.2#egg=diff_cover
+-e git+https://github.com/edx/diff-cover.git@v0.2.3#egg=diff_cover
-e git+https://github.com/edx/js-test-tool.git@v0.0.7#egg=js_test_tool
From c9ca5599e101f26a7b2441f5a3bc13128603c796 Mon Sep 17 00:00:00 2001
From: Julian Arni
Date: Tue, 10 Sep 2013 09:54:27 -0400
Subject: [PATCH 7/9] Cleaner tests
---
common/lib/xmodule/xmodule/seq_module.py | 2 +-
.../lib/xmodule/xmodule/tests/test_import.py | 24 ++++++-------------
.../chapter/handout_container.xml | 3 ---
.../data/test_unicode/chapter/poll_test.xml | 7 ------
.../test_unicode/chapter/secret/magic.xml | 3 ---
.../data/test_unicode/chapter/simple_html.xml | 8 +++++++
.../data/test_unicode/course/2012_Fall.xml | 16 ++-----------
.../test/data/test_unicode/info/handouts.html | 1 -
.../data/test_unicode/policies/2012_Fall.json | 14 ++---------
.../static/handouts/sample_handout.txt | 0
.../test_unicode/static/sample_static.txt | 0
.../static_import/∫hould_be_imπorted.html | 1 -
.../test/data/test_unicode/tabs/syllabus.html | 1 -
.../test/data/test_unicode/video/a_video.xml | 1 -
14 files changed, 20 insertions(+), 61 deletions(-)
delete mode 100644 common/test/data/test_unicode/chapter/handout_container.xml
delete mode 100644 common/test/data/test_unicode/chapter/poll_test.xml
delete mode 100644 common/test/data/test_unicode/chapter/secret/magic.xml
create mode 100644 common/test/data/test_unicode/chapter/simple_html.xml
delete mode 100644 common/test/data/test_unicode/info/handouts.html
delete mode 100644 common/test/data/test_unicode/static/handouts/sample_handout.txt
delete mode 100644 common/test/data/test_unicode/static/sample_static.txt
delete mode 100644 common/test/data/test_unicode/static_import/∫hould_be_imπorted.html
delete mode 100644 common/test/data/test_unicode/tabs/syllabus.html
delete mode 100644 common/test/data/test_unicode/video/a_video.xml
diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py
index ebd49d75cf..867eae19ce 100644
--- a/common/lib/xmodule/xmodule/seq_module.py
+++ b/common/lib/xmodule/xmodule/seq_module.py
@@ -133,7 +133,7 @@ class SequenceDescriptor(SequenceFields, MakoModuleDescriptor, XmlDescriptor):
except Exception as e:
log.exception("Unable to load child when parsing Sequence. Continuing...")
if system.error_tracker is not None:
- system.error_tracker("ERROR: " + str(e))
+ system.error_tracker("ERROR: " + unicode(e))
continue
return {}, children
diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py
index f40e00e8d0..b44a2044bb 100644
--- a/common/lib/xmodule/xmodule/tests/test_import.py
+++ b/common/lib/xmodule/xmodule/tests/test_import.py
@@ -381,32 +381,22 @@ class ImportTestCase(BaseCourseTestCase):
courses = modulestore.get_courses()
self.assertEquals(len(courses), 1)
course = courses[0]
- course_id = course.id
print("course errors:")
# Expect to find an error/exception about characters in "®esources"
- found = False
expect = "Invalid characters in '®esources'"
- for (msg, err) in modulestore.get_item_errors(course.location):
- msg_u = msg.encode("utf-8")
- err_u = err.encode("utf-8")
- print(msg_u)
- print(err_u)
- if max(msg_u.find(expect), err_u.find(expect)) >= 1:
- found = True
+ errors = [(msg.encode("utf-8"), err.encode("utf-8"))
+ for msg, err in
+ modulestore.get_item_errors(course.location)]
- self.assertTrue(found)
+ self.assertTrue(any(expect in msg or expect in err
+ for msg, err in errors))
chapters = course.get_children()
- self.assertEquals(len(chapters), 5)
+ self.assertEqual(len(chapters), 3)
- ch2 = chapters[1]
- self.assertEquals(ch2.url_name, "secret:magic")
+ ch3 = chapters[2]
- print("Ch2 location: ", ch2.location)
-
- also_ch2 = modulestore.get_instance(course_id, ch2.location)
- self.assertEquals(ch2, also_ch2)
def test_url_name_mangling(self):
"""
diff --git a/common/test/data/test_unicode/chapter/handout_container.xml b/common/test/data/test_unicode/chapter/handout_container.xml
deleted file mode 100644
index 0be4e30bbb..0000000000
--- a/common/test/data/test_unicode/chapter/handout_container.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- handouts
-
\ No newline at end of file
diff --git a/common/test/data/test_unicode/chapter/poll_test.xml b/common/test/data/test_unicode/chapter/poll_test.xml
deleted file mode 100644
index bbb340e936..0000000000
--- a/common/test/data/test_unicode/chapter/poll_test.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
- Have you changed your mind?
- Yes
- No
-
-
\ No newline at end of file
diff --git a/common/test/data/test_unicode/chapter/secret/magic.xml b/common/test/data/test_unicode/chapter/secret/magic.xml
deleted file mode 100644
index fe7adb7967..0000000000
--- a/common/test/data/test_unicode/chapter/secret/magic.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/common/test/data/test_unicode/chapter/simple_html.xml b/common/test/data/test_unicode/chapter/simple_html.xml
new file mode 100644
index 0000000000..c07cf4a7c7
--- /dev/null
+++ b/common/test/data/test_unicode/chapter/simple_html.xml
@@ -0,0 +1,8 @@
+
+
+
+ This is upside down text:
+ ˙ʇxəʇ uʍop əpısdn sı sıɥʇ
+
+
+
diff --git a/common/test/data/test_unicode/course/2012_Fall.xml b/common/test/data/test_unicode/course/2012_Fall.xml
index 4eb9d66171..b402922bcb 100644
--- a/common/test/data/test_unicode/course/2012_Fall.xml
+++ b/common/test/data/test_unicode/course/2012_Fall.xml
@@ -1,20 +1,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
diff --git a/common/test/data/test_unicode/info/handouts.html b/common/test/data/test_unicode/info/handouts.html
deleted file mode 100644
index 85fa34d71d..0000000000
--- a/common/test/data/test_unicode/info/handouts.html
+++ /dev/null
@@ -1 +0,0 @@
-Sample
\ No newline at end of file
diff --git a/common/test/data/test_unicode/policies/2012_Fall.json b/common/test/data/test_unicode/policies/2012_Fall.json
index c35877b8f5..71f88849ec 100644
--- a/common/test/data/test_unicode/policies/2012_Fall.json
+++ b/common/test/data/test_unicode/policies/2012_Fall.json
@@ -2,13 +2,13 @@
"course/2012_Fall": {
"graceperiod": "2 days 5 hours 59 minutes 59 seconds",
"start": "2015-07-17T12:00",
- "display_name": "Toy Course",
+ "display_name": "Üñîçø∂e †es† Course",
"graded": "true",
"tabs": [
{"type": "courseware"},
{"type": "course_info", "name": "Course Info"},
{"type": "static_tab", "url_slug": "syllabus", "name": "ßyllabus"},
- {"type": "static_tab", "url_slug": "resources", "name": "Resources"},
+ {"type": "static_tab", "url_slug": "resources", "name": "®esources"},
{"type": "discussion", "name": "∂iscussion"},
{"type": "wiki", "name": "∑iki"},
{"type": "progress", "name": "πrogress"}
@@ -17,16 +17,6 @@
"chapter/Overview": {
"display_name": "O√erview"
},
- "videosequence/Toy_Videos": {
- "display_name": "†oy Videos",
- "format": "Lecture Sequence"
- },
- "html/secret:toylab": {
- "display_name": "Toy lab"
- },
- "video/Video_Resources": {
- "display_name": "Video Resources"
- },
"video/Welcome": {
"display_name": "Welcome"
}
diff --git a/common/test/data/test_unicode/static/handouts/sample_handout.txt b/common/test/data/test_unicode/static/handouts/sample_handout.txt
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/common/test/data/test_unicode/static/sample_static.txt b/common/test/data/test_unicode/static/sample_static.txt
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/common/test/data/test_unicode/static_import/∫hould_be_imπorted.html b/common/test/data/test_unicode/static_import/∫hould_be_imπorted.html
deleted file mode 100644
index 2ab33a3a03..0000000000
--- a/common/test/data/test_unicode/static_import/∫hould_be_imπorted.html
+++ /dev/null
@@ -1 +0,0 @@
-this ƒîlë should be in the contentstore
diff --git a/common/test/data/test_unicode/tabs/syllabus.html b/common/test/data/test_unicode/tabs/syllabus.html
deleted file mode 100644
index 45c2a7fc8f..0000000000
--- a/common/test/data/test_unicode/tabs/syllabus.html
+++ /dev/null
@@ -1 +0,0 @@
-This is a syllabus
diff --git a/common/test/data/test_unicode/video/a_video.xml b/common/test/data/test_unicode/video/a_video.xml
deleted file mode 100644
index b90ea9d8c4..0000000000
--- a/common/test/data/test_unicode/video/a_video.xml
+++ /dev/null
@@ -1 +0,0 @@
-
From 9fe1582f39375c7aef1b9d13a1e8260f6a1f318b Mon Sep 17 00:00:00 2001
From: Julian Arni
Date: Thu, 12 Sep 2013 12:27:19 -0400
Subject: [PATCH 8/9] Utility functions for finding courses by id
---
.../xmodule/modulestore/tests/test_mongo.py | 30 +++++++++++++------
.../lib/xmodule/xmodule/tests/test_import.py | 3 --
2 files changed, 21 insertions(+), 12 deletions(-)
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
index 74ba7aa2e1..ba1266cb14 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
@@ -2,6 +2,7 @@ from pprint import pprint
# pylint: disable=E0611
from nose.tools import assert_equals, assert_raises, \
assert_not_equals, assert_false
+from itertools import ifilter
# pylint: enable=E0611
import pymongo
import logging
@@ -88,6 +89,19 @@ class TestMongoModuleStore(object):
def tearDown(self):
pass
+ def get_course_by_id(self, name):
+ """
+ Returns the first course with `id` of `name`, or `None` if there are none.
+ """
+ courses = self.store.get_courses()
+ return next(ifilter(lambda x: x.id == name, courses), None)
+
+ def course_with_id_exists(self, name):
+ """
+ Returns true iff there exists some course with `id` of `name`.
+ """
+ return (self.get_course_by_id(name) is not None)
+
def test_init(self):
'''Make sure the db loads, and print all the locations in the db.
Call this directly from failing tests to see what is loaded'''
@@ -103,13 +117,11 @@ class TestMongoModuleStore(object):
'''Make sure the course objects loaded properly'''
courses = self.store.get_courses()
assert_equals(len(courses), 5)
- courses.sort(key=lambda c: c.id)
- assert_equals(courses[0].id, 'edX/simple/2012_Fall')
- assert_equals(courses[1].id, 'edX/simple_with_draft/2012_Fall')
- assert_equals(courses[2].id, 'edX/test_import_course/2012_Fall')
- assert_equals(courses[3].id, 'edX/test_unicode/2012_Fall')
- assert_equals(courses[4].id, 'edX/toy/2012_Fall')
- log.debug(str(courses))
+ assert self.course_with_id_exists('edX/simple/2012_Fall')
+ assert self.course_with_id_exists('edX/simple_with_draft/2012_Fall')
+ assert self.course_with_id_exists('edX/test_import_course/2012_Fall')
+ assert self.course_with_id_exists('edX/test_unicode/2012_Fall')
+ assert self.course_with_id_exists('edX/toy/2012_Fall')
def test_loads(self):
assert_not_equals(
@@ -173,7 +185,6 @@ class TestMongoModuleStore(object):
)
def test_static_tab_names(self):
- courses = self.store.get_courses()
def get_tab_name(index):
"""
@@ -181,7 +192,8 @@ class TestMongoModuleStore(object):
Assumes the information is desired for courses[4] ('toy' course).
"""
- return courses[4].tabs[index]['name']
+ course = self.get_course_by_id('edX/toy/2012_Fall')
+ return course.tabs[index]['name']
# There was a bug where model.save was not getting called after the static tab name
# was set set for tabs that have a URL slug. 'Syllabus' and 'Resources' fall into that
diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py
index b44a2044bb..cd7749dc09 100644
--- a/common/lib/xmodule/xmodule/tests/test_import.py
+++ b/common/lib/xmodule/xmodule/tests/test_import.py
@@ -395,9 +395,6 @@ class ImportTestCase(BaseCourseTestCase):
chapters = course.get_children()
self.assertEqual(len(chapters), 3)
- ch3 = chapters[2]
-
-
def test_url_name_mangling(self):
"""
Make sure that url_names are only mangled once.
From 9d54ceea0b997a7ee14152654e60da8fc18bda2f Mon Sep 17 00:00:00 2001
From: Diana Huang
Date: Wed, 18 Sep 2013 17:09:14 -0400
Subject: [PATCH 9/9] Add unicode handling to the signing and verifying logic
---
.../shoppingcart/processors/CyberSource.py | 13 +++++-----
.../processors/tests/test_CyberSource.py | 24 +++++++++++++++++++
2 files changed, 31 insertions(+), 6 deletions(-)
diff --git a/lms/djangoapps/shoppingcart/processors/CyberSource.py b/lms/djangoapps/shoppingcart/processors/CyberSource.py
index 6162b325e3..5d43482bd4 100644
--- a/lms/djangoapps/shoppingcart/processors/CyberSource.py
+++ b/lms/djangoapps/shoppingcart/processors/CyberSource.py
@@ -54,7 +54,7 @@ def processor_hash(value):
Performs the base64(HMAC_SHA1(key, value)) used by CyberSource Hosted Order Page
"""
shared_secret = settings.CC_PROCESSOR['CyberSource'].get('SHARED_SECRET', '')
- hash_obj = hmac.new(shared_secret, value, sha1)
+ hash_obj = hmac.new(shared_secret.encode('utf-8'), value.encode('utf-8'), sha1)
return binascii.b2a_base64(hash_obj.digest())[:-1] # last character is a '\n', which we don't want
@@ -71,10 +71,10 @@ def sign(params, signed_fields_key='orderPage_signedFields', full_sig_key='order
params['orderPage_timestamp'] = int(time.time() * 1000)
params['orderPage_version'] = order_page_version
params['orderPage_serialNumber'] = serial_number
- fields = ",".join(params.keys())
- values = ",".join(["{0}={1}".format(i, params[i]) for i in params.keys()])
+ fields = u",".join(params.keys())
+ values = u",".join([u"{0}={1}".format(i, params[i]) for i in params.keys()])
fields_sig = processor_hash(fields)
- values += ",signedFieldsPublicSignature=" + fields_sig
+ values += u",signedFieldsPublicSignature=" + fields_sig
params[full_sig_key] = processor_hash(values)
params[signed_fields_key] = fields
@@ -90,13 +90,14 @@ def verify_signatures(params, signed_fields_key='signedFields', full_sig_key='si
raises CCProcessorSignatureException if not verified
"""
signed_fields = params.get(signed_fields_key, '').split(',')
- data = ",".join(["{0}={1}".format(k, params.get(k, '')) for k in signed_fields])
+ data = u",".join([u"{0}={1}".format(k, params.get(k, '')) for k in signed_fields])
signed_fields_sig = processor_hash(params.get(signed_fields_key, ''))
- data += ",signedFieldsPublicSignature=" + signed_fields_sig
+ data += u",signedFieldsPublicSignature=" + signed_fields_sig
returned_sig = params.get(full_sig_key, '')
if processor_hash(data) != returned_sig:
raise CCProcessorSignatureException()
+
def render_purchase_form_html(cart):
"""
Renders the HTML of the hidden POST form that must be used to initiate a purchase with CyberSource
diff --git a/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py b/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py
index c88d0ca5e0..6f708f3bc3 100644
--- a/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py
+++ b/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py
@@ -58,6 +58,28 @@ class CyberSourceTests(TestCase):
# testing for the absence of that exception. the trivial assert below does that
self.assertEqual(1, 1)
+ def test_sign_then_verify_unicode(self):
+ """
+ Similar to the test above, which loops back to the original.
+ Testing to make sure we can handle unicode parameters
+ """
+ params = {
+ 'card_accountNumber': '1234',
+ 'card_cardType': '001',
+ 'billTo_firstName': u'\u2699',
+ 'billTo_lastName': u"\u2603",
+ 'orderNumber': '1',
+ 'orderCurrency': 'usd',
+ 'decision': 'ACCEPT',
+ 'ccAuthReply_amount': '0.00'
+ }
+
+ verify_signatures(sign(params), signed_fields_key='orderPage_signedFields',
+ full_sig_key='orderPage_signaturePublic')
+ # if the above verify_signature fails it will throw an exception, so basically we're just
+ # testing for the absence of that exception. the trivial assert below does that
+ self.assertEqual(1, 1)
+
def test_verify_exception(self):
"""
Tests that failure to verify raises the proper CCProcessorSignatureException
@@ -162,6 +184,7 @@ class CyberSourceTests(TestCase):
'card_accountNumber': '1234',
'card_cardType': '001',
'billTo_firstName': student1.first_name,
+ 'billTo_lastName': u"\u2603",
'orderNumber': str(order1.id),
'orderCurrency': 'usd',
'decision': 'ACCEPT',
@@ -194,6 +217,7 @@ class CyberSourceTests(TestCase):
# finally, tests an accepted order
self.assertTrue(payment_accepted(params)['accepted'])
+
@patch('shoppingcart.processors.CyberSource.render_to_string', autospec=True)
def test_render_purchase_form_html(self, render):
"""