Commit 62dcd09b by VikParuchuri

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

Close OE Problems
parents 53312713 f3fa4380
......@@ -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
......@@ -157,12 +161,35 @@ 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:
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
# 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))
......@@ -185,11 +212,13 @@ class CombinedOpenEndedModule(XModule):
'rubric': definition['rubric'],
'display_name': self.display_name,
'accept_file_upload': self.accept_file_upload,
'close_date': self.close_date
}
self.task_xml = definition['task_xml']
self.setup_next_task()
def get_tag_name(self, xml):
"""
Gets the tag name of a given xml block.
......@@ -299,6 +328,7 @@ class CombinedOpenEndedModule(XModule):
return True
def check_allow_reset(self):
"""
Checks to see if the student has passed the criteria to move to the next module. If not, sets
......
......@@ -549,14 +549,11 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
@param system: modulesystem
@return: Success indicator
"""
if self.attempts > self.max_attempts:
# If too many attempts, prevent student from saving answer and
# seeing rubric. In normal use, students shouldn't see this because
# they won't see the reset button once they're out of attempts.
return {
'success': False,
'error': 'Too many attempts.'
}
# Once we close the problem, we should not allow students
# to save answers
closed, msg = self.check_if_closed()
if closed:
return msg
if self.state != self.INITIAL:
return self.out_of_sync_error(get)
......
......@@ -74,7 +74,7 @@ class OpenEndedChild(object):
'done': 'Problem complete',
}
def __init__(self, system, location, definition, descriptor, static_data,
def __init__(self, system, location, definition, descriptor, static_data,
instance_state=None, shared_state=None, **kwargs):
# Load instance state
if instance_state is not None:
......@@ -99,6 +99,7 @@ class OpenEndedChild(object):
self.rubric = static_data['rubric']
self.display_name = static_data['display_name']
self.accept_file_upload = static_data['accept_file_upload']
self.close_date = static_data['close_date']
# Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect).
......@@ -117,6 +118,27 @@ class OpenEndedChild(object):
"""
pass
def closed(self):
if self.close_date is not None and datetime.utcnow() > self.close_date:
return True
return False
def check_if_closed(self):
if self.closed():
return True, {
'success': False,
'error': 'This problem is now closed.'
}
elif self.attempts > self.max_attempts:
return True, {
'success': False,
'error': 'Too many attempts.'
}
else:
return False, {}
def latest_answer(self):
"""Empty string if not available"""
if not self.history:
......
......@@ -190,15 +190,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
Dictionary with keys 'success' and either 'error' (if not success),
or 'rubric_html' (if success).
"""
# Check to see if attempts are less than max
if self.attempts > self.max_attempts:
# If too many attempts, prevent student from saving answer and
# seeing rubric. In normal use, students shouldn't see this because
# they won't see the reset button once they're out of attempts.
return {
'success': False,
'error': 'Too many attempts.'
}
# Check to see if this problem is closed
closed, msg = self.check_if_closed()
if closed:
return msg
if self.state != self.INITIAL:
return self.out_of_sync_error(get)
......
......@@ -42,6 +42,7 @@ class OpenEndedChildTest(unittest.TestCase):
'max_score': max_score,
'display_name': 'Name',
'accept_file_upload': False,
'close_date': None
}
definition = Mock()
descriptor = Mock()
......@@ -157,6 +158,7 @@ class OpenEndedModuleTest(unittest.TestCase):
'max_score': max_score,
'display_name': 'Name',
'accept_file_upload': False,
'close_date': None
}
oeparam = etree.XML('''
......
......@@ -46,11 +46,13 @@ class SelfAssessmentTest(unittest.TestCase):
'max_score': 1,
'display_name': "Name",
'accept_file_upload': False,
'close_date': None
}
self.module = SelfAssessmentModule(test_system, self.location,
self.definition, self.descriptor,
static_data, state, metadata=self.metadata)
static_data,
state, metadata=self.metadata)
def test_get_html(self):
html = self.module.get_html(test_system)
......
......@@ -2,9 +2,12 @@
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<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?) hour(?:s?))?(\s)?((?P<minutes>\d+?) minute(?:s)?)?(\s)?((?P<seconds>\d+?) second(?:s)?)?$')
def parse_time(time_str):
"""
......@@ -22,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:
<D> day[s] (optional)
<H> hour[s] (optional)
<M> minute[s] (optional)
<S> 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)
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