Skip to content

Commit

Permalink
Merge pull request #2062 from jazkarta/feature-idde2
Browse files Browse the repository at this point in the history
Individual Due Date Extension feature
  • Loading branch information
sarina committed Jan 14, 2014
2 parents e475f83 + 831f907 commit 129c244
Show file tree
Hide file tree
Showing 24 changed files with 1,244 additions and 54 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ cms/envs/private.py
.redcar/
codekit-config.json

### NFS artifacts
.nfs*

### OS X artifacts
*.DS_Store
.AppleDouble
Expand Down
11 changes: 10 additions & 1 deletion common/lib/xmodule/xmodule/capa_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from xblock.fields import Scope, String, Boolean, Dict, Integer, Float
from .fields import Timedelta, Date
from django.utils.timezone import UTC
from .util.duedate import get_extended_due_date

log = logging.getLogger("edx.courseware")

Expand Down Expand Up @@ -95,6 +96,14 @@ class CapaFields(object):
values={"min": 0}, scope=Scope.settings
)
due = Date(help="Date that this problem is due by", scope=Scope.settings)
extended_due = Date(
help="Date that this problem is due by for a particular student. This "
"can be set by an instructor, and will override the global due "
"date if it is set to a date that is later than the global due "
"date.",
default=None,
scope=Scope.user_state,
)
graceperiod = Timedelta(
help="Amount of time after the due date that submissions will be accepted",
scope=Scope.settings
Expand Down Expand Up @@ -191,7 +200,7 @@ def __init__(self, *args, **kwargs):
"""
super(CapaModule, self).__init__(*args, **kwargs)

due_date = self.due
due_date = get_extended_due_date(self)

if self.graceperiod is not None and due_date:
self.close_date = due_date + self.graceperiod
Expand Down
9 changes: 9 additions & 0 deletions common/lib/xmodule/xmodule/combined_open_ended_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"accept_file_upload",
"skip_spelling_checks",
"due",
"extended_due",
"graceperiod",
"weight",
"min_to_calibrate",
Expand Down Expand Up @@ -262,6 +263,14 @@ class CombinedOpenEndedFields(object):
help="Date that this problem is due by",
scope=Scope.settings
)
extended_due = Date(
help="Date that this problem is due by for a particular student. This "
"can be set by an instructor, and will override the global due "
"date if it is set to a date that is later than the global due "
"date.",
default=None,
scope=Scope.user_state,
)
graceperiod = Timedelta(
help="Amount of time after the due date that submissions will be accepted",
scope=Scope.settings
Expand Down
11 changes: 10 additions & 1 deletion common/lib/xmodule/xmodule/foldit_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from xmodule.xml_module import XmlDescriptor
from xblock.fields import Scope, Integer, String
from .fields import Date
from .util.duedate import get_extended_due_date


log = logging.getLogger(__name__)
Expand All @@ -20,6 +21,14 @@ class FolditFields(object):
required_level = Integer(default=4, scope=Scope.settings)
required_sublevel = Integer(default=5, scope=Scope.settings)
due = Date(help="Date that this problem is due by", scope=Scope.settings)
extended_due = Date(
help="Date that this problem is due by for a particular student. This "
"can be set by an instructor, and will override the global due "
"date if it is set to a date that is later than the global due "
"date.",
default=None,
scope=Scope.user_state,
)

show_basic_score = String(scope=Scope.settings, default='false')
show_leaderboard = String(scope=Scope.settings, default='false')
Expand All @@ -40,7 +49,7 @@ def __init__(self, *args, **kwargs):
show_leaderboard="false"/>
"""
super(FolditModule, self).__init__(*args, **kwargs)
self.due_time = self.due
self.due_time = get_extended_due_date(self)

def is_complete(self):
"""
Expand Down
8 changes: 8 additions & 0 deletions common/lib/xmodule/xmodule/modulestore/inheritance.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ class InheritanceMixin(XBlockMixin):
scope=Scope.settings
)
due = Date(help="Date that this problem is due by", scope=Scope.settings)
extended_due = Date(
help="Date that this problem is due by for a particular student. This "
"can be set by an instructor, and will override the global due "
"date if it is set to a date that is later than the global due "
"date.",
default=None,
scope=Scope.user_state,
)
giturl = String(help="url root for course data git repository", scope=Scope.settings)
xqa_key = String(help="DO NOT USE", scope=Scope.settings)
graceperiod = Timedelta(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
from xmodule.capa_module import ComplexEncoder
from xmodule.progress import Progress
from xmodule.stringify import stringify_children
from xmodule.open_ended_grading_classes import self_assessment_module
from xmodule.open_ended_grading_classes import open_ended_module
from functools import partial
from xmodule.open_ended_grading_classes import self_assessment_module
from xmodule.open_ended_grading_classes import open_ended_module
from xmodule.util.duedate import get_extended_due_date
from .combined_open_ended_rubric import CombinedOpenEndedRubric, GRADER_TYPE_IMAGE_DICT, HUMAN_GRADER_TYPE, LEGEND_LIST
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, MockPeerGradingService, GradingServiceError
from xmodule.open_ended_grading_classes.openendedchild import OpenEndedChild
Expand Down Expand Up @@ -132,8 +132,7 @@ def __init__(self, system, location, definition, descriptor,
'peer_grade_finished_submissions_when_none_pending', False
)

due_date = instance_state.get('due', None)

due_date = get_extended_due_date(instance_state)
grace_period_string = instance_state.get('graceperiod', None)
try:
self.timeinfo = TimeInfo(due_date, grace_period_string)
Expand Down
18 changes: 14 additions & 4 deletions common/lib/xmodule/xmodule/peer_grading_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
from pkg_resources import resource_string
from .capa_module import ComplexEncoder
from .x_module import XModule, module_attr
from xmodule.raw_module import RawDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from .raw_module import RawDescriptor
from .modulestore.exceptions import ItemNotFoundError, NoPathToItem
from .timeinfo import TimeInfo
from .util.duedate import get_extended_due_date
from xblock.fields import Dict, String, Scope, Boolean, Float
from xmodule.fields import Date, Timedelta

Expand Down Expand Up @@ -46,6 +47,14 @@ class PeerGradingFields(object):
due = Date(
help="Due date that should be displayed.",
scope=Scope.settings)
extended_due = Date(
help="Date that this problem is due by for a particular student. This "
"can be set by an instructor, and will override the global due "
"date if it is set to a date that is later than the global due "
"date.",
default=None,
scope=Scope.user_state,
)
graceperiod = Timedelta(
help="Amount of grace to give on the due date.",
scope=Scope.settings
Expand Down Expand Up @@ -128,7 +137,8 @@ def __init__(self, *args, **kwargs):
self.linked_problem = self.system.get_module(linked_descriptors[0])

try:
self.timeinfo = TimeInfo(self.due, self.graceperiod)
self.timeinfo = TimeInfo(
get_extended_due_date(self), self.graceperiod)
except Exception:
log.error("Error parsing due date information in location {0}".format(self.location))
raise
Expand Down Expand Up @@ -556,7 +566,7 @@ def peer_grading(self, _data=None):
except (NoPathToItem, ItemNotFoundError):
continue
if descriptor:
problem['due'] = descriptor.due
problem['due'] = get_extended_due_date(descriptor)
grace_period = descriptor.graceperiod
try:
problem_timeinfo = TimeInfo(problem['due'], grace_period)
Expand Down
21 changes: 16 additions & 5 deletions common/lib/xmodule/xmodule/seq_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@

from lxml import etree

from xmodule.mako_module import MakoModuleDescriptor
from xmodule.xml_module import XmlDescriptor
from xmodule.x_module import XModule
from xmodule.progress import Progress
from xmodule.exceptions import NotFoundError
from xblock.fields import Integer, Scope
from xblock.fragment import Fragment
from pkg_resources import resource_string

from .exceptions import NotFoundError
from .fields import Date
from .mako_module import MakoModuleDescriptor
from .progress import Progress
from .x_module import XModule
from .xml_module import XmlDescriptor

log = logging.getLogger(__name__)

# HACK: This shouldn't be hard-coded to two types
Expand All @@ -25,6 +27,15 @@ class SequenceFields(object):
# NOTE: Position is 1-indexed. This is silly, but there are now student
# positions saved on prod, so it's not easy to fix.
position = Integer(help="Last tab viewed in this sequence", scope=Scope.user_state)
due = Date(help="Date that this problem is due by", scope=Scope.settings)
extended_due = Date(
help="Date that this problem is due by for a particular student. This "
"can be set by an instructor, and will override the global due "
"date if it is set to a date that is later than the global due "
"date.",
default=None,
scope=Scope.user_state,
)


class SequenceModule(SequenceFields, XModule):
Expand Down
35 changes: 9 additions & 26 deletions common/lib/xmodule/xmodule/tests/test_capa_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,10 @@ def answer_key(cls, input_num=2):

@classmethod
def create(cls,
graceperiod=None,
due=None,
max_attempts=None,
showanswer=None,
rerandomize=None,
force_save_button=None,
attempts=None,
problem_state=None,
correct=False,
done=None,
text_customization=None
**kwargs
):
"""
All parameters are optional, and are added to the created problem if specified.
Expand All @@ -109,24 +102,7 @@ def create(cls,
location = Location(["i4x", "edX", "capa_test", "problem",
"SampleProblem{0}".format(cls.next_num())])
field_data = {'data': cls.sample_problem_xml}

if graceperiod is not None:
field_data['graceperiod'] = graceperiod
if due is not None:
field_data['due'] = due
if max_attempts is not None:
field_data['max_attempts'] = max_attempts
if showanswer is not None:
field_data['showanswer'] = showanswer
if force_save_button is not None:
field_data['force_save_button'] = force_save_button
if rerandomize is not None:
field_data['rerandomize'] = rerandomize
if done is not None:
field_data['done'] = done
if text_customization is not None:
field_data['text_customization'] = text_customization

field_data.update(kwargs)
descriptor = Mock(weight="1")
if problem_state is not None:
field_data.update(problem_state)
Expand Down Expand Up @@ -379,6 +355,13 @@ def test_closed(self):
due=self.yesterday_str)
self.assertTrue(module.closed())

def test_due_date_extension(self):

module = CapaFactory.create(
max_attempts="1", attempts="0", due=self.yesterday_str,
extended_due=self.tomorrow_str)
self.assertFalse(module.closed())

def test_parse_get_params(self):

# Valid GET param dict
Expand Down
62 changes: 62 additions & 0 deletions common/lib/xmodule/xmodule/tests/test_util_duedate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
Tests for extended due date utilities.
"""
import mock
import unittest

from ..util import duedate


class TestGetExtendedDueDate(unittest.TestCase):
"""
Test `get_extended_due_date` function.
"""

def call_fut(self, node):
"""
Call function under test.
"""
fut = duedate.get_extended_due_date
return fut(node)

def test_no_due_date(self):
"""
Test no due date.
"""
node = object()
self.assertEqual(self.call_fut(node), None)

def test_due_date_no_extension(self):
"""
Test due date without extension.
"""
node = mock.Mock(due=1, extended_due=None)
self.assertEqual(self.call_fut(node), 1)

def test_due_date_with_extension(self):
"""
Test due date with extension.
"""
node = mock.Mock(due=1, extended_due=2)
self.assertEqual(self.call_fut(node), 2)

def test_due_date_extension_is_earlier(self):
"""
Test due date with extension, but due date is later than extension.
"""
node = mock.Mock(due=2, extended_due=1)
self.assertEqual(self.call_fut(node), 2)

def test_extension_without_due_date(self):
"""
Test non-sensical extension without due date.
"""
node = mock.Mock(due=None, extended_due=1)
self.assertEqual(self.call_fut(node), None)

def test_due_date_with_extension_dict(self):
"""
Test due date with extension when node is a dict.
"""
node = {'due': 1, 'extended_due': 2}
self.assertEqual(self.call_fut(node), 2)
23 changes: 23 additions & 0 deletions common/lib/xmodule/xmodule/util/duedate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
Miscellaneous utility functions.
"""
from functools import partial


def get_extended_due_date(node):
"""
Gets the actual due date for the logged in student for this node, returning
the extendeded due date if one has been granted and it is later than the
global due date, otherwise returning the global due date for the unit.
"""
if isinstance(node, dict):
get = node.get
else:
get = partial(getattr, node)
due_date = get('due', None)
if not due_date:
return due_date
extended = get('extended_due', None)
if not extended or extended < due_date:
return due_date
return extended
Loading

0 comments on commit 129c244

Please sign in to comment.