KeyError in XBlock using MathJax LateX

(this post was moved from the Educators category)

I’m just getting started with XBlock development. I’m trying to render LateX within my XBlock. The following gives a KeyError on the LateX fraction. Any suggestions on what I’m doing wrong?

<head>
  <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
  <script type="text/javascript" id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js">
</script>
</head>

<div class="test01_block">
<p>Test01XBlock: count is now
    <span class='count'>{self.count}</span> (click me to increment).
</p>
<p>A point charge of +1\(\mu\)C is placed at (x, y, z) = (0, 0, 0). Units of the Cartesian coordinate system are in meters.</p>
<p>The electric field that results from a point charge can be calculated with the following equation:</p>
<p>\[\frac{a}{b}\]</p>
</div>

error message in the browser:

KeyError at /scenario/test02.0/
'a'
Request Method:	GET
Request URL:	http://localhost:8000/scenario/test02.0/
Django Version:	3.2.18
Exception Type:	KeyError
Exception Value:	
'a'
Exception Location:	/Users/d24lau/src/xblock3/test02/test02/test02.py, line 35, in student_view
Python Executable:	/Users/d24lau/src/xblock3/venv/bin/python
Python Version:	3.9.0
Python Path:	
['/Users/d24lau/src/xblock3/xblock-sdk',
 '/Users/d24lau/.pyenv/versions/3.9.0/lib/python39.zip',
 '/Users/d24lau/.pyenv/versions/3.9.0/lib/python3.9',
 '/Users/d24lau/.pyenv/versions/3.9.0/lib/python3.9/lib-dynload',
 '/Users/d24lau/src/xblock3/venv/lib/python3.9/site-packages',
 '/Users/d24lau/src/xblock3/xblock-sdk',
 '/Users/d24lau/src/xblock3/test02']
Server time:	Tue, 23 May 2023 08:11:58 -0400
Traceback Switch to copy-and-paste view
/Users/d24lau/src/xblock3/venv/lib/python3.9/site-packages/django/core/handlers/exception.py, line 47, in inner
                response = get_response(request) …
▶ Local vars
/Users/d24lau/src/xblock3/venv/lib/python3.9/site-packages/django/core/handlers/base.py, line 181, in _get_response
                response = wrapped_callback(request, *callback_args, **callback_kwargs) …
▶ Local vars
/Users/d24lau/src/xblock3/venv/lib/python3.9/site-packages/django/utils/decorators.py, line 130, in _wrapped_view
                    response = view_func(request, *args, **kwargs) …
▶ Local vars
/Users/d24lau/src/xblock3/xblock-sdk/workbench/views.py, line 77, in show_scenario
    frag = block.render(view_name, render_context) …
▶ Local vars
/Users/d24lau/src/xblock3/venv/lib/python3.9/site-packages/xblock/core.py, line 198, in render
        return self.runtime.render(self, view, context) …
▶ Local vars
/Users/d24lau/src/xblock3/xblock-sdk/workbench/runtime.py, line 319, in render
            return super().render(block, view_name, context) …
▶ Local vars
/Users/d24lau/src/xblock3/venv/lib/python3.9/site-packages/xblock/runtime.py, line 847, in render
            frag = view_fn(context) …
▶ Local vars
/Users/d24lau/src/xblock3/test02/test02/test02.py, line 35, in student_view
        frag = Fragment(html.format(self=self)) …
▶ Local vars
Request information
USER
AnonymousUser

GET
No GET data

POST
No POST data

FILES
No FILES data

