From 85abc435159d4db466f04c24460b1a53f13f78f6 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Mon, 4 Feb 2013 14:02:42 -0500 Subject: [PATCH] Add in the ability to handle due dates and grace periods --- .../xmodule/combined_open_ended_module.py | 31 +++++++++++++++---- common/lib/xmodule/xmodule/timeparse.py | 25 +++++++++++++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index e9b3d1d4d0..4c3e8da8c6 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -7,7 +7,11 @@ from lxml import etree from lxml.html import rewrite_links from path import path import os +import dateutil +import dateutil.parser +import datetime import sys +from timeparse import parse_timedelta from pkg_resources import resource_string @@ -155,12 +159,27 @@ class CombinedOpenEndedModule(XModule): self.attempts = instance_state.get('attempts', 0) + #Allow reset is true if student has failed the criteria to move to the next child task self.allow_reset = instance_state.get('ready_to_reset', False) self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS)) self.is_scored = self.metadata.get('is_graded', IS_SCORED) 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) + if display_due_date_string is not None: + self.display_due_date = dateutil.parser.parse(display_due_date_string) + 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: + self.grace_period = parse_timedelta(grace_period_string) + self.close_date = self.display_due_date + self.grace_period + else: + self.grace_period = None + self.close_date = self.display_due_date + # Used for progress / grading. Currently get credit just for # completion (doesn't matter if you self-assessed correct/incorrect). self._max_score = int(self.metadata.get('max_score', MAX_SCORE)) @@ -189,12 +208,12 @@ class CombinedOpenEndedModule(XModule): self.setup_next_task() def closed(self): - return True - #''' Is the student still allowed to submit answers? ''' - #if self.attempts == self.max_attempts: - # return True - #if self.close_date is not None and datetime.datetime.utcnow() > self.close_date: - # return True + ''' Is the student still allowed to submit answers? ''' + if self.attempts == self.max_attempts: + return True + if self.close_date is not None and datetime.datetime.utcnow() > self.close_date: + return True + def get_tag_name(self, xml): diff --git a/common/lib/xmodule/xmodule/timeparse.py b/common/lib/xmodule/xmodule/timeparse.py index 36c0f725e5..605662654d 100644 --- a/common/lib/xmodule/xmodule/timeparse.py +++ b/common/lib/xmodule/xmodule/timeparse.py @@ -2,9 +2,14 @@ Helper functions for handling time in the format we like. """ import time +import re +from datetime import timedelta TIME_FORMAT = "%Y-%m-%dT%H:%M" +TIMEDELTA_REGEX = re.compile(r'^((?P\d+?) day(?:s?))?(\s)?((?P\d+?) hour(?:s?))?(\s)?((?P\d+?) minute(?:s)?)?(\s)?((?P\d+?) second(?:s)?)?$') + + def parse_time(time_str): """ Takes a time string in TIME_FORMAT @@ -20,3 +25,23 @@ def stringify_time(time_struct): Convert a time struct to a string """ return time.strftime(TIME_FORMAT, time_struct) + +def parse_timedelta(time_str): + """ + time_str: A string with the following components: + day[s] (optional) + hour[s] (optional) + minute[s] (optional) + second[s] (optional) + + Returns a datetime.timedelta parsed from the string + """ + parts = TIMEDELTA_REGEX.match(time_str) + if not parts: + return + parts = parts.groupdict() + time_params = {} + for (name, param) in parts.iteritems(): + if param: + time_params[name] = int(param) + return timedelta(**time_params)