From 2d677a834c737a527f0ba6debcc911986db4dd59 Mon Sep 17 00:00:00 2001 From: cahrens Date: Thu, 28 Mar 2013 10:24:31 -0400 Subject: [PATCH 01/13] Test for due dates. --- .../contentstore/features/common.py | 14 ++++++ .../contentstore/features/section.py | 14 +----- .../contentstore/features/subsection.feature | 48 +++++++++++-------- .../contentstore/features/subsection.py | 30 ++++++++++-- cms/templates/overview.html | 2 +- 5 files changed, 71 insertions(+), 37 deletions(-) diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 820b60123b..9de3898c54 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -11,6 +11,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__) @@ -207,3 +210,14 @@ def add_subsection(name='Subsection One'): save_css = 'input.new-subsection-name-save' css_fill(name_css, name) css_click(save_css) + + +def set_date_and_time(date_css, desired_date, time_css, desired_time): + css_fill(date_css, desired_date) + # hit TAB to get to the time field + e = css_find(date_css).first + e._element.send_keys(Keys.TAB) + css_fill(time_css, desired_time) + e = css_find(time_css).first + e._element.send_keys(Keys.TAB) + time.sleep(float(1)) \ No newline at end of file diff --git a/cms/djangoapps/contentstore/features/section.py b/cms/djangoapps/contentstore/features/section.py index b5ddb48a09..1a5f9e860f 100644 --- a/cms/djangoapps/contentstore/features/section.py +++ b/cms/djangoapps/contentstore/features/section.py @@ -1,8 +1,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 #################### @@ -36,16 +34,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' - css_fill(date_css, '12/25/2013') - # hit TAB to get to the time field - e = css_find(date_css).first - e._element.send_keys(Keys.TAB) - css_fill(time_css, '12:00am') - e = 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 1be5f4aeb9..2e1c4ad3d5 100644 --- a/cms/djangoapps/contentstore/features/subsection.feature +++ b/cms/djangoapps/contentstore/features/subsection.feature @@ -3,25 +3,33 @@ Feature: Create Subsection As a course author I want to create and edit subsections - Scenario: Add a new subsection to a section - Given I have opened a new course section in Studio - When I click the New Subsection link - And I enter the subsection name and click save - Then I see my subsection on the Courseware page +# Scenario: Add a new subsection to a section +# Given I have opened a new course section in Studio +# When I click the New Subsection link +# And I enter the subsection name and click save +# Then I see my subsection on the Courseware page +# +# Scenario: Add a new subsection (with a name containing a quote) to a section (bug #216) +# Given I have opened a new course section in Studio +# When I click the New Subsection link +# And I enter a subsection name with a quote and click save +# Then I see my subsection name with a quote on the Courseware page +# And I click to edit the subsection name +# Then I see the complete subsection name with a quote in the editor - Scenario: Add a new subsection (with a name containing a quote) to a section (bug #216) - Given I have opened a new course section in Studio - When I click the New Subsection link - And I enter a subsection name with a quote and click save - Then I see my subsection name with a quote on the Courseware page - And I click to edit the subsection name - Then I see the complete subsection name with a quote in the editor +# @skip-phantom +# Scenario: Delete a subsection +# Given I have opened a new course section in Studio +# And I have added a new subsection +# And I see my subsection on the Courseware page +# When I press the "subsection" delete icon +# And I confirm the alert +# Then the subsection does not exist + + 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 - And I have added a new subsection - And I see my subsection on the Courseware page - 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 88e1424898..a52c91a251 100644 --- a/cms/djangoapps/contentstore/features/subsection.py +++ b/cms/djangoapps/contentstore/features/subsection.py @@ -1,6 +1,6 @@ from lettuce import world, step from common import * -from nose.tools import assert_equal +from nose.tools import assert_equal, assert_true ############### ACTIONS #################### @@ -13,6 +13,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') + css_click('span.subsection-name-value') + + @step('I click the New Subsection link') def i_click_the_new_subsection_link(step): css = 'a.new-subsection-item' @@ -41,9 +53,19 @@ def i_see_complete_subsection_name_with_quote_in_editor(step): assert_equal(world.browser.find_by_css(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/2013', 'input#start_time', '3:00am') + css_click('.set-date') + set_date_and_time('input#due_date', '1/2/2014', 'input#due_time', '4:00am') + + +@step('I see the correct dates$') +def i_see_the_correct_dates(step): + assert_equal('12/25/2013', css_find('input#start_date').first.value) + assert_equal('3:00am', css_find('input#start_time').first.value) + assert_equal('1/2/2014', css_find('input#due_date').first.value) + assert_equal('4:00am', css_find('input#due_time').first.value) ############ ASSERTIONS ################### diff --git a/cms/templates/overview.html b/cms/templates/overview.html index 904f654717..d45a90093e 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -200,7 +200,7 @@ -
+
From 16f4a3f9c11640d0191ab777a713ae7ba0d90a1f Mon Sep 17 00:00:00 2001 From: cahrens Date: Mon, 1 Apr 2013 11:37:05 -0400 Subject: [PATCH 02/13] Convert due date to a Date (vs. a string with no year). --- .../contentstore/features/subsection.feature | 2 +- .../contentstore/features/subsection.py | 14 ++++----- cms/djangoapps/contentstore/utils.py | 4 --- cms/djangoapps/contentstore/views.py | 16 +++++++--- cms/static/js/base.js | 22 ++++++------- cms/templates/edit_subsection.html | 9 ++---- common/lib/xmodule/xmodule/capa_module.py | 15 +++------ .../xmodule/combined_open_ended_module.py | 6 ++-- common/lib/xmodule/xmodule/course_module.py | 9 +++--- common/lib/xmodule/xmodule/foldit_module.py | 21 +++++-------- .../combined_open_ended_modulev1.py | 4 +-- .../xmodule/xmodule/peer_grading_module.py | 7 ++--- .../xmodule/xmodule/tests/test_date_utils.py | 26 ++++++++++++++++ .../lib/xmodule/xmodule/tests/test_import.py | 12 +++---- common/lib/xmodule/xmodule/timeinfo.py | 17 ++++------ common/lib/xmodule/xmodule/util/date_utils.py | 31 +++++++++++++++++++ .../courseware/tests/test_module_render.py | 20 ++++++------ lms/templates/courseware/accordion.html | 3 +- lms/templates/courseware/progress.html | 4 ++- lms/xmodule_namespace.py | 2 +- 20 files changed, 139 insertions(+), 105 deletions(-) create mode 100644 common/lib/xmodule/xmodule/tests/test_date_utils.py create mode 100644 common/lib/xmodule/xmodule/util/date_utils.py diff --git a/cms/djangoapps/contentstore/features/subsection.feature b/cms/djangoapps/contentstore/features/subsection.feature index fb966ec1b1..cc3b2b1cbb 100644 --- a/cms/djangoapps/contentstore/features/subsection.feature +++ b/cms/djangoapps/contentstore/features/subsection.feature @@ -31,7 +31,7 @@ Feature: Create Subsection 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 diff --git a/cms/djangoapps/contentstore/features/subsection.py b/cms/djangoapps/contentstore/features/subsection.py index 77df778b75..234d2d81b0 100644 --- a/cms/djangoapps/contentstore/features/subsection.py +++ b/cms/djangoapps/contentstore/features/subsection.py @@ -25,7 +25,7 @@ def i_have_added_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 added a new subsection') - css_click('span.subsection-name-value') + world.css_click('span.subsection-name-value') @step('I click the New Subsection link') @@ -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$') def test_have_set_dates_in_different_years(step): set_date_and_time('input#start_date', '12/25/2013', 'input#start_time', '3:00am') - css_click('.set-date') - set_date_and_time('input#due_date', '1/2/2014', 'input#due_time', '4:00am') + world.css_click('.set-date') + set_date_and_time('input#due_date', '01/02/2014', 'input#due_time', '4:00am') @step('I see the correct dates$') def i_see_the_correct_dates(step): - assert_equal('12/25/2013', css_find('input#start_date').first.value) - assert_equal('3:00am', css_find('input#start_time').first.value) - assert_equal('1/2/2014', css_find('input#due_date').first.value) - assert_equal('4:00am', css_find('input#due_time').first.value) + assert_equal('12/25/2013', world.css_find('input#start_date').first.value) + assert_equal('3:00am', world.css_find('input#start_time').first.value) + assert_equal('01/02/2014', 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 63dfe5bf5f..62e45c51fd 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -147,10 +147,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 561708c833..229f36242b 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 + UnitState, get_course_for_item, get_url_reverse from xmodule.modulestore.xml_importer import import_from_xml from contentstore.course_info_model import get_course_updates, \ @@ -253,6 +253,12 @@ def edit_subsection(request, location): can_view_live = True 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', {'subsection': item, 'context_course': course, @@ -374,7 +380,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, @@ -830,7 +836,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' @@ -1402,7 +1408,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 7466233331..ad08f84d5c 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'); @@ -228,7 +231,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 != '') { @@ -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 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) { @@ -291,10 +291,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", @@ -316,8 +314,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/templates/edit_subsection.html b/cms/templates/edit_subsection.html index eb5a9a9824..ed316062b8 100644 --- a/cms/templates/edit_subsection.html +++ b/cms/templates/edit_subsection.html @@ -4,6 +4,7 @@ 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 %> @@ -66,12 +67,8 @@ Set a due date

- <% - # 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/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index da8b5b4f96..43b696cea9 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -1,14 +1,11 @@ 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 @@ -18,8 +15,9 @@ from .progress import Progress from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor from xmodule.exceptions import NotFoundError -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") @@ -86,7 +84,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) @@ -124,10 +122,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 48fbfcced1..da2dfc12a6 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, 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 collections import namedtuple +from .fields import Date log = logging.getLogger("mitx.courseware") @@ -63,7 +63,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) max_score = Integer(help="Maximum score for the problem.", default=1, scope=Scope.settings) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 6f3b8e94c9..caf5225318 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 = time_to_datetime(time.gmtime()) return announcement, start, now diff --git a/common/lib/xmodule/xmodule/foldit_module.py b/common/lib/xmodule/xmodule/foldit_module.py index 884f9e2df2..20d4e464fa 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, default='') 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 6fe37b9525..c65698e953 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 @@ -143,11 +143,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 e18f2ceca3..30b99a63b7 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -6,13 +6,12 @@ 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 .fields import Date from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService @@ -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) 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) 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): 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)">
@@ -10,7 +11,7 @@
  • ${section['display_name']}

    -

    ${section['format']} ${"due " + section['due'] if 'due' in section and section['due'] != '' else ''}

    +

    ${section['format']} ${"due " + get_default_time_display(section['due']) if 'due' in section else ''}

  • % endfor diff --git a/lms/templates/courseware/progress.html b/lms/templates/courseware/progress.html index 9c3df5237c..0555752699 100644 --- a/lms/templates/courseware/progress.html +++ b/lms/templates/courseware/progress.html @@ -13,6 +13,8 @@ from django.core.urlresolvers import reverse %> +<%! from xmodule.util.date_utils import get_default_time_display %> + <%block name="js_extra"> @@ -62,7 +64,7 @@ ${progress_graph.body(grade_summary, course.grade_cutoffs, "grade-detail-graph", %if 'due' in section and section['due']!="": - due ${section['due']} + due ${get_default_time_display(section['due'])} %endif

    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) From 93b0fafea8766a66b1a4648bfcad3044796dec11 Mon Sep 17 00:00:00 2001 From: cahrens Date: Mon, 1 Apr 2013 12:41:34 -0400 Subject: [PATCH 03/13] Merge branch 'master' into bug/christina/studio Conflicts: cms/djangoapps/contentstore/views.py common/lib/xmodule/xmodule/capa_module.py common/lib/xmodule/xmodule/combined_open_ended_module.py common/lib/xmodule/xmodule/peer_grading_module.py --- common/lib/xmodule/xmodule/capa_module.py | 3 ++- common/lib/xmodule/xmodule/peer_grading_module.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 72174c1f2c..b49646cab8 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -9,7 +9,8 @@ import sys from pkg_resources import resource_string from capa.capa_problem import LoncapaProblem -from capa.responsetypes import StudentInputError +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 diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index 4c0ebf4e0a..5d064378bf 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -12,6 +12,7 @@ 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 From 42b4ffdcf9d29aad6ad9ff95940aef51d9303813 Mon Sep 17 00:00:00 2001 From: cahrens Date: Mon, 1 Apr 2013 13:40:32 -0400 Subject: [PATCH 04/13] Cleanup. --- .../contentstore/features/subsection.py | 2 +- cms/static/sass/views/_outline.scss | 2 +- cms/templates/edit_subsection.html | 17 +++++------------ cms/templates/overview.html | 11 ++++------- 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/cms/djangoapps/contentstore/features/subsection.py b/cms/djangoapps/contentstore/features/subsection.py index 234d2d81b0..0556fa48d5 100644 --- a/cms/djangoapps/contentstore/features/subsection.py +++ b/cms/djangoapps/contentstore/features/subsection.py @@ -3,7 +3,7 @@ from lettuce import world, step from common import * -from nose.tools import assert_equal, assert_true +from nose.tools import assert_equal ############### ACTIONS #################### 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 ed316062b8..80385de829 100644 --- a/cms/templates/edit_subsection.html +++ b/cms/templates/edit_subsection.html @@ -1,9 +1,6 @@ <%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 %> @@ -14,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">
    @@ -39,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}.

    % endif 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 @@ -163,11 +161,10 @@