COOKIES
Variable	Value
csrftoken	
'Q9AAcrNgFMakYF1rGZ5l2uIGsWS5ohps2eSDeaEAQXtC06ksFkPd9PtJPaLLCAGy'
META
Variable	Value
CONTENT_LENGTH	
''
CONTENT_TYPE	
'text/plain'
CSRF_COOKIE	
'Q9AAcrNgFMakYF1rGZ5l2uIGsWS5ohps2eSDeaEAQXtC06ksFkPd9PtJPaLLCAGy'
CSRF_COOKIE_USED	
True
DJANGO_SETTINGS_MODULE	
'workbench.settings'
GATEWAY_INTERFACE	
'CGI/1.1'
HOME	
'/Users/d24lau'
HTTP_ACCEPT	
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7'
HTTP_ACCEPT_ENCODING	
'gzip, deflate, br'
HTTP_ACCEPT_LANGUAGE	
'en-US,en;q=0.9'
HTTP_CACHE_CONTROL	
'max-age=0'
HTTP_CONNECTION	
'keep-alive'
HTTP_COOKIE	
'csrftoken=Q9AAcrNgFMakYF1rGZ5l2uIGsWS5ohps2eSDeaEAQXtC06ksFkPd9PtJPaLLCAGy'
HTTP_DNT	
'1'
HTTP_HOST	
'localhost:8000'
HTTP_REFERER	
'http://localhost:8000/'
HTTP_SEC_CH_UA	
'"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"'
HTTP_SEC_CH_UA_MOBILE	
'?0'
HTTP_SEC_CH_UA_PLATFORM	
'"macOS"'
HTTP_SEC_FETCH_DEST	
'document'
HTTP_SEC_FETCH_MODE	
'navigate'
HTTP_SEC_FETCH_SITE	
'same-origin'
HTTP_SEC_FETCH_USER	
'?1'
HTTP_UPGRADE_INSECURE_REQUESTS	
'1'
HTTP_USER_AGENT	
('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, '
 'like Gecko) Chrome/113.0.0.0 Safari/537.36')
LANG	
'en_CA.UTF-8'
LOGNAME	
'd24lau'
LaunchInstanceID	
'25A5A1D3-85EF-4B5A-A9DD-69E4C1D3EE85'
OLDPWD	
'/Users/d24lau/src'
PATH	
'/Users/d24lau/src/xblock3/venv/bin:/Users/d24lau/.pyenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'
PATH_INFO	
'/scenario/test02.0/'
PS1	
'(venv) \\h:\\W \\u\\$ '
PWD	
'/Users/d24lau/src/xblock3'
PYENV_SHELL	
'bash'
QUERY_STRING	
''
REMOTE_ADDR	
'127.0.0.1'
REMOTE_HOST	
''
REQUEST_METHOD	
'GET'
RUN_MAIN	
'true'
SCRIPT_NAME	
''
SECURITYSESSIONID	
'186a3'
SERVER_NAME	
'1.0.0.127.in-addr.arpa'
SERVER_PORT	
'8000'
SERVER_PROTOCOL	
'HTTP/1.1'
SERVER_SOFTWARE	
'WSGIServer/0.2'
SHELL	
'/bin/bash'
SHLVL	
'1'
SSH_AUTH_SOCK	
'/private/tmp/com.apple.launchd.YUg2X6Ccjl/Listeners'
TERM	
'xterm-256color'
TERM_PROGRAM	
'Apple_Terminal'
TERM_PROGRAM_VERSION	
'445'
TERM_SESSION_ID	
'6138864A-0F78-45B8-BF89-693E8BBFAA5B'
TMPDIR	
'/var/folders/bt/hbj1wpcd5m321tqj6tyv8jsr0000gn/T/'
TZ	
'America/New_York'
USER	
'd24lau'
VIRTUAL_ENV	
'/Users/d24lau/src/xblock3/venv'
XPC_FLAGS	
'0x0'
XPC_SERVICE_NAME	
'0'
_	
'/Users/d24lau/src/xblock3/venv/bin/python'
__CFBundleIdentifier	
'com.apple.Terminal'
__CF_USER_TEXT_ENCODING	
'0x1F5:0x0:0x52'
wsgi.errors	
<_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>
wsgi.file_wrapper	
<class 'wsgiref.util.FileWrapper'>
wsgi.input	
<django.core.handlers.wsgi.LimitedStream object at 0x104ff9af0>
wsgi.multiprocess	
False
wsgi.multithread	
True
wsgi.run_once	
False
wsgi.url_scheme	
'http'
wsgi.version	
(1, 0)
Settings
Using settings module workbench.settings
Setting	Value
ABSOLUTE_URL_OVERRIDES	
{}
ADMINS	
()
ALLOWED_HOSTS	
[]
APPEND_SLASH	
True
AUTHENTICATION_BACKENDS	
['django.contrib.auth.backends.ModelBackend']
AUTH_PASSWORD_VALIDATORS	
'********************'
AUTH_USER_MODEL	
'auth.User'
BASE_DIR	
'/Users/d24lau/src/xblock3/xblock-sdk'
CACHES	
{'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
             'LOCATION': 'unique-snowflake'}}
