Commit 2542b6cb by Vik Paruchuri

Merge remote-tracking branch 'origin/master' into feature/vik/improve-oe-ui

parents 6a6962df a6b35853
......@@ -77,18 +77,19 @@ def replace_static_urls(text, data_directory, course_namespace=None):
# course_namespace is not None, then use studio style urls
if course_namespace is not None and not isinstance(modulestore(), XMLModuleStore):
url = StaticContent.convert_legacy_static_url(rest, course_namespace)
# If we're in debug mode, and the file as requested exists, then don't change the links
elif (settings.DEBUG and finders.find(rest, True)):
return original
# Otherwise, look the file up in staticfiles_storage without the data directory
# Otherwise, look the file up in staticfiles_storage, and append the data directory if needed
else:
course_path = "/".join((data_directory, rest))
try:
url = staticfiles_storage.url(rest)
if staticfiles_storage.exists(rest):
url = staticfiles_storage.url(rest)
else:
url = staticfiles_storage.url(course_path)
# And if that fails, assume that it's course content, and add manually data directory
except Exception as err:
log.warning("staticfiles_storage couldn't find path {0}: {1}".format(
rest, str(err)))
url = "".join([prefix, data_directory, '/', rest])
url = "".join([prefix, course_path])
return "".join([quote, url, quote])
......
......@@ -24,15 +24,24 @@ def test_multi_replace():
)
@patch('static_replace.finders')
@patch('static_replace.settings')
def test_debug_no_modify(mock_settings, mock_finders):
mock_settings.DEBUG = True
mock_finders.find.return_value = True
@patch('static_replace.staticfiles_storage')
def test_storage_url_exists(mock_storage):
mock_storage.exists.return_value = True
mock_storage.url.return_value = '/static/file.png'
assert_equals('"/static/file.png"', replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY))
mock_storage.exists.called_once_with('file.png')
mock_storage.url.called_once_with('data_dir/file.png')
assert_equals(STATIC_SOURCE, replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY))
@patch('static_replace.staticfiles_storage')
def test_storage_url_not_exists(mock_storage):
mock_storage.exists.return_value = False
mock_storage.url.return_value = '/static/data_dir/file.png'
mock_finders.find.assert_called_once_with('file.png', True)
assert_equals('"/static/data_dir/file.png"', replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY))
mock_storage.exists.called_once_with('file.png')
mock_storage.url.called_once_with('file.png')
@patch('static_replace.StaticContent')
......@@ -59,7 +68,10 @@ def test_mongo_filestore(mock_modulestore, mock_static_content):
@patch('static_replace.staticfiles_storage')
def test_data_dir_fallback(mock_storage, mock_modulestore, mock_settings):
mock_modulestore.return_value = Mock(XMLModuleStore)
mock_settings.DEBUG = False
mock_storage.url.side_effect = Exception
mock_storage.exists.return_value = True
assert_equals('"/static/data_dir/file.png"', replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY))
mock_storage.exists.return_value = False
assert_equals('"/static/data_dir/file.png"', replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY))
......@@ -834,3 +834,108 @@ class DragAndDropInput(InputTypeBase):
registry.register(DragAndDropInput)
#--------------------------------------------------------------------------------------------------------------------
class EditAMoleculeInput(InputTypeBase):
"""
An input type for edit-a-molecule. Integrates with the molecule editor java applet.
Example:
<editamolecule size="50"/>
options: size -- width of the textbox.
"""
template = "editamolecule.html"
tags = ['editamoleculeinput']
@classmethod
def get_attributes(cls):
"""
Can set size of text field.
"""
return [Attribute('file'),
Attribute('missing', None)]
def _extra_context(self):
"""
"""
context = {
'applet_loader': '/static/js/capa/editamolecule.js',
}
return context
registry.register(EditAMoleculeInput)
#-----------------------------------------------------------------------------
class DesignProtein2dInput(InputTypeBase):
"""
An input type for design of a protein in 2D. Integrates with the Protex java applet.
Example:
<designprotein2d width="800" hight="500" target_shape="E;NE;NW;W;SW;E;none" />
"""
template = "designprotein2dinput.html"
tags = ['designprotein2dinput']
@classmethod
def get_attributes(cls):
"""
Note: width, hight, and target_shape are required.
"""
return [Attribute('width'),
Attribute('height'),
Attribute('target_shape')
]
def _extra_context(self):
"""
"""
context = {
'applet_loader': '/static/js/capa/design-protein-2d.js',
}
return context
registry.register(DesignProtein2dInput)
#-----------------------------------------------------------------------------
class EditAGeneInput(InputTypeBase):
"""
An input type for editing a gene. Integrates with the genex java applet.
Example:
<editagene width="800" hight="500" dna_sequence="ETAAGGCTATAACCGA" />
"""
template = "editageneinput.html"
tags = ['editageneinput']
@classmethod
def get_attributes(cls):
"""
Note: width, hight, and dna_sequencee are required.
"""
return [Attribute('width'),
Attribute('height'),
Attribute('dna_sequence')
]
def _extra_context(self):
"""
"""
context = {
'applet_loader': '/static/js/capa/edit-a-gene.js',
}
return context
registry.register(EditAGeneInput)
......@@ -875,7 +875,8 @@ def sympy_check2():
allowed_inputfields = ['textline', 'textbox', 'crystallography',
'chemicalequationinput', 'vsepr_input',
'drag_and_drop_input']
'drag_and_drop_input', 'editamoleculeinput',
'designprotein2dinput', 'editageneinput']
def setup_response(self):
xml = self.xml
......
......@@ -31,7 +31,6 @@
<div id="input_${id}_preview" class="equation">
</div>
<p id="answer_${id}" class="answer"></p>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
......
<section id="designprotein2dinput_${id}" class="designprotein2dinput">
<div class="script_placeholder" data-src="/static/js/capa/protex/protex.nocache.js"/>
<div class="script_placeholder" data-src="${applet_loader}"/>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incomplete" id="status_${id}">
% endif
<div id="protex_container"></div>
<input type="hidden" name="target_shape" id="target_shape" value ="${target_shape}"></input>
<input type="hidden" name="input_${id}" id="input_${id}" value="${value|h}"/>
<p class="status">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
</p>
<p id="answer_${id}" class="answer"></p>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
</div>
% endif
</section>
<section id="editageneinput_${id}" class="editageneinput">
<div class="script_placeholder" data-src="${applet_loader}"/>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incorrect" id="status_${id}">
% endif
<object type="application/x-java-applet" id="applet_${id}" class="applet" width="${width}" height="${height}">
<param name="archive" value="/static/applets/capa/genex.jar" />
<param name="code" value="GX.GenexApplet.class" />
<param name="DNA_SEQUENCE" value="${dna_sequence}" />
Applet failed to run. No Java plug-in was found.
</object>
<input type="hidden" name="input_${id}" id="input_${id}" value="${value|h}"/>
<p class="status">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
</p>
<p id="answer_${id}" class="answer"></p>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
</div>
% endif
</section>
<section id="editamoleculeinput_${id}" class="editamoleculeinput">
<div class="script_placeholder" data-src="${applet_loader}"/>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incorrect" id="status_${id}">
% endif
<div id="applet_${id}" class="applet" data-molfile-src="${file}" style="display:block;width:500px;height:400px">
</div>
<br/>
<input type="hidden" name="input_${id}" id="input_${id}" value="${value|h}"/>
<button id="reset_${id}" class="reset">Reset</button>
<p id="answer_${id}" class="answer"></p>
<p class="status">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
</p>
<br/> <br/>
<div class="error_message" style="padding: 5px 5px 5px 5px; background-color:#FA6666; height:60px;width:400px; display: none"></div>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
</div>
% endif
</section>
# A simple script demonstrating how to have an external program post problem
# responses to an edx server.
#
# ***** NOTE *****
# This is not intended as a stable public API. In fact, it is almost certainly
# going to change. If you use this for some reason, be prepared to change your
# code.
#
# We will be working to define a stable public API for external programs. We
# don't have have one yet (Feb 2013).
import requests
import sys
import getpass
def prompt(msg, default=None, safe=False):
d = ' [{0}]'.format(default) if default is not None else ''
prompt = 'Enter {msg}{default}: '.format(msg=msg, default=d)
if not safe:
print prompt
x = sys.stdin.readline().strip()
else:
x = getpass.getpass(prompt=prompt)
if x == '' and default is not None:
return default
return x
server = 'https://www.edx.org'
course_id = 'HarvardX/PH207x/2012_Fall'
location = 'i4x://HarvardX/PH207x/problem/ex_practice_2'
#server = prompt('Server (no trailing slash)', 'http://127.0.0.1:8000')
#course_id = prompt('Course id', 'MITx/7012x/2013_Spring')
#location = prompt('problem location', 'i4x://MITx/7012x/problem/example_upload_answer')
value = prompt('value to upload')
username = prompt('username on server', 'victor@edx.org')
password = prompt('password', 'abc123', safe=True)
print "get csrf cookie"
session = requests.session()
r = session.get(server + '/')
r.raise_for_status()
# print session.cookies
# for some reason, the server expects a header containing the csrf cookie, not just the
# cookie itself.
session.headers['X-CSRFToken'] = session.cookies['csrftoken']
# for https, need a referer header
session.headers['Referer'] = server + '/'
login_url = '/'.join([server, 'login'])
print "log in"
r = session.post(login_url, {'email': 'victor@edx.org', 'password': 'Secret!', 'remember': 'false'})
#print "request headers: ", r.request.headers
#print "response headers: ", r.headers
r.raise_for_status()
url = '/'.join([server, 'courses', course_id, 'modx', location, 'problem_check'])
data = {'input_{0}_2_1'.format(location.replace('/','-').replace(':','').replace('--','-')): value}
#data = {'input_i4x-MITx-7012x-problem-example_upload_answer_2_1': value}
print "Posting to '{0}': {1}".format(url, data)
r = session.post(url, data)
r.raise_for_status()
print ("To see the uploaded answer, go to {server}/courses/{course_id}/jump_to/{location}"
.format(server=server, course_id=course_id, location=location))
......@@ -45,6 +45,10 @@ class ConditionalModule(XModule):
XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs)
self.contents = None
self.condition = self.metadata.get('condition', '')
self._get_required_modules()
children = self.get_display_items()
if children:
self.icon_class = children[0].get_icon_class()
#log.debug('conditional module required=%s' % self.required_modules_list)
def _get_required_modules(self):
......@@ -129,7 +133,14 @@ class ConditionalDescriptor(SequenceDescriptor):
required_module_list = [tuple(x.split('/', 1)) for x in self.metadata.get('required', '').split('&')]
self.required_module_locations = []
for (tag, name) in required_module_list:
for rm in required_module_list:
try:
(tag, name) = rm
except Exception as err:
msg = "Specification of required module in conditional is broken: %s" % self.metadata.get('required')
log.warning(msg)
self.system.error_tracker(msg)
continue
loc = self.location.dict()
loc['category'] = tag
loc['name'] = name
......
......@@ -11,8 +11,9 @@ class @Problem
$(selector, @el)
bind: =>
@el.find('.problem > div').each (index, element) =>
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element]
if MathJax?
@el.find('.problem > div').each (index, element) =>
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element]
window.update_schematics()
......@@ -31,8 +32,9 @@ class @Problem
# Dynamath
@$('input.math').keyup(@refreshMath)
@$('input.math').each (index, element) =>
MathJax.Hub.Queue [@refreshMath, null, element]
if MathJax?
@$('input.math').each (index, element) =>
MathJax.Hub.Queue [@refreshMath, null, element]
updateProgress: (response) =>
if response.progress_changed
......@@ -230,8 +232,9 @@ class @Problem
showMethod = @inputtypeShowAnswerMethods[cls]
showMethod(inputtype, display, answers) if showMethod?
@el.find('.problem > div').each (index, element) =>
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element]
if MathJax?
@el.find('.problem > div').each (index, element) =>
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element]
@$('.show').val 'Hide Answer'
@el.addClass 'showed'
......@@ -273,7 +276,7 @@ class @Problem
preprocessor_tag = "inputtype_" + elid
mathjax_preprocessor = @inputtypeDisplays[preprocessor_tag]
if jax = MathJax.Hub.getAllJax(target)[0]
if MathJax? and jax = MathJax.Hub.getAllJax(target)[0]
eqn = $(element).val()
if mathjax_preprocessor
eqn = mathjax_preprocessor(eqn)
......@@ -286,7 +289,8 @@ class @Problem
$("##{element.id}_dynamath").val(jax.root.toMathML '')
catch exception
throw exception unless exception.restart
MathJax.Callback.After [@refreshMath, jax], exception.restart
if MathJax?
MathJax.Callback.After [@refreshMath, jax], exception.restart
refreshAnswers: =>
@$('input.schematic').each (index, element) ->
......
......@@ -20,7 +20,10 @@
return module
catch error
console.error "Unable to load #{moduleType}: #{error.message}" if console
if window.console and console.log
console.error "Unable to load #{moduleType}: #{error.message}"
else
throw error
###
Load all modules on the page of the specified type.
......
......@@ -128,6 +128,17 @@ class VideoModule(XModule):
else:
caption_asset_path = StaticContent.get_base_url_path_for_course_assets(self.location) + '/subs_'
# We normally let JS parse this, but in the case that we need a hacked
# out <object> player because YouTube has broken their <iframe> API for
# the third time in a year, we need to extract it server side.
normal_speed_video_id = None # The 1.0 speed video
# video_list() example:
# "0.75:nugHYNiD3fI,1.0:7m8pab1MfYY,1.25:3CxdPGXShq8,1.50:F-D7bOFCnXA"
for video_id_str in self.video_list().split(","):
if video_id_str.startswith("1.0:"):
normal_speed_video_id = video_id_str.split(":")[1]
return self.system.render_template('video.html', {
'streams': self.video_list(),
'id': self.location.html_id(),
......@@ -140,7 +151,8 @@ class VideoModule(XModule):
'caption_asset_path': caption_asset_path,
'show_captions': self.show_captions,
'start': self.start_time,
'end': self.end_time
'end': self.end_time,
'normal_speed_video_id': normal_speed_video_id
})
......
(function () {
var timeout = 1000;
waitForProtex();
function waitForProtex() {
if (typeof(protex) !== "undefined" && protex) {
protex.onInjectionDone("protex");
}
/*if (typeof(protex) !== "undefined") {
//initializeProtex();
}*/
else {
setTimeout(function() { waitForProtex(); }, timeout);
}
}
//NOTE:
// Protex uses three global functions:
// protexSetTargetShape (exported from GWT)
// exported protexCheckAnswer (exported from GWT)
// It calls protexIsReady with a deferred command when it has finished
// initialization and has drawn itself
protexIsReady = function() {
//Load target shape
var target_shape = $('#target_shape').val();
protexSetTargetShape(target_shape);
//Get answer from protex and store it into the hidden input field
//when Check button is clicked
var problem = $('#protex_container').parents('.problem');
var check_button = problem.find('input.check');
var input_field = problem.find('input[type=hidden]');
check_button.on('click', function() {
var protex_answer = protexCheckAnswer();
var value = {protex_answer: protex_answer};
input_field.val(JSON.stringify(value));
});
};
/*function initializeProtex() {
//Check to see if the two exported GWT functions protexSetTargetShape
// and protexCheckAnswer have been appended to global scope -- this
//happens at the end of onModuleLoad() in GWT
if (typeof(protexSetTargetShape) === "function" &&
typeof(protexCheckAnswer) === "function") {
//Load target shape
var target_shape = $('#target_shape').val();
//protexSetTargetShape(target_shape);
//Get answer from protex and store it into the hidden input field
//when Check button is clicked
var problem = $('#protex_container').parents('.problem');
var check_button = problem.find('input.check');
var input_field = problem.find('input[type=hidden]');
check_button.on('click', function() {
var protex_answer = protexCheckAnswer();
var value = {protex_answer: protex_answer};
input_field.val(JSON.stringify(value));
});
//TO DO: Fix this, it works but is utterly ugly and unreliable
setTimeout(function() {
protexSetTargetShape(target_shape);}, 2000);
}
else {
setTimeout(function() {initializeProtex(); }, timeout);
}
}*/
}).call(this);
(function () {
var timeout = 1000;
function initializeApplet(applet) {
console.log("Initializing " + applet);
waitForApplet(applet);
}
function waitForApplet(applet) {
if (applet.isActive && applet.isActive()) {
console.log("Applet is ready.");
var answerStr = applet.checkAnswer();
console.log(answerStr);
var input = $('.editageneinput input');
console.log(input);
input.val(answerStr);
} else if (timeout > 30 * 1000) {
console.error("Applet did not load on time.");
} else {
console.log("Waiting for applet...");
setTimeout(function() { waitForApplet(applet); }, timeout);
}
}
var applets = $('.editageneinput object');
applets.each(function(i, el) { initializeApplet(el); });
}).call(this);
(function () {
var timeout = 100;
// Simple "lock" to prevent applets from being initialized more than once
if (typeof(_editamolecule_loaded) == 'undefined' || _editamolecule_loaded == false) {
_editamolecule_loaded = true;
loadGWTScripts();
waitForGWT();
} else {
return;
}
function loadScript(src) {
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', src);
$('head')[0].appendChild(script);
}
function loadGWTScripts() {
// The names of the script are split to prevent them from
// being rewritten by LMS. GWT uses the filename of the script
// to find the URL path in which the script lives. If the name
// of the file is changed, GWT won't load correctly
var jsmolcalc_src = '/sta' + 'tic/js/capa/jsmolcalc/jsmolcalc.nocache.js';
var jsme_src = '/sta' + 'tic/js/capa/jsme/jsme_export.nocache.js';
// Make sure we don't request the scripts twice
if (typeof (_jsmolcalc) == 'undefined') {
_jsmolcalc = true;
loadScript(jsmolcalc_src);
}
if (typeof (_jsme) == 'undefined') {
_jsme = true;
loadScript(jsme_src);
}
}
function waitForGWT() {
// jsme and jsmolcalc are not initialized automatically by the GWT
// script loader. To fix this, wait for the scripts to load,
// initialize them manually and wait until they are ready
if (typeof(jsmolcalc) != 'undefined' && jsmolcalc)
{
jsmolcalc.onInjectionDone('jsmolcalc');
}
if (typeof(jsme_export) != 'undefined' && jsme_export)
{
// dummy function called by jsme_export
window.jsmeOnLoad = function() {};
jsme_export.onInjectionDone('jsme_export');
}
// jsmol is defined my jsmolcalc and JavaScriptApplet is defined by jsme
if (typeof(jsmol) != 'undefined' && typeof(JavaScriptApplet) != 'undefined') {
// ready, initialize applets
initializeApplets();
_editamolecule_loaded = false; // for reloading when checking is pressed
} else {
setTimeout(waitForGWT, timeout);
}
}
function initializeApplets() {
var applets = $('.editamoleculeinput div.applet');
applets.each(function(i, element) {
if (!$(element).hasClass('loaded')) {
var applet = new JavaScriptApplet.JSME(
element.id,
$(element).width(),
$(element).height(),
{
"options" : "query, hydrogens"
});
$(element).addClass('loaded');
configureApplet(element, applet);
}
});
}
function configureApplet(element, applet) {
// Traverse up the DOM tree and get the other relevant elements
var parent = $(element).parent();
var input_field = parent.find('input[type=hidden]');
var reset_button = parent.find('button.reset');
var message_field = parent.find('.error_message');
// Applet options
applet.setAntialias(true);
// Load initial data
var value = input_field.val();
if (value) {
var data = JSON.parse(value)["mol"];
loadAppletData(applet, data, input_field);
} else {
requestAppletData(element, applet, input_field);
}
reset_button.on('click', function() {
requestAppletData(element, applet, input_field);
message_field.html('').hide(); // clear messages
});
// Update the input element everytime the is an interaction
// with the applet (click, drag, etc)
$(element).on('mouseup', function() {
var values = updateInput(applet, input_field);
updateMessages(message_field, values);
});
}
function requestAppletData(element, applet, input_field) {
var molFile = $(element).data('molfile-src');
jQuery.ajax({
url: molFile,
dataType: "text",
success: function(data) {
loadAppletData(applet, data, input_field);
},
error: function() {
console.error("Cannot load mol data from: " + molFile);
}
});
}
function loadAppletData(applet, data, input_field) {
applet.readMolFile(data);
updateInput(applet, input_field);
}
function updateInput(applet, input_field) {
var mol = applet.molFile();
var smiles = applet.smiles();
var jme = applet.jmeFile();
var raw_info = jsmol.API.getInfo(mol, smiles, jme).toString();
var info = formatInfo(raw_info);
var error = formatError(raw_info);
var value = { mol: mol, info: info, error: error };
input_field.val(JSON.stringify(value));
return value;
}
function formatInfo(raw_info) {
var results = [];
if (raw_info.search("It is not possible") == -1) {
var fragment = $('<div>').append(raw_info);
fragment.find('font').each(function () {
results.push($(this).html());
});
}
return results;
}
function formatError(raw_info) {
var error = '';
if (raw_info.search("It is not possible") != -1) {
var tags = /<((\/)?\w{1,7})>/g;
error = raw_info.replace(tags, ' ');
}
return error;
}
function updateMessages(message_field, values) {
var error = values['error'];
if (error) {
message_field.html(error).show();
} else {
// Clear messages
message_field.html('').hide();
}
}
}).call(this);
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
.protex-absolute-panel {
background-color: #BBBBBB;
}
.protex-canvas {
outline: none;
}
.protex-caption-panel {
padding: 10px 10px 10px 10px;
margin: 0px 0px 0px 0px;
border:2px solid #4E4E4E;
font: normal normal normal 14pt/normal 'Open Sans', sans-serif;
background-color: #B2B2FF;
}
.protex-textbox {
font: normal normal normal 12pt/normal 'Open Sans', sans-serif !important;
padding: 2px 2px 2px 2px !important;
}
.protex-caption-panel-ss-bonds-on {
padding: 10px 10px 10px 10px;
margin: 0px 0px 0px 0px;
border:2px solid #4E4E4E;
font: normal normal normal 14pt/normal 'Open Sans', sans-serif;
background-color: #B2FFFF;
}
.protex-button {
margin-right: 15px;
}
.protex-listbox {
}
.gwt-ProgressBar-shell {
border: 2px solid #faf9f7;
border-right: 2px solid #848280;
border-bottom: 2px solid #848280;
background-color: #AAAAAA;
height: 14pt;
width: 50%;
margin-left: 15px;
}
.gwt-ProgressBar-shell .gwt-ProgressBar-bar {
background-color: #67A7E3;
}
.gwt-ProgressBar-shell .gwt-ProgressBar-text {
padding: 0px;
margin: 0px;
color: white;
}
function protex(){var P='',xb='" for "gwt:onLoadErrorFn"',vb='" for "gwt:onPropertyErrorFn"',ib='"><\/script>',Z='#',Xb='.cache.html',_='/',lb='//',Qb='39CC89519B0E1FCB47B935AC9FE13D7B',Rb='6E05B1CD5BFCAF7D53C7C64D84318178',Wb=':',pb='::',dc='<script defer="defer">protex.onInjectionDone(\'protex\')<\/script>',hb='<script id="',sb='=',$='?',ub='Bad handler "',Sb='C824A958AB642DC2213DFFDAC640BEAA',Tb='D9267DE8FB02F8B995B4A58C66C76E29',cc='DOMContentLoaded',Ub='F275492F7098103BCB05F4F86ABF6218',Vb='F3301B0E65F38C7FCF2EF3764B3BB0B6',jb='SCRIPT',gb='__gwt_marker_protex',kb='base',cb='baseUrl',T='begin',S='bootstrap',bb='clear.cache.gif',rb='content',Y='end',Kb='gecko',Lb='gecko1_8',U='gwt.codesvr=',V='gwt.hosted=',W='gwt.hybrid',wb='gwt:onLoadErrorFn',tb='gwt:onPropertyErrorFn',qb='gwt:property',bc='head',Ob='hosted.html?protex',ac='href',Jb='ie6',Ib='ie8',Hb='ie9',yb='iframe',ab='img',zb="javascript:''",Zb='link',Nb='loadExternalRefs',mb='meta',Bb='moduleRequested',X='moduleStartup',Gb='msie',nb='name',Db='opera',Ab='position:absolute;width:0;height:0;border:none',Q='protex',Yb='protex.css',eb='protex.nocache.js',ob='protex::',$b='rel',Fb='safari',db='script',Pb='selectingPermutation',R='startup',_b='stylesheet',fb='undefined',Mb='unknown',Cb='user.agent',Eb='webkit';var m=window,n=document,o=m.__gwtStatsEvent?function(a){return m.__gwtStatsEvent(a)}:null,p=m.__gwtStatsSessionId?m.__gwtStatsSessionId:null,q,r,s,t=P,u={},v=[],w=[],x=[],y=0,z,A;o&&o({moduleName:Q,sessionId:p,subSystem:R,evtGroup:S,millis:(new Date).getTime(),type:T});if(!m.__gwt_stylesLoaded){m.__gwt_stylesLoaded={}}if(!m.__gwt_scriptsLoaded){m.__gwt_scriptsLoaded={}}function B(){var b=false;try{var c=m.location.search;return (c.indexOf(U)!=-1||(c.indexOf(V)!=-1||m.external&&m.external.gwtOnLoad))&&c.indexOf(W)==-1}catch(a){}B=function(){return b};return b}
function C(){if(q&&r){var b=n.getElementById(Q);var c=b.contentWindow;if(B()){c.__gwt_getProperty=function(a){return H(a)}}protex=null;c.gwtOnLoad(z,Q,t,y);o&&o({moduleName:Q,sessionId:p,subSystem:R,evtGroup:X,millis:(new Date).getTime(),type:Y})}}
function D(){function e(a){var b=a.lastIndexOf(Z);if(b==-1){b=a.length}var c=a.indexOf($);if(c==-1){c=a.length}var d=a.lastIndexOf(_,Math.min(c,b));return d>=0?a.substring(0,d+1):P}
function f(a){if(a.match(/^\w+:\/\//)){}else{var b=n.createElement(ab);b.src=a+bb;a=e(b.src)}return a}
function g(){var a=F(cb);if(a!=null){return a}return P}
function h(){var a=n.getElementsByTagName(db);for(var b=0;b<a.length;++b){if(a[b].src.indexOf(eb)!=-1){return e(a[b].src)}}return P}
function i(){var a;if(typeof isBodyLoaded==fb||!isBodyLoaded()){var b=gb;var c;n.write(hb+b+ib);c=n.getElementById(b);a=c&&c.previousSibling;while(a&&a.tagName!=jb){a=a.previousSibling}if(c){c.parentNode.removeChild(c)}if(a&&a.src){return e(a.src)}}return P}
function j(){var a=n.getElementsByTagName(kb);if(a.length>0){return a[a.length-1].href}return P}
function k(){var a=n.location;return a.href==a.protocol+lb+a.host+a.pathname+a.search+a.hash}
var l=g();if(l==P){l=h()}if(l==P){l=i()}if(l==P){l=j()}if(l==P&&k()){l=e(n.location.href)}l=f(l);t=l;return l}
function E(){var b=document.getElementsByTagName(mb);for(var c=0,d=b.length;c<d;++c){var e=b[c],f=e.getAttribute(nb),g;if(f){f=f.replace(ob,P);if(f.indexOf(pb)>=0){continue}if(f==qb){g=e.getAttribute(rb);if(g){var h,i=g.indexOf(sb);if(i>=0){f=g.substring(0,i);h=g.substring(i+1)}else{f=g;h=P}u[f]=h}}else if(f==tb){g=e.getAttribute(rb);if(g){try{A=eval(g)}catch(a){alert(ub+g+vb)}}}else if(f==wb){g=e.getAttribute(rb);if(g){try{z=eval(g)}catch(a){alert(ub+g+xb)}}}}}}
function F(a){var b=u[a];return b==null?null:b}
function G(a,b){var c=x;for(var d=0,e=a.length-1;d<e;++d){c=c[a[d]]||(c[a[d]]=[])}c[a[e]]=b}
function H(a){var b=w[a](),c=v[a];if(b in c){return b}var d=[];for(var e in c){d[c[e]]=e}if(A){A(a,d,b)}throw null}
var I;function J(){if(!I){I=true;var a=n.createElement(yb);a.src=zb;a.id=Q;a.style.cssText=Ab;a.tabIndex=-1;n.body.appendChild(a);o&&o({moduleName:Q,sessionId:p,subSystem:R,evtGroup:X,millis:(new Date).getTime(),type:Bb});a.contentWindow.location.replace(t+L)}}
w[Cb]=function(){var b=navigator.userAgent.toLowerCase();var c=function(a){return parseInt(a[1])*1000+parseInt(a[2])};if(function(){return b.indexOf(Db)!=-1}())return Db;if(function(){return b.indexOf(Eb)!=-1}())return Fb;if(function(){return b.indexOf(Gb)!=-1&&n.documentMode>=9}())return Hb;if(function(){return b.indexOf(Gb)!=-1&&n.documentMode>=8}())return Ib;if(function(){var a=/msie ([0-9]+)\.([0-9]+)/.exec(b);if(a&&a.length==3)return c(a)>=6000}())return Jb;if(function(){return b.indexOf(Kb)!=-1}())return Lb;return Mb};v[Cb]={gecko1_8:0,ie6:1,ie8:2,ie9:3,opera:4,safari:5};protex.onScriptLoad=function(){if(I){r=true;C()}};protex.onInjectionDone=function(){q=true;o&&o({moduleName:Q,sessionId:p,subSystem:R,evtGroup:Nb,millis:(new Date).getTime(),type:Y});C()};E();D();var K;var L;if(B()){if(m.external&&(m.external.initModule&&m.external.initModule(Q))){m.location.reload();return}L=Ob;K=P}o&&o({moduleName:Q,sessionId:p,subSystem:R,evtGroup:S,millis:(new Date).getTime(),type:Pb});if(!B()){try{G([Db],Qb);G([Ib],Rb);G([Fb],Sb);G([Hb],Tb);G([Jb],Ub);G([Lb],Vb);K=x[H(Cb)];var M=K.indexOf(Wb);if(M!=-1){y=Number(K.substring(M+1));K=K.substring(0,M)}L=K+Xb}catch(a){return}}var N;function O(){if(!s){s=true;if(!__gwt_stylesLoaded[Yb]){var a=n.createElement(Zb);__gwt_stylesLoaded[Yb]=a;a.setAttribute($b,_b);a.setAttribute(ac,t+Yb);n.getElementsByTagName(bc)[0].appendChild(a)}C();if(n.removeEventListener){n.removeEventListener(cc,O,false)}if(N){clearInterval(N)}}}
if(n.addEventListener){n.addEventListener(cc,function(){J();O()},false)}var N=setInterval(function(){if(/loaded|complete/.test(n.readyState)){J();O()}},50);o&&o({moduleName:Q,sessionId:p,subSystem:R,evtGroup:S,millis:(new Date).getTime(),type:Y});o&&o({moduleName:Q,sessionId:p,subSystem:R,evtGroup:Nb,millis:(new Date).getTime(),type:T});n.write(dc)}
protex();
\ No newline at end of file
......@@ -81,6 +81,9 @@ MITX_FEATURES = {
'AUTH_USE_OPENID': False,
'AUTH_USE_MIT_CERTIFICATES': False,
'AUTH_USE_OPENID_PROVIDER': False,
# Flip to True when the YouTube iframe API breaks (again)
'USE_YOUTUBE_OBJECT_API': False,
}
# Used for A/B testing
......
(Originally written by Ike.)
At a high level, the main challenges of checking symbolic math expressions are (1) making sure the expression is mathematically legal, and (2) simplifying the expression for comparison with what is expected.
(1) Generation (and testing) of legal input is done by using MathJax to provide input math in an XML format known as Presentation MathML (PMathML). Such expressions typeset correctly, but may not be mathematically legal, like "5 / (1 = 2)". The PMathML is converted into "Content MathML" (CMathML), which is by definition mathematically legal, using an XSLT 2.0 stylesheet, via a module in SnuggleTeX. CMathML is then converted into a sympy expression. This work is all done in `lms/lib/symmath/formula.py`
(2) Simplifying the expression and checking against what is expected is done by using sympy, and a set of heuristics based on options flags provided by the problem author. For example, the problem author may specify that the expected expression is a matrix, in which case the dimensionality of the input expression is checked. Other options include specifying that the comparison be checked numerically in addition to symbolically. The checking is done in stages, first with no simplification, then with increasing levels of testing; if a match is found at any stage, then an "ok" is returned. Helpful messages are also returned, eg if the input expression is of a different type than the expected. This work is all done in `lms/lib/symmath/symmath_check.py`
Links:
SnuggleTex: http://www2.ph.ed.ac.uk/snuggletex/documentation/overview-and-features.html
MathML: http://www.w3.org/TR/MathML2/overview.html
SymPy: http://sympy.org/en/index.html
......@@ -21,7 +21,7 @@
<div class="input-group">
% if has_extauth_info is UNDEFINED:
<label data-field="email">E-mail*</label>
<input name="email" type="email" placeholder="eg. example@edx.org">
<input name="email" type="email" placeholder="eg. yourname@domain.com">
<label data-field="password">Password*</label>
<input name="password" type="password" placeholder="****">
<label data-field="username">Public Username*</label>
......
......@@ -2,9 +2,19 @@
<h2> ${display_name} </h2>
% endif
%if settings.MITX_FEATURES['STUB_VIDEO_FOR_TESTING']:
<div id="stub_out_video_for_testing"></div>
%elif settings.MITX_FEATURES.get('USE_YOUTUBE_OBJECT_API') and normal_speed_video_id:
<object width="640" height="390">
<param name="movie"
value="https://www.youtube.com/v/${normal_speed_video_id}?version=3&amp;autoplay=1&amp;rel=0"></param>
<param name="allowScriptAccess" value="always"></param>
<embed src="https://www.youtube.com/v/${normal_speed_video_id}?version=3&amp;autoplay=1&amp;rel=0"
type="application/x-shockwave-flash"
allowscriptaccess="always"
width="640" height="390"></embed>
</object>
%else:
<div id="video_${id}" class="video" data-streams="${streams}" data-caption-data-dir="${data_dir}" data-show-captions="${show_captions}" data-start="${start}" data-end="${end}" data-caption-asset-path="${caption_asset_path}">
<div class="tc-wrapper">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment