Commit be369938 by Rocky Duan

Merge branch 'master' of github.com:MITx/mitx

parents 2b89a82a 703c7a95
...@@ -39,7 +39,7 @@ import responsetypes ...@@ -39,7 +39,7 @@ import responsetypes
# dict of tagname, Response Class -- this should come from auto-registering # dict of tagname, Response Class -- this should come from auto-registering
response_tag_dict = dict([(x.response_tag,x) for x in responsetypes.__all__]) response_tag_dict = dict([(x.response_tag,x) for x in responsetypes.__all__])
entry_types = ['textline', 'schematic', 'choicegroup', 'textbox', 'imageinput', 'optioninput'] entry_types = ['textline', 'schematic', 'textbox', 'imageinput', 'optioninput', 'choicegroup', 'radiogroup', 'checkboxgroup']
solution_types = ['solution'] # extra things displayed after "show answers" is pressed solution_types = ['solution'] # extra things displayed after "show answers" is pressed
response_properties = ["responseparam", "answer"] # these get captured as student responses response_properties = ["responseparam", "answer"] # these get captured as student responses
...@@ -118,6 +118,9 @@ class LoncapaProblem(object): ...@@ -118,6 +118,9 @@ class LoncapaProblem(object):
# the dict has keys = xml subtree of Response, values = Response instance # the dict has keys = xml subtree of Response, values = Response instance
self._preprocess_problem(self.tree) self._preprocess_problem(self.tree)
if not self.student_answers: # True when student_answers is an empty dict
self.set_initial_display()
def do_reset(self): def do_reset(self):
''' '''
Reset internal state to unfinished, with no answers Reset internal state to unfinished, with no answers
...@@ -126,6 +129,14 @@ class LoncapaProblem(object): ...@@ -126,6 +129,14 @@ class LoncapaProblem(object):
self.correct_map = CorrectMap() self.correct_map = CorrectMap()
self.done = False self.done = False
def set_initial_display(self):
initial_answers = dict()
for responder in self.responders.values():
if hasattr(responder,'get_initial_display'):
initial_answers.update(responder.get_initial_display())
self.student_answers = initial_answers
def __unicode__(self): def __unicode__(self):
return u"LoncapaProblem ({0})".format(self.problem_id) return u"LoncapaProblem ({0})".format(self.problem_id)
...@@ -180,14 +191,31 @@ class LoncapaProblem(object): ...@@ -180,14 +191,31 @@ class LoncapaProblem(object):
return {'score': correct, return {'score': correct,
'total': self.get_max_score()} 'total': self.get_max_score()}
def update_score(self, score_msg): def update_score(self, score_msg, queuekey):
newcmap = CorrectMap() '''
Deliver grading response (e.g. from async code checking) to
the specific ResponseType that requested grading
Returns an updated CorrectMap
'''
cmap = CorrectMap()
cmap.update(self.correct_map)
for responder in self.responders.values(): for responder in self.responders.values():
if hasattr(responder,'update_score'): # Is this the best way to implement 'update_score' for CodeResponse? if hasattr(responder,'update_score'):
results = responder.update_score(score_msg) # Each LoncapaResponse will update the specific entries of 'cmap' that it's responsible for
newcmap.update(results) cmap = responder.update_score(score_msg, cmap, queuekey)
self.correct_map = newcmap self.correct_map.set_dict(cmap.get_dict())
return newcmap return cmap
def is_queued(self):
'''
Returns True if any part of the problem has been submitted to an external queue
'''
queued = False
for answer_id in self.correct_map:
if self.correct_map.is_queued(answer_id):
queued = True
return queued
def grade_answers(self, answers): def grade_answers(self, answers):
''' '''
...@@ -457,7 +485,7 @@ class LoncapaProblem(object): ...@@ -457,7 +485,7 @@ class LoncapaProblem(object):
self.responder_answers = {} self.responder_answers = {}
for response in self.responders.keys(): for response in self.responders.keys():
try: try:
self.responder_answers[response] = responder.get_answers() self.responder_answers[response] = self.responders[response].get_answers()
except: except:
log.debug('responder %s failed to properly return get_answers()' % self.responders[response]) # FIXME log.debug('responder %s failed to properly return get_answers()' % self.responders[response]) # FIXME
raise raise
......
...@@ -14,6 +14,7 @@ class CorrectMap(object): ...@@ -14,6 +14,7 @@ class CorrectMap(object):
- msg : string (may have HTML) giving extra message response (displayed below textline or textbox) - msg : string (may have HTML) giving extra message response (displayed below textline or textbox)
- hint : string (may have HTML) giving optional hint (displayed below textline or textbox, above msg) - hint : string (may have HTML) giving optional hint (displayed below textline or textbox, above msg)
- hintmode : one of (None,'on_request','always') criteria for displaying hint - hintmode : one of (None,'on_request','always') criteria for displaying hint
- queuekey : a random integer for xqueue_callback verification
Behaves as a dict. Behaves as a dict.
''' '''
...@@ -29,13 +30,14 @@ class CorrectMap(object): ...@@ -29,13 +30,14 @@ class CorrectMap(object):
def __iter__(self): def __iter__(self):
return self.cmap.__iter__() return self.cmap.__iter__()
def set(self, answer_id=None, correctness=None, npoints=None, msg='', hint='', hintmode=None): def set(self, answer_id=None, correctness=None, npoints=None, msg='', hint='', hintmode=None, queuekey=None):
if answer_id is not None: if answer_id is not None:
self.cmap[answer_id] = {'correctness': correctness, self.cmap[answer_id] = {'correctness': correctness,
'npoints': npoints, 'npoints': npoints,
'msg': msg, 'msg': msg,
'hint' : hint, 'hint' : hint,
'hintmode' : hintmode, 'hintmode' : hintmode,
'queuekey' : queuekey,
} }
def __repr__(self): def __repr__(self):
...@@ -63,6 +65,12 @@ class CorrectMap(object): ...@@ -63,6 +65,12 @@ class CorrectMap(object):
if answer_id in self.cmap: return self.cmap[answer_id]['correctness'] == 'correct' if answer_id in self.cmap: return self.cmap[answer_id]['correctness'] == 'correct'
return None return None
def is_queued(self,answer_id):
return answer_id in self.cmap and self.cmap[answer_id]['queuekey'] is not None
def is_right_queuekey(self, answer_id, test_key):
return answer_id in self.cmap and self.cmap[answer_id]['queuekey'] == test_key
def get_npoints(self,answer_id): def get_npoints(self,answer_id):
if self.is_correct(answer_id): if self.is_correct(answer_id):
npoints = self.cmap[answer_id].get('npoints',1) # default to 1 point if correct npoints = self.cmap[answer_id].get('npoints',1) # default to 1 point if correct
......
...@@ -8,7 +8,9 @@ Module containing the problem elements which render into input objects ...@@ -8,7 +8,9 @@ Module containing the problem elements which render into input objects
- textline - textline
- textbox (change this to textarea?) - textbox (change this to textarea?)
- schemmatic - schemmatic
- choicegroup (for multiplechoice: checkbox, radio, or select option) - choicegroup
- radiogroup
- checkboxgroup
- imageinput (for clickable image) - imageinput (for clickable image)
- optioninput (for option list) - optioninput (for option list)
...@@ -132,7 +134,8 @@ def optioninput(element, value, status, render_template, msg=''): ...@@ -132,7 +134,8 @@ def optioninput(element, value, status, render_template, msg=''):
oset = [x[1:-1] for x in list(oset)] oset = [x[1:-1] for x in list(oset)]
# osetdict = dict([('option_%s_%s' % (eid,x),oset[x]) for x in range(len(oset)) ]) # make dict with IDs # osetdict = dict([('option_%s_%s' % (eid,x),oset[x]) for x in range(len(oset)) ]) # make dict with IDs
osetdict = dict([(oset[x],oset[x]) for x in range(len(oset)) ]) # make dict with key,value same osetdict = [(oset[x],oset[x]) for x in range(len(oset)) ] # make ordered list with (key,value) same
# TODO: allow ordering to be randomized
context={'id':eid, context={'id':eid,
'value':value, 'value':value,
...@@ -145,6 +148,9 @@ def optioninput(element, value, status, render_template, msg=''): ...@@ -145,6 +148,9 @@ def optioninput(element, value, status, render_template, msg=''):
return etree.XML(html) return etree.XML(html)
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function @register_render_function
def choicegroup(element, value, status, render_template, msg=''): def choicegroup(element, value, status, render_template, msg=''):
''' '''
...@@ -160,7 +166,7 @@ def choicegroup(element, value, status, render_template, msg=''): ...@@ -160,7 +166,7 @@ def choicegroup(element, value, status, render_template, msg=''):
type="checkbox" type="checkbox"
else: else:
type="radio" type="radio"
choices={} choices=[]
for choice in element: for choice in element:
if not choice.tag=='choice': if not choice.tag=='choice':
raise Exception("[courseware.capa.inputtypes.choicegroup] Error only <choice> tags should be immediate children of a <choicegroup>, found %s instead" % choice.tag) raise Exception("[courseware.capa.inputtypes.choicegroup] Error only <choice> tags should be immediate children of a <choicegroup>, found %s instead" % choice.tag)
...@@ -168,8 +174,66 @@ def choicegroup(element, value, status, render_template, msg=''): ...@@ -168,8 +174,66 @@ def choicegroup(element, value, status, render_template, msg=''):
ctext += ''.join([etree.tostring(x) for x in choice]) # TODO: what if choice[0] has math tags in it? ctext += ''.join([etree.tostring(x) for x in choice]) # TODO: what if choice[0] has math tags in it?
if choice.text is not None: if choice.text is not None:
ctext += choice.text # TODO: fix order? ctext += choice.text # TODO: fix order?
choices[choice.get("name")] = ctext choices.append((choice.get("name"),ctext))
context={'id':eid, 'value':value, 'state':status, 'type':type, 'choices':choices} context={'id':eid, 'value':value, 'state':status, 'input_type':type, 'choices':choices, 'inline':True, 'name_array_suffix':''}
html = render_template("choicegroup.html", context)
return etree.XML(html)
#-----------------------------------------------------------------------------
def extract_choices(element):
'''
Extracts choices for a few input types, such as radiogroup and
checkboxgroup.
TODO: allow order of choices to be randomized, following lon-capa spec. Use "location" attribute,
ie random, top, bottom.
'''
choices = []
for choice in element:
if not choice.tag=='choice':
raise Exception("[courseware.capa.inputtypes.extract_choices] \
Expected a <choice> tag; got %s instead"
% choice.tag)
choice_text = ''.join([etree.tostring(x) for x in choice])
choices.append((choice.get("name"), choice_text))
return choices
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function
def radiogroup(element, value, status, render_template, msg=''):
'''
Radio button inputs: (multiple choice)
'''
eid=element.get('id')
choices = extract_choices(element)
context = { 'id':eid, 'value':value, 'state':status, 'input_type': 'radio', 'choices':choices, 'inline': False, 'name_array_suffix': '[]' }
html = render_template("choicegroup.html", context)
return etree.XML(html)
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function
def checkboxgroup(element, value, status, render_template, msg=''):
'''
Checkbox inputs: (select one or more choices)
'''
eid=element.get('id')
choices = extract_choices(element)
context = { 'id':eid, 'value':value, 'state':status, 'input_type': 'checkbox', 'choices':choices, 'inline': False, 'name_array_suffix': '[]' }
html = render_template("choicegroup.html", context) html = render_template("choicegroup.html", context)
return etree.XML(html) return etree.XML(html)
......
<form class="multiple-choice"> <form class="choicegroup">
% for choice_id, choice_description in choices.items(): % for choice_id, choice_description in choices:
<label for="input_${id}_${choice_id}"> <input type="${type}" name="input_${id}" id="input_${id}_${choice_id}" value="${choice_id}" <label for="input_${id}_${choice_id}"> <input type="${input_type}" name="input_${id}${name_array_suffix}" id="input_${id}_${choice_id}" value="${choice_id}"
% if choice_id in value: % if choice_id in value:
checked="true" checked="true"
% endif % endif
/> ${choice_description} </label> /> ${choice_description} </label>
% if not inline:
<br/>
% endif
% endfor % endfor
<span id="answer_${id}"></span> <span id="answer_${id}"></span>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<select name="input_${id}" id="input_${id}" > <select name="input_${id}" id="input_${id}" >
<option value="option_${id}_dummy_default"> </option> <option value="option_${id}_dummy_default"> </option>
% for option_id, option_description in options.items(): % for option_id, option_description in options:
<option value="${option_id}" <option value="${option_id}"
% if (option_id==value): % if (option_id==value):
selected="true" selected="true"
......
...@@ -13,6 +13,7 @@ import numpy ...@@ -13,6 +13,7 @@ import numpy
import xmodule import xmodule
import capa.calc as calc import capa.calc as calc
import capa.capa_problem as lcp import capa.capa_problem as lcp
from capa.correctmap import CorrectMap
from xmodule import graders, x_module from xmodule import graders, x_module
from xmodule.graders import Score, aggregate_scores from xmodule.graders import Score, aggregate_scores
from xmodule.progress import Progress from xmodule.progress import Progress
...@@ -271,6 +272,86 @@ class StringResponseWithHintTest(unittest.TestCase): ...@@ -271,6 +272,86 @@ class StringResponseWithHintTest(unittest.TestCase):
self.assertEquals(cmap.get_correctness('1_2_1'), 'incorrect') self.assertEquals(cmap.get_correctness('1_2_1'), 'incorrect')
self.assertTrue('St. Paul' in cmap.get_hint('1_2_1')) self.assertTrue('St. Paul' in cmap.get_hint('1_2_1'))
class CodeResponseTest(unittest.TestCase):
'''
Test CodeResponse
'''
def test_update_score(self):
problem_file = os.path.dirname(__file__)+"/test_files/coderesponse.xml"
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
# CodeResponse requires internal CorrectMap state. Build it now in the 'queued' state
old_cmap = CorrectMap()
answer_ids = sorted(test_lcp.get_question_answers().keys())
numAnswers = len(answer_ids)
for i in range(numAnswers):
old_cmap.update(CorrectMap(answer_id=answer_ids[i], queuekey=1000+i))
# Message format inherited from ExternalResponse
correct_score_msg = "<edxgrade><awarddetail>EXACT_ANS</awarddetail><message>MESSAGE</message></edxgrade>"
incorrect_score_msg = "<edxgrade><awarddetail>WRONG_FORMAT</awarddetail><message>MESSAGE</message></edxgrade>"
xserver_msgs = {'correct': correct_score_msg,
'incorrect': incorrect_score_msg,
}
# Incorrect queuekey, state should not be updated
for correctness in ['correct', 'incorrect']:
test_lcp.correct_map = CorrectMap()
test_lcp.correct_map.update(old_cmap) # Deep copy
test_lcp.update_score(xserver_msgs[correctness], queuekey=0)
self.assertEquals(test_lcp.correct_map.get_dict(), old_cmap.get_dict()) # Deep comparison
for i in range(numAnswers):
self.assertTrue(test_lcp.correct_map.is_queued(answer_ids[i])) # Should be still queued, since message undelivered
# Correct queuekey, state should be updated
for correctness in ['correct', 'incorrect']:
for i in range(numAnswers): # Target specific answer_id's
test_lcp.correct_map = CorrectMap()
test_lcp.correct_map.update(old_cmap)
new_cmap = CorrectMap()
new_cmap.update(old_cmap)
new_cmap.set(answer_id=answer_ids[i], correctness=correctness, msg='MESSAGE', queuekey=None)
test_lcp.update_score(xserver_msgs[correctness], queuekey=1000+i)
self.assertEquals(test_lcp.correct_map.get_dict(), new_cmap.get_dict())
for j in range(numAnswers):
if j == i:
self.assertFalse(test_lcp.correct_map.is_queued(answer_ids[j])) # Should be dequeued, message delivered
else:
self.assertTrue(test_lcp.correct_map.is_queued(answer_ids[j])) # Should be queued, message undelivered
class ChoiceResponseTest(unittest.TestCase):
def test_cr_rb_grade(self):
problem_file = os.path.dirname(__file__)+"/test_files/choiceresponse_radio.xml"
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
correct_answers = {'1_2_1':'choice_2',
'1_3_1':['choice_2', 'choice_3']}
test_answers = {'1_2_1':'choice_2',
'1_3_1':'choice_2',
}
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct')
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_1'), 'incorrect')
def test_cr_cb_grade(self):
problem_file = os.path.dirname(__file__)+"/test_files/choiceresponse_checkbox.xml"
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
correct_answers = {'1_2_1':'choice_2',
'1_3_1':['choice_2', 'choice_3'],
'1_4_1':['choice_2', 'choice_3']}
test_answers = {'1_2_1':'choice_2',
'1_3_1':'choice_2',
'1_4_1':['choice_2', 'choice_3'],
}
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct')
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_1'), 'incorrect')
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_4_1'), 'correct')
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# Grading tests # Grading tests
......
<problem>
<choiceresponse>
<checkboxgroup>
<choice correct="false">
<startouttext />This is foil One.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Two.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Three.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Four.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Five.<endouttext />
</choice>
</checkboxgroup>
</choiceresponse>
<choiceresponse>
<checkboxgroup>
<choice correct="false">
<startouttext />This is foil One.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Two.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Three.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Four.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Five.<endouttext />
</choice>
</checkboxgroup>
</choiceresponse>
<choiceresponse>
<checkboxgroup>
<choice correct="false">
<startouttext />This is foil One.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Two.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Three.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Four.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Five.<endouttext />
</choice>
</checkboxgroup>
</choiceresponse>
</problem>
<problem>
<choiceresponse>
<radiogroup>
<choice correct="false">
<startouttext />This is foil One.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Two.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Three.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Four.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Five.<endouttext />
</choice>
</radiogroup>
</choiceresponse>
<choiceresponse>
<radiogroup>
<choice correct="false">
<startouttext />This is foil One.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Two.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Three.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Four.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Five.<endouttext />
</choice>
</radiogroup>
</choiceresponse>
</problem>
<problem>
<text>
<h2>Code response</h2>
<p>
</p>
<text>
Write a program to compute the square of a number
<coderesponse tests="repeat:2,generate">
<textbox rows="10" cols="70" mode="python"/>
<answer><![CDATA[
initial_display = """
def square(n):
"""
answer = """
def square(n):
return n**2
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testSquare(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: square(%d)'%n
return str(square(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testSquare(0))
elif test == 2: f.write(testSquare(1))
else: f.write(testSquare())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
</coderesponse>
</text>
<text>
Write a program to compute the cube of a number
<coderesponse tests="repeat:2,generate">
<textbox rows="10" cols="70" mode="python"/>
<answer><![CDATA[
initial_display = """
def cube(n):
"""
answer = """
def cube(n):
return n**3
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testCube(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: cube(%d)'%n
return str(cube(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testCube(0))
elif test == 2: f.write(testCube(1))
else: f.write(testCube())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
</coderesponse>
</text>
</text>
</problem>
...@@ -323,8 +323,18 @@ class CapaModule(XModule): ...@@ -323,8 +323,18 @@ class CapaModule(XModule):
raise self.system.exception404 raise self.system.exception404
def update_score(self, get): def update_score(self, get):
"""
Delivers grading response (e.g. from asynchronous code checking) to
the capa problem, so its score can be updated
'get' must have a field 'response' which is a string that contains the
grader's response
No ajax return is needed. Return empty dict.
"""
queuekey = get['queuekey']
score_msg = get['response'] score_msg = get['response']
self.lcp.update_score(score_msg) self.lcp.update_score(score_msg, queuekey)
return dict() # No AJAX return is needed return dict() # No AJAX return is needed
...@@ -361,7 +371,16 @@ class CapaModule(XModule): ...@@ -361,7 +371,16 @@ class CapaModule(XModule):
for key in get: for key in get:
# e.g. input_resistor_1 ==> resistor_1 # e.g. input_resistor_1 ==> resistor_1
_, _, name = key.partition('_') _, _, name = key.partition('_')
answers[name] = get[key]
# This allows for answers which require more than one value for
# the same form input (e.g. checkbox inputs). The convention is that
# if the name ends with '[]' (which looks like an array), then the
# answer will be an array.
if not name.endswith('[]'):
answers[name] = get[key]
else:
name = name[:-2]
answers[name] = get.getlist(key)
return answers return answers
...@@ -424,7 +443,8 @@ class CapaModule(XModule): ...@@ -424,7 +443,8 @@ class CapaModule(XModule):
if not correct_map.is_correct(answer_id): if not correct_map.is_correct(answer_id):
success = 'incorrect' success = 'incorrect'
# log this in the track_function # NOTE: We are logging both full grading and queued-grading submissions. In the latter,
# 'success' will always be incorrect
event_info['correct_map'] = correct_map.get_dict() event_info['correct_map'] = correct_map.get_dict()
event_info['success'] = success event_info['success'] = success
self.system.track_function('save_problem_check', event_info) self.system.track_function('save_problem_check', event_info)
......
...@@ -81,7 +81,7 @@ class SequenceModule(XModule): ...@@ -81,7 +81,7 @@ class SequenceModule(XModule):
# of script, even if it occurs mid-string. Do this after json.dumps()ing # of script, even if it occurs mid-string. Do this after json.dumps()ing
# so that we can be sure of the quotations being used # so that we can be sure of the quotations being used
import re import re
params = {'items': re.sub(r'</(script)', r'\u003c/\1', json.dumps(contents), flags=re.IGNORECASE), params = {'items': re.sub(r'(?i)</(script)', r'\u003c/\1', json.dumps(contents)), # ?i = re.IGNORECASE for py2.6 compatability
'element_id': self.location.html_id(), 'element_id': self.location.html_id(),
'item_id': self.id, 'item_id': self.id,
'position': self.position, 'position': self.position,
......
...@@ -4,8 +4,10 @@ import logging ...@@ -4,8 +4,10 @@ import logging
from django.conf import settings from django.conf import settings
from django.http import Http404 from django.http import Http404
from django.http import HttpResponse from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from functools import wraps from functools import wraps
from django.contrib.auth.models import User
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from models import StudentModule, StudentModuleCache from models import StudentModule, StudentModuleCache
...@@ -33,6 +35,8 @@ class I4xSystem(object): ...@@ -33,6 +35,8 @@ class I4xSystem(object):
Create a closure around the system environment. Create a closure around the system environment.
ajax_url - the url where ajax calls to the encapsulating module go. ajax_url - the url where ajax calls to the encapsulating module go.
xqueue_callback_url - the url where external queueing system (e.g. for grading)
returns its response
track_function - function of (event_type, event), intended for logging track_function - function of (event_type, event), intended for logging
or otherwise tracking the event. or otherwise tracking the event.
TODO: Not used, and has inconsistent args in different TODO: Not used, and has inconsistent args in different
...@@ -208,7 +212,7 @@ def get_module(user, request, location, student_module_cache, position=None): ...@@ -208,7 +212,7 @@ def get_module(user, request, location, student_module_cache, position=None):
# Setup system context for module instance # Setup system context for module instance
ajax_url = settings.MITX_ROOT_URL + '/modx/' + descriptor.location.url() + '/' ajax_url = settings.MITX_ROOT_URL + '/modx/' + descriptor.location.url() + '/'
xqueue_callback_url = settings.MITX_ROOT_URL + '/xqueue/' + user.username + '/' + descriptor.location.url() + '/' xqueue_callback_url = settings.MITX_ROOT_URL + '/xqueue/' + str(user.id) + '/' + descriptor.location.url() + '/'
def _get_module(location): def _get_module(location):
(module, _, _, _) = get_module(user, request, location, student_module_cache, position) (module, _, _, _) = get_module(user, request, location, student_module_cache, position)
...@@ -324,11 +328,9 @@ def add_histogram(module): ...@@ -324,11 +328,9 @@ def add_histogram(module):
module.get_html = get_html module.get_html = get_html
return module return module
# THK: TEMPORARY BYPASS OF AUTH! # TODO: TEMPORARY BYPASS OF AUTH!
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.models import User
@csrf_exempt @csrf_exempt
def xqueue_callback(request, username, id, dispatch): def xqueue_callback(request, userid, id, dispatch):
# Parse xqueue response # Parse xqueue response
get = request.POST.copy() get = request.POST.copy()
try: try:
...@@ -336,12 +338,9 @@ def xqueue_callback(request, username, id, dispatch): ...@@ -336,12 +338,9 @@ def xqueue_callback(request, username, id, dispatch):
except Exception as err: except Exception as err:
msg = "Error in xqueue_callback %s: Invalid return format" % err msg = "Error in xqueue_callback %s: Invalid return format" % err
raise Exception(msg) raise Exception(msg)
# Should proceed only when the request timestamp is more recent than problem timestamp
timestamp = header['timestamp']
# Retrieve target StudentModule # Retrieve target StudentModule
user = User.objects.get(username=username) user = User.objects.get(id=userid)
student_module_cache = StudentModuleCache(user, modulestore().get_item(id)) student_module_cache = StudentModuleCache(user, modulestore().get_item(id))
instance, instance_module, shared_module, module_type = get_module(request.user, request, id, student_module_cache) instance, instance_module, shared_module, module_type = get_module(request.user, request, id, student_module_cache)
...@@ -354,6 +353,10 @@ def xqueue_callback(request, username, id, dispatch): ...@@ -354,6 +353,10 @@ def xqueue_callback(request, username, id, dispatch):
oldgrade = instance_module.grade oldgrade = instance_module.grade
old_instance_state = instance_module.state old_instance_state = instance_module.state
# Transfer 'queuekey' from xqueue response header to 'get'. This is required to
# use the interface defined by 'handle_ajax'
get.update({'queuekey': header['queuekey']})
# We go through the "AJAX" path # We go through the "AJAX" path
# So far, the only dispatch from xqueue will be 'score_update' # So far, the only dispatch from xqueue will be 'score_update'
try: try:
......
body {
margin: 0;
padding: 0; }
.wrapper, .subpage, section.copyright, section.tos, section.privacy-policy, section.honor-code, header.announcement div, section.index-content, footer {
margin: 0;
overflow: hidden; }
div#enroll form {
display: none; }
...@@ -95,7 +95,7 @@ if settings.COURSEWARE_ENABLED: ...@@ -95,7 +95,7 @@ if settings.COURSEWARE_ENABLED:
url(r'^masquerade/', include('masquerade.urls')), url(r'^masquerade/', include('masquerade.urls')),
url(r'^jumpto/(?P<probname>[^/]+)/$', 'courseware.views.jump_to'), url(r'^jumpto/(?P<probname>[^/]+)/$', 'courseware.views.jump_to'),
url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'), url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
url(r'^xqueue/(?P<username>[^/]*)/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.xqueue_callback'), url(r'^xqueue/(?P<userid>[^/]*)/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.xqueue_callback'),
url(r'^change_setting$', 'student.views.change_setting'), url(r'^change_setting$', 'student.views.change_setting'),
url(r'^s/(?P<template>[^/]*)$', 'static_template_view.views.auth_index'), url(r'^s/(?P<template>[^/]*)$', 'static_template_view.views.auth_index'),
# url(r'^course_info/$', 'student.views.courseinfo'), # url(r'^course_info/$', 'student.views.courseinfo'),
......
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