Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature idde #1928

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
91075eb
nfs and testing artifacts
Oct 3, 2013
ebd2759
An individual due date extension may be set for a student for a
Oct 10, 2013
c8794b8
Ability to set due date extension for entire homework or exam.
Oct 10, 2013
8f57c59
Display correct dates in UI.
Oct 10, 2013
eb76cf1
Fix InvalidWriteError when viewing a combined open ended problem in t…
Oct 11, 2013
7f40071
Get extended due date to work with combined open ended module.
Oct 11, 2013
b1716f4
Use extended due date (unable to test FoldIt module).
Oct 11, 2013
083ab0d
More user friendly unit selection.
Oct 11, 2013
0eaafba
Dump due date extension data and reset due date extensions.
Oct 11, 2013
12579d1
Test closing date respects due date extension.
Oct 12, 2013
7e9cbac
Fix test broken by eb76cf117e1da3311c5aa6be3b308fcbb88021e9
Oct 12, 2013
087b539
Unit tests for due date extensions.
Oct 12, 2013
04b70a5
Don't gather units with due dates unless due date extension is active.
Oct 14, 2013
aff6970
Require user to actively choose a unit for due date extensions. User …
Oct 17, 2013
94ea0f9
Improved success message for due date extensions.
Oct 17, 2013
8ea1441
Merge MITx version of IDDE feature to mainstream edX.
Oct 23, 2013
37bc9ce
Change due date in beta dashboard.
Oct 24, 2013
28a2bce
Show students with due date extensions.
Oct 25, 2013
7834d82
Looks like they changed where the user's name is kept.
Oct 25, 2013
b95a991
Show due date extensions for student.
Oct 25, 2013
81b9c63
Merge in updates from master.
Dec 9, 2013
53325df
MITX_FEATURES has been renamed just FEATURES.
Dec 9, 2013
f5eb670
Fix test.
Dec 9, 2013
97c562b
Pep8
Dec 11, 2013
8e9138c
Pyling
Dec 11, 2013
4dc3a5c
Make sure all new code is i18n. Note that I did not make the new legacy
Dec 11, 2013
9d19986
Use dateutil.
Dec 18, 2013
1f00bf5
Use global due date if it is later than the individual 'extension' du…
Dec 18, 2013
757267c
Revert changes to legacy dashboard. IDDE feature will only be availa…
Dec 26, 2013
a164e4d
Refactor to be more functional/testable, use edX test fixtures per Sa…
Dec 27, 2013
b1c71db
Use student module directly to get at extended_due date in test.
Dec 27, 2013
4edbae7
Finish refactor.
Dec 30, 2013
685314e
Test API.
Dec 30, 2013
93292e5
Don't need a 'utils' module and a 'util' package. Rearrange and add …
Dec 31, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -23,6 +23,7 @@
from .fields import Timedelta, Date
from django.utils.timezone import UTC
from django.utils.translation import ugettext as _
from .util.duedate import get_extended_due_date

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

Expand Down Expand Up @@ -96,6 +97,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 @@ -192,7 +201,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 @@ -132,7 +132,9 @@ def __init__(self, system, location, definition, descriptor,
'peer_grade_finished_submissions_when_none_pending', False
)

due_date = instance_state.get('due', None)
due_date = instance_state.get('extended_due', None)
if due_date is None:
due_date = instance_state.get('due', None)

grace_period_string = instance_state.get('graceperiod', None)
try:
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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this test is good, capa is a notoriously flaky part of our system, and I would like to see some other tests of this functionality. Does it do what's expected when we're past the extended due date too? What does the module claim its due date is when it's being presented to the user? Is that correct?


def test_parse_get_params(self):

# Valid GET param dict
Expand Down
52 changes: 52 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,52 @@
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)
18 changes: 18 additions & 0 deletions common/lib/xmodule/xmodule/util/duedate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
Miscellaneous utility functions.
"""


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.
"""
due_date = getattr(node, 'due', None)
if not due_date:
return due_date
extended = getattr(node, 'extended_due', None)
if not extended or extended < due_date:
return due_date
return extended
3 changes: 2 additions & 1 deletion lms/djangoapps/courseware/grades.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from xmodule import graders
from xmodule.capa_module import CapaModule
from xmodule.graders import Score
from xmodule.util.duedate import get_extended_due_date
from .models import StudentModule
from .module_render import get_module, get_module_for_descriptor

Expand Down Expand Up @@ -353,7 +354,7 @@ def _progress_summary(student, request, course):
'scores': scores,
'section_total': section_total,
'format': module_format,
'due': section_module.due,
'due': get_extended_due_date(section_module),
'graded': graded,
})

Expand Down
5 changes: 3 additions & 2 deletions lms/djangoapps/courseware/module_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from django.core.urlresolvers import reverse
from django.http import Http404
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.views.decorators.csrf import csrf_exempt

from capa.xqueue_interface import XQueueInterface
from courseware.access import has_access
Expand All @@ -36,6 +36,7 @@
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.util.duedate import get_extended_due_date
from xmodule_modifiers import replace_course_urls, replace_jump_to_id_urls, replace_static_urls, add_histogram, wrap_xblock
from xmodule.lti_module import LTIModule

Expand Down Expand Up @@ -110,7 +111,7 @@ def toc_for_course(user, request, course, active_chapter, active_section, field_
sections.append({'display_name': section.display_name_with_default,
'url_name': section.url_name,
'format': section.format if section.format is not None else '',
'due': section.due,
'due': get_extended_due_date(section),
'active': active,
'graded': section.graded,
})
Expand Down
Loading