Merge pull request #1428 from MITx/feature/diana/close-oe-problems
Close OE Problems
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user