CACHE_MIDDLEWARE_ALIAS	
'default'
CACHE_MIDDLEWARE_KEY_PREFIX	
'********************'
CACHE_MIDDLEWARE_SECONDS	
600
CSRF_COOKIE_AGE	
31449600
CSRF_COOKIE_DOMAIN	
None
CSRF_COOKIE_HTTPONLY	
False
CSRF_COOKIE_NAME	
'csrftoken'
CSRF_COOKIE_PATH	
'/'
CSRF_COOKIE_SAMESITE	
'Lax'
CSRF_COOKIE_SECURE	
False
CSRF_FAILURE_VIEW	
'django.views.csrf.csrf_failure'
CSRF_HEADER_NAME	
'HTTP_X_CSRFTOKEN'
CSRF_TRUSTED_ORIGINS	
[]
CSRF_USE_SESSIONS	
False
DATABASES	
{'default': {'ATOMIC_REQUESTS': False,
             'AUTOCOMMIT': True,
             'CONN_MAX_AGE': 0,
             'ENGINE': 'django.db.backends.sqlite3',
             'HOST': '',
             'NAME': 'var/workbench.db',
             'OPTIONS': {},
             'PASSWORD': '********************',
             'PORT': '',
             'TEST': {'CHARSET': None,
                      'COLLATION': None,
                      'MIGRATE': True,
                      'MIRROR': None,
                      'NAME': None},
             'TIME_ZONE': None,
             'USER': ''}}
DATABASE_ROUTERS	
[]
DATA_UPLOAD_MAX_MEMORY_SIZE	
2621440
DATA_UPLOAD_MAX_NUMBER_FIELDS	
1000
DATA_UPLOAD_MAX_NUMBER_FILES	
100
DATETIME_FORMAT	
'N j, Y, P'
DATETIME_INPUT_FORMATS	
['%Y-%m-%d %H:%M:%S',
 '%Y-%m-%d %H:%M:%S.%f',
 '%Y-%m-%d %H:%M',
 '%m/%d/%Y %H:%M:%S',
 '%m/%d/%Y %H:%M:%S.%f',
 '%m/%d/%Y %H:%M',
 '%m/%d/%y %H:%M:%S',
 '%m/%d/%y %H:%M:%S.%f',
 '%m/%d/%y %H:%M']
DATE_FORMAT	
'N j, Y'
DATE_INPUT_FORMATS	
['%Y-%m-%d',
 '%m/%d/%Y',
 '%m/%d/%y',
 '%b %d %Y',
 '%b %d, %Y',
 '%d %b %Y',
 '%d %b, %Y',
 '%B %d %Y',
 '%B %d, %Y',
 '%d %B %Y',
 '%d %B, %Y']
DEBUG	
True
DEBUG_PROPAGATE_EXCEPTIONS	
False
DECIMAL_SEPARATOR	
'.'
DEFAULT_AUTO_FIELD	
'django.db.models.AutoField'
DEFAULT_CHARSET	
'utf-8'
DEFAULT_EXCEPTION_REPORTER	
'django.views.debug.ExceptionReporter'
DEFAULT_EXCEPTION_REPORTER_FILTER	
'django.views.debug.SafeExceptionReporterFilter'
DEFAULT_FILE_STORAGE	
'django.core.files.storage.FileSystemStorage'
DEFAULT_FROM_EMAIL	
'webmaster@localhost'
DEFAULT_HASHING_ALGORITHM	
'sha256'
DEFAULT_INDEX_TABLESPACE	
''
DEFAULT_TABLESPACE	
''
DISALLOWED_USER_AGENTS	
[]
DJFS	
{'directory_root': 'workbench/static/djpyfs',
 'type': 'osfs',
 'url_root': '/static/djpyfs'}
