diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index f7b9b6a026..eaea908f3a 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -205,4 +205,4 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): for child in ['task']: add_child(child) - return elt \ No newline at end of file + return elt diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 49851f7452..bf6c51ac15 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -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 @@ -14,9 +13,6 @@ from xmodule.xml_module import XmlDescriptor import self_assessment_module import open_ended_module 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") @@ -148,26 +144,13 @@ class CombinedOpenEndedV1Module(): 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) - 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) - 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} for location {1}".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). @@ -185,7 +168,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, } @@ -820,4 +803,4 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor): for child in ['task']: add_child(child) - return elt \ No newline at end of file + return elt diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index 457c8ee72a..4dfe8e0dfa 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -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 @@ -50,6 +53,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) @@ -58,10 +62,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("/"): @@ -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 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. @@ -92,6 +123,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: @@ -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)) 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): ''' Show a peer grading interface @@ -404,6 +447,40 @@ class PeerGradingModule(XModule): error_text = "Could not get problem list" 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, diff --git a/common/lib/xmodule/xmodule/timeinfo.py b/common/lib/xmodule/xmodule/timeinfo.py new file mode 100644 index 0000000000..6c6a72e700 --- /dev/null +++ b/common/lib/xmodule/xmodule/timeinfo.py @@ -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 diff --git a/lms/templates/peer_grading/peer_grading.html b/lms/templates/peer_grading/peer_grading.html index d309b4486c..0485b698b2 100644 --- a/lms/templates/peer_grading/peer_grading.html +++ b/lms/templates/peer_grading/peer_grading.html @@ -14,6 +14,7 @@ + @@ -22,7 +23,18 @@ %for problem in problem_list: +
Problem NameDue date Graded Available Required
- ${problem['problem_name']} + %if problem['closed']: + ${problem['problem_name']} + %else: + ${problem['problem_name']} + %endif + + % if problem['due']: + ${problem['due']} + % else: + No due date + % endif ${problem['num_graded']} diff --git a/lms/templates/peer_grading/peer_grading_closed.html b/lms/templates/peer_grading/peer_grading_closed.html new file mode 100644 index 0000000000..712ad8b380 --- /dev/null +++ b/lms/templates/peer_grading/peer_grading_closed.html @@ -0,0 +1,10 @@ +
+

Peer Grading

+

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 +

+