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

Conflicts:
	common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
This commit is contained in:
Vik Paruchuri
2013-02-20 16:43:04 -05:00
31 changed files with 521 additions and 71 deletions

View File

@@ -208,4 +208,4 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
for child in ['task']:
add_child(child)
return elt
return elt

View File

@@ -2,8 +2,7 @@ import json
import logging
from lxml import etree
from lxml.html import rewrite_links
from xmodule.timeinfo import TimeInfo
from xmodule.capa_module import only_one, ComplexEncoder
from xmodule.editing_module import EditingDescriptor
from xmodule.html_checker import check_html
@@ -153,28 +152,14 @@ class CombinedOpenEndedV1Module():
self.skip_basic_checks = self.metadata.get('skip_spelling_checks', SKIP_BASIC_CHECKS)
display_due_date_string = self.metadata.get('due', None)
if display_due_date_string is not None:
try:
self.display_due_date = dateutil.parser.parse(display_due_date_string)
except ValueError:
#This is a staff_facing_error
log.error("Could not parse due date {0} for location {1}. Contact the learning sciences group for assistance.".format(display_due_date_string, location))
raise
else:
self.display_due_date = None
grace_period_string = self.metadata.get('graceperiod', None)
if grace_period_string is not None and self.display_due_date:
try:
self.grace_period = parse_timedelta(grace_period_string)
self.close_date = self.display_due_date + self.grace_period
except:
#This is a staff_facing_error
log.error("Error parsing the grace period {0} for location {1}. Contact the learning sciences group for assistance.".format(grace_period_string, location))
raise
else:
self.grace_period = None
self.close_date = self.display_due_date
try:
self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
except:
log.error("Error parsing due date information in location {0}".format(location))
raise
self.display_due_date = self.timeinfo.display_due_date
# Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect).
@@ -192,7 +177,7 @@ class CombinedOpenEndedV1Module():
'rubric': definition['rubric'],
'display_name': self.display_name,
'accept_file_upload': self.accept_file_upload,
'close_date' : self.close_date,
'close_date' : self.timeinfo.close_date,
's3_interface' : self.system.s3_interface,
'skip_basic_checks' : self.skip_basic_checks,
}

View File

@@ -106,7 +106,7 @@ class MockPeerGradingService(object):
'max_score': 4})
def save_grade(self, location, grader_id, submission_id,
score, feedback, submission_key):
score, feedback, submission_key, rubric_scores, submission_flagged):
return json.dumps({'success': True})
def is_student_calibrated(self, problem_location, grader_id):
@@ -122,7 +122,8 @@ class MockPeerGradingService(object):
'max_score': 4})
def save_calibration_essay(self, problem_location, grader_id,
calibration_essay_id, submission_key, score, feedback):
calibration_essay_id, submission_key, score,
feedback, rubric_scores):
return {'success': True, 'actual_score': 2}
def get_problem_list(self, course_id, grader_id):

View File