EMAIL_BACKEND	
'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST	
'localhost'
EMAIL_HOST_PASSWORD	
'********************'
EMAIL_HOST_USER	
''
EMAIL_PORT	
25
EMAIL_SSL_CERTFILE	
None
EMAIL_SSL_KEYFILE	
'********************'
EMAIL_SUBJECT_PREFIX	
'[Django] '
EMAIL_TIMEOUT	
None
EMAIL_USE_LOCALTIME	
False
EMAIL_USE_SSL	
False
EMAIL_USE_TLS	
False
EXCLUDED_XBLOCKS	
set()
FILE_UPLOAD_DIRECTORY_PERMISSIONS	
None
FILE_UPLOAD_HANDLERS	
['django.core.files.uploadhandler.MemoryFileUploadHandler',
 'django.core.files.uploadhandler.TemporaryFileUploadHandler']
FILE_UPLOAD_MAX_MEMORY_SIZE	
2621440
FILE_UPLOAD_PERMISSIONS	
420
FILE_UPLOAD_TEMP_DIR	
None
FIRST_DAY_OF_WEEK	
0
FIXTURE_DIRS	
[]
FORCE_SCRIPT_NAME	
None
FORMAT_MODULE_PATH	
None
FORM_RENDERER	
'django.forms.renderers.DjangoTemplates'
IGNORABLE_404_URLS	
[]
INSTALLED_APPS	
['django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'djpyfs',
 'workbench',
 'django.contrib.admin']
INTERNAL_IPS	
[]
LANGUAGES	
[('af', 'Afrikaans'),
 ('ar', 'Arabic'),
 ('ar-dz', 'Algerian Arabic'),
 ('ast', 'Asturian'),
 ('az', 'Azerbaijani'),
 ('bg', 'Bulgarian'),
 ('be', 'Belarusian'),
 ('bn', 'Bengali'),
 ('br', 'Breton'),
 ('bs', 'Bosnian'),
 ('ca', 'Catalan'),
 ('cs', 'Czech'),
 ('cy', 'Welsh'),
 ('da', 'Danish'),
 ('de', 'German'),
 ('dsb', 'Lower Sorbian'),
 ('el', 'Greek'),
 ('en', 'English'),
 ('en-au', 'Australian English'),
 ('en-gb', 'British English'),
 ('eo', 'Esperanto'),
 ('es', 'Spanish'),
 ('es-ar', 'Argentinian Spanish'),
 ('es-co', 'Colombian Spanish'),
 ('es-mx', 'Mexican Spanish'),
 ('es-ni', 'Nicaraguan Spanish'),
 ('es-ve', 'Venezuelan Spanish'),
 ('et', 'Estonian'),
 ('eu', 'Basque'),
 ('fa', 'Persian'),
 ('fi', 'Finnish'),
 ('fr', 'French'),
 ('fy', 'Frisian'),
 ('ga', 'Irish'),
 ('gd', 'Scottish Gaelic'),
 ('gl', 'Galician'),
 ('he', 'Hebrew'),
 ('hi', 'Hindi'),
 ('hr', 'Croatian'),
 ('hsb', 'Upper Sorbian'),
 ('hu', 'Hungarian'),
 ('hy', 'Armenian'),
 ('ia', 'Interlingua'),
 ('id', 'Indonesian'),
 ('ig', 'Igbo'),
 ('io', 'Ido'),
 ('is', 'Icelandic'),
 ('it', 'Italian'),
 ('ja', 'Japanese'),
 ('ka', 'Georgian'),
 ('kab', 'Kabyle'),
 ('kk', 'Kazakh'),
 ('km', 'Khmer'),
 ('kn', 'Kannada'),
 ('ko', 'Korean'),
 ('ky', 'Kyrgyz'),
 ('lb', 'Luxembourgish'),
 ('lt', 'Lithuanian'),
 ('lv', 'Latvian'),
 ('mk', 'Macedonian'),
 ('ml', 'Malayalam'),
 ('mn', 'Mongolian'),
 ('mr', 'Marathi'),
 ('my', 'Burmese'),
 ('nb', 'Norwegian Bokmål'),
 ('ne', 'Nepali'),
 ('nl', 'Dutch'),
 ('nn', 'Norwegian Nynorsk'),
 ('os', 'Ossetic'),
 ('pa', 'Punjabi'),
 ('pl', 'Polish'),
 ('pt', 'Portuguese'),
 ('pt-br', 'Brazilian Portuguese'),
 ('ro', 'Romanian'),
 ('ru', 'Russian'),
 ('sk', 'Slovak'),
 ('sl', 'Slovenian'),
 ('sq', 'Albanian'),
 ('sr', 'Serbian'),
 ('sr-latn', 'Serbian Latin'),
 ('sv', 'Swedish'),
 ('sw', 'Swahili'),
 ('ta', 'Tamil'),
 ('te', 'Telugu'),
 ('tg', 'Tajik'),
 ('th', 'Thai'),
 ('tk', 'Turkmen'),
 ('tr', 'Turkish'),
 ('tt', 'Tatar'),
 ('udm', 'Udmurt'),
 ('uk', 'Ukrainian'),
 ('ur', 'Urdu'),
 ('uz', 'Uzbek'),
 ('vi', 'Vietnamese'),
 ('zh-hans', 'Simplified Chinese'),
 ('zh-hant', 'Traditional Chinese')]
