Commit 4673c739 by VikParuchuri

Merge pull request #1458 from MITx/feature/diana/close-oe-problems

Support due dates for Peer Grading XModule
parents 03a4deb8 eefe8749
...@@ -205,4 +205,4 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): ...@@ -205,4 +205,4 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
for child in ['task']: for child in ['task']:
add_child(child) add_child(child)
return elt return elt
\ No newline at end of file
...@@ -2,8 +2,7 @@ import json ...@@ -2,8 +2,7 @@ import json
import logging import logging
from lxml import etree from lxml import etree
from lxml.html import rewrite_links from lxml.html import rewrite_links
from xmodule.timeinfo import TimeInfo
from xmodule.capa_module import only_one, ComplexEncoder from xmodule.capa_module import only_one, ComplexEncoder
from xmodule.editing_module import EditingDescriptor from xmodule.editing_module import EditingDescriptor
from xmodule.html_checker import check_html from xmodule.html_checker import check_html
...@@ -14,9 +13,6 @@ from xmodule.xml_module import XmlDescriptor ...@@ -14,9 +13,6 @@ from xmodule.xml_module import XmlDescriptor
import self_assessment_module import self_assessment_module
import open_ended_module import open_ended_module
from combined_open_ended_rubric import CombinedOpenEndedRubric, GRADER_TYPE_IMAGE_DICT, HUMAN_GRADER_TYPE, LEGEND_LIST from combined_open_ended_rubric import CombinedOpenEndedRubric, GRADER_TYPE_IMAGE_DICT, HUMAN_GRADER_TYPE, LEGEND_LIST
import dateutil
import dateutil.parser
from xmodule.timeparse import parse_timedelta
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
...@@ -148,26 +144,13 @@ class CombinedOpenEndedV1Module(): ...@@ -148,26 +144,13 @@ class CombinedOpenEndedV1Module():
self.accept_file_upload = self.metadata.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT self.accept_file_upload = self.metadata.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT
display_due_date_string = self.metadata.get('due', None) 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:
log.error("Could not parse due date {0} for location {1}".format(display_due_date_string, location))
raise
else:
self.display_due_date = None
grace_period_string = self.metadata.get('graceperiod', None) grace_period_string = self.metadata.get('graceperiod', None)
if grace_period_string is not None and self.display_due_date: try:
try: self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
self.grace_period = parse_timedelta(grace_period_string) except:
self.close_date = self.display_due_date + self.grace_period log.error("Error parsing due date information in location {0}".format(location))
except: raise
log.error("Error parsing the grace period {0} for location {1}".format(grace_period_string, location)) self.display_due_date = self.timeinfo.display_due_date
raise
else:
self.grace_period = None
self.close_date = self.display_due_date
# Used for progress / grading. Currently get credit just for # Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect). # completion (doesn't matter if you self-assessed correct/incorrect).
...@@ -185,7 +168,7 @@ class CombinedOpenEndedV1Module(): ...@@ -185,7 +168,7 @@ class CombinedOpenEndedV1Module():
'rubric': definition['rubric'], 'rubric': definition['rubric'],
'display_name': self.display_name, 'display_name': self.display_name,
'accept_file_upload': self.accept_file_upload, 'accept_file_upload': self.accept_file_upload,
'close_date' : self.close_date, 'close_date' : self.timeinfo.close_date,
's3_interface' : self.system.s3_interface, 's3_interface' : self.system.s3_interface,
} }
...@@ -820,4 +803,4 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor): ...@@ -820,4 +803,4 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor):
for child in ['task']: for child in ['task']:
add_child(child) add_child(child)
return elt return elt
\ No newline at end of file
...@@ -3,6 +3,7 @@ import logging ...@@ -3,6 +3,7 @@ import logging
from lxml import etree from lxml import etree
from datetime import datetime
from pkg_resources import resource_string from pkg_resources import resource_string
from .capa_module import ComplexEncoder from .capa_module import ComplexEncoder
from .editing_module import EditingDescriptor from .editing_module import EditingDescriptor
...@@ -10,6 +11,8 @@ from .stringify import stringify_children ...@@ -10,6 +11,8 @@ from .stringify import stringify_children
from .x_module import XModule from .x_module import XModule
from .xml_module import XmlDescriptor from .xml_module import XmlDescriptor
from xmodule.modulestore import Location 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 from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError
...@@ -50,6 +53,7 @@ class PeerGradingModule(XModule): ...@@ -50,6 +53,7 @@ class PeerGradingModule(XModule):
self.system = system self.system = system
self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.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) self.use_for_single_location = self.metadata.get('use_for_single_location', USE_FOR_SINGLE_LOCATION)
if isinstance(self.use_for_single_location, basestring): if isinstance(self.use_for_single_location, basestring):
self.use_for_single_location = (self.use_for_single_location in TRUE_DICT) self.use_for_single_location = (self.use_for_single_location in TRUE_DICT)
...@@ -58,10 +62,28 @@ class PeerGradingModule(XModule): ...@@ -58,10 +62,28 @@ class PeerGradingModule(XModule):
if isinstance(self.is_graded, basestring): if isinstance(self.is_graded, basestring):
self.is_graded = (self.is_graded in TRUE_DICT) 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) self.link_to_location = self.metadata.get('link_to_location', USE_FOR_SINGLE_LOCATION)
if self.use_for_single_location == True: if self.use_for_single_location == True:
#This will raise an exception if the location is invalid try:
link_to_location_object = Location(self.link_to_location) 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 self.ajax_url = self.system.ajax_url
if not self.ajax_url.endswith("/"): if not self.ajax_url.endswith("/"):
...@@ -73,6 +95,15 @@ class PeerGradingModule(XModule): ...@@ -73,6 +95,15 @@ class PeerGradingModule(XModule):
#This could result in an exception, but not wrapping in a try catch block so it moves up the stack #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) 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): def _err_response(self, msg):
""" """
Return a HttpResponse with a json dump with success=False, and the given error message. Return a HttpResponse with a json dump with success=False, and the given error message.
...@@ -92,6 +123,8 @@ class PeerGradingModule(XModule): ...@@ -92,6 +123,8 @@ class PeerGradingModule(XModule):
Needs to be implemented by inheritors. Renders the HTML that students see. Needs to be implemented by inheritors. Renders the HTML that students see.
@return: @return:
""" """
if self.closed():
return self.peer_grading_closed()
if not self.use_for_single_location: if not self.use_for_single_location:
return self.peer_grading() return self.peer_grading()
else: else:
...@@ -378,6 +411,16 @@ class PeerGradingModule(XModule): ...@@ -378,6 +411,16 @@ class PeerGradingModule(XModule):
log.exception("Error saving calibration grade, location: {0}, submission_id: {1}, submission_key: {2}, grader_id: {3}".format(location, submission_id, submission_key, grader_id)) log.exception("Error saving calibration grade, location: {0}, submission_id: {1}, submission_key: {2}, grader_id: {3}".format(location, submission_id, submission_key, grader_id))
return self._err_response('Could not connect to grading service') return self._err_response('Could not connect to grading service')
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): def peer_grading(self, get=None):
''' '''
Show a peer grading interface Show a peer grading interface
...@@ -404,6 +447,40 @@ class PeerGradingModule(XModule): ...@@ -404,6 +447,40 @@ class PeerGradingModule(XModule):
error_text = "Could not get problem list" error_text = "Could not get problem list"
success = False 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 ajax_url = self.ajax_url
html = self.system.render_template('peer_grading/peer_grading.html', { html = self.system.render_template('peer_grading/peer_grading.html', {
'course_id': self.system.course_id, 'course_id': self.system.course_id,
......
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
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
<table class="problem-list"> <table class="problem-list">
<tr> <tr>
<th>Problem Name</th> <th>Problem Name</th>
<th>Due date</th>
<th>Graded</th> <th>Graded</th>
<th>Available</th> <th>Available</th>
<th>Required</th> <th>Required</th>
...@@ -22,7 +23,18 @@ ...@@ -22,7 +23,18 @@
%for problem in problem_list: %for problem in problem_list:
<tr data-graded="${problem['num_graded']}" data-required="${problem['num_required']}"> <tr data-graded="${problem['num_graded']}" data-required="${problem['num_required']}">
<td class="problem-name"> <td class="problem-name">
<a href="#problem" data-location="${problem['location']}" class="problem-button">${problem['problem_name']}</a> %if problem['closed']:
${problem['problem_name']}
%else:
<a href="#problem" data-location="${problem['location']}" class="problem-button">${problem['problem_name']}</a>
%endif
</td>
<td>
% if problem['due']:
${problem['due']}
% else:
No due date
% endif
</td> </td>
<td> <td>
${problem['num_graded']} ${problem['num_graded']}
......
<section class="container peer-grading-container">
<h2>Peer Grading</h2>
<p>The due date has passed, and
% if use_for_single_location:
peer grading for this problem is closed at this time.
%else:
peer grading is closed at this time.
%endif
</p>
</section>
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