Commit b378af87 by Victor Shnayder

Merge pull request #1011 from MITx/vik/add_self_assessment

Vik/add self assessment
parents 1e956d44 6a313fd5
......@@ -28,3 +28,4 @@ cms/static/sass/*.css
lms/lib/comment_client/python
nosetests.xml
cover_html/
.idea/
......@@ -28,6 +28,7 @@ setup(
"problem = xmodule.capa_module:CapaDescriptor",
"problemset = xmodule.seq_module:SequenceDescriptor",
"section = xmodule.backcompat_module:SemanticSectionDescriptor",
"selfassessment = xmodule.self_assessment_module:SelfAssessmentDescriptor",
"sequential = xmodule.seq_module:SequenceDescriptor",
"slides = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"vertical = xmodule.vertical_module:VerticalDescriptor",
......
......@@ -30,15 +30,17 @@ TIMEDELTA_REGEX = re.compile(r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?)
def only_one(lst, default="", process=lambda x: x):
"""
If lst is empty, returns default
If lst has a single element, applies process to that element and returns it
Otherwise, raises an exeception
If lst has a single element, applies process to that element and returns it.
Otherwise, raises an exception.
"""
if len(lst) == 0:
return default
elif len(lst) == 1:
return process(lst[0])
else:
raise Exception('Malformed XML')
raise Exception('Malformed XML: expected at most one element in list.')
def parse_timedelta(time_str):
......@@ -292,11 +294,11 @@ class CapaModule(XModule):
# check button is context-specific.
# Put a "Check" button if unlimited attempts or still some left
if self.max_attempts is None or self.attempts < self.max_attempts-1:
if self.max_attempts is None or self.attempts < self.max_attempts-1:
check_button = "Check"
else:
# Will be final check so let user know that
check_button = "Final Check"
check_button = "Final Check"
reset_button = True
save_button = True
......@@ -527,11 +529,11 @@ class CapaModule(XModule):
# Problem queued. Students must wait a specified waittime before they are allowed to submit
if self.lcp.is_queued():
current_time = datetime.datetime.now()
prev_submit_time = self.lcp.get_recentmost_queuetime()
prev_submit_time = self.lcp.get_recentmost_queuetime()
waittime_between_requests = self.system.xqueue['waittime']
if (current_time-prev_submit_time).total_seconds() < waittime_between_requests:
msg = 'You must wait at least %d seconds between submissions' % waittime_between_requests
return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback
return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback
try:
old_state = self.lcp.get_state()
......@@ -672,10 +674,10 @@ class CapaDescriptor(RawDescriptor):
'problems/' + path[8:],
path[8:],
]
def __init__(self, *args, **kwargs):
super(CapaDescriptor, self).__init__(*args, **kwargs)
weight_string = self.metadata.get('weight', None)
if weight_string:
self.weight = float(weight_string)
......
class @SelfAssessment
constructor: (element) ->
@el = $(element).find('section.self-assessment')
@id = @el.data('id')
@ajax_url = @el.data('ajax-url')
@state = @el.data('state')
@allow_reset = @el.data('allow_reset')
# valid states: 'initial', 'assessing', 'request_hint', 'done'
# Where to put the rubric once we load it
@errors_area = @$('.error')
@answer_area = @$('textarea.answer')
@rubric_wrapper = @$('.rubric-wrapper')
@hint_wrapper = @$('.hint-wrapper')
@message_wrapper = @$('.message-wrapper')
@submit_button = @$('.submit-button')
@reset_button = @$('.reset-button')
@reset_button.click @reset
@find_assessment_elements()
@find_hint_elements()
@rebind()
# locally scoped jquery.
$: (selector) ->
$(selector, @el)
rebind: () =>
# rebind to the appropriate function for the current state
@submit_button.unbind('click')
@submit_button.show()
@reset_button.hide()
if @state == 'initial'
@submit_button.prop('value', 'Submit')
@submit_button.click @save_answer
else if @state == 'assessing'
@submit_button.prop('value', 'Submit assessment')
@submit_button.click @save_assessment
else if @state == 'request_hint'
@submit_button.prop('value', 'Submit hint')
@submit_button.click @save_hint
else if @state == 'done'
@submit_button.hide()
if @allow_reset
@reset_button.show()
else
@reset_button.hide()
find_assessment_elements: ->
@assessment = @$('select.assessment')
find_hint_elements: ->
@hint_area = @$('textarea.hint')
save_answer: (event) =>
event.preventDefault()
if @state == 'initial'
data = {'student_answer' : @answer_area.val()}
$.postWithPrefix "#{@ajax_url}/save_answer", data, (response) =>
if response.success
@rubric_wrapper.html(response.rubric_html)
@state = 'assessing'
@find_assessment_elements()
@rebind()
else
@errors_area.html(response.message)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
save_assessment: (event) =>
event.preventDefault()
if @state == 'assessing'
data = {'assessment' : @assessment.find(':selected').text()}
$.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) =>
if response.success
@hint_wrapper.html(response.hint_html)
@state = 'request_hint'
@find_hint_elements()
@rebind()
else
@errors_area.html(response.message)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
save_hint: (event) =>
event.preventDefault()
if @state == 'request_hint'
data = {'hint' : @hint_area.val()}
$.postWithPrefix "#{@ajax_url}/save_hint", data, (response) =>
if response.success
@message_wrapper.html(response.message_html)
@state = 'done'
@allow_reset = response.allow_reset
@rebind()
else
@errors_area.html(response.message)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
reset: (event) =>
event.preventDefault()
if @state == 'done'
$.postWithPrefix "#{@ajax_url}/reset", {}, (response) =>
if response.success
@rubric_wrapper.html('')
@hint_wrapper.html('')
@message_wrapper.html('')
@state = 'initial'
@rebind()
@reset_button.hide()
else
@errors_area.html(response.message)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
......@@ -113,3 +113,7 @@ class RoundTripTestCase(unittest.TestCase):
def test_full_roundtrip(self):
self.check_export_roundtrip(DATA_DIR, "full")
def test_selfassessment_roundtrip(self):
#Test selfassessment xmodule to see if it exports correctly
self.check_export_roundtrip(DATA_DIR,"self_assessment")
......@@ -339,4 +339,16 @@ class ImportTestCase(unittest.TestCase):
self.assertRaises(etree.XMLSyntaxError, system.process_xml, bad_xml)
def test_selfassessment_import(self):
'''
Check to see if definition_from_xml in self_assessment_module.py
works properly. Pulls data from the self_assessment directory in the test data directory.
'''
modulestore = XMLModuleStore(DATA_DIR, course_dirs=['self_assessment'])
sa_id = "edX/sa_test/2012_Fall"
location = Location(["i4x", "edX", "sa_test", "selfassessment", "SampleQuestion"])
sa_sample = modulestore.get_instance(sa_id, location)
#10 attempts is hard coded into SampleQuestion, which is the url_name of a selfassessment xml tag
self.assertEqual(sa_sample.metadata['attempts'], '10')
This is a very very simple course, useful for debugging self assessment code.
roots/2012_Fall.xml
\ No newline at end of file
<course>
<chapter url_name="Overview">
<selfassessment url_name="SampleQuestion"/>
</chapter>
</course>
{
"course/2012_Fall": {
"graceperiod": "2 days 5 hours 59 minutes 59 seconds",
"start": "2015-07-17T12:00",
"display_name": "Self Assessment Test",
"graded": "true"
},
"chapter/Overview": {
"display_name": "Overview"
},
"selfassessment/SampleQuestion": {
"display_name": "Sample Question",
},
}
<course org="edX" course="sa_test" url_name="2012_Fall"/>
<selfassessment attempts='10'>
<prompt>
What is the meaning of life?
</prompt>
<rubric>
This is a rubric.
</rubric>
<submitmessage>
Thanks for your submission!
</submitmessage>
<hintprompt>
Enter a hint below:
</hintprompt>
</selfassessment>
......@@ -242,4 +242,25 @@ div.course-wrapper {
}
section.self-assessment {
textarea.answer {
height: 200px;
padding: 5px;
margin-top: 5px;
margin-bottom: 5px;
}
textarea.hint {
height: 100px;
padding: 5px;
margin-top: 5px;
margin-bottom: 5px;
}
div {
margin-top: 5px;
margin-bottom: 5px;
}
}
<div class="hint">
<div class="hint-prompt">
${hint_prompt}
</div>
<textarea name="hint" class="hint" cols="70" rows="5"
${'readonly="true"' if read_only else ''}>${hint}</textarea>
</div>
<section id="self_assessment_${id}" class="self-assessment" data-ajax-url="${ajax_url}"
data-id="${id}" data-state="${state}" data-allow_reset="${allow_reset}">
<div class="error"></div>
<div class="prompt">
${prompt}
</div>
<div>
<textarea name="answer" class="answer" cols="70" rows="20">${previous_answer|h}</textarea>
</div>
<div class="rubric-wrapper">${initial_rubric}</div>
<div class="hint-wrapper">${initial_hint}</div>
<div class="message-wrapper">${initial_message}</div>
<input type="button" value="Submit" class="submit-button" name="show"/>
<input type="button" value="Reset" class="reset-button" name="reset"/>
</section>
<div class="assessment">
<div class="rubric">
<h3>Self-assess your answer with this rubric:</h3>
${rubric}
</div>
% if not read_only:
<select name="assessment" class="assessment">
<option value="incorrect">Incorrect</option>
<option value="correct">Correct</option>
</select>
% endif
</div>
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