LANGUAGES_BIDI	
['he', 'ar', 'ar-dz', 'fa', 'ur']
LANGUAGE_CODE	
'en-us'
LANGUAGE_COOKIE_AGE	
None
LANGUAGE_COOKIE_DOMAIN	
None
LANGUAGE_COOKIE_HTTPONLY	
False
LANGUAGE_COOKIE_NAME	
'django_language'
LANGUAGE_COOKIE_PATH	
'/'
LANGUAGE_COOKIE_SAMESITE	
None
LANGUAGE_COOKIE_SECURE	
False
LOCALE_PATHS	
[]
LOGGING	
{'disable_existing_loggers': False,
 'filters': {'require_debug_false': {'()': 'django.utils.log.RequireDebugFalse'}},
 'handlers': {'logfile': {'backupCount': 2,
                          'class': 'logging.handlers.RotatingFileHandler',
                          'filename': 'var/workbench.log',
                          'level': 'DEBUG',
                          'maxBytes': 50000},
              'null': {'class': 'logging.NullHandler', 'level': 'DEBUG'}},
 'loggers': {'django': {'handlers': ['logfile'], 'level': 'DEBUG'},
             'django.request': {'handlers': ['logfile'],
                                'level': 'DEBUG',
                                'propagate': True}},
 'version': 1}
