Merge pull request #19072 from shadinaif/fix_lazy_text_with_json_dumps
Fix exceptions raised when a lazy text is used in json dump
This commit is contained in:
@@ -1476,7 +1476,7 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
resp = self.client.get_html('/home/')
|
||||
self.assertContains(
|
||||
resp,
|
||||
'<h1 class="page-header">Studio Home</h1>',
|
||||
u'<h1 class="page-header">{} Home</h1>'.format(settings.STUDIO_SHORT_NAME),
|
||||
status_code=200,
|
||||
html=True
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for validate Internationalization and Module i18n service.
|
||||
"""
|
||||
@@ -207,7 +208,7 @@ class InternationalizationTest(ModuleStoreTestCase):
|
||||
|
||||
resp = self.client.get_html('/home/')
|
||||
self.assertContains(resp,
|
||||
'<h1 class="page-header">Studio Home</h1>',
|
||||
u'<h1 class="page-header">𝓢𝓽𝓾𝓭𝓲𝓸 Home</h1>',
|
||||
status_code=200,
|
||||
html=True)
|
||||
|
||||
@@ -223,7 +224,7 @@ class InternationalizationTest(ModuleStoreTestCase):
|
||||
)
|
||||
|
||||
self.assertContains(resp,
|
||||
'<h1 class="page-header">Studio Home</h1>',
|
||||
u'<h1 class="page-header">𝓢𝓽𝓾𝓭𝓲𝓸 Home</h1>',
|
||||
status_code=200,
|
||||
html=True)
|
||||
|
||||
|
||||
@@ -91,7 +91,6 @@
|
||||
"LOG_DIR": "** OVERRIDDEN **",
|
||||
"MEDIA_URL": "/media/",
|
||||
"MKTG_URL_LINK_MAP": {},
|
||||
"PLATFORM_NAME": "édX",
|
||||
"SERVER_EMAIL": "devops@example.com",
|
||||
"SESSION_COOKIE_DOMAIN": null,
|
||||
"SITE_NAME": "localhost",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Settings for Bok Choy tests that are used when running Studio.
|
||||
|
||||
@@ -13,6 +14,7 @@ from the same directory.
|
||||
import os
|
||||
from path import Path as path
|
||||
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from openedx.core.release import RELEASE_LINE
|
||||
|
||||
########################## Prod-like settings ###################################
|
||||
@@ -52,6 +54,11 @@ XBLOCK_SETTINGS.update({'VideoDescriptor': {'licensing_enabled': True}})
|
||||
# Capture the console log via template includes, until webdriver supports log capture again
|
||||
CAPTURE_CONSOLE_LOG = True
|
||||
|
||||
PLATFORM_NAME = ugettext_lazy(u"édX")
|
||||
PLATFORM_DESCRIPTION = ugettext_lazy(u"Open édX Platform")
|
||||
STUDIO_NAME = ugettext_lazy(u"Your Platform 𝓢𝓽𝓾𝓭𝓲𝓸")
|
||||
STUDIO_SHORT_NAME = ugettext_lazy(u"𝓢𝓽𝓾𝓭𝓲𝓸")
|
||||
|
||||
############################ STATIC FILES #############################
|
||||
|
||||
# Enable debug so that static assets are served by Django
|
||||
|
||||
@@ -91,7 +91,6 @@
|
||||
"LOG_DIR": "** OVERRIDDEN **",
|
||||
"MEDIA_URL": "/media/",
|
||||
"MKTG_URL_LINK_MAP": {},
|
||||
"PLATFORM_NAME": "édX",
|
||||
"SERVER_EMAIL": "devops@example.com",
|
||||
"SESSION_COOKIE_DOMAIN": null,
|
||||
"SITE_NAME": "localhost",
|
||||
|
||||
@@ -254,10 +254,14 @@ LOGGING = get_logger_config(LOG_DIR,
|
||||
service_variant=SERVICE_VARIANT)
|
||||
|
||||
#theming start:
|
||||
PLATFORM_NAME = ENV_TOKENS.get('PLATFORM_NAME', PLATFORM_NAME)
|
||||
PLATFORM_DESCRIPTION = ENV_TOKENS.get('PLATFORM_DESCRIPTION', PLATFORM_DESCRIPTION)
|
||||
STUDIO_NAME = ENV_TOKENS.get('STUDIO_NAME', STUDIO_NAME)
|
||||
STUDIO_SHORT_NAME = ENV_TOKENS.get('STUDIO_SHORT_NAME', STUDIO_SHORT_NAME)
|
||||
|
||||
# The following variables use (or) instead of the default value inside (get). This is to enforce using the Lazy Text
|
||||
# values when the varibale is an empty string. Therefore, setting these variable as empty text in related
|
||||
# json files will make the system reads thier values from django translation files
|
||||
PLATFORM_NAME = ENV_TOKENS.get('PLATFORM_NAME') or PLATFORM_NAME
|
||||
PLATFORM_DESCRIPTION = ENV_TOKENS.get('PLATFORM_DESCRIPTION') or PLATFORM_DESCRIPTION
|
||||
STUDIO_NAME = ENV_TOKENS.get('STUDIO_NAME') or STUDIO_NAME
|
||||
STUDIO_SHORT_NAME = ENV_TOKENS.get('STUDIO_SHORT_NAME') or STUDIO_SHORT_NAME
|
||||
|
||||
# Event Tracking
|
||||
if "TRACKING_IGNORE_URL_PATTERNS" in ENV_TOKENS:
|
||||
|
||||
@@ -13,6 +13,8 @@ sessions. Assumes structure:
|
||||
# want to import all variables from base settings files
|
||||
# pylint: disable=wildcard-import, unused-wildcard-import
|
||||
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
from .common import *
|
||||
import os
|
||||
from path import Path as path
|
||||
@@ -25,6 +27,7 @@ from openedx.core.lib.derived import derive_settings
|
||||
from lms.envs.test import (
|
||||
WIKI_ENABLED,
|
||||
PLATFORM_NAME,
|
||||
PLATFORM_DESCRIPTION,
|
||||
SITE_NAME,
|
||||
DEFAULT_FILE_STORAGE,
|
||||
MEDIA_ROOT,
|
||||
@@ -35,6 +38,12 @@ from lms.envs.test import (
|
||||
ECOMMERCE_API_URL,
|
||||
)
|
||||
|
||||
|
||||
# Include a non-ascii character in STUDIO_NAME and STUDIO_SHORT_NAME to uncover possible
|
||||
# UnicodeEncodeErrors in tests. Also use lazy text to reveal possible json dumps errors
|
||||
STUDIO_NAME = ugettext_lazy(u"Your Platform 𝓢𝓽𝓾𝓭𝓲𝓸")
|
||||
STUDIO_SHORT_NAME = ugettext_lazy(u"𝓢𝓽𝓾𝓭𝓲𝓸")
|
||||
|
||||
# Allow all hosts during tests, we use a lot of different ones all over the codebase.
|
||||
ALLOWED_HOSTS = [
|
||||
'*'
|
||||
|
||||
@@ -5,7 +5,6 @@ that are stored in a database an accessible using their Location as an identifie
|
||||
|
||||
import logging
|
||||
import re
|
||||
import json
|
||||
import datetime
|
||||
|
||||
from pytz import UTC
|
||||
@@ -22,11 +21,15 @@ from xblock.plugin import default_select
|
||||
from .exceptions import InvalidLocationError, InsufficientSpecificationError
|
||||
from xmodule.errortracker import make_error_tracker
|
||||
from xmodule.assetstore import AssetMetadata
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey, AssetKey
|
||||
from opaque_keys.edx.keys import CourseKey, AssetKey
|
||||
from opaque_keys.edx.locations import Location # For import backwards compatibility
|
||||
from xblock.runtime import Mixologist
|
||||
from xblock.core import XBlock
|
||||
|
||||
# The below import is not used within this module, but ir is still needed becuase
|
||||
# other modules are imorting EdxJSONEncoder from here
|
||||
from openedx.core.lib.json_utils import EdxJSONEncoder # pylint: disable=unused-import
|
||||
|
||||
log = logging.getLogger('edx.modulestore')
|
||||
|
||||
new_contract('CourseKey', CourseKey)
|
||||
@@ -1430,25 +1433,3 @@ def prefer_xmodules(identifier, entry_points):
|
||||
return default_select(identifier, from_xmodule)
|
||||
else:
|
||||
return default_select(identifier, entry_points)
|
||||
|
||||
|
||||
class EdxJSONEncoder(json.JSONEncoder):
|
||||
"""
|
||||
Custom JSONEncoder that handles `Location` and `datetime.datetime` objects.
|
||||
|
||||
`Location`s are encoded as their url string form, and `datetime`s as
|
||||
ISO date strings
|
||||
"""
|
||||
def default(self, obj):
|
||||
if isinstance(obj, (CourseKey, UsageKey)):
|
||||
return unicode(obj)
|
||||
elif isinstance(obj, datetime.datetime):
|
||||
if obj.tzinfo is not None:
|
||||
if obj.utcoffset() is None:
|
||||
return obj.isoformat() + 'Z'
|
||||
else:
|
||||
return obj.isoformat()
|
||||
else:
|
||||
return obj.isoformat()
|
||||
else:
|
||||
return super(EdxJSONEncoder, self).default(obj)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests of XML export
|
||||
"""
|
||||
@@ -10,6 +11,7 @@ import shutil
|
||||
import unittest
|
||||
|
||||
from datetime import datetime, timedelta, tzinfo
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from fs.osfs import OSFS
|
||||
from path import Path as path
|
||||
from six import text_type
|
||||
@@ -212,3 +214,17 @@ class TestEdxJsonEncoder(unittest.TestCase):
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
self.encoder.default({})
|
||||
|
||||
def test_encode_unicode_lazy_text(self):
|
||||
"""
|
||||
Verify that the encoding is functioning fine with lazy text
|
||||
"""
|
||||
|
||||
# Initializing a lazy text object with Unicode
|
||||
unicode_text = u"Your 𝓟𝓵𝓪𝓽𝓯𝓸𝓻𝓶 Name Here"
|
||||
lazy_text = ugettext_lazy(unicode_text)
|
||||
|
||||
self.assertEquals(
|
||||
unicode_text,
|
||||
self.encoder.default(lazy_text)
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Test the Studio help links.
|
||||
"""
|
||||
@@ -189,7 +190,7 @@ class HomeHelpTest(StudioCourseTest):
|
||||
test=self,
|
||||
page=self.home_page,
|
||||
href=expected_url,
|
||||
help_text='Getting Started with Your Platform Studio',
|
||||
help_text=u'Getting Started with Your Platform 𝓢𝓽𝓾𝓭𝓲𝓸',
|
||||
as_list_item=True
|
||||
)
|
||||
|
||||
@@ -242,7 +243,7 @@ class NewCourseHelpTest(AcceptanceTest):
|
||||
test=self,
|
||||
page=self.dashboard_page,
|
||||
href=expected_url,
|
||||
help_text='Getting Started with Your Platform Studio',
|
||||
help_text=u'Getting Started with Your Platform 𝓢𝓽𝓾𝓭𝓲𝓸',
|
||||
as_list_item=True
|
||||
)
|
||||
|
||||
@@ -295,7 +296,7 @@ class NewLibraryHelpTest(AcceptanceTest):
|
||||
test=self,
|
||||
page=self.dashboard_page,
|
||||
href=expected_url,
|
||||
help_text='Getting Started with Your Platform Studio',
|
||||
help_text=u'Getting Started with Your Platform 𝓢𝓽𝓾𝓭𝓲𝓸',
|
||||
as_list_item=True
|
||||
)
|
||||
|
||||
|
||||
@@ -115,7 +115,6 @@
|
||||
"ROOT": "root",
|
||||
"SITEMAP.XML": "sitemap_xml"
|
||||
},
|
||||
"PLATFORM_NAME": "édX",
|
||||
"REGISTRATION_EXTENSION_FORM": "openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm",
|
||||
"REGISTRATION_EXTRA_FIELDS": {
|
||||
"level_of_education": "optional",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Settings for Bok Choy tests that are used when running LMS.
|
||||
|
||||
@@ -14,6 +15,7 @@ import os
|
||||
from path import Path as path
|
||||
from tempfile import mkdtemp
|
||||
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from openedx.core.release import RELEASE_LINE
|
||||
|
||||
CONFIG_ROOT = path(__file__).abspath().dirname()
|
||||
@@ -52,6 +54,9 @@ update_module_store_settings(
|
||||
# Capture the console log via template includes, until webdriver supports log capture again
|
||||
CAPTURE_CONSOLE_LOG = True
|
||||
|
||||
PLATFORM_NAME = ugettext_lazy(u"édX")
|
||||
PLATFORM_DESCRIPTION = ugettext_lazy(u"Open édX Platform")
|
||||
|
||||
############################ STATIC FILES #############################
|
||||
|
||||
# Enable debug so that static assets are served by Django
|
||||
|
||||
@@ -115,7 +115,6 @@
|
||||
"ROOT": "root",
|
||||
"SITEMAP.XML": "sitemap_xml"
|
||||
},
|
||||
"PLATFORM_NAME": "édX",
|
||||
"REGISTRATION_EXTENSION_FORM": "openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm",
|
||||
"REGISTRATION_EXTRA_FIELDS": {
|
||||
"level_of_education": "optional",
|
||||
|
||||
@@ -131,8 +131,12 @@ COURSE_MODE_DEFAULTS = ENV_TOKENS.get('COURSE_MODE_DEFAULTS', COURSE_MODE_DEFAUL
|
||||
MEDIA_ROOT = ENV_TOKENS.get('MEDIA_ROOT', MEDIA_ROOT)
|
||||
MEDIA_URL = ENV_TOKENS.get('MEDIA_URL', MEDIA_URL)
|
||||
|
||||
PLATFORM_NAME = ENV_TOKENS.get('PLATFORM_NAME', PLATFORM_NAME)
|
||||
PLATFORM_DESCRIPTION = ENV_TOKENS.get('PLATFORM_DESCRIPTION', PLATFORM_DESCRIPTION)
|
||||
# The following variables use (or) instead of the default value inside (get). This is to enforce using the Lazy Text
|
||||
# values when the varibale is an empty string. Therefore, setting these variable as empty text in related
|
||||
# json files will make the system reads thier values from django translation files
|
||||
PLATFORM_NAME = ENV_TOKENS.get('PLATFORM_NAME') or PLATFORM_NAME
|
||||
PLATFORM_DESCRIPTION = ENV_TOKENS.get('PLATFORM_DESCRIPTION') or PLATFORM_DESCRIPTION
|
||||
|
||||
# For displaying on the receipt. At Stanford PLATFORM_NAME != MERCHANT_NAME, but PLATFORM_NAME is a fine default
|
||||
PLATFORM_TWITTER_ACCOUNT = ENV_TOKENS.get('PLATFORM_TWITTER_ACCOUNT', PLATFORM_TWITTER_ACCOUNT)
|
||||
PLATFORM_FACEBOOK_ACCOUNT = ENV_TOKENS.get('PLATFORM_FACEBOOK_ACCOUNT', PLATFORM_FACEBOOK_ACCOUNT)
|
||||
|
||||
@@ -13,6 +13,8 @@ sessions. Assumes structure:
|
||||
# want to import all variables from base settings files
|
||||
# pylint: disable=wildcard-import, unused-wildcard-import
|
||||
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
from .common import *
|
||||
import os
|
||||
from path import Path as path
|
||||
@@ -397,8 +399,12 @@ FEATURES['CLASS_DASHBOARD'] = True
|
||||
import openid.oidutil
|
||||
openid.oidutil.log = lambda message, level=0: None
|
||||
|
||||
# Include a non-ascii character in PLATFORM_NAME to uncover possible UnicodeEncodeErrors in tests.
|
||||
PLATFORM_NAME = u"édX"
|
||||
|
||||
# Include a non-ascii character in PLATFORM_NAME and PLATFORM_DESCRIPTION to uncover possible
|
||||
# UnicodeEncodeErrors in tests. Also use lazy text to reveal possible json dumps errors
|
||||
PLATFORM_NAME = ugettext_lazy(u"édX")
|
||||
PLATFORM_DESCRIPTION = ugettext_lazy(u"Open édX Platform")
|
||||
|
||||
SITE_NAME = "edx.org"
|
||||
|
||||
# set up some testing for microsites
|
||||
|
||||
@@ -4,11 +4,11 @@ Message Types for user_api emails
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from edx_ace import message
|
||||
from openedx.core.djangoapps.ace_common.message import BaseMessageType
|
||||
from openedx.core.djangoapps.site_configuration import helpers
|
||||
|
||||
|
||||
class DeletionNotificationMessage(message.MessageType):
|
||||
class DeletionNotificationMessage(BaseMessageType):
|
||||
"""
|
||||
Message to notify learners that their account is queued for deletion.
|
||||
"""
|
||||
|
||||
29
openedx/core/lib/json_utils.py
Normal file
29
openedx/core/lib/json_utils.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
Helpers for json serialization
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
|
||||
|
||||
class EdxJSONEncoder(DjangoJSONEncoder):
|
||||
"""
|
||||
Custom JSONEncoder that handles `Location` and `datetime.datetime` objects.
|
||||
|
||||
`Location`s are encoded as their url string form, and `datetime`s as
|
||||
ISO date strings
|
||||
"""
|
||||
def default(self, o): # pylint: disable=method-hidden
|
||||
if isinstance(o, (CourseKey, UsageKey)):
|
||||
return unicode(o)
|
||||
elif isinstance(o, datetime.datetime):
|
||||
if o.tzinfo is not None:
|
||||
if o.utcoffset() is None:
|
||||
return o.isoformat() + 'Z'
|
||||
else:
|
||||
return o.isoformat()
|
||||
else:
|
||||
return o.isoformat()
|
||||
else:
|
||||
return super(EdxJSONEncoder, self).default(o)
|
||||
@@ -64,7 +64,7 @@ django-webpack-loader # Used to wire webpack bundles into the djan
|
||||
djangorestframework-jwt
|
||||
django-xforwardedfor-middleware==2.0 # Middleware to use the X-Forwarded-For header as the request IP
|
||||
dogapi==1.2.1 # Python bindings to Datadog's API, for metrics gathering
|
||||
edx-ace==0.1.9
|
||||
edx-ace==0.1.10
|
||||
edx-analytics-data-api-client
|
||||
edx-ccx-keys
|
||||
edx-celeryutils
|
||||
|
||||
@@ -109,7 +109,7 @@ dm.xmlsec.binding==1.3.3 # via python-saml
|
||||
docopt==0.6.2
|
||||
docutils==0.14 # via botocore
|
||||
dogapi==1.2.1
|
||||
edx-ace==0.1.9
|
||||
edx-ace==0.1.10
|
||||
edx-analytics-data-api-client==0.14.4
|
||||
edx-ccx-keys==0.2.1
|
||||
edx-celeryutils==0.2.7
|
||||
|
||||
@@ -128,7 +128,7 @@ dm.xmlsec.binding==1.3.3
|
||||
docopt==0.6.2
|
||||
docutils==0.14
|
||||
dogapi==1.2.1
|
||||
edx-ace==0.1.9
|
||||
edx-ace==0.1.10
|
||||
edx-analytics-data-api-client==0.14.4
|
||||
edx-ccx-keys==0.2.1
|
||||
edx-celeryutils==0.2.7
|
||||
|
||||
@@ -123,7 +123,7 @@ dm.xmlsec.binding==1.3.3
|
||||
docopt==0.6.2
|
||||
docutils==0.14
|
||||
dogapi==1.2.1
|
||||
edx-ace==0.1.9
|
||||
edx-ace==0.1.10
|
||||
edx-analytics-data-api-client==0.14.4
|
||||
edx-ccx-keys==0.2.1
|
||||
edx-celeryutils==0.2.7
|
||||
|
||||
Reference in New Issue
Block a user