From 344c72b3e2d045a946df90d30a222b4ad2531d26 Mon Sep 17 00:00:00 2001
From: Matjaz Gregoric <mtyaka@gmail.com>
Date: Sun, 18 Sep 2016 12:59:03 +0200
Subject: [PATCH] Use non-ascii PLATFORM_NAME in tests.

It should help catch unicode encoding problems earlier.

This also updates python-saml dependency to a version that includes a
patch for unicode strings.
---
 cms/djangoapps/contentstore/tests/tests.py                         |  2 +-
 cms/djangoapps/contentstore/views/tests/test_programs.py           |  2 +-
 cms/djangoapps/maintenance/tests.py                                |  2 +-
 cms/envs/bok_choy.env.json                                         |  2 +-
 common/djangoapps/external_auth/tests/test_shib.py                 |  2 +-
 common/djangoapps/microsite_configuration/tests/test_microsites.py |  6 +++---
 common/djangoapps/student/tests/test_email.py                      |  8 ++++----
 common/djangoapps/student/tests/test_linkedin.py                   | 18 +++++++++++-------
 common/djangoapps/student/tests/test_login.py                      |  2 +-
 common/djangoapps/student/tests/tests.py                           | 13 +++++++------
 common/djangoapps/third_party_auth/tests/test_views.py             |  7 +++++--
 common/test/acceptance/tests/lms/test_lms.py                       | 10 ++++++++--
 common/test/acceptance/tests/studio/test_studio_outline.py         | 11 +++++++----
 lms/djangoapps/bulk_email/tests/test_email.py                      |  3 ++-
 lms/djangoapps/certificates/tests/test_webview_views.py            |  2 +-
 lms/djangoapps/courseware/features/lti.py                          |  8 +++++++-
 lms/djangoapps/courseware/tests/test_views.py                      |  4 ++--
 lms/djangoapps/courseware/views/views.py                           | 40 +++++++++++++++++++++++-----------------
 lms/djangoapps/shoppingcart/tests/test_models.py                   |  2 +-
 lms/djangoapps/static_template_view/tests/test_views.py            |  2 +-
 lms/djangoapps/verify_student/tests/test_views.py                  |  2 +-
 lms/envs/bok_choy.env.json                                         |  2 +-
 lms/envs/test.py                                                   |  3 ++-
 openedx/core/djangoapps/api_admin/widgets.py                       |  2 +-
 openedx/core/djangoapps/user_api/tests/test_views.py               | 35 +++++++++++++++++++----------------
 requirements/edx/post.txt                                          | 12 +++++++-----
 26 files changed, 119 insertions(+), 83 deletions(-)

diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py
index 386bbbd..5c18e04 100644
--- a/cms/djangoapps/contentstore/tests/tests.py
+++ b/cms/djangoapps/contentstore/tests/tests.py
@@ -236,7 +236,7 @@ class AuthTestCase(ContentStoreTestCase):
         # check the the HTML has links to the right login page. Note that this is merely a content
         # check and thus could be fragile should the wording change on this page
         expected = 'You can now <a href="' + reverse('login') + '">sign in</a>.'
-        self.assertIn(expected, resp.content)
+        self.assertIn(expected, resp.content.decode('utf-8'))
 
     def test_private_pages_auth(self):
         """Make sure pages that do require login work."""
diff --git a/cms/djangoapps/contentstore/views/tests/test_programs.py b/cms/djangoapps/contentstore/views/tests/test_programs.py
index b2f4db0..0d6dff1 100644
--- a/cms/djangoapps/contentstore/views/tests/test_programs.py
+++ b/cms/djangoapps/contentstore/views/tests/test_programs.py
@@ -64,7 +64,7 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule
         self.mock_programs_api(data={'results': []})
 
         response = self.client.get(self.studio_home)
-        self.assertIn(Text("You haven't created any programs yet."), response.content)
+        self.assertIn(Text("You haven't created any programs yet."), response.content.decode('utf-8'))
 
         # When data is provided, expect a program listing.
         self.mock_programs_api()
diff --git a/cms/djangoapps/maintenance/tests.py b/cms/djangoapps/maintenance/tests.py
index a10eede..08a5c08 100644
--- a/cms/djangoapps/maintenance/tests.py
+++ b/cms/djangoapps/maintenance/tests.py
@@ -120,7 +120,7 @@ class MaintenanceViewAccessTests(MaintenanceViewTestCase):
         response = self.client.get(url)
         self.assertContains(
             response,
-            'Must be {platform_name} staff to perform this action.'.format(platform_name=settings.PLATFORM_NAME),
+            u'Must be {platform_name} staff to perform this action.'.format(platform_name=settings.PLATFORM_NAME),
             status_code=403
         )
 
diff --git a/cms/envs/bok_choy.env.json b/cms/envs/bok_choy.env.json
index 068c750..3819be7 100644
--- a/cms/envs/bok_choy.env.json
+++ b/cms/envs/bok_choy.env.json
@@ -97,7 +97,7 @@
     "LOG_DIR": "** OVERRIDDEN **",
     "MEDIA_URL": "/media/",
     "MKTG_URL_LINK_MAP": {},
-    "PLATFORM_NAME": "edX",
+    "PLATFORM_NAME": "édX",
     "SERVER_EMAIL": "devops@example.com",
     "SESSION_COOKIE_DOMAIN": null,
     "SITE_NAME": "localhost",
diff --git a/common/djangoapps/external_auth/tests/test_shib.py b/common/djangoapps/external_auth/tests/test_shib.py
index 62615bb..bfd4511 100644
--- a/common/djangoapps/external_auth/tests/test_shib.py
+++ b/common/djangoapps/external_auth/tests/test_shib.py
@@ -209,7 +209,7 @@ class ShibSPTest(CacheIsolationTestCase):
                 else:
                     self.assertEqual(response.status_code, 200)
                     self.assertContains(response,
-                                        ("Preferences for {platform_name}"
+                                        (u"Preferences for {platform_name}"
                                          .format(platform_name=settings.PLATFORM_NAME)))
                     # no audit logging calls
                     self.assertEquals(len(audit_log_calls), 0)
diff --git a/common/djangoapps/microsite_configuration/tests/test_microsites.py b/common/djangoapps/microsite_configuration/tests/test_microsites.py
index 6073d09..49e1c1b 100644
--- a/common/djangoapps/microsite_configuration/tests/test_microsites.py
+++ b/common/djangoapps/microsite_configuration/tests/test_microsites.py
@@ -22,13 +22,13 @@ class MicrositeTests(TestCase):
     """
     def test_breadcrumbs(self):
         crumbs = ['my', 'less specific', 'Page']
-        expected = u'my | less specific | Page | edX'
+        expected = u'my | less specific | Page | {}'.format(settings.PLATFORM_NAME)
         title = configuration_helpers.page_title_breadcrumbs(*crumbs)
         self.assertEqual(expected, title)
 
     def test_unicode_title(self):
         crumbs = [u'øo', u'π tastes gréât', u'驴']
-        expected = u'øo | π tastes gréât | 驴 | edX'
+        expected = u'øo | π tastes gréât | 驴 | {}'.format(settings.PLATFORM_NAME)
         title = configuration_helpers.page_title_breadcrumbs(*crumbs)
         self.assertEqual(expected, title)
 
@@ -38,7 +38,7 @@ class MicrositeTests(TestCase):
 
     def test_breadcrumb_tag(self):
         crumbs = ['my', 'less specific', 'Page']
-        expected = u'my | less specific | Page | edX'
+        expected = u'my | less specific | Page | {}'.format(settings.PLATFORM_NAME)
         title = configuration_tags.page_title_breadcrumbs_tag(None, *crumbs)
         self.assertEqual(expected, title)
 
diff --git a/common/djangoapps/student/tests/test_email.py b/common/djangoapps/student/tests/test_email.py
index f13db30..16bc351 100644
--- a/common/djangoapps/student/tests/test_email.py
+++ b/common/djangoapps/student/tests/test_email.py
@@ -70,23 +70,23 @@ class EmailTestMixin(object):
 class ActivationEmailTests(TestCase):
     """Test sending of the activation email. """
 
-    ACTIVATION_SUBJECT = "Activate Your edX Account"
+    ACTIVATION_SUBJECT = u"Activate Your {} Account".format(settings.PLATFORM_NAME)
 
     # Text fragments we expect in the body of an email
     # sent from an OpenEdX installation.
     OPENEDX_FRAGMENTS = [
-        "Thank you for creating an account with {platform}!".format(platform=settings.PLATFORM_NAME),
+        u"Thank you for creating an account with {platform}!".format(platform=settings.PLATFORM_NAME),
         "http://edx.org/activate/",
         (
             "Check the help section of the "
-            "{platform} website".format(platform=settings.PLATFORM_NAME)
+            u"{platform} website".format(platform=settings.PLATFORM_NAME)
         )
     ]
 
     # Text fragments we expect in the body of an email
     # sent from an EdX-controlled domain.
     EDX_DOMAIN_FRAGMENTS = [
-        "Thank you for creating an account with {platform}!".format(platform=settings.PLATFORM_NAME),
+        u"Thank you for creating an account with {platform}!".format(platform=settings.PLATFORM_NAME),
         "http://edx.org/activate/",
         "https://www.edx.org/contact-us",
         "This email message was automatically sent by edx.org"
diff --git a/common/djangoapps/student/tests/test_linkedin.py b/common/djangoapps/student/tests/test_linkedin.py
index ef39946..12139eb 100644
--- a/common/djangoapps/student/tests/test_linkedin.py
+++ b/common/djangoapps/student/tests/test_linkedin.py
@@ -2,8 +2,9 @@
 """Tests for LinkedIn Add to Profile configuration. """
 
 import ddt
-from urllib import urlencode
+from urllib import urlencode, quote
 
+from django.conf import settings
 from django.test import TestCase
 from opaque_keys.edx.locator import CourseLocator
 from student.models import LinkedInAddToProfileConfiguration
@@ -18,10 +19,10 @@ class LinkedInAddToProfileUrlTests(TestCase):
     CERT_URL = u"http://s3.edx/cert"
 
     @ddt.data(
-        ('honor', u'edX+Honor+Code+Certificate+for+Test+Course+%E2%98%83'),
-        ('verified', u'edX+Verified+Certificate+for+Test+Course+%E2%98%83'),
-        ('professional', u'edX+Professional+Certificate+for+Test+Course+%E2%98%83'),
-        ('default_mode', u'edX+Certificate+for+Test+Course+%E2%98%83')
+        ('honor', u'Honor+Code+Certificate+for+Test+Course+%E2%98%83'),
+        ('verified', u'Verified+Certificate+for+Test+Course+%E2%98%83'),
+        ('professional', u'Professional+Certificate+for+Test+Course+%E2%98%83'),
+        ('default_mode', u'Certificate+for+Test+Course+%E2%98%83')
     )
     @ddt.unpack
     def test_linked_in_url(self, cert_mode, expected_cert_name):
@@ -33,10 +34,13 @@ class LinkedInAddToProfileUrlTests(TestCase):
         expected_url = (
             'http://www.linkedin.com/profile/add'
             '?_ed=0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9&'
-            'pfCertificationName={expected_cert_name}&'
+            'pfCertificationName={platform_name}+{expected_cert_name}&'
             'pfCertificationUrl=http%3A%2F%2Fs3.edx%2Fcert&'
             'source=o'
-        ).format(expected_cert_name=expected_cert_name)
+        ).format(
+            expected_cert_name=expected_cert_name,
+            platform_name=quote(settings.PLATFORM_NAME.encode('utf-8'))
+        )
 
         actual_url = config.add_to_profile_url(
             self.COURSE_KEY,
diff --git a/common/djangoapps/student/tests/test_login.py b/common/djangoapps/student/tests/test_login.py
index 6f48965..4ce14fd 100644
--- a/common/djangoapps/student/tests/test_login.py
+++ b/common/djangoapps/student/tests/test_login.py
@@ -505,7 +505,7 @@ class ExternalAuthShibTest(ModuleStoreTestCase):
         noshib_response = self.client.get(TARGET_URL, follow=True)
         self.assertEqual(noshib_response.redirect_chain[-1],
                          ('http://testserver/login?next={url}'.format(url=TARGET_URL), 302))
-        self.assertContains(noshib_response, ("Sign in or Register | {platform_name}"
+        self.assertContains(noshib_response, (u"Sign in or Register | {platform_name}"
                                               .format(platform_name=settings.PLATFORM_NAME)))
         self.assertEqual(noshib_response.status_code, 200)
 
diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py
index 529d3a3..adefb22 100644
--- a/common/djangoapps/student/tests/tests.py
+++ b/common/djangoapps/student/tests/tests.py
@@ -6,6 +6,7 @@ from datetime import datetime, timedelta
 import json
 import logging
 import unittest
+from urllib import quote
 
 import ddt
 from django.conf import settings
@@ -440,12 +441,12 @@ class DashboardTest(ModuleStoreTestCase):
         self.assertIn('Add Certificate to LinkedIn', response.content)
 
         expected_url = (
-            'http://www.linkedin.com/profile/add'
-            '?_ed=0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9&'
-            'pfCertificationName=edX+Honor+Code+Certificate+for+Omega&'
-            'pfCertificationUrl=www.edx.org&'
-            'source=o'
-        )
+            u'http://www.linkedin.com/profile/add'
+            u'?_ed=0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9&'
+            u'pfCertificationName={platform}+Honor+Code+Certificate+for+Omega&'
+            u'pfCertificationUrl=www.edx.org&'
+            u'source=o'
+        ).format(platform=quote(settings.PLATFORM_NAME.encode('utf-8')))
         self.assertContains(response, escape(expected_url))
 
     @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
diff --git a/common/djangoapps/third_party_auth/tests/test_views.py b/common/djangoapps/third_party_auth/tests/test_views.py
index 29d14b0..138b170 100644
--- a/common/djangoapps/third_party_auth/tests/test_views.py
+++ b/common/djangoapps/third_party_auth/tests/test_views.py
@@ -6,6 +6,9 @@ import ddt
 from lxml import etree
 from onelogin.saml2.errors import OneLogin_Saml2_Error
 import unittest
+
+from django.conf import settings
+
 from .testutil import AUTH_FEATURE_ENABLED, SAMLTestCase
 
 # Define some XML namespaces:
@@ -39,9 +42,9 @@ class SAMLMetadataTest(SAMLTestCase):
         self.enable_saml()
         self.check_metadata_contacts(
             xml=self._fetch_metadata(),
-            tech_name="edX Support",
+            tech_name=u"{} Support".format(settings.PLATFORM_NAME),
             tech_email="technical@example.com",
-            support_name="edX Support",
+            support_name=u"{} Support".format(settings.PLATFORM_NAME),
             support_email="technical@example.com"
         )
 
diff --git a/common/test/acceptance/tests/lms/test_lms.py b/common/test/acceptance/tests/lms/test_lms.py
index b223f23..9238dd0 100644
--- a/common/test/acceptance/tests/lms/test_lms.py
+++ b/common/test/acceptance/tests/lms/test_lms.py
@@ -175,7 +175,10 @@ class LoginFromCombinedPageTest(UniqueCourseTest):
         # The user will be redirected somewhere and then back to the login page:
         msg_text = self.login_page.wait_for_auth_status_message()
         self.assertIn("You have successfully signed into Dummy", msg_text)
-        self.assertIn("To link your accounts, sign in now using your edX password", msg_text)
+        self.assertIn(
+            u"To link your accounts, sign in now using your édX password",
+            msg_text
+        )
 
         # Now login with username and password:
         self.login_page.login(email=email, password=password)
@@ -337,7 +340,10 @@ class RegisterFromCombinedPageTest(UniqueCourseTest):
         # Verify that the expected errors are displayed.
         errors = self.register_page.wait_for_errors()
         self.assertIn(u'Please enter your Public username.', errors)
-        self.assertIn(u'You must agree to the edX Terms of Service and Honor Code', errors)
+        self.assertIn(
+            u'You must agree to the édX Terms of Service and Honor Code',
+            errors
+        )
         self.assertIn(u'Please select your Country.', errors)
         self.assertIn(u'Please tell us your favorite movie.', errors)
 
diff --git a/common/test/acceptance/tests/studio/test_studio_outline.py b/common/test/acceptance/tests/studio/test_studio_outline.py
index 6a18e3c..b99c0b4 100644
--- a/common/test/acceptance/tests/studio/test_studio_outline.py
+++ b/common/test/acceptance/tests/studio/test_studio_outline.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 """
 Acceptance tests for studio related to the outline page.
 """
@@ -1604,10 +1605,12 @@ class DeprecationWarningMessageTest(CourseOutlineTest):
     """
     HEADING_TEXT = 'This course uses features that are no longer supported.'
     COMPONENT_LIST_HEADING = 'You must delete or replace the following components.'
-    ADVANCE_MODULES_REMOVE_TEXT = ('To avoid errors, edX strongly recommends that you remove unsupported features '
-                                   'from the course advanced settings. To do this, go to the Advanced Settings '
-                                   'page, locate the "Advanced Module List" setting, and then delete the following '
-                                   'modules from the list.')
+    ADVANCE_MODULES_REMOVE_TEXT = (
+        u'To avoid errors, édX strongly recommends that you remove unsupported features '
+        u'from the course advanced settings. To do this, go to the Advanced Settings '
+        u'page, locate the "Advanced Module List" setting, and then delete the following '
+        u'modules from the list.'
+    )
     DEFAULT_DISPLAYNAME = "Deprecated Component"
 
     def _add_deprecated_advance_modules(self, block_types):
diff --git a/lms/djangoapps/bulk_email/tests/test_email.py b/lms/djangoapps/bulk_email/tests/test_email.py
index 9a5d354..fefc497 100644
--- a/lms/djangoapps/bulk_email/tests/test_email.py
+++ b/lms/djangoapps/bulk_email/tests/test_email.py
@@ -9,6 +9,7 @@ from nose.plugins.attrib import attr
 import os
 from unittest import skipIf
 
+from django.conf import settings
 from django.core import mail
 from django.core.mail.message import forbid_multi_line_headers
 from django.core.urlresolvers import reverse
@@ -503,7 +504,7 @@ class TestCourseEmailContext(SharedModuleStoreTestCase):
         """
         This test tests that the bulk email context uses http or https urls as appropriate.
         """
-        self.assertEquals(email_context['platform_name'], 'edX')
+        self.assertEquals(email_context['platform_name'], settings.PLATFORM_NAME)
         self.assertEquals(email_context['course_title'], self.course_title)
         self.assertEquals(email_context['course_url'],
                           '{}://edx.org/courses/{}/{}/{}/'.format(scheme,
diff --git a/lms/djangoapps/certificates/tests/test_webview_views.py b/lms/djangoapps/certificates/tests/test_webview_views.py
index 86b2356..85e9c9e 100644
--- a/lms/djangoapps/certificates/tests/test_webview_views.py
+++ b/lms/djangoapps/certificates/tests/test_webview_views.py
@@ -190,7 +190,7 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
         params = OrderedDict([
             ('_ed', '0_0dPSPyS070e0HsE9HNz_13_d11_',),
             ('pfCertificationName', '{platform_name} Honor Code Certificate for {course_name}'.format(
-                platform_name=settings.PLATFORM_NAME,
+                platform_name=settings.PLATFORM_NAME.encode('utf-8'),
                 course_name=self.course.display_name,
             ),),
             ('pfCertificationUrl', self.request.build_absolute_uri(test_url),),
diff --git a/lms/djangoapps/courseware/features/lti.py b/lms/djangoapps/courseware/features/lti.py
index 4e5f958..6901eb2 100644
--- a/lms/djangoapps/courseware/features/lti.py
+++ b/lms/djangoapps/courseware/features/lti.py
@@ -272,7 +272,13 @@ def check_lti_popup(parent_window):
     # For verification, iterate through the window titles and make sure that
     # both are there.
     tabs = []
-    expected_tabs = [u'LTI | Test Section | {0} Courseware | edX'.format(TEST_COURSE_NAME), u'TEST TITLE']
+    expected_tabs = [
+        u'LTI | Test Section | {course} Courseware | {platform}'.format(
+            course=TEST_COURSE_NAME,
+            platform=settings.PLATFORM_NAME
+        ),
+        u'TEST TITLE'
+    ]
 
     for window in windows:
         world.browser.switch_to_window(window)
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 08e1d0c..066b231 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -1773,9 +1773,9 @@ class GenerateUserCertTests(ModuleStoreTestCase):
         self.client.logout()
         resp = self.client.post(self.url)
         self.assertEqual(resp.status_code, HttpResponseBadRequest.status_code)
-        self.assertIn("You must be signed in to {platform_name} to create a certificate.".format(
+        self.assertIn(u"You must be signed in to {platform_name} to create a certificate.".format(
             platform_name=settings.PLATFORM_NAME
-        ), resp.content)
+        ), resp.content.decode('utf-8'))
 
 
 class ActivateIDCheckerBlock(XBlock):
diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py
index 611a883..3ac3e2b 100644
--- a/lms/djangoapps/courseware/views/views.py
+++ b/lms/djangoapps/courseware/views/views.py
@@ -767,8 +767,8 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
     if enrollment_mode == CourseMode.AUDIT:
         return CertData(
             CertificateStatuses.audit_passing,
-            'Your enrollment: Audit track',
-            'You are enrolled in the audit track for this course. The audit track does not include a certificate.',
+            _('Your enrollment: Audit track'),
+            _('You are enrolled in the audit track for this course. The audit track does not include a certificate.'),
             download_url=None,
             cert_web_view_url=None
         )
@@ -784,8 +784,8 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
     if certs_api.is_certificate_invalid(student, course_key):
         return CertData(
             CertificateStatuses.invalidated,
-            'Your certificate has been invalidated',
-            'Please contact your course team if you have any questions.',
+            _('Your certificate has been invalidated'),
+            _('Please contact your course team if you have any questions.'),
             download_url=None,
             cert_web_view_url=None
         )
@@ -794,8 +794,8 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
 
     if cert_downloadable_status['is_downloadable']:
         cert_status = CertificateStatuses.downloadable
-        title = 'Your certificate is available'
-        msg = 'You can keep working for a higher grade, or request your certificate now.'
+        title = _('Your certificate is available')
+        msg = _('You can keep working for a higher grade, or request your certificate now.')
         if certs_api.has_html_certificates_enabled(course_key, course):
             if certs_api.get_active_web_certificate(course) is not None:
                 cert_web_view_url = certs_api.get_certificate_url(
@@ -805,9 +805,11 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
             else:
                 return CertData(
                     CertificateStatuses.generating,
-                    "We're working on it...",
-                    "We're creating your certificate. You can keep working in your courses and a link "
-                    "to it will appear here and on your Dashboard when it is ready.",
+                    _("We're working on it..."),
+                    _(
+                        "We're creating your certificate. You can keep working in your courses and a link "
+                        "to it will appear here and on your Dashboard when it is ready."
+                    ),
                     download_url=None,
                     cert_web_view_url=None
                 )
@@ -819,9 +821,11 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
     if cert_downloadable_status['is_generating']:
         return CertData(
             CertificateStatuses.generating,
-            "We're working on it...",
-            "We're creating your certificate. You can keep working in your courses and a link to "
-            "it will appear here and on your Dashboard when it is ready.",
+            _("We're working on it..."),
+            _(
+                "We're creating your certificate. You can keep working in your courses and a link to "
+                "it will appear here and on your Dashboard when it is ready."
+            ),
             download_url=None,
             cert_web_view_url=None
         )
@@ -835,17 +839,19 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
         platform_name = configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME)
         return CertData(
             CertificateStatuses.unverified,
-            'Certificate unavailable',
-            'You have not received a certificate because you do not have a current {platform_name} verified '
-            'identity.'.format(platform_name=platform_name),
+            _('Certificate unavailable'),
+            _(
+                'You have not received a certificate because you do not have a current {platform_name} '
+                'verified identity.'
+            ).format(platform_name=platform_name),
             download_url=None,
             cert_web_view_url=None
         )
 
     return CertData(
         CertificateStatuses.requesting,
-        'Congratulations, you qualified for a certificate!',
-        'You can keep working for a higher grade, or request your certificate now.',
+        _('Congratulations, you qualified for a certificate!'),
+        _('You can keep working for a higher grade, or request your certificate now.'),
         download_url=None,
         cert_web_view_url=None
     )
diff --git a/lms/djangoapps/shoppingcart/tests/test_models.py b/lms/djangoapps/shoppingcart/tests/test_models.py
index a21973d..05d6253 100644
--- a/lms/djangoapps/shoppingcart/tests/test_models.py
+++ b/lms/djangoapps/shoppingcart/tests/test_models.py
@@ -1106,7 +1106,7 @@ class DonationTest(ModuleStoreTestCase):
             donation,
             donation_type="general",
             unit_cost=self.COST,
-            line_desc="Donation for edX"
+            line_desc=u"Donation for {}".format(settings.PLATFORM_NAME)
         )
 
     def test_donate_to_course(self):
diff --git a/lms/djangoapps/static_template_view/tests/test_views.py b/lms/djangoapps/static_template_view/tests/test_views.py
index 92ab0e4..45550f0 100644
--- a/lms/djangoapps/static_template_view/tests/test_views.py
+++ b/lms/djangoapps/static_template_view/tests/test_views.py
@@ -50,7 +50,7 @@ class MarketingSiteViewTests(TestCase):
         resp = self.client.get(url)
         self.assertContains(
             resp,
-            'There has been a 500 error on the <em>{platform_name}</em> servers'.format(
+            u'There has been a 500 error on the <em>{platform_name}</em> servers'.format(
                 platform_name=settings.PLATFORM_NAME
             ),
             status_code=500
diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py
index 3d9833c..14a2f9d 100644
--- a/lms/djangoapps/verify_student/tests/test_views.py
+++ b/lms/djangoapps/verify_student/tests/test_views.py
@@ -2491,7 +2491,7 @@ class TestEmailMessageWithCustomICRVBlock(ModuleStoreTestCase):
 
         self.assertIn("Thanks,", body)
         self.assertIn(
-            "The {platform_name} team".format(
+            u"The {platform_name} team".format(
                 platform_name=settings.PLATFORM_NAME
             ),
             body
diff --git a/lms/envs/bok_choy.env.json b/lms/envs/bok_choy.env.json
index 4bb4250..493e2ad 100644
--- a/lms/envs/bok_choy.env.json
+++ b/lms/envs/bok_choy.env.json
@@ -118,7 +118,7 @@
         "ROOT": "root",
         "SITEMAP.XML": "sitemap_xml"
     },
-    "PLATFORM_NAME": "edX",
+    "PLATFORM_NAME": "édX",
     "REGISTRATION_EXTENSION_FORM": "openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm",
     "REGISTRATION_EXTRA_FIELDS": {
         "level_of_education": "optional",
diff --git a/lms/envs/test.py b/lms/envs/test.py
index def7a84..6d5781b 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -424,7 +424,8 @@ FEATURES['CLASS_DASHBOARD'] = True
 import openid.oidutil
 openid.oidutil.log = lambda message, level=0: None
 
-PLATFORM_NAME = "edX"
+# Include a non-ascii character in PLATFORM_NAME to uncover possible UnicodeEncodeErrors in tests.
+PLATFORM_NAME = u"édX"
 SITE_NAME = "edx.org"
 
 # set up some testing for microsites
diff --git a/openedx/core/djangoapps/api_admin/widgets.py b/openedx/core/djangoapps/api_admin/widgets.py
index 4430e3c..1841daf 100644
--- a/openedx/core/djangoapps/api_admin/widgets.py
+++ b/openedx/core/djangoapps/api_admin/widgets.py
@@ -29,7 +29,7 @@ class TermsOfServiceCheckboxInput(CheckboxInput):
             link_end='</a>',
         )
 
-        html = '<input{{}} /> <label class="tos-checkbox-label" for="{id}">{label}</label>'.format(
+        html = u'<input{{}} /> <label class="tos-checkbox-label" for="{id}">{label}</label>'.format(
             id=final_attrs['id'],
             label=label
         )
diff --git a/openedx/core/djangoapps/user_api/tests/test_views.py b/openedx/core/djangoapps/user_api/tests/test_views.py
index bb190b7..b47b909 100644
--- a/openedx/core/djangoapps/user_api/tests/test_views.py
+++ b/openedx/core/djangoapps/user_api/tests/test_views.py
@@ -588,7 +588,7 @@ class LoginSessionViewTest(UserAPITestCase):
                 "required": True,
                 "label": "Email",
                 "placeholder": "username@domain.com",
-                "instructions": "The email address you used to register with {platform_name}".format(
+                "instructions": u"The email address you used to register with {platform_name}".format(
                     platform_name=settings.PLATFORM_NAME
                 ),
                 "restrictions": {
@@ -756,7 +756,7 @@ class PasswordResetViewTest(UserAPITestCase):
                 "required": True,
                 "label": "Email",
                 "placeholder": "username@domain.com",
-                "instructions": "The email address you used to register with {platform_name}".format(
+                "instructions": u"The email address you used to register with {platform_name}".format(
                     platform_name=settings.PLATFORM_NAME
                 ),
                 "restrictions": {
@@ -1126,7 +1126,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
                 "name": "goals",
                 "type": "textarea",
                 "required": False,
-                "label": "Tell us why you're interested in {platform_name}".format(
+                "label": u"Tell us why you're interested in {platform_name}".format(
                     platform_name=settings.PLATFORM_NAME
                 )
             }
@@ -1185,7 +1185,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
         self._assert_reg_field(
             {"honor_code": "required"},
             {
-                "label": "I agree to the {platform_name} {link_label}".format(
+                "label": u"I agree to the {platform_name} {link_label}".format(
                     platform_name=settings.PLATFORM_NAME,
                     link_label=link_label
                 ),
@@ -1194,7 +1194,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
                 "type": "checkbox",
                 "required": True,
                 "errorMessages": {
-                    "required": "You must agree to the {platform_name} {link_label}".format(
+                    "required": u"You must agree to the {platform_name} {link_label}".format(
                         platform_name=settings.PLATFORM_NAME,
                         link_label=link_label
                     )
@@ -1209,7 +1209,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
         self._assert_reg_field(
             {"honor_code": "required"},
             {
-                "label": "I agree to the {platform_name} {link_label}".format(
+                "label": u"I agree to the {platform_name} {link_label}".format(
                     platform_name=settings.PLATFORM_NAME,
                     link_label=link_label
                 ),
@@ -1218,7 +1218,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
                 "type": "checkbox",
                 "required": True,
                 "errorMessages": {
-                    "required": "You must agree to the {platform_name} {link_label}".format(
+                    "required": u"You must agree to the {platform_name} {link_label}".format(
                         platform_name=settings.PLATFORM_NAME,
                         link_label=link_label
                     )
@@ -1239,7 +1239,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
         self._assert_reg_field(
             {"honor_code": "required", "terms_of_service": "required"},
             {
-                "label": "I agree to the {platform_name} {link_label}".format(
+                "label": u"I agree to the {platform_name} {link_label}".format(
                     platform_name=settings.PLATFORM_NAME,
                     link_label=link_label
                 ),
@@ -1248,7 +1248,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
                 "type": "checkbox",
                 "required": True,
                 "errorMessages": {
-                    "required": "You must agree to the {platform_name} {link_label}".format(
+                    "required": u"You must agree to the {platform_name} {link_label}".format(
                         platform_name=settings.PLATFORM_NAME,
                         link_label=link_label
                     )
@@ -1261,7 +1261,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
         self._assert_reg_field(
             {"honor_code": "required", "terms_of_service": "required"},
             {
-                "label": "I agree to the {platform_name} {link_label}".format(
+                "label": u"I agree to the {platform_name} {link_label}".format(
                     platform_name=settings.PLATFORM_NAME,
                     link_label=link_label
                 ),
@@ -1270,7 +1270,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
                 "type": "checkbox",
                 "required": True,
                 "errorMessages": {
-                    "required": "You must agree to the {platform_name} {link_label}".format(
+                    "required": u"You must agree to the {platform_name} {link_label}".format(
                         platform_name=settings.PLATFORM_NAME,
                         link_label=link_label
                     )
@@ -1286,7 +1286,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
         self._assert_reg_field(
             {"honor_code": "required", "terms_of_service": "required"},
             {
-                "label": "I agree to the {platform_name} Honor Code".format(
+                "label": u"I agree to the {platform_name} Honor Code".format(
                     platform_name=settings.PLATFORM_NAME
                 ),
                 "name": "honor_code",
@@ -1294,7 +1294,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
                 "type": "checkbox",
                 "required": True,
                 "errorMessages": {
-                    "required": "You must agree to the {platform_name} Honor Code".format(
+                    "required": u"You must agree to the {platform_name} Honor Code".format(
                         platform_name=settings.PLATFORM_NAME
                     )
                 }
@@ -1305,7 +1305,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
         self._assert_reg_field(
             {"honor_code": "required", "terms_of_service": "required"},
             {
-                "label": "I agree to the {platform_name} Terms of Service".format(
+                "label": u"I agree to the {platform_name} Terms of Service".format(
                     platform_name=settings.PLATFORM_NAME
                 ),
                 "name": "terms_of_service",
@@ -1313,7 +1313,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
                 "type": "checkbox",
                 "required": True,
                 "errorMessages": {
-                    "required": "You must agree to the {platform_name} Terms of Service".format(  # pylint: disable=line-too-long
+                    "required": u"You must agree to the {platform_name} Terms of Service".format(  # pylint: disable=line-too-long
                         platform_name=settings.PLATFORM_NAME
                     )
                 }
@@ -1479,7 +1479,10 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
         self.assertEqual(len(mail.outbox), 1)
         sent_email = mail.outbox[0]
         self.assertEqual(sent_email.to, [self.EMAIL])
-        self.assertEqual(sent_email.subject, "Activate Your edX Account")
+        self.assertEqual(
+            sent_email.subject,
+            u"Activate Your {platform} Account".format(platform=settings.PLATFORM_NAME)
+        )
         self.assertIn(
             u"you need to activate your {platform} account".format(platform=settings.PLATFORM_NAME),
             sent_email.body
diff --git a/requirements/edx/post.txt b/requirements/edx/post.txt
index d16778b..bd16192 100644
--- a/requirements/edx/post.txt
+++ b/requirements/edx/post.txt
@@ -10,8 +10,10 @@
 # python-saml only after lxml has been successfully installed. 
 
 # In addition, we are currently utilizing a forked version of python-saml,
-# managed by OpenCraft, which features enhanced logging. We will return to
-# the official version of python-saml on PyPI when
-# https://github.com/onelogin/python-saml/pull/159 (or its derivative) has
-# been incorporated into the main project.
-git+https://github.com/open-craft/python-saml.git@87d4c18865e4997061ec62fd0e8d1e070b92e4e7#egg=python-saml==2.1.9
+# managed by OpenCraft, which features enhanced logging and improved
+# unicode support. We will return to the official version of python-saml
+# on PyPI when these pull requests (or their derivatives) have been
+# incorporated into the main project.
+# https://github.com/onelogin/python-saml/pull/159
+# https://github.com/onelogin/python-saml/pull/164
+git+https://github.com/open-craft/python-saml.git@7f7dc389abeba44c1dac154d6aafcbe4187ae221#egg=python-saml==2.1.9
--
libgit2 0.26.0