LOGGING_CONFIG	
'logging.config.dictConfig'
LOGIN_REDIRECT_URL	
'/accounts/profile/'
LOGIN_URL	
'/accounts/login/'
LOGOUT_REDIRECT_URL	
None
MANAGERS	
()
MEDIA_ROOT	
''
MEDIA_URL	
'/'
MESSAGE_STORAGE	
'django.contrib.messages.storage.fallback.FallbackStorage'
MIDDLEWARE	
['django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware']
MIGRATION_MODULES	
{}
MONTH_DAY_FORMAT	
'F j'
NUMBER_GROUPING	
0
PASSWORD_HASHERS	
'********************'
PASSWORD_RESET_TIMEOUT	
'********************'
PASSWORD_RESET_TIMEOUT_DAYS	
'********************'
PREPEND_WWW	
False
ROOT_URLCONF	
'workbench.urls'
SECRET_KEY	
'********************'
SECURE_BROWSER_XSS_FILTER	
False
SECURE_CONTENT_TYPE_NOSNIFF	
True
SECURE_HSTS_INCLUDE_SUBDOMAINS	
False
SECURE_HSTS_PRELOAD	
False
SECURE_HSTS_SECONDS	
0
SECURE_PROXY_SSL_HEADER	
None
SECURE_REDIRECT_EXEMPT	
[]
SECURE_REFERRER_POLICY	
'same-origin'
SECURE_SSL_HOST	
None
SECURE_SSL_REDIRECT	
False
SERVER_EMAIL	
'root@localhost'
SESSION_CACHE_ALIAS	
'default'
SESSION_COOKIE_AGE	
1209600
SESSION_COOKIE_DOMAIN	
None
SESSION_COOKIE_HTTPONLY	
True
SESSION_COOKIE_NAME	
'sessionid'
SESSION_COOKIE_PATH	
'/'
SESSION_COOKIE_SAMESITE	
'Lax'
SESSION_COOKIE_SECURE	
False
SESSION_ENGINE	
'django.contrib.sessions.backends.db'
SESSION_EXPIRE_AT_BROWSER_CLOSE	
False
SESSION_FILE_PATH	
None
SESSION_SAVE_EVERY_REQUEST	
False
SESSION_SERIALIZER	
'django.contrib.sessions.serializers.JSONSerializer'
SETTINGS_MODULE	
'workbench.settings'
SHORT_DATETIME_FORMAT	
'm/d/Y P'
SHORT_DATE_FORMAT	
'm/d/Y'
SIGNING_BACKEND	
'django.core.signing.TimestampSigner'
SILENCED_SYSTEM_CHECKS	
[]
SITE_ID	
1
STATICFILES_DIRS	
()
STATICFILES_FINDERS	
['django.contrib.staticfiles.finders.FileSystemFinder',
 'django.contrib.staticfiles.finders.AppDirectoriesFinder']
STATICFILES_STORAGE	
'django.contrib.staticfiles.storage.StaticFilesStorage'
STATIC_ROOT	
''
STATIC_URL	
'/static/'
TEMPLATES	
[{'APP_DIRS': True,
  'BACKEND': 'django.template.backends.django.DjangoTemplates',
  'DIRS': ['/Users/d24lau/src/xblock3/xblock-sdk/workbench/templates',
           '/Users/d24lau/src/xblock3/xblock-sdk/sample_xblocks/basic/templates'],
  'OPTIONS': {'context_processors': ['django.contrib.auth.context_processors.auth',
                                     'django.template.context_processors.debug',
                                     'django.template.context_processors.i18n',
                                     'django.template.context_processors.media',
                                     'django.template.context_processors.static',
                                     'django.template.context_processors.tz',
                                     'django.contrib.messages.context_processors.messages'],
              'debug': True}}]
TEMPLATE_DIRS	
[]
TEST_NON_SERIALIZED_APPS	
[]
TEST_RUNNER	
'django.test.runner.DiscoverRunner'
THOUSAND_SEPARATOR	
','
TIME_FORMAT	
'P'
TIME_INPUT_FORMATS	
['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
TIME_ZONE	
'America/New_York'
USE_I18N	
True
USE_L10N	
True
USE_THOUSAND_SEPARATOR	
False
USE_TZ	
True
USE_X_FORWARDED_HOST	
False
USE_X_FORWARDED_PORT	
False
WORKBENCH	
{'reset_state_on_restart': False,
 'services': {'fs': 'xblock.reference.plugins.FSService',
              'settings': 'workbench.services.SettingsService'}}
WSGI_APPLICATION	
'workbench.wsgi.application'
X_FRAME_OPTIONS	
'DENY'
YEAR_MONTH_FORMAT	
'F Y'
You’re seeing this error because you have DEBUG = True in your Django settings file. Change that to False, and Django will display a standard page generated by the handler for this status code.

I finally figured this out. It seems that the MathJax requires the use of double braces, instead of the single braces that I’m used to in LateX. So, once I changed my MathJax string to the following:

<p>\[\frac{{a}}{{b}}\]</p>

things worked!