diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py
index 3878340af3..afb38c3f9e 100644
--- a/cms/djangoapps/contentstore/features/common.py
+++ b/cms/djangoapps/contentstore/features/common.py
@@ -9,6 +9,9 @@ from xmodule.modulestore.django import _MODULESTORES, modulestore
from xmodule.templates import update_templates
from auth.authz import get_user_by_email
+from selenium.webdriver.common.keys import Keys
+import time
+
from logging import getLogger
logger = getLogger(__name__)
@@ -140,3 +143,14 @@ def add_subsection(name='Subsection One'):
save_css = 'input.new-subsection-name-save'
world.css_fill(name_css, name)
world.css_click(save_css)
+
+
+def set_date_and_time(date_css, desired_date, time_css, desired_time):
+ world.css_fill(date_css, desired_date)
+ # hit TAB to get to the time field
+ e = world.css_find(date_css).first
+ e._element.send_keys(Keys.TAB)
+ world.css_fill(time_css, desired_time)
+ e = world.css_find(time_css).first
+ e._element.send_keys(Keys.TAB)
+ time.sleep(float(1))
diff --git a/cms/djangoapps/contentstore/features/section.py b/cms/djangoapps/contentstore/features/section.py
index 0c0f5536a0..fca14e21f0 100644
--- a/cms/djangoapps/contentstore/features/section.py
+++ b/cms/djangoapps/contentstore/features/section.py
@@ -4,8 +4,6 @@
from lettuce import world, step
from common import *
from nose.tools import assert_equal
-from selenium.webdriver.common.keys import Keys
-import time
############### ACTIONS ####################
@@ -39,16 +37,8 @@ def i_click_the_edit_link_for_the_release_date(step):
@step('I save a new section release date$')
def i_save_a_new_section_release_date(step):
- date_css = 'input.start-date.date.hasDatepicker'
- time_css = 'input.start-time.time.ui-timepicker-input'
- world.css_fill(date_css, '12/25/2013')
- # hit TAB to get to the time field
- e = world.css_find(date_css).first
- e._element.send_keys(Keys.TAB)
- world.css_fill(time_css, '12:00am')
- e = world.css_find(time_css).first
- e._element.send_keys(Keys.TAB)
- time.sleep(float(1))
+ set_date_and_time('input.start-date.date.hasDatepicker', '12/25/2013',
+ 'input.start-time.time.ui-timepicker-input', '12:00am')
world.browser.click_link_by_text('Save')
diff --git a/cms/djangoapps/contentstore/features/subsection.feature b/cms/djangoapps/contentstore/features/subsection.feature
index e913c6a4bf..cc3b2b1cbb 100644
--- a/cms/djangoapps/contentstore/features/subsection.feature
+++ b/cms/djangoapps/contentstore/features/subsection.feature
@@ -25,6 +25,13 @@ Feature: Create Subsection
And I reload the page
Then I see it marked as Homework
+ Scenario: Set a due date in a different year (bug #256)
+ Given I have opened a new subsection in Studio
+ And I have set a release date and due date in different years
+ Then I see the correct dates
+ And I reload the page
+ Then I see the correct dates
+
@skip-phantom
Scenario: Delete a subsection
Given I have opened a new course section in Studio
@@ -33,3 +40,5 @@ Feature: Create Subsection
When I press the "subsection" delete icon
And I confirm the alert
Then the subsection does not exist
+
+
diff --git a/cms/djangoapps/contentstore/features/subsection.py b/cms/djangoapps/contentstore/features/subsection.py
index 4ab27fcb49..1ec43e6971 100644
--- a/cms/djangoapps/contentstore/features/subsection.py
+++ b/cms/djangoapps/contentstore/features/subsection.py
@@ -16,6 +16,18 @@ def i_have_opened_a_new_course_section(step):
add_section()
+@step('I have added a new subsection$')
+def i_have_added_a_new_subsection(step):
+ add_subsection()
+
+
+@step('I have opened a new subsection in Studio$')
+def i_have_opened_a_new_subsection(step):
+ step.given('I have opened a new course section in Studio')
+ step.given('I have added a new subsection')
+ world.css_click('span.subsection-name-value')
+
+
@step('I click the New Subsection link')
def i_click_the_new_subsection_link(step):
world.css_click('a.new-subsection-item')
@@ -43,9 +55,20 @@ def i_see_complete_subsection_name_with_quote_in_editor(step):
assert_equal(world.css_find(css).value, 'Subsection With "Quote"')
-@step('I have added a new subsection$')
-def i_have_added_a_new_subsection(step):
- add_subsection()
+@step('I have set a release date and due date in different years$')
+def test_have_set_dates_in_different_years(step):
+ set_date_and_time('input#start_date', '12/25/2011', 'input#start_time', '3:00am')
+ world.css_click('.set-date')
+ # Use a year in the past so that current year will always be different.
+ set_date_and_time('input#due_date', '01/02/2012', 'input#due_time', '4:00am')
+
+
+@step('I see the correct dates$')
+def i_see_the_correct_dates(step):
+ assert_equal('12/25/2011', world.css_find('input#start_date').first.value)
+ assert_equal('3:00am', world.css_find('input#start_time').first.value)
+ assert_equal('01/02/2012', world.css_find('input#due_date').first.value)
+ assert_equal('4:00am', world.css_find('input#due_time').first.value)
@step('I mark it as Homework$')
diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py
index d38918d6b0..83a2bde72d 100644
--- a/cms/djangoapps/contentstore/utils.py
+++ b/cms/djangoapps/contentstore/utils.py
@@ -151,10 +151,6 @@ def compute_unit_state(unit):
return UnitState.public
-def get_date_display(date):
- return date.strftime("%d %B, %Y at %I:%M %p")
-
-
def update_item(location, value):
"""
If value is None, delete the db entry. Otherwise, update it using the correct modulestore.
diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 9681f54350..8850f230eb 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -6,7 +6,6 @@ import sys
import time
import tarfile
import shutil
-from datetime import datetime
from collections import defaultdict
from uuid import uuid4
from path import path
@@ -47,12 +46,13 @@ from functools import partial
from xmodule.contentstore.django import contentstore
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 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 .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, add_open_ended_panel_tab, \
+ UnitState, get_course_for_item, get_url_reverse, add_open_ended_panel_tab, \
remove_open_ended_panel_tab
from xmodule.modulestore.xml_importer import import_from_xml
@@ -365,7 +365,7 @@ def edit_unit(request, location):
'draft_preview_link': preview_lms_link,
'published_preview_link': lms_link,
'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,
'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty'),
'unit_state': unit_state,
@@ -828,7 +828,7 @@ def upload_asset(request, org, course, coursename):
readback = contentstore().find(content.location)
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),
'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None,
'msg': 'Upload completed'
@@ -1433,7 +1433,7 @@ def asset_index(request, org, course, name):
id = asset['_id']
display_info = {}
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'])
display_info['url'] = StaticContent.get_url_path_from_location(asset_location)
diff --git a/cms/static/js/base.js b/cms/static/js/base.js
index 657eb5c8d4..6ea918cc36 100644
--- a/cms/static/js/base.js
+++ b/cms/static/js/base.js
@@ -4,6 +4,9 @@ var $modalCover;
var $newComponentItem;
var $changedInput;
var $spinner;
+var $newComponentTypePicker;
+var $newComponentTemplatePickers;
+var $newComponentButton;
$(document).ready(function () {
$body = $('body');
@@ -242,7 +245,7 @@ function syncReleaseDate(e) {
$("#start_time").val("");
}
-function getEdxTimeFromDateTimeVals(date_val, time_val, format) {
+function getEdxTimeFromDateTimeVals(date_val, time_val) {
var edxTimeStr = null;
if (date_val != '') {
@@ -251,20 +254,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
var date = Date.parse(date_val + " " + time_val);
- if (format == null)
- format = 'yyyy-MM-ddTHH:mm';
-
- edxTimeStr = date.toString(format);
+ edxTimeStr = date.toString('yyyy-MM-ddTHH:mm');
}
return edxTimeStr;
}
-function getEdxTimeFromDateTimeInputs(date_id, time_id, format) {
+function getEdxTimeFromDateTimeInputs(date_id, time_id) {
var input_date = $('#' + date_id).val();
var input_time = $('#' + time_id).val();
- return getEdxTimeFromDateTimeVals(input_date, input_time, format);
+ return getEdxTimeFromDateTimeVals(input_date, input_time);
}
function autosaveInput(e) {
@@ -305,10 +305,8 @@ function saveSubsection() {
}
// 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['due'] = getEdxTimeFromDateTimeInputs('due_date', 'due_time', 'MMMM dd HH:mm');
+ metadata['due'] = getEdxTimeFromDateTimeInputs('due_date', 'due_time');
$.ajax({
url: "/save_item",
@@ -330,8 +328,8 @@ function saveSubsection() {
function createNewUnit(e) {
e.preventDefault();
- parent = $(this).data('parent');
- template = $(this).data('template');
+ var parent = $(this).data('parent');
+ var template = $(this).data('template');
$.post('/clone_item',
{'parent_location': parent,
diff --git a/cms/static/sass/views/_outline.scss b/cms/static/sass/views/_outline.scss
index 0d72e2d2bf..e5a294467e 100644
--- a/cms/static/sass/views/_outline.scss
+++ b/cms/static/sass/views/_outline.scss
@@ -271,7 +271,7 @@ body.course.outline {
.section-published-date {
float: right;
- width: 265px;
+ width: 278px;
margin-right: 220px;
@include border-radius(3px);
background: $lightGrey;
diff --git a/cms/templates/edit_subsection.html b/cms/templates/edit_subsection.html
index eb5a9a9824..80385de829 100644
--- a/cms/templates/edit_subsection.html
+++ b/cms/templates/edit_subsection.html
@@ -1,9 +1,7 @@
<%inherit file="base.html" />
<%!
- from time import mktime
- import dateutil.parser
import logging
- from datetime import datetime
+ from xmodule.util.date_utils import get_time_struct_display
%>
<%! from django.core.urlresolvers import reverse %>
@@ -13,7 +11,6 @@
<%namespace name="units" file="widgets/units.html" />
<%namespace name='static' file='static_content.html'/>
-<%namespace name='datetime' module='datetime'/>
<%block name="content">
@@ -38,18 +35,15 @@
- <%
- start_date = datetime.fromtimestamp(mktime(subsection.lms.start)) if subsection.lms.start is not None else None
- parent_start_date = datetime.fromtimestamp(mktime(parent_item.lms.start)) if parent_item.lms.start is not None else None
- %>
-
-
+
+
% if subsection.lms.start != parent_item.lms.start and subsection.lms.start:
- % if parent_start_date is None:
+ % if parent_item.lms.start is None:
The date above differs from the release date of ${parent_item.display_name_with_default}, which is unset.
% else:
-
The date above differs from the release date of ${parent_item.display_name_with_default} – ${parent_start_date.strftime('%m/%d/%Y')} at ${parent_start_date.strftime('%H:%M')}.
+
The date above differs from the release date of ${parent_item.display_name_with_default} –
+ ${get_time_struct_display(parent_item.lms.start, '%m/%d/%Y at %I:%M %p')}.
% endif
Sync to ${parent_item.display_name_with_default}.
- <%
- # 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
- due_date = dateutil.parser.parse(subsection.lms.due) if subsection.lms.due else None
- %>
-
-
+
+
Remove due date
diff --git a/cms/templates/overview.html b/cms/templates/overview.html
index d45a90093e..04aae12f4a 100644
--- a/cms/templates/overview.html
+++ b/cms/templates/overview.html
@@ -1,9 +1,7 @@
<%inherit file="base.html" />
<%!
- from time import mktime
- import dateutil.parser
import logging
- from datetime import datetime
+ from xmodule.util.date_utils import get_time_struct_display
%>
<%! from django.core.urlresolvers import reverse %>
<%block name="title">Course Outline%block>
@@ -163,11 +161,10 @@
<%
- start_date = datetime.fromtimestamp(mktime(section.lms.start)) if section.lms.start is not None else None
- start_date_str = start_date.strftime('%m/%d/%Y') if start_date is not None else ''
- start_time_str = start_date.strftime('%H:%M') if start_date is not None else ''
+ start_date_str = get_time_struct_display(section.lms.start, '%m/%d/%Y')
+ start_time_str = get_time_struct_display(section.lms.start, '%I:%M %p')
%>
- %if start_date is None:
+ %if section.lms.start is None:
This section has not been released.Schedule
%else:
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 7ca38ea30a..ca7e052e7e 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -1,26 +1,24 @@
import cgi
import datetime
-import dateutil
-import dateutil.parser
import hashlib
import json
import logging
import traceback
import sys
-from lxml import etree
from pkg_resources import resource_string
from capa.capa_problem import LoncapaProblem
-from capa.responsetypes import StudentInputError, \
- ResponseError, LoncapaProblemError
+from capa.responsetypes import StudentInputError,\
+ ResponseError, LoncapaProblemError
from capa.util import convert_files_to_filenames
from .progress import Progress
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError
-from xblock.core import Integer, Scope, BlockScope, ModelType, String, Boolean, Object, Float
-from .fields import Timedelta
+from xblock.core import Integer, Scope, String, Boolean, Object, Float
+from .fields import Timedelta, Date
+from xmodule.util.date_utils import time_to_datetime
log = logging.getLogger("mitx.courseware")
@@ -87,7 +85,7 @@ class ComplexEncoder(json.JSONEncoder):
class CapaFields(object):
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)
- 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)
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)
@@ -125,10 +123,7 @@ class CapaModule(CapaFields, XModule):
def __init__(self, system, location, descriptor, model_data):
XModule.__init__(self, system, location, descriptor, model_data)
- if self.due:
- due_date = dateutil.parser.parse(self.due)
- else:
- due_date = None
+ due_date = time_to_datetime(self.due)
if self.graceperiod is not None and due_date:
self.close_date = due_date + self.graceperiod
diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py
index f45ad39e35..f70cf62d29 100644
--- a/common/lib/xmodule/xmodule/combined_open_ended_module.py
+++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py
@@ -1,4 +1,3 @@
-import json
import logging
from lxml import etree
@@ -6,9 +5,10 @@ from pkg_resources import resource_string
from xmodule.raw_module import RawDescriptor
from .x_module import XModule
-from xblock.core import Integer, Scope, BlockScope, ModelType, String, Boolean, Object, List
+from xblock.core import Integer, Scope, String, Boolean, List
from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
from collections import namedtuple
+from .fields import Date
from xmodule.open_ended_grading_classes.xblock_field_types import StringyFloat
log = logging.getLogger("mitx.courseware")
@@ -64,7 +64,7 @@ class CombinedOpenEndedFields(object):
scope=Scope.settings)
skip_spelling_checks = Boolean(help="Whether or not to skip initial spelling checks.", default=True,
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,
scope=Scope.settings)
version = VersionInteger(help="Current version number", default=DEFAULT_VERSION, scope=Scope.settings)
@@ -105,10 +105,11 @@ class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
icon_class = 'problem'
- js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/display.coffee'),
- resource_string(__name__, 'js/src/collapsible.coffee'),
- resource_string(__name__, 'js/src/javascript_loader.coffee'),
- ]}
+ js = {'coffee':
+ [resource_string(__name__, 'js/src/combinedopenended/display.coffee'),
+ resource_string(__name__, 'js/src/collapsible.coffee'),
+ resource_string(__name__, 'js/src/javascript_loader.coffee'),
+ ]}
js_module_name = "CombinedOpenEnded"
css = {'scss': [resource_string(__name__, 'css/combinedopenended/display.scss')]}
@@ -219,4 +220,3 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
stores_state = True
has_score = True
template_dir_name = "combinedopenended"
-
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index 6f3b8e94c9..ed5a37e580 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -14,6 +14,7 @@ from xmodule.seq_module import SequenceDescriptor, SequenceModule
from xmodule.timeparse import parse_time
from xmodule.util.decorators import lazyproperty
from xmodule.graders import grader_from_conf
+from xmodule.util.date_utils import time_to_datetime
import json
from xblock.core import Scope, List, String, Object, Boolean
@@ -533,19 +534,17 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
def _sorting_dates(self):
# utility function to get datetime objects for dates used to
# compute the is_new flag and the sorting_score
- def to_datetime(timestamp):
- return datetime(*timestamp[:6])
announcement = self.announcement
if announcement is not None:
- announcement = to_datetime(announcement)
+ announcement = time_to_datetime(announcement)
try:
start = dateutil.parser.parse(self.advertised_start)
except (ValueError, AttributeError):
- start = to_datetime(self.start)
+ start = time_to_datetime(self.start)
- now = to_datetime(time.gmtime())
+ now = datetime.utcnow()
return announcement, start, now
diff --git a/common/lib/xmodule/xmodule/foldit_module.py b/common/lib/xmodule/xmodule/foldit_module.py
index 884f9e2df2..1851a4adc2 100644
--- a/common/lib/xmodule/xmodule/foldit_module.py
+++ b/common/lib/xmodule/xmodule/foldit_module.py
@@ -1,6 +1,5 @@
import logging
from lxml import etree
-from dateutil import parser
from pkg_resources import resource_string
@@ -8,6 +7,9 @@ from xmodule.editing_module import EditingDescriptor
from xmodule.x_module import XModule
from xmodule.xml_module import XmlDescriptor
from xblock.core import Scope, Integer, String
+from .fields import Date
+from xmodule.util.date_utils import time_to_datetime
+
log = logging.getLogger(__name__)
@@ -16,7 +18,7 @@ class FolditFields(object):
# default to what Spring_7012x uses
required_level = Integer(default=4, 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)
show_basic_score = String(scope=Scope.settings, default='false')
show_leaderboard = String(scope=Scope.settings, default='false')
@@ -36,17 +38,8 @@ class FolditModule(FolditFields, XModule):
required_sublevel="3"
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):
"""
@@ -178,8 +171,8 @@ class FolditDescriptor(FolditFields, XmlDescriptor, EditingDescriptor):
@classmethod
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')
return xml_object
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
index eaa43c0d86..59df481954 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
@@ -139,11 +139,11 @@ class CombinedOpenEndedV1Module():
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
- 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)
try:
- self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
+ self.timeinfo = TimeInfo(due_date, grace_period_string)
except:
log.error("Error parsing due date information in location {0}".format(location))
raise
diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py
index 564356fcc3..5d064378bf 100644
--- a/common/lib/xmodule/xmodule/peer_grading_module.py
+++ b/common/lib/xmodule/xmodule/peer_grading_module.py
@@ -6,14 +6,13 @@ from lxml import etree
from datetime import datetime
from pkg_resources import resource_string
from .capa_module import ComplexEncoder
-from .stringify import stringify_children
from .x_module import XModule
from xmodule.raw_module import RawDescriptor
-from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from .timeinfo import TimeInfo
from xblock.core import Object, Integer, Boolean, String, Scope
from xmodule.open_ended_grading_classes.xblock_field_types import StringyFloat
+from xmodule.fields import Date
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService
@@ -34,7 +33,7 @@ class PeerGradingFields(object):
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)
- 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)
max_grade = Integer(help="The maximum grade that a student can receieve for this problem.", default=MAX_SCORE,
scope=Scope.settings)
@@ -78,7 +77,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
self._model_data['due'] = due_date
try:
- self.timeinfo = TimeInfo(self.display_due_date_string, self.grace_period_string)
+ self.timeinfo = TimeInfo(self.due_date, self.grace_period_string)
except:
log.error("Error parsing due date information in location {0}".format(location))
raise
diff --git a/common/lib/xmodule/xmodule/tests/test_date_utils.py b/common/lib/xmodule/xmodule/tests/test_date_utils.py
new file mode 100644
index 0000000000..2b294e028f
--- /dev/null
+++ b/common/lib/xmodule/xmodule/tests/test_date_utils.py
@@ -0,0 +1,26 @@
+# 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))
diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py
index 37b1d35938..9d73fdcc17 100644
--- a/common/lib/xmodule/xmodule/tests/test_import.py
+++ b/common/lib/xmodule/xmodule/tests/test_import.py
@@ -1,20 +1,16 @@
# -*- coding: utf-8 -*-
-from path import path
import unittest
from fs.memoryfs import MemoryFS
from lxml import etree
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.errortracker import make_error_tracker
from xmodule.modulestore import Location
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
-from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.inheritance import compute_inherited_metadata
+from xmodule.fields import Date
from .test_export import DATA_DIR
@@ -137,7 +133,7 @@ class ImportTestCase(BaseCourseTestCase):
- inherited metadata doesn't leak to children.
"""
system = self.get_system()
- v = '1 hour'
+ v = 'March 20 17:00'
url_name = 'test1'
start_xml = '''
+<%! from xmodule.util.date_utils import get_default_time_display %>
<%def name="make_chapter(chapter)">
${section['format']}
- %if 'due' in section and section['due']!="":
+ %if section.get('due') is not None:
- due ${section['due']}
+ due ${get_default_time_display(section['due'])}
%endif
Status:
diff --git a/lms/xmodule_namespace.py b/lms/xmodule_namespace.py
index 423c0eb0ec..3ff1ed3971 100644
--- a/lms/xmodule_namespace.py
+++ b/lms/xmodule_namespace.py
@@ -51,7 +51,7 @@ class LmsNamespace(Namespace):
)
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)
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)