Commit bced905a by Chris Rossi

Copy jsdraw xblock, file off serial numbers.

parents
.*.swp
*.pyc
*.egg-info
.coverage
==========
edX JSDraw
==========
`edx-jsdraw` is an add on component for the `edX platform
<https://github.com/edx/edx-platform>`_ which provides a new problem type:
'JSDraw Input'. This problem type integrates the `JSDraw molecule editor
<http://www.scilligence.com/web/jsdraw.aspx>`_ seamlessly into an edX course.
`JSDraw` is not FOSS--it does require a license. Scilligence has provided a
license for edx.org to use this component, but it may not be used by other
parties without a valid license. As such, this repository is private and should
only be installed by edx.org.
Usage
-----
When `edx-jsdraw` is installed, a new problem type, 'JSDraw Input', is available
under 'Advanced'. Once a 'JSDraw Input' problem is added to a course, the
problem template itself contains instructions for editing.
Installation
------------
This package is installed on the Python path in the normal way, eg. via a pip
requirements file. Then you just need to add this app to `INSTALLED_APPS` for
both STUDIO and the LMS::
# Add JSDraw problem type
INSTALLED_APPS += ('edx_jsdraw',)
That's it! Pretty easy.
"""
Adds input and response types for JSDraw problems.
"""
import json
import re
from capa import inputtypes, responsetypes
from capa.correctmap import CorrectMap
@inputtypes.registry.register
class JSDrawInput(inputtypes.InputTypeBase):
"""
A JSDraw input type.
"""
template = 'jsdrawinput.html'
tags = ['jsdraw']
def setup(self):
if self.value:
stored_state = json.loads(json.loads(self.value)['state'])
else:
stored_state = {}
answer = None
state = stored_state.get('state')
dirty = False
for child in self.xml:
if not child.text.strip():
continue
if child.tag == 'initial-state' and state is None:
stored_state['state'] = self.read_molfile(child.text)
dirty = True
elif child.tag == 'answer':
answer = self.read_molfile(child.text)
if answer != stored_state.get('answer'):
stored_state['answer'] = answer
dirty = True
if not answer:
raise ValueError("Must provide an answer.")
if dirty:
self.value = json.dumps(
{'answer': '', 'state': json.dumps(stored_state)});
def read_molfile(self, text):
lines = filter(None, text.split('\n'))
padding = re.search(r"\S", lines[1]).start() - 1
return '\n'.join(
(lines[0],) + tuple(line[padding:] for line in lines[1:]))
def _extra_context(self):
static_url = self.capa_system.STATIC_URL
return {
'html_file': static_url + 'jsdraw_frame.html',
'params': None,
'gradefn': 'getGrade',
'get_statefn': 'getState',
'set_statefn': 'setState',
'width': '700',
'height': '350',
'jschannel_loader': '{static_url}js/capa/src/jschannel.js'.format(
static_url=static_url),
'jsinput_loader': '{static_url}js/capa/src/jsinput.js'.format(
static_url=static_url),
'saved_state': self.value
}
@responsetypes.registry.register
class JSDrawResponse(responsetypes.LoncapaResponse):
"""
A JSDraw response type.
"""
allowed_inputfields = ['jsdraw']
required_attributes = []
tags = ['jsdrawresponse']
max_inputfields = 1
correct_answer = []
def get_answers(self):
elements = self.xml.xpath("./answer")
if elements:
answer = elements[0].text.strip()
else:
answer = ''
return {self.answer_id: answer}
def get_score(self, student_answers):
graded_answer = json.loads(
student_answers[self.answer_id].strip())['answer']
return CorrectMap(self.answer_id, graded_answer)
"""
Django initialization.
"""
from edxmako import add_lookup
from xmodule.x_module import ResourceTemplates
def run():
"""
Add our templates to the Django site.
"""
# Add our problem boiler plate templates
ResourceTemplates.template_packages.append(__name__)
# Add our mako templates
add_lookup('main', 'templates', __name__) # For LMS
add_lookup('lms.main', 'templates', __name__) # For CMS
<!DOCTYPE html>
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js" type="text/javascript"></script>
<script type="text/javascript" src='vendor/JSDraw3.1.2/Scilligence.JSDraw2.Pro.js'></script>
<script src="js/capa/src/jschannel.js"></script>
</head>
<body>
<div class="JSDraw" id="jsdraw" skin="w8"
style="width: 660px; height: 300px;border:1px solid gray" />
<script type="text/javascript">
JSDraw.init();
var editor = null;
var correct_answer = null;
var state = null;
function getState() {
if (editor) return JSON.stringify(
{ state: editor.getMolfile(), answer: correct_answer });
}
function setState(data) {
data = JSON.parse(data);
correct_answer = data.answer;
state = data.state;
if (editor) editor.setMolfile(state);
}
function getGrade() {
var expected = new JSDraw2.Mol();
var response = new JSDraw2.Mol();
expected.setMolfile(correct_answer);
response.setMolfile(editor.getMolfile());
return expected.fullstructureMatch(response) ?
'correct': 'incorrect';
}
window.onload = function() {
editor = JSDraw.get("jsdraw");
if (state) editor.setMolfile(state);
}
/* Establish a channel only if this application is embedded in an iframe.
This will let the parent window communicate with this application using
RPC and bypass SOP restrictions.*/
if (window.parent !== window) {
channel = Channel.build({
window: window.parent,
origin: "*",
scope: "JSInput"
});
channel.bind("getGrade", function(trans, params) {
return getGrade(params)
});
channel.bind("getState", function(trans, params) {
return getState(params);
});
channel.bind("setState", function(trans, params) {
return setState(params)
});
}
</script>
</body>
</html>
<section id="inputtype_${id}" class="capa_inputtype textline" >
<!--div class="script_placeholder"
data-src="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js"/-->
<!--div class="script_placeholder" data-src="${STATIC_URL}fu.js"/-->
<div class="script_placeholder" data-src="${STATIC_URL}js/jsdraw-3.1.2.js"/>
<input type="hidden" name="input_${id}" id="input_${id}"
aria-describedby="answer_${id}" value="${value|h}" />
<div class='JSDraw' skin="w8" id="jsdraw_${id}"
style="width: 660px; height: 300px;border:1px solid gray"
ondatachange="molchange"></div>
<p class="status" aria-describedby="input_${id}">
% 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 msg:
<span class="message">${msg|n}</span>
% endif
</section>
<section id="inputtype_${id}" class="jsinput"
data="${gradefn}"
% if saved_state:
data-stored="${saved_state|x}"
% endif
% if get_statefn:
data-getstate="${get_statefn}"
% endif
% if set_statefn:
data-setstate="${set_statefn}"
% endif
data-sop="false"
data-processed="false"
>
<div class="script_placeholder" data-src="${jschannel_loader}"/>
<div class="script_placeholder" data-src="${jsinput_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
<iframe name="iframe_${id}"
id="iframe_${id}"
sandbox="allow-scripts allow-popups allow-same-origin allow-forms allow-pointer-lock"
seamless="seamless"
frameborder="0"
src="${html_file}"
height="${height}"
width="${width}"
/>
<input type="hidden" name="input_${id}" id="input_${id}"
waitfor=""
value="${value|h}"/>
<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
% if msg:
<span class="message">${msg|n}</span>
% endif
</section>
---
metadata:
display_name: JSDraw Input
markdown: !!null
data: |
<problem>
<p>
A JSDraw problem lets the user use the JSDraw editor component to draw a
new molecule or update an existing drawing and then submit their work.
Answers are specified as SMILES strings.
</p>
<p>
I was trying to draw my favorite molecule, caffeine. Unfortunately,
I'm not a very good biochemist. Can you correct my molecule?
</p>
<jsdrawresponse>
<jsdraw>
<!-- You can set initial state of the editor by including a molfile
inside of the jsdraw tag. Take care that indentation is preserved.
The molfile format is sensitive to whitespace. -->
<initial-state>
JSDraw201081410342D
12 13 0 0 0 0 0 V2000
12.0000 -6.7600 0.0000 N 0 0 0 0 0 0 0
10.6490 -5.9800 0.0000 C 0 0 0 0 0 0 0
10.6490 -4.4200 0.0000 N 0 0 0 0 0 0 0
12.0000 -3.6400 0.0000 C 0 0 0 0 0 0 0
13.3510 -4.4200 0.0000 C 0 0 0 0 0 0 0
13.3510 -5.9800 0.0000 C 0 0 0 0 0 0 0
14.8347 -6.4620 0.0000 N 0 0 0 0 0 0 0
15.7515 -5.1998 0.0000 C 0 0 0 0 0 0 0
14.8346 -3.9379 0.0000 N 0 0 0 0 0 0 0
15.3166 -2.4542 0.0000 C 0 0 0 0 0 0 0
9.2980 -3.6400 0.0000 C 0 0 0 0 0 0 0
9.2980 -6.7600 0.0000 O 0 0 0 0 0 0 0
1 2 1 0 0 0 0
2 3 1 0 0 0 0
3 4 1 0 0 0 0
4 5 1 0 0 0 0
5 6 2 0 0 0 0
6 1 1 0 0 0 0
6 7 1 0 0 0 0
7 8 1 0 0 0 0
8 9 1 0 0 0 0
9 5 1 0 0 0 0
9 10 1 0 0 0 0
3 11 1 0 0 0 0
2 12 1 0 0 0 0
M END
</initial-state>
<!-- The answer is also in molfile format. Take care that
indentation is preserved. The molfile format is sensitive to
whitespace. -->
<answer>
JSDraw203201413042D
14 15 0 0 0 0 0 V2000
12.9049 -6.2400 0.0000 N 0 0 0 0 0 0 0
11.5539 -5.4600 0.0000 C 0 0 0 0 0 0 0
11.5539 -3.9000 0.0000 N 0 0 0 0 0 0 0
12.9049 -3.1200 0.0000 C 0 0 0 0 0 0 0
14.2558 -3.9000 0.0000 C 0 0 0 0 0 0 0
14.2558 -5.4600 0.0000 C 0 0 0 0 0 0 0
15.7395 -5.9420 0.0000 N 0 0 0 0 0 0 0
16.6563 -4.6798 0.0000 C 0 0 0 0 0 0 0
15.7394 -3.4179 0.0000 N 0 0 0 0 0 0 0
16.2214 -1.9342 0.0000 C 0 0 0 0 0 0 0
10.2029 -3.1200 0.0000 C 0 0 0 0 0 0 0
10.2029 -6.2400 0.0000 O 0 0 0 0 0 0 0
12.9049 -7.8000 0.0000 C 0 0 0 0 0 0 0
12.9050 -1.5600 0.0000 O 0 0 0 0 0 0 0
1 2 1 0 0 0 0
2 3 1 0 0 0 0
3 4 1 0 0 0 0
4 5 1 0 0 0 0
5 6 2 0 0 0 0
6 1 1 0 0 0 0
6 7 1 0 0 0 0
7 8 2 0 0 0 0
8 9 1 0 0 0 0
9 5 1 0 0 0 0
9 10 1 0 0 0 0
3 11 1 0 0 0 0
2 12 2 0 0 0 0
1 13 1 0 0 0 0
4 14 2 0 0 0 0
M END
</answer>
</jsdraw>
<!-- This answer is shown to the student when they click the
"Show Answer" button in the UI. It is not used for grading and
should be human readable. -->
<answer>C8H10N4O2</answer>
</jsdrawresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>
Some scholars have hypothesized that the renaissance was made possible
by the introduction of coffee to Italy. Likewise scholars have linked
the Enlightenment with the rise of coffee houses in England.
</p>
</div>
</solution>
</problem>
"""
Tests for JSDraw problem type.
"""
import json
import mock
import unittest
from lxml import etree
from . import JSDrawInput, JSDrawResponse
class JSDrawInputTests(unittest.TestCase):
"""
Test JSDrawInput
"""
xml = etree.fromstring("""
<jsdraw id="foo">
Some molfile
Testing
1 2 3
</jsdraw>
""")
def test_setup(self):
"""
Test that molfile data is extracted properly from XML.
"""
system = mock.Mock()
obj = JSDrawInput(system, self.xml, {})
obj.setup()
value = json.loads(obj.value)
self.assertEqual(value['answer'], '')
self.assertEqual(value['state'], u'Some molfile\n Testing\n 1 2 3\n')
def test_setup_blank_molfile_with_whitespace(self):
"""
Test that molfile data that is empty except for whitespace is properly
parsed.
"""
system = mock.Mock()
xml = etree.fromstring('<jsdraw id="foo"> </jsdraw>')
obj = JSDrawInput(system, xml, {})
obj.setup()
self.assertEqual(obj.value, '')
def test_extra_context(self):
"""
Test that _extra_context is properly populated.
"""
system = mock.Mock(STATIC_URL='/static/')
obj = JSDrawInput(system, self.xml, {})
obj.setup()
self.assertEqual(obj._extra_context(), {
'set_statefn': 'setMolfile',
'height': '350',
'html_file': '/static/jsdraw_frame.html',
'get_statefn': 'getMolfile',
'gradefn': 'getSmiles',
'jsinput_loader': '/static/js/capa/src/jsinput.js',
'saved_state': '{"answer": "", "state": "Some molfile\\n '
'Testing\\n 1 2 3\\n"}',
'width': '700',
'params': None,
'jschannel_loader': '/static/js/capa/src/jschannel.js',
'sop': None})
class JSDrawResponseTests(unittest.TestCase):
"""
Test JSDrawResponse
"""
xml = etree.fromstring("""
<jsdrawresponse>
Foo
<answer>One</answer>
<answer>Two</answer>
</jsdrawresponse>
""")
def test_setup_response(self):
"""
Test method `setup_response`.
"""
system = mock.Mock()
input_fields = [JSDrawInputTests.xml]
obj = JSDrawResponse(self.xml, input_fields, {}, system)
obj.setup_response()
self.assertEqual(obj.correct_answer, ['One', 'Two'])
def test_check_string(self):
"""
Test method `check_string`.
"""
system = mock.Mock()
input_fields = [JSDrawInputTests.xml]
obj = JSDrawResponse(self.xml, input_fields, {}, system)
self.assertTrue(obj.check_string(['One', 'Two'], '{"answer": "One"}'))
self.assertFalse(
obj.check_string(['One', 'Two'], '{"answer": "Three"}'))
from setuptools import setup, find_packages
setup(
name="edx-jsme",
version="1.0",
packages=find_packages(exclude=["tests"]),
install_requires=[
'XModule',
],
)
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