Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -5,9 +5,12 @@
*.orig
*.DS_Store
*.mo
*.nfs*
:2e_*
:2e#
.AppleDouble
.noseids
.testids
database.sqlite
requirements/private.txt
lms/envs/private.py
Expand Down
10 changes: 9 additions & 1 deletion common/lib/xmodule/xmodule/capa_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from xblock.core import Scope, String, Boolean, Dict, Integer, Float
from .fields import Timedelta, Date
from django.utils.timezone import UTC
from .utils import get_extended_due_date

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

Expand Down Expand Up @@ -94,6 +95,13 @@ 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 "
"may differ from the global due date if an instructor has granted "
"an extension to the student.",
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 @@ -179,7 +187,7 @@ def __init__(self, *args, **kwargs):
"""
XModule.__init__(self, *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
17 changes: 13 additions & 4 deletions common/lib/xmodule/xmodule/combined_open_ended_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@

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

V1_SETTINGS_ATTRIBUTES = ["display_name", "max_attempts", "graded", "accept_file_upload",
"skip_spelling_checks", "due", "graceperiod", "weight"]
V1_SETTINGS_ATTRIBUTES = [
"display_name", "max_attempts", "graded", "accept_file_upload",
"skip_spelling_checks", "due", "graceperiod", "weight", "extended_due"]

V1_STUDENT_ATTRIBUTES = ["current_task_number", "task_states", "state",
"student_attempts", "ready_to_reset"]
V1_STUDENT_ATTRIBUTES = [
"current_task_number", "task_states", "state",
"student_attempts", "ready_to_reset"]

V1_ATTRIBUTES = V1_SETTINGS_ATTRIBUTES + V1_STUDENT_ATTRIBUTES

Expand Down Expand Up @@ -229,6 +231,13 @@ class CombinedOpenEndedFields(object):
default=None,
scope=Scope.settings
)
extended_due = Date(
help="Date that this problem is due by for a particular student. This "
"may differ from the global due date if an instructor has granted "
"an extension to the student.",
default=None,
scope=Scope.user_state,
)
graceperiod = String(
help="Amount of time after the due date that submissions will be accepted",
default=None,
Expand Down
10 changes: 9 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.core import Scope, Integer, String
from .fields import Date
from .utils import get_extended_due_date


log = logging.getLogger(__name__)
Expand All @@ -20,6 +21,13 @@ 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 "
"may differ from the global due date if an instructor has granted "
"an extension to the student.",
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 +48,7 @@ def __init__(self, *args, **kwargs):
show_leaderboard="false"/>
"""
XModule.__init__(self, *args, **kwargs)
self.due_time = self.due
self.due_time = get_extended_due_date(self)

def is_complete(self):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ def __init__(self, system, location, definition, descriptor,
self.accept_file_upload = instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT
self.skip_basic_checks = instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) in TRUE_DICT

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
21 changes: 15 additions & 6 deletions common/lib/xmodule/xmodule/peer_grading_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
from pkg_resources import resource_string
from .capa_module import ComplexEncoder
from .x_module import XModule
from xmodule.raw_module import RawDescriptor
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from .raw_module import RawDescriptor
from .modulestore.django import modulestore
from .modulestore.exceptions import ItemNotFoundError
from .timeinfo import TimeInfo
from .utils import get_extended_due_date
from xblock.core import Dict, String, Scope, Boolean, Integer, Float
from xmodule.fields import Date, Timedelta

Expand Down Expand Up @@ -48,6 +49,13 @@ class PeerGradingFields(object):
help="Due date that should be displayed.",
default=None,
scope=Scope.settings)
extended_due = Date(
help="Date that this problem is due by for a particular student. This "
"may differ from the global due date if an instructor has granted "
"an extension to the student.",
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 @@ -110,12 +118,13 @@ def __init__(self, *args, **kwargs):
log.error("Linked location {0} for peer grading module {1} does not exist".format(
self.link_to_location, self.location))
raise
due_date = self.linked_problem.lms.due
due_date = get_extended_due_date(self.linked_problem.lms)
if due_date:
self.lms.due = due_date

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 @@ -522,7 +531,7 @@ def _find_corresponding_module_for_location(location):
problem_location = problem['location']
descriptor = _find_corresponding_module_for_location(problem_location)
if descriptor:
problem['due'] = descriptor.lms.due
problem['due'] = get_extended_due_date(descriptor.lms)
grace_period = descriptor.lms.graceperiod
try:
problem_timeinfo = TimeInfo(problem['due'], grace_period)
Expand Down
20 changes: 15 additions & 5 deletions common/lib/xmodule/xmodule/seq_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@

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.core import Integer, Scope
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 @@ -24,6 +26,14 @@ 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 "
"may differ from the global due date if an instructor has granted "
"an extension to the student.",
default=None,
scope=Scope.user_state,
)


class SequenceModule(SequenceFields, XModule):
Expand Down
33 changes: 10 additions & 23 deletions common/lib/xmodule/xmodule/tests/test_capa_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,10 @@ def answer_key():
"_2_1")

@staticmethod
def create(graceperiod=None,
due=None,
max_attempts=None,
showanswer=None,
rerandomize=None,
force_save_button=None,
attempts=None,
def create(attempts=None,
problem_state=None,
correct=False,
done=None
**kwargs
):
"""
All parameters are optional, and are added to the created problem if specified.
Expand All @@ -97,21 +91,7 @@ def create(graceperiod=None,
"SampleProblem{0}".format(CapaFactory.next_num())])
model_data = {'data': CapaFactory.sample_problem_xml, 'location': location}

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

model_data.update(kwargs)
descriptor = Mock(weight="1")
if problem_state is not None:
model_data.update(problem_state)
Expand Down Expand Up @@ -318,6 +298,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):

# We have to set up Django settings in order to use QueryDict
Expand Down
7 changes: 7 additions & 0 deletions common/lib/xmodule/xmodule/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@


def get_extended_due_date(node):
due_date = getattr(node, 'extended_due', None)
if not due_date:
due_date = getattr(node, 'due', None)
return due_date
3 changes: 2 additions & 1 deletion lms/djangoapps/courseware/grades.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from xmodule import graders
from xmodule.capa_module import CapaModule
from xmodule.graders import Score
from xmodule.utils import get_extended_due_date
from .models import StudentModule

log = logging.getLogger("mitx.courseware")
Expand Down Expand Up @@ -325,7 +326,7 @@ def progress_summary(student, request, course, model_data_cache):
'scores': scores,
'section_total': section_total,
'format': module_format,
'due': section_module.lms.due,
'due': get_extended_due_date(section_module.lms),
'graded': graded,
})

Expand Down
8 changes: 7 additions & 1 deletion lms/djangoapps/courseware/model_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,13 @@ def set_many(self, kv_dict):
for field in kv_dict:
# Check field for validity
if field.field_name in self._descriptor_model_data:
raise InvalidWriteError("Not allowed to overwrite descriptor model data", field.field_name)
# xblock model data will set any mutable field as dirty whether
# it's been mutated or not. In light of that it's better to
# silently skip these fields rather than raise an error.
#raise InvalidWriteError(
# "Not allowed to overwrite descriptor model data",
# field.field_name)
continue

if field.scope not in self._allowed_scopes:
raise InvalidScopeError(field.scope)
Expand Down
3 changes: 2 additions & 1 deletion lms/djangoapps/courseware/module_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.utils import get_extended_due_date
from xmodule.x_module import ModuleSystem
from xmodule_modifiers import replace_course_urls, replace_jump_to_id_urls, replace_static_urls, add_histogram, wrap_xmodule, save_module # pylint: disable=F0401

Expand Down Expand Up @@ -110,7 +111,7 @@ def toc_for_course(user, request, course, active_chapter, active_section, model_
sections.append({'display_name': section.display_name_with_default,
'url_name': section.url_name,
'format': section.lms.format if section.lms.format is not None else '',
'due': section.lms.due,
'due': get_extended_due_date(section.lms),
'active': active,
'graded': section.lms.graded,
})
Expand Down
6 changes: 3 additions & 3 deletions lms/djangoapps/courseware/tests/test_model_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ def test_get_from_descriptor(self):
self.assertEquals('settings', self.kvs.get(settings_key('field_b')))

def test_write_to_descriptor(self):
self.assertRaises(InvalidWriteError, self.kvs.set, content_key('field_a'), 'foo')
self.kvs.set(content_key('field_a'), 'foo')
self.assertEquals('content', self.desc_md['field_a'])
self.assertRaises(InvalidWriteError, self.kvs.set, settings_key('field_b'), 'foo')
self.kvs.set(settings_key('field_b'), 'foo')
self.assertEquals('settings', self.desc_md['field_b'])

self.assertRaises(InvalidWriteError, self.kvs.set_many, {content_key('field_a'): 'foo'})
self.kvs.set_many({content_key('field_a'): 'foo'})
self.assertEquals('content', self.desc_md['field_a'])

self.assertRaises(InvalidWriteError, self.kvs.delete, content_key('field_a'))
Expand Down
Loading