@@ -3,6 +3,7 @@ import logging
from lxml import etree
from datetime import datetime
from pkg_resources import resource_string
from .capa_module import ComplexEncoder
from .editing_module import EditingDescriptor
@@ -10,6 +11,8 @@ from .stringify import stringify_children
from .x_module import XModule
from .xml_module import XmlDescriptor
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from timeinfo import TimeInfo
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError
@@ -52,6 +55,7 @@ class PeerGradingModule(XModule):
self.system = system
self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.system)
self.use_for_single_location = self.metadata.get('use_for_single_location', USE_FOR_SINGLE_LOCATION)
if isinstance(self.use_for_single_location, basestring):
self.use_for_single_location = (self.use_for_single_location in TRUE_DICT)
@@ -60,10 +64,28 @@ class PeerGradingModule(XModule):
if isinstance(self.is_graded, basestring):
self.is_graded = (self.is_graded in TRUE_DICT)
display_due_date_string = self.metadata.get('due', None)
grace_period_string = self.metadata.get('graceperiod', None)
try:
self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
except:
log.error("Error parsing due date information in location {0}".format(location))
raise
self.display_due_date = self.timeinfo.display_due_date
self.link_to_location = self.metadata.get('link_to_location', USE_FOR_SINGLE_LOCATION)
if self.use_for_single_location == True:
#This will raise an exception if the location is invalid
link_to_location_object = Location(self.link_to_location)
try:
self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location)
except:
log.error("Linked location {0} for peer grading module {1} does not exist".format(
self.link_to_location, self.location))
raise
due_date = self.linked_problem.metadata.get('peer_grading_due', None)
if due_date:
self.metadata['due'] = due_date
self.ajax_url = self.system.ajax_url
if not self.ajax_url.endswith("/"):
@@ -75,6 +97,15 @@ class PeerGradingModule(XModule):
#This could result in an exception, but not wrapping in a try catch block so it moves up the stack
self.max_grade = int(self.max_grade)
def closed(self):
return self._closed(self.timeinfo)
def _closed(self, timeinfo):
if timeinfo.close_date is not None and datetime.utcnow() > timeinfo.close_date:
return True
return False
def _err_response(self, msg):
"""
Return a HttpResponse with a json dump with success=False, and the given error message.
@@ -94,6 +125,8 @@ class PeerGradingModule(XModule):
Needs to be implemented by inheritors. Renders the HTML that students see.
@return:
"""
if self.closed():
return self.peer_grading_closed()
if not self.use_for_single_location:
return self.peer_grading()
else:
@@ -397,6 +430,16 @@ class PeerGradingModule(XModule):
#This is a student_facing_error
return self._err_response('')
def peer_grading_closed(self):
'''
Show the Peer grading closed template
'''
html = self.system.render_template('peer_grading/peer_grading_closed.html', {
'use_for_single_location': self.use_for_single_location
})
return html
def peer_grading(self, get=None):
'''
Show a peer grading interface
@@ -425,6 +468,40 @@ class PeerGradingModule(XModule):
error_text = "Could not get list of problems to peer grade. Please notify course staff."
success = False
def _find_corresponding_module_for_location(location):
'''
find the peer grading module that links to the given location
'''
try:
return modulestore().get_instance(self.system.course_id, location)
except:
# the linked problem doesn't exist
log.error("Problem {0} does not exist in this course".format(location))
raise
for problem in problem_list:
problem_location = problem['location']
descriptor = _find_corresponding_module_for_location(problem_location)
if descriptor:
problem['due'] = descriptor.metadata.get('peer_grading_due', None)
grace_period_string = descriptor.metadata.get('graceperiod', None)
try:
problem_timeinfo = TimeInfo(problem['due'], grace_period_string)
except:
log.error("Malformed due date or grace period string for location {0}".format(problem_location))
raise
if self._closed(problem_timeinfo):
problem['closed'] = True
else:
problem['closed'] = False
else:
# if we can't find the due date, assume that it doesn't have one
problem['due'] = None
problem['closed'] = False
ajax_url = self.ajax_url
html = self.system.render_template('peer_grading/peer_grading.html', {
'course_id': self.system.course_id,

View File

@@ -1,11 +1,10 @@
import json
from mock import Mock
from mock import Mock, MagicMock
import unittest
from xmodule.open_ended_grading_classes.self_assessment_module import SelfAssessmentModule
from xmodule.modulestore import Location
from lxml import etree
from nose.plugins.skip import SkipTest
from . import test_system
@@ -64,13 +63,21 @@ class SelfAssessmentTest(unittest.TestCase):
self.assertTrue("This is sample prompt text" in html)
def test_self_assessment_flow(self):
raise SkipTest()
responses = {'assessment': '0', 'score_list[]': ['0', '0']}
def get_fake_item(name):
return responses[name]
mock_query_dict = MagicMock()
mock_query_dict.__getitem__.side_effect = get_fake_item
mock_query_dict.getlist = get_fake_item
self.assertEqual(self.module.get_score()['score'], 0)
self.module.save_answer({'student_answer': "I am an answer"}, test_system)
self.assertEqual(self.module.state, self.module.ASSESSING)
self.module.save_assessment({'assessment': '0'}, test_system)
self.module.save_assessment(mock_query_dict, test_system)
self.assertEqual(self.module.state, self.module.DONE)
@@ -80,5 +87,6 @@ class SelfAssessmentTest(unittest.TestCase):
# if we now assess as right, skip the REQUEST_HINT state
self.module.save_answer({'student_answer': 'answer 4'}, test_system)
self.module.save_assessment({'assessment': '1'}, test_system)
responses['assessment'] = '1'
self.module.save_assessment(mock_query_dict, test_system)
self.assertEqual(self.module.state, self.module.DONE)

View File

@@ -0,0 +1,39 @@
import dateutil
import dateutil.parser
import datetime
from timeparse import parse_timedelta
import logging
log = logging.getLogger(__name__)
class TimeInfo(object):
"""
This is a simple object that calculates and stores datetime information for an XModule
based on the due date string and the grace period string
So far it parses out three different pieces of time information:
self.display_due_date - the 'official' due date that gets displayed to students
self.grace_period - the length of the grace period
self.close_date - the real due date
"""
def __init__(self, display_due_date_string, grace_period_string):
if display_due_date_string is not None:
try:
self.display_due_date = dateutil.parser.parse(display_due_date_string)
except ValueError:
log.error("Could not parse due date {0}".format(display_due_date_string))
raise
else:
self.display_due_date = None
if grace_period_string is not None and self.display_due_date:
try:
self.grace_period = parse_timedelta(grace_period_string)
self.close_date = self.display_due_date + self.grace_period
except:
log.error("Error parsing the grace period {0}".format(grace_period_string))
raise
else:
self.grace_period = None
self.close_date = self.display_due_date