Commit 16f4a3f9 by cahrens

Convert due date to a Date (vs. a string with no year).

parent b2bda104
...@@ -25,7 +25,7 @@ def i_have_added_a_new_subsection(step): ...@@ -25,7 +25,7 @@ def i_have_added_a_new_subsection(step):
def i_have_opened_a_new_subsection(step): def i_have_opened_a_new_subsection(step):
step.given('I have opened a new course section in Studio') step.given('I have opened a new course section in Studio')
step.given('I have added a new subsection') step.given('I have added a new subsection')
css_click('span.subsection-name-value') world.css_click('span.subsection-name-value')
@step('I click the New Subsection link') @step('I click the New Subsection link')
...@@ -58,16 +58,16 @@ def i_see_complete_subsection_name_with_quote_in_editor(step): ...@@ -58,16 +58,16 @@ def i_see_complete_subsection_name_with_quote_in_editor(step):
@step('I have set a release date and due date in different years$') @step('I have set a release date and due date in different years$')
def test_have_set_dates_in_different_years(step): def test_have_set_dates_in_different_years(step):
set_date_and_time('input#start_date', '12/25/2013', 'input#start_time', '3:00am') set_date_and_time('input#start_date', '12/25/2013', 'input#start_time', '3:00am')
css_click('.set-date') world.css_click('.set-date')
set_date_and_time('input#due_date', '1/2/2014', 'input#due_time', '4:00am') set_date_and_time('input#due_date', '01/02/2014', 'input#due_time', '4:00am')
@step('I see the correct dates$') @step('I see the correct dates$')
def i_see_the_correct_dates(step): def i_see_the_correct_dates(step):
assert_equal('12/25/2013', css_find('input#start_date').first.value) assert_equal('12/25/2013', world.css_find('input#start_date').first.value)
assert_equal('3:00am', css_find('input#start_time').first.value) assert_equal('3:00am', world.css_find('input#start_time').first.value)
assert_equal('1/2/2014', css_find('input#due_date').first.value) assert_equal('01/02/2014', world.css_find('input#due_date').first.value)
assert_equal('4:00am', css_find('input#due_time').first.value) assert_equal('4:00am', world.css_find('input#due_time').first.value)
@step('I mark it as Homework$') @step('I mark it as Homework$')
......
...@@ -147,10 +147,6 @@ def compute_unit_state(unit): ...@@ -147,10 +147,6 @@ def compute_unit_state(unit):
return UnitState.public return UnitState.public
def get_date_display(date):
return date.strftime("%d %B, %Y at %I:%M %p")
def update_item(location, value): def update_item(location, value):
""" """
If value is None, delete the db entry. Otherwise, update it using the correct modulestore. If value is None, delete the db entry. Otherwise, update it using the correct modulestore.
......
...@@ -6,7 +6,6 @@ import sys ...@@ -6,7 +6,6 @@ import sys
import time import time
import tarfile import tarfile
import shutil import shutil
from datetime import datetime
from collections import defaultdict from collections import defaultdict
from uuid import uuid4 from uuid import uuid4
from path import path from path import path
...@@ -47,12 +46,13 @@ from functools import partial ...@@ -47,12 +46,13 @@ from functools import partial
from xmodule.contentstore.django import contentstore from xmodule.contentstore.django import contentstore
from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import StaticContent
from xmodule.util.date_utils import get_default_time_display
from auth.authz import is_user_in_course_group_role, get_users_in_course_group_by_role from auth.authz import is_user_in_course_group_role, get_users_in_course_group_by_role
from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group
from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME, create_all_course_groups from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME, create_all_course_groups
from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, \ from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, \
get_date_display, UnitState, get_course_for_item, get_url_reverse UnitState, get_course_for_item, get_url_reverse
from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml_importer import import_from_xml
from contentstore.course_info_model import get_course_updates, \ from contentstore.course_info_model import get_course_updates, \
...@@ -253,6 +253,12 @@ def edit_subsection(request, location): ...@@ -253,6 +253,12 @@ def edit_subsection(request, location):
can_view_live = True can_view_live = True
break break
# item.lms.start is a struct_time using GMT
# item.lms.due is a String, 'March 20 17:00'
# edit_subsection.html, due is converted to dateutil.parser.parse(item.lms.due) = {datetime} 2013-03-20 17:00:00
#parsed_due_date = dateutil.parser.parse(item.lms.due)
return render_to_response('edit_subsection.html', return render_to_response('edit_subsection.html',
{'subsection': item, {'subsection': item,
'context_course': course, 'context_course': course,
...@@ -374,7 +380,7 @@ def edit_unit(request, location): ...@@ -374,7 +380,7 @@ def edit_unit(request, location):
'draft_preview_link': preview_lms_link, 'draft_preview_link': preview_lms_link,
'published_preview_link': lms_link, 'published_preview_link': lms_link,
'subsection': containing_subsection, 'subsection': containing_subsection,
'release_date': get_date_display(datetime.fromtimestamp(time.mktime(containing_subsection.lms.start))) if containing_subsection.lms.start is not None else None, 'release_date': get_default_time_display(containing_subsection.lms.start) if containing_subsection.lms.start is not None else None,
'section': containing_section, 'section': containing_section,
'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty'), 'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty'),
'unit_state': unit_state, 'unit_state': unit_state,
...@@ -830,7 +836,7 @@ def upload_asset(request, org, course, coursename): ...@@ -830,7 +836,7 @@ def upload_asset(request, org, course, coursename):
readback = contentstore().find(content.location) readback = contentstore().find(content.location)
response_payload = {'displayname': content.name, response_payload = {'displayname': content.name,
'uploadDate': get_date_display(readback.last_modified_at), 'uploadDate': get_default_time_display(readback.last_modified_at.timetuple()),
'url': StaticContent.get_url_path_from_location(content.location), 'url': StaticContent.get_url_path_from_location(content.location),
'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None, 'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None,
'msg': 'Upload completed' 'msg': 'Upload completed'
...@@ -1402,7 +1408,7 @@ def asset_index(request, org, course, name): ...@@ -1402,7 +1408,7 @@ def asset_index(request, org, course, name):
id = asset['_id'] id = asset['_id']
display_info = {} display_info = {}
display_info['displayname'] = asset['displayname'] display_info['displayname'] = asset['displayname']
display_info['uploadDate'] = get_date_display(asset['uploadDate']) display_info['uploadDate'] = get_default_time_display(asset['uploadDate'].timetuple())
asset_location = StaticContent.compute_location(id['org'], id['course'], id['name']) asset_location = StaticContent.compute_location(id['org'], id['course'], id['name'])
display_info['url'] = StaticContent.get_url_path_from_location(asset_location) display_info['url'] = StaticContent.get_url_path_from_location(asset_location)
......
...@@ -4,6 +4,9 @@ var $modalCover; ...@@ -4,6 +4,9 @@ var $modalCover;
var $newComponentItem; var $newComponentItem;
var $changedInput; var $changedInput;
var $spinner; var $spinner;
var $newComponentTypePicker;
var $newComponentTemplatePickers;
var $newComponentButton;
$(document).ready(function () { $(document).ready(function () {
$body = $('body'); $body = $('body');
...@@ -228,7 +231,7 @@ function syncReleaseDate(e) { ...@@ -228,7 +231,7 @@ function syncReleaseDate(e) {
$("#start_time").val(""); $("#start_time").val("");
} }
function getEdxTimeFromDateTimeVals(date_val, time_val, format) { function getEdxTimeFromDateTimeVals(date_val, time_val) {
var edxTimeStr = null; var edxTimeStr = null;
if (date_val != '') { if (date_val != '') {
...@@ -237,20 +240,17 @@ function getEdxTimeFromDateTimeVals(date_val, time_val, format) { ...@@ -237,20 +240,17 @@ function getEdxTimeFromDateTimeVals(date_val, time_val, format) {
// Note, we are using date.js utility which has better parsing abilities than the built in JS date parsing // Note, we are using date.js utility which has better parsing abilities than the built in JS date parsing
var date = Date.parse(date_val + " " + time_val); var date = Date.parse(date_val + " " + time_val);
if (format == null) edxTimeStr = date.toString('yyyy-MM-ddTHH:mm');
format = 'yyyy-MM-ddTHH:mm';
edxTimeStr = date.toString(format);
} }
return edxTimeStr; return edxTimeStr;
} }
function getEdxTimeFromDateTimeInputs(date_id, time_id, format) { function getEdxTimeFromDateTimeInputs(date_id, time_id) {
var input_date = $('#' + date_id).val(); var input_date = $('#' + date_id).val();
var input_time = $('#' + time_id).val(); var input_time = $('#' + time_id).val();
return getEdxTimeFromDateTimeVals(input_date, input_time, format); return getEdxTimeFromDateTimeVals(input_date, input_time);
} }
function autosaveInput(e) { function autosaveInput(e) {
...@@ -291,10 +291,8 @@ function saveSubsection() { ...@@ -291,10 +291,8 @@ function saveSubsection() {
} }
// Piece back together the date/time UI elements into one date/time string // Piece back together the date/time UI elements into one date/time string
// NOTE: our various "date/time" metadata elements don't always utilize the same formatting string
// so make sure we're passing back the correct format
metadata['start'] = getEdxTimeFromDateTimeInputs('start_date', 'start_time'); metadata['start'] = getEdxTimeFromDateTimeInputs('start_date', 'start_time');
metadata['due'] = getEdxTimeFromDateTimeInputs('due_date', 'due_time', 'MMMM dd HH:mm'); metadata['due'] = getEdxTimeFromDateTimeInputs('due_date', 'due_time');
$.ajax({ $.ajax({
url: "/save_item", url: "/save_item",
...@@ -316,8 +314,8 @@ function saveSubsection() { ...@@ -316,8 +314,8 @@ function saveSubsection() {
function createNewUnit(e) { function createNewUnit(e) {
e.preventDefault(); e.preventDefault();
parent = $(this).data('parent'); var parent = $(this).data('parent');
template = $(this).data('template'); var template = $(this).data('template');
$.post('/clone_item', $.post('/clone_item',
{'parent_location': parent, {'parent_location': parent,
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import dateutil.parser import dateutil.parser
import logging import logging
from datetime import datetime from datetime import datetime
from xmodule.util.date_utils import get_time_struct_display
%> %>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
...@@ -66,12 +67,8 @@ ...@@ -66,12 +67,8 @@
<a href="#" class="set-date">Set a due date</a> <a href="#" class="set-date">Set a due date</a>
<div class="datepair date-setter"> <div class="datepair date-setter">
<p class="date-description"> <p class="date-description">
<% <input type="text" id="due_date" name="due_date" value="${get_time_struct_display(subsection.lms.due, '%m/%d/%Y')}" placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/>
# due date uses it own formatting for stringifying the date. As with capa_module.py, there's a utility module available for us to use <input type="text" id="due_time" name="due_time" value="${get_time_struct_display(subsection.lms.due, '%H:%M')}" placeholder="HH:MM" class="time" size='10' autocomplete="off"/>
due_date = dateutil.parser.parse(subsection.lms.due) if subsection.lms.due else None
%>
<input type="text" id="due_date" name="due_date" value="${due_date.strftime('%m/%d/%Y') if due_date is not None else ''}" placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/>
<input type="text" id="due_time" name="due_time" value="${due_date.strftime('%H:%M') if due_date is not None else ''}" placeholder="HH:MM" class="time" size='10' autocomplete="off"/>
<a href="#" class="remove-date">Remove due date</a> <a href="#" class="remove-date">Remove due date</a>
</p> </p>
</div> </div>
......
import cgi import cgi
import datetime import datetime
import dateutil
import dateutil.parser
import hashlib import hashlib
import json import json
import logging import logging
import traceback import traceback
import sys import sys
from lxml import etree
from pkg_resources import resource_string from pkg_resources import resource_string
from capa.capa_problem import LoncapaProblem from capa.capa_problem import LoncapaProblem
...@@ -18,8 +15,9 @@ from .progress import Progress ...@@ -18,8 +15,9 @@ from .progress import Progress
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError
from xblock.core import Integer, Scope, BlockScope, ModelType, String, Boolean, Object, Float from xblock.core import Integer, Scope, String, Boolean, Object, Float
from .fields import Timedelta from .fields import Timedelta, Date
from xmodule.util.date_utils import time_to_datetime
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
...@@ -86,7 +84,7 @@ class ComplexEncoder(json.JSONEncoder): ...@@ -86,7 +84,7 @@ class ComplexEncoder(json.JSONEncoder):
class CapaFields(object): class CapaFields(object):
attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.student_state) attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.student_state)
max_attempts = StringyInteger(help="Maximum number of attempts that a student is allowed", scope=Scope.settings) max_attempts = StringyInteger(help="Maximum number of attempts that a student is allowed", scope=Scope.settings)
due = String(help="Date that this problem is due by", scope=Scope.settings) due = Date(help="Date that this problem is due by", scope=Scope.settings)
graceperiod = Timedelta(help="Amount of time after the due date that submissions will be accepted", scope=Scope.settings) graceperiod = Timedelta(help="Amount of time after the due date that submissions will be accepted", scope=Scope.settings)
showanswer = String(help="When to show the problem answer to the student", scope=Scope.settings, default="closed") showanswer = String(help="When to show the problem answer to the student", scope=Scope.settings, default="closed")
force_save_button = Boolean(help="Whether to force the save button to appear on the page", scope=Scope.settings, default=False) force_save_button = Boolean(help="Whether to force the save button to appear on the page", scope=Scope.settings, default=False)
...@@ -124,10 +122,7 @@ class CapaModule(CapaFields, XModule): ...@@ -124,10 +122,7 @@ class CapaModule(CapaFields, XModule):
def __init__(self, system, location, descriptor, model_data): def __init__(self, system, location, descriptor, model_data):
XModule.__init__(self, system, location, descriptor, model_data) XModule.__init__(self, system, location, descriptor, model_data)
if self.due: due_date = time_to_datetime(self.due)
due_date = dateutil.parser.parse(self.due)
else:
due_date = None
if self.graceperiod is not None and due_date: if self.graceperiod is not None and due_date:
self.close_date = due_date + self.graceperiod self.close_date = due_date + self.graceperiod
......
import json
import logging import logging
from lxml import etree from lxml import etree
...@@ -6,9 +5,10 @@ from pkg_resources import resource_string ...@@ -6,9 +5,10 @@ from pkg_resources import resource_string
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from .x_module import XModule from .x_module import XModule
from xblock.core import Integer, Scope, BlockScope, ModelType, String, Boolean, Object, Float, List from xblock.core import Integer, Scope, String, Boolean, List
from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
from collections import namedtuple from collections import namedtuple
from .fields import Date
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
...@@ -63,7 +63,7 @@ class CombinedOpenEndedFields(object): ...@@ -63,7 +63,7 @@ class CombinedOpenEndedFields(object):
scope=Scope.settings) scope=Scope.settings)
skip_spelling_checks = Boolean(help="Whether or not to skip initial spelling checks.", default=True, skip_spelling_checks = Boolean(help="Whether or not to skip initial spelling checks.", default=True,
scope=Scope.settings) scope=Scope.settings)
due = String(help="Date that this problem is due by", default=None, scope=Scope.settings) due = Date(help="Date that this problem is due by", default=None, scope=Scope.settings)
graceperiod = String(help="Amount of time after the due date that submissions will be accepted", default=None, graceperiod = String(help="Amount of time after the due date that submissions will be accepted", default=None,
scope=Scope.settings) scope=Scope.settings)
max_score = Integer(help="Maximum score for the problem.", default=1, scope=Scope.settings) max_score = Integer(help="Maximum score for the problem.", default=1, scope=Scope.settings)
......
...@@ -14,6 +14,7 @@ from xmodule.seq_module import SequenceDescriptor, SequenceModule ...@@ -14,6 +14,7 @@ from xmodule.seq_module import SequenceDescriptor, SequenceModule
from xmodule.timeparse import parse_time from xmodule.timeparse import parse_time
from xmodule.util.decorators import lazyproperty from xmodule.util.decorators import lazyproperty
from xmodule.graders import grader_from_conf from xmodule.graders import grader_from_conf
from xmodule.util.date_utils import time_to_datetime
import json import json
from xblock.core import Scope, List, String, Object, Boolean from xblock.core import Scope, List, String, Object, Boolean
...@@ -533,19 +534,17 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): ...@@ -533,19 +534,17 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
def _sorting_dates(self): def _sorting_dates(self):
# utility function to get datetime objects for dates used to # utility function to get datetime objects for dates used to
# compute the is_new flag and the sorting_score # compute the is_new flag and the sorting_score
def to_datetime(timestamp):
return datetime(*timestamp[:6])
announcement = self.announcement announcement = self.announcement
if announcement is not None: if announcement is not None:
announcement = to_datetime(announcement) announcement = time_to_datetime(announcement)
try: try:
start = dateutil.parser.parse(self.advertised_start) start = dateutil.parser.parse(self.advertised_start)
except (ValueError, AttributeError): except (ValueError, AttributeError):
start = to_datetime(self.start) start = time_to_datetime(self.start)
now = to_datetime(time.gmtime()) now = time_to_datetime(time.gmtime())
return announcement, start, now return announcement, start, now
......
import logging import logging
from lxml import etree from lxml import etree
from dateutil import parser
from pkg_resources import resource_string from pkg_resources import resource_string
...@@ -8,6 +7,9 @@ from xmodule.editing_module import EditingDescriptor ...@@ -8,6 +7,9 @@ from xmodule.editing_module import EditingDescriptor
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.xml_module import XmlDescriptor from xmodule.xml_module import XmlDescriptor
from xblock.core import Scope, Integer, String from xblock.core import Scope, Integer, String
from .fields import Date
from xmodule.util.date_utils import time_to_datetime
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -16,7 +18,7 @@ class FolditFields(object): ...@@ -16,7 +18,7 @@ class FolditFields(object):
# default to what Spring_7012x uses # default to what Spring_7012x uses
required_level = Integer(default=4, scope=Scope.settings) required_level = Integer(default=4, scope=Scope.settings)
required_sublevel = Integer(default=5, scope=Scope.settings) required_sublevel = Integer(default=5, scope=Scope.settings)
due = String(help="Date that this problem is due by", scope=Scope.settings, default='') due = Date(help="Date that this problem is due by", scope=Scope.settings, default='')
show_basic_score = String(scope=Scope.settings, default='false') show_basic_score = String(scope=Scope.settings, default='false')
show_leaderboard = String(scope=Scope.settings, default='false') show_leaderboard = String(scope=Scope.settings, default='false')
...@@ -36,17 +38,8 @@ class FolditModule(FolditFields, XModule): ...@@ -36,17 +38,8 @@ class FolditModule(FolditFields, XModule):
required_sublevel="3" required_sublevel="3"
show_leaderboard="false"/> show_leaderboard="false"/>
""" """
def parse_due_date():
"""
Pull out the date, or None
"""
s = self.due
if s:
return parser.parse(s)
else:
return None
self.due_time = parse_due_date() self.due_time = time_to_datetime(self.due)
def is_complete(self): def is_complete(self):
""" """
...@@ -178,8 +171,8 @@ class FolditDescriptor(FolditFields, XmlDescriptor, EditingDescriptor): ...@@ -178,8 +171,8 @@ class FolditDescriptor(FolditFields, XmlDescriptor, EditingDescriptor):
@classmethod @classmethod
def definition_from_xml(cls, xml_object, system): def definition_from_xml(cls, xml_object, system):
return ({}, []) return {}, []
def definition_to_xml(self): def definition_to_xml(self, resource_fs):
xml_object = etree.Element('foldit') xml_object = etree.Element('foldit')
return xml_object return xml_object
...@@ -143,11 +143,11 @@ class CombinedOpenEndedV1Module(): ...@@ -143,11 +143,11 @@ class CombinedOpenEndedV1Module():
self.accept_file_upload = self.instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT self.accept_file_upload = self.instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT
self.skip_basic_checks = self.instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) in TRUE_DICT self.skip_basic_checks = self.instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) in TRUE_DICT
display_due_date_string = self.instance_state.get('due', None) due_date = self.instance_state.get('due', None)
grace_period_string = self.instance_state.get('graceperiod', None) grace_period_string = self.instance_state.get('graceperiod', None)
try: try:
self.timeinfo = TimeInfo(display_due_date_string, grace_period_string) self.timeinfo = TimeInfo(due_date, grace_period_string)
except: except:
log.error("Error parsing due date information in location {0}".format(location)) log.error("Error parsing due date information in location {0}".format(location))
raise raise
......
...@@ -6,13 +6,12 @@ from lxml import etree ...@@ -6,13 +6,12 @@ from lxml import etree
from datetime import datetime from datetime import datetime
from pkg_resources import resource_string from pkg_resources import resource_string
from .capa_module import ComplexEncoder from .capa_module import ComplexEncoder
from .stringify import stringify_children
from .x_module import XModule from .x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from .timeinfo import TimeInfo from .timeinfo import TimeInfo
from xblock.core import Object, Integer, Boolean, String, Scope from xblock.core import Object, Integer, Boolean, String, Scope
from .fields import Date
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService
...@@ -31,7 +30,7 @@ class PeerGradingFields(object): ...@@ -31,7 +30,7 @@ class PeerGradingFields(object):
use_for_single_location = Boolean(help="Whether to use this for a single location or as a panel.", default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings) use_for_single_location = Boolean(help="Whether to use this for a single location or as a panel.", default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings)
link_to_location = String(help="The location this problem is linked to.", default=LINK_TO_LOCATION, scope=Scope.settings) link_to_location = String(help="The location this problem is linked to.", default=LINK_TO_LOCATION, scope=Scope.settings)
is_graded = Boolean(help="Whether or not this module is scored.",default=IS_GRADED, scope=Scope.settings) is_graded = Boolean(help="Whether or not this module is scored.",default=IS_GRADED, scope=Scope.settings)
display_due_date_string = String(help="Due date that should be displayed.", default=None, scope=Scope.settings) due_date = Date(help="Due date that should be displayed.", default=None, scope=Scope.settings)
grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings) grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings)
max_grade = Integer(help="The maximum grade that a student can receieve for this problem.", default=MAX_SCORE, scope=Scope.settings) max_grade = Integer(help="The maximum grade that a student can receieve for this problem.", default=MAX_SCORE, scope=Scope.settings)
student_data_for_location = Object(help="Student data for a given peer grading problem.", default=json.dumps({}),scope=Scope.student_state) student_data_for_location = Object(help="Student data for a given peer grading problem.", default=json.dumps({}),scope=Scope.student_state)
...@@ -72,7 +71,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -72,7 +71,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
self._model_data['due'] = due_date self._model_data['due'] = due_date
try: try:
self.timeinfo = TimeInfo(self.display_due_date_string, self.grace_period_string) self.timeinfo = TimeInfo(self.due_date, self.grace_period_string)
except: except:
log.error("Error parsing due date information in location {0}".format(location)) log.error("Error parsing due date information in location {0}".format(location))
raise raise
......
# Tests for xmodule.util.date_utils
from nose.tools import assert_equals
from xmodule.util import date_utils
import datetime
import time
def test_get_time_struct_display():
assert_equals("", date_utils.get_time_struct_display(None, ""))
test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0))
assert_equals("03/12/1992", date_utils.get_time_struct_display(test_time, '%m/%d/%Y'))
assert_equals("15:03", date_utils.get_time_struct_display(test_time, '%H:%M'))
def test_get_default_time_display():
assert_equals("", date_utils.get_default_time_display(None))
test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0))
assert_equals("Mar 12, 1992 at 03:03 PM",
date_utils.get_default_time_display(test_time))
def test_time_to_datetime():
assert_equals(None, date_utils.time_to_datetime(None))
test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0))
assert_equals(datetime.datetime(1992, 3, 12, 15, 3, 30),
date_utils.time_to_datetime(test_time))
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from path import path
import unittest import unittest
from fs.memoryfs import MemoryFS from fs.memoryfs import MemoryFS
from lxml import etree from lxml import etree
from mock import Mock, patch from mock import Mock, patch
from collections import defaultdict
from xmodule.x_module import XMLParsingSystem, XModuleDescriptor
from xmodule.xml_module import is_pointer_tag from xmodule.xml_module import is_pointer_tag
from xmodule.errortracker import make_error_tracker
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.inheritance import compute_inherited_metadata from xmodule.modulestore.inheritance import compute_inherited_metadata
from xmodule.fields import Date
from .test_export import DATA_DIR from .test_export import DATA_DIR
...@@ -137,7 +133,7 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -137,7 +133,7 @@ class ImportTestCase(BaseCourseTestCase):
- inherited metadata doesn't leak to children. - inherited metadata doesn't leak to children.
""" """
system = self.get_system() system = self.get_system()
v = '1 hour' v = 'March 20 17:00'
url_name = 'test1' url_name = 'test1'
start_xml = ''' start_xml = '''
<course org="{org}" course="{course}" <course org="{org}" course="{course}"
...@@ -150,11 +146,11 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -150,11 +146,11 @@ class ImportTestCase(BaseCourseTestCase):
compute_inherited_metadata(descriptor) compute_inherited_metadata(descriptor)
print descriptor, descriptor._model_data print descriptor, descriptor._model_data
self.assertEqual(descriptor.lms.due, v) self.assertEqual(descriptor.lms.due, Date().from_json(v))
# Check that the child inherits due correctly # Check that the child inherits due correctly
child = descriptor.get_children()[0] child = descriptor.get_children()[0]
self.assertEqual(child.lms.due, v) self.assertEqual(child.lms.due, Date().from_json(v))
# Now export and check things # Now export and check things
resource_fs = MemoryFS() resource_fs = MemoryFS()
......
import dateutil
import dateutil.parser
import datetime
from .timeparse import parse_timedelta from .timeparse import parse_timedelta
from xmodule.util.date_utils import time_to_datetime
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -9,7 +7,7 @@ log = logging.getLogger(__name__) ...@@ -9,7 +7,7 @@ log = logging.getLogger(__name__)
class TimeInfo(object): class TimeInfo(object):
""" """
This is a simple object that calculates and stores datetime information for an XModule This is a simple object that calculates and stores datetime information for an XModule
based on the due date string and the grace period string based on the due date and the grace period string
So far it parses out three different pieces of time information: So far it parses out three different pieces of time information:
self.display_due_date - the 'official' due date that gets displayed to students self.display_due_date - the 'official' due date that gets displayed to students
...@@ -17,13 +15,10 @@ class TimeInfo(object): ...@@ -17,13 +15,10 @@ class TimeInfo(object):
self.close_date - the real due date self.close_date - the real due date
""" """
def __init__(self, display_due_date_string, grace_period_string): def __init__(self, due_date, grace_period_string):
if display_due_date_string is not None: if due_date is not None:
try: self.display_due_date = time_to_datetime(due_date)
self.display_due_date = dateutil.parser.parse(display_due_date_string)
except ValueError:
log.error("Could not parse due date {0}".format(display_due_date_string))
raise
else: else:
self.display_due_date = None self.display_due_date = None
......
import time
import datetime
def get_default_time_display(time_struct):
"""
Converts a time struct to a string representation. This is the default
representation used in Studio and LMS.
It is of the form "Apr 09, 2013 at 04:00 PM".
If None is passed in, an empty string will be returned.
"""
return get_time_struct_display(time_struct, "%b %d, %Y at %I:%M %p")
def get_time_struct_display(time_struct, format):
"""
Converts a time struct to a string based on the given format.
If None is passed in, an empty string will be returned.
"""
return '' if time_struct is None else time.strftime(format, time_struct)
def time_to_datetime(time_struct):
"""
Convert a time struct to a datetime.
If None is passed in, None will be returned.
"""
return datetime.datetime(*time_struct[:6]) if time_struct else None
...@@ -124,17 +124,17 @@ class TestTOC(TestCase): ...@@ -124,17 +124,17 @@ class TestTOC(TestCase):
expected = ([{'active': True, 'sections': expected = ([{'active': True, 'sections':
[{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True, [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True,
'format': u'Lecture Sequence', 'due': '', 'active': False}, 'format': u'Lecture Sequence', 'due': None, 'active': False},
{'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True, {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True,
'format': '', 'due': '', 'active': False}, 'format': '', 'due': None, 'active': False},
{'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True, {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True,
'format': '', 'due': '', 'active': False}, 'format': '', 'due': None, 'active': False},
{'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True, {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True,
'format': '', 'due': '', 'active': False}], 'format': '', 'due': None, 'active': False}],
'url_name': 'Overview', 'display_name': u'Overview'}, 'url_name': 'Overview', 'display_name': u'Overview'},
{'active': False, 'sections': {'active': False, 'sections':
[{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True, [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
'format': '', 'due': '', 'active': False}], 'format': '', 'due': None, 'active': False}],
'url_name': 'secret:magic', 'display_name': 'secret:magic'}]) 'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, None, model_data_cache) actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, None, model_data_cache)
...@@ -151,17 +151,17 @@ class TestTOC(TestCase): ...@@ -151,17 +151,17 @@ class TestTOC(TestCase):
expected = ([{'active': True, 'sections': expected = ([{'active': True, 'sections':
[{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True, [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True,
'format': u'Lecture Sequence', 'due': '', 'active': False}, 'format': u'Lecture Sequence', 'due': None, 'active': False},
{'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True, {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True,
'format': '', 'due': '', 'active': True}, 'format': '', 'due': None, 'active': True},
{'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True, {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True,
'format': '', 'due': '', 'active': False}, 'format': '', 'due': None, 'active': False},
{'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True, {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True,
'format': '', 'due': '', 'active': False}], 'format': '', 'due': None, 'active': False}],
'url_name': 'Overview', 'display_name': u'Overview'}, 'url_name': 'Overview', 'display_name': u'Overview'},
{'active': False, 'sections': {'active': False, 'sections':
[{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True, [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
'format': '', 'due': '', 'active': False}], 'format': '', 'due': None, 'active': False}],
'url_name': 'secret:magic', 'display_name': 'secret:magic'}]) 'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, section, model_data_cache) actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, section, model_data_cache)
......
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%! from xmodule.util.date_utils import get_default_time_display %>
<%def name="make_chapter(chapter)"> <%def name="make_chapter(chapter)">
<div class="chapter"> <div class="chapter">
...@@ -10,7 +11,7 @@ ...@@ -10,7 +11,7 @@
<li class="${'active' if 'active' in section and section['active'] else ''} ${'graded' if 'graded' in section and section['graded'] else ''}"> <li class="${'active' if 'active' in section and section['active'] else ''} ${'graded' if 'graded' in section and section['graded'] else ''}">
<a href="${reverse('courseware_section', args=[course_id, chapter['url_name'], section['url_name']])}"> <a href="${reverse('courseware_section', args=[course_id, chapter['url_name'], section['url_name']])}">
<p>${section['display_name']}</p> <p>${section['display_name']}</p>
<p class="subtitle">${section['format']} ${"due " + section['due'] if 'due' in section and section['due'] != '' else ''}</p> <p class="subtitle">${section['format']} ${"due " + get_default_time_display(section['due']) if 'due' in section else ''}</p>
</a> </a>
</li> </li>
% endfor % endfor
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
%> %>
<%! from xmodule.util.date_utils import get_default_time_display %>
<%block name="js_extra"> <%block name="js_extra">
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.stack.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.stack.js')}"></script>
...@@ -62,7 +64,7 @@ ${progress_graph.body(grade_summary, course.grade_cutoffs, "grade-detail-graph", ...@@ -62,7 +64,7 @@ ${progress_graph.body(grade_summary, course.grade_cutoffs, "grade-detail-graph",
%if 'due' in section and section['due']!="": %if 'due' in section and section['due']!="":
<em> <em>
due ${section['due']} due ${get_default_time_display(section['due'])}
</em> </em>
%endif %endif
</p> </p>
......
...@@ -51,7 +51,7 @@ class LmsNamespace(Namespace): ...@@ -51,7 +51,7 @@ class LmsNamespace(Namespace):
) )
start = Date(help="Start time when this module is visible", scope=Scope.settings) start = Date(help="Start time when this module is visible", scope=Scope.settings)
due = String(help="Date that this problem is due by", scope=Scope.settings, default='') due = Date(help="Date that this problem is due by", scope=Scope.settings)
source_file = String(help="DO NOT USE", scope=Scope.settings) source_file = String(help="DO NOT USE", scope=Scope.settings)
xqa_key = String(help="DO NOT USE", scope=Scope.settings) xqa_key = String(help="DO NOT USE", scope=Scope.settings)
ispublic = Boolean(help="Whether this course is open to the public, or only to admins", scope=Scope.settings) ispublic = Boolean(help="Whether this course is open to the public, or only to admins", scope=Scope.settings